I am trying to understand how encapsulation works in Typescript and come up with an example which makes me confusing why am I able to access and even change directly the private members of a certain class.
class Encapsulate {
str:string = "hello"
private str2:string = "world"
}
var obj = new Encapsulate();
console.log(obj.str); //accessible
obj.str2 = "something else";
console.log(obj.str2); //compilation Error as str2 is private
OUTPUT: hello something else
I am getting a compile time warning like compilation Error as str2 is private, but still I am able to change it or access it. Am I missing the concept of Encapsulation in general, what it is and how it works on Typescript.
The problem is that typeScript just compiles to javaScript, but it doesn't have a runtime.
TypeScript is giving you those warnings at compilation type. It is telling you:
Ey, Encapsulate str2 is private, and you're still trying to access it. You shouldn't do that.
However, once ts compiles and generates the js code, all the typescript annotations are lost in the code. JavaScript does not know anything about private, enum or interfaces. Once your code gets compiled and then runs in a browser or any other js runtime, you'll have an Encapsulate javaScript object with two fields, str1 and str2. They cannot be private or public because js does not know about that. Encapsulate will be a plain bare javaScript object.
So, basically, typeScript helps you catch errors at compile time. But, once the code compiles, it will 'forget' everything about typeScript annotations.
It can tell you that you shouldn't access str2. It can even refuse to compile if such an error is found (this depends upon tsconfig configurations). But the compiled code won't have anything to do with typescript.
This is expected as TypeScript is just a static type checking language.
Once compiled to js, the operation you are performing is valid, hence no runtime error
Javascript has no concept of private and public members for a class (though this might change in the future). At this time marking a property or function in a class as private is only checked at compile time (this is why you get a syntax error).
Your code will still run though because even though Typescript generated a syntax error it also most likely still outputted valid Javascript code.
Related
I have a system (it happens to be Gatsby, but I don't believe that's relevant to this question) which is using webpack DefinePlugin to attach some EnvironmentVariables to the global variable: process.env
I can read this just fine.
Unfortunatley, due to weirdnesses in the app startup proces, I need have chosen to do some brief overwritting of those EnvironmentVariables after the site loads. (Not interested in discussing whether that's the best option, in the context of this question. I know there are other options; I want to know whether this is possible)
But it doesn't work :(
If I try to do it explicitly:
process.env.myVar = 'foo';
Then I get ReferenceError: invalid assignment left-hand side.
If I do it by indexer (which appears to be what dotenv does) then it doesn't error, but also doesn't work:
console.log(process.env.myVar);
process.env['myVar'] = 'foo';
console.log(process.env.myVar);
will log undefined twice.
What am I doing wrong, and how do I fix this?
The premise behind this attempted solution was flawed.
I was under the impression that webpack "made process.env.* available as an object in the browser".
It doesn't!
What it actually does is to transpile you code down into literals wherever you reference process.env. So what looks like fetch(process.env.MY_URL_VAR); isn't in fact referencing a variable, it's actually being transpiled down into fetch("http://theActualValue.com") at compile time.
That means that it's conceptually impossible to modify the values on the "process.env object", because there is not in fact an actual object, in the transpiled javascript.
This explains why the direct assignment gives a ref error (you tried to execute "someString" = "someOtherString";) but the indexer doesn't. (I assume that process.env gets compiled into some different literal, which technically supports an indexed setter)
The only solutions available would be to modify the webpack build process (not an option, though I will shortly raise a PR to make it possible :) ), use a different process for getting the Env.Vars into the frontEnd (sub-optimal for various other reasons) or to hack around with various bits of environment control that Gatsby provides to make it all kinda-sorta work (distasteful for yet other reasons).
I am creating a WebExtension using TypeScript which is later compiled to JavaScript.
My extension depends on one of the APIs the browser (Firefox) offers, specifically the extension API. As an example, I use the getURL() method, which is called like this:
browser.extension.getURL("foo/bar.js");
Of course, TypeScript gives an error "Cannot find name 'browser'". This prevents me from fully compiling the code. I would like to know if there is any way to bypass this. Preferably not only at compile level, but also at the linting level.
I have tried:
Defining browser at the beginning as var browser: any;: breaks the API.
Compiling with --noEmit, --noEmitOnErrors: irrelevant, still complains.
Any suggestions?
If you want to let Typescript know that the variable exists but not actually emit any code for it you can use declare
declare var browser: any;
When declaring qx.log.appender.Native or qx.log.appender.Console, my IDE (PyCharm) complains about the syntax:
// Enable logging in debug variant
if (qx.core.Environment.get("qx.debug"))
{
qx.log.appender.Native;
qx.log.appender.Console;
}
(as documented here)
The warning I get is
Expression statement is not assignment or call
Is this preprocessor magic or a feature of JavaScript syntax I'm not aware yet?
Clarification as my question is ambiguous:
I know that this is perfectly fine JavaScript syntax. From the comments I conclude that here's no magic JS behavior that causes the log appenders to be attached, but rather some preprocessor feature?!
But how does this work? Is it an hardcoded handling or is this syntax available for all classes which follow a specific convention?
The hints how to turn off linter warnings are useful, but I rather wanted to know how this "magic" works
Although what's there by default is legal code, I find it to be somewhat ugly since it's a "useless statement" (result is ignored), aside from the fact that my editor complains about it too. In my code I always change it to something like this:
var appender;
appender = qx.log.appender.Native;
appender = qx.log.appender.Console;
Derrell
The generator reads your code to determine what classes are required by your application, so that it can produce an optimised application with only the minimum classes.
Those two lines are valid Javascript syntax, and exist in order to create a reference to the two classes so that the generator knows to include them - without them, you wouldn't have any logging in your application.
Another way to create the references is to use the #use compiler hint in a class comment, eg:
/**
* #use(qx.log.appender.Native)
* #use(qx.log.appender.Console)
*/
qx.Class.define("mypackage.Application", {
extend: qx.application.Standalone,
members: {
main: function() {
this.base(arguments);
this.debug("Hello world");
}
}
});
This works just as well and there is no unusual syntax - however, in this version your app will always refer to the those log appenders, whereas in the skeleton you are using the references to qx.log.appender.Native/Console were surrounded by if (qx.core.Environment.get("qx.debug")) {...} which means that in the non-debug, ./generate.py build version of your app the log appenders would normally be excluded.
Whether you think this is a good thing or not is up to you - personally, these days I ship all applications with the log appenders enabled and working so that if someone has a problem I can look at the logs (you can write your own appender that sends the logs to the server, or just remote control the user's computer)
EDIT: One other detail is that when a class is created, it can have a defer function that does extra initialisation - in this case, the generator detects qx.log.appender.Console is needed so it makes sure the class is loaded; the class' defer method then adds itself as an appender to the Qooxdoo logging system
This is a valid JS syntax, so most likely it's linter's/preprocessor's warning (looks like something similar to ESLint's no-unused-expressions).
Edit:
For the other part of the question - this syntax most likely uses getters or (rather unlikely as it is a new feature) Proxies. MDN provides simple examples of how this works under the hood.
Btw: there is no such thing as "native" JS preprocessor. There are compilers like Babel or TypeScript's compiler, but they are separate projects, not related to the vanilla JavaScript.
Recently I've been studying ES6, and that lead to me using Babel a lot. Being the curious type, I started looking at the Babel Github repository to know how they built this awesome tool, and know if I can somehow contribute.
However, I came across this file, and it has things like declare class BabelNodeSourceLocation {} written all over it, and the file ends with .js.
This got me very confused, and I am now wondering whether there's a declare keyword in JavaScript that I didn't know of, or is this just a Babel-specific syntax? All my Google searches resulted in nothing.
Update: Putting the code in the Babel REPL resulted in nothing. Babel just ignored the code and did not produce any equivalent ES5 output. It also did not throw any error.
and the file ends with .js.
That doesn't mean a lot these days :-)
I am wondering whether there's a declare keyword in JavaScript that I didn't know of
No, there is not.
Or is this just a Babel-specific syntax?
No. It is a type declaration file for the Flow typechecker.
With Flow, you can declare a global class that allows you to reference the class type anywhere in your project. This has no affect on runtime code and won't affect babel output.
An example from the docs:
declare class URL {
constructor(urlStr: string): URL;
toString(): string;
static compare(url1: URL, url2: URL): boolean;
};
And then in your project you can reference URL as a class type.
Similarly, you can declare other global Types, Modules, Functions, Variables. A good way to keep them organized.
I just started using TypeScript and sometimes get compiler errors "use of undeclared variable". For example the following works in plain JavaScript :
var foo = {};
foo.bar = 42;
If I try to do the same in TypeScript it won't work and give me the mentioned error above. I have to write it like that:
var foo :any = {};
foo.bar = 42;
In plain JavaScript the type definition with any is neither required nor valid, but in TypeScript this seems to be mandatory. I understand the error and the reason for it, but I always heard in Videos and read in the documentation:
typescriptlang.org:
"TypeScript is a typed superset of JavaScript [...]"
Introduction Video #minute 3:20:
"All JavaScript code is TypeScript code, simply copy and paste"
Is that a thing that changed during the development of TypeScript or do I have to pass a specific compiler setting to make this work?
The reason for TypeScript's existence is to have a compiler and language which can enforce types better than vanilla Javascript does. Any regular Javascript is valid TypeScript, syntactically. That does not mean that the compiler must be entirely happy with it. Vanilla Javascript often contains code which is problematic in terms of type security. That doesn't make it invalid TypeScript code, but it's exactly the reason why TypeScript exists and it's exactly the compiler's job to point out those problems to you.
The languages as such are still sub/supersets of one another.
Theorem: TypeScript is neither a subset nor a superset of JavaScript.
Proof:
When we say language A is a subset of language B, we mean all valid A-programs are also valid B-programs.
Here is a valid TypeScript program that is not a valid JavaScript program:
let x: number = 3;
You identified a valid JavaScript program that is not a valid TypeScript program:
var foo = {};
foo.bar = 42;
Complicating factor 1: TypeScript is almost a superset. TypeScript is intended to be a near superset of JavaScript. Most valid JS is also valid TS. What JS is not can usually be easily tweaked to compile without errors in TS. In other words, most valid JS is also valid TS.
Complicating factor 2: non-fatal errors The TypeScript compiler generates the JavaScript code you intend sometimes even if there are errors. The your example that I referenced earlier emits this error
error TS2339: Property 'bar' does not exist on type '{}'.
but also this JS code
var foo = {};
foo.bar = 42;
The TS documentation notes
You can use TypeScript even if there are errors in your code. But in this case, TypeScript is warning that your code will likely not run as expected.
I think we can call this a failed compilation (and thus invalid TypeScript) for the following reasons:
The compiler seems to use the term warning in the conventional sense, so we should interpret error in the conventional sense too: an error indicates the compilation failed.
The documentation indicates that the resulting JavaScript is not necessarily correct as to what was intended. An incorrect output seems just as bad as (if not worse than) no output. They should both be considered failed.
The TypeScript compiler exits with a non-zero status code, which conventionally indicates that the process failed in some way.
If we call any TypeScript program that outputs JavaScript "valid", then we would have to call the following TypeScript program valid, because it a dot compiles to the empty string after issuing errors:
.
Complicating factor 3: TS accepts JS files: The TypeScript compiler can passthrough files ending in .js (see compiler documentation for --allowJs). In this sense TypeScript is a superset of JS. All .js files can be compiled with TypeScript. This is probably not what people who visit this question are meaning to ask.
I think complicating factor 1 is the thing that Anders Hejlsberg is getting at. It might also justify the misleading marketing on TypeScript's homepage. The other answers have fallen prey to complicating factor 2. However the general advice given in the other answers is correct: TypeScript is a layer on top of JavaScript designed to tell you when you do something bad. They are different tools for different purposes.
No. In other answers I believe the technical reason has been well explained, but I notice an example that could immediately serve as a contradiction to the claim in the question (different semantics):
// In TypeScript
function f<A>(a: A) { return a; };
console.log(f<Function>(f)); // <-- This line. It will print the function f since it is an identify function that in this case takes self and returns self.
Comparing to the below JavaScript example
// In JavaScript
function f(a) { return a; };
console.log(f<Function>(f)); // <-- This line. This is VALID JavaScript
At first glance you might think there should be a syntax error for the JavaScript example. HOWEVER, once you examine it closely, you'll notice that actually the line is executed as
console.log((f < Function) > f); // Evaluate to false
which is completely valid in JavaScript. This essentially means the same line of code resulted in 2 completely different interpretation in JavaScript and TypeScript, therefore a counterexample to the question.
The definition
"All JavaScript code is TypeScript code, simply copy and paste"
is true. Because any JavaScript code can passed to the TypeScript compiler.
So it's sort of a Layer on top of JavaScript. So, of course the underlaying Layer (JavaScript) can be passed through the layers to the top (TypeScript), but not the other way around.
Why not?
Think of it as a bike (JavaScript) and a motorcycle (TypeScript). The basics are the same (two wheels, a frame), but the motorcycle as an engine and some enhanced features.
So, you can use your motorcycle (TypeScript) as a bike (JavaScript), but you cannot use a bike as a motorcycle.
EDIT:
If your compiler throws a warning, why does it make the
statement wrong? It just says: Hey, you are using TypeScript, and it's
more strict than what you gave me.
See this example, it compiles perfectly to JavaScript, but throws a warning.