In strong code, accessing objects (strong or not) throws on missing properties.
New object properties have to be defined explicitly and cannot be removed
from strong objects.
To me, this seems to break a fundamental aspect of the language. I've found it very acceptable to be able to define an object literal property "on the fly." However, with strong mode trying to make the language friendlier to eventually being more statically typed, I see the necessity. It just boggles my dynamically typed mind :-)The given justifications for doing this, though, seem to be all about performance rather than an attempt to evolve the language.
If that's true, I think it's possible the proposal has a naming problem.
Calling it "use strong" implies that this is about evolving the JS language so that devs are spending time writing more strongly-typed code.
Calling "use optimize" would make it a lot clearer that this is not an attempt to Java-ify JS, and this is more something you'd primarily invoke for performance-critical code paths.
"use strong"
try {
foo = bar[maybeMissing]
} catch {
// only runs in strong mode
}
The above will take different code paths depending on whether your JS engine supports "strong" mode.I suppose it's a lot easier to declare one's intention to create a backwards-compatible subset than to actually create one.
Hope the V8 people are open to revisions on the details at least.
"use strict";
try {
a = true;
} catch(e) {
// only runs in strict mode
}
It's true of any mode switch that makes the language smaller. You shouldn't write non-strict (or non-strong) code if you opted into that mode, and catching those errors defeats the purpose of using it.But it's not only true of mode switches...
try {
JSON.parse("{}");
} catch(e) {
// only runs in browsers that don't support JSON.parse
}
try {
[ 1, 2, 3 ].forEach(function(x) { /* ... */ });
} catch(e) {
// only runs in browsers that don't support forEach
}
Or even just: const x = 10;
// only runs in browsers that support const
Any language change can cause differences between what executes in one browser vs. what executes in another. What strong mode guarantees is that if your code doesn't throw errors in strong mode, it won't throw errors in non-strong-mode (which is more than many changes guarantee!). Any other kinds of compatibility guarantees are impossible to make unless your changes are literally meaningless.However, a mode directive has the significant advantage that any program +not hitting any of the strong mode restriction+ should run unchanged in a VM not recognising the directive, and no translation step should be required.
Your next two points are different: the standard library additions can be polyfilled and the syntax change is intentionally backwards incompatible.
However, making nonexistent property accesses silently return undefined is just an amazing way to ensure typos in property names never get caught. I don't think `foo.bar || baz` is much to sacrifice - Python, for example, has getattr(foo, 'bar', baz), which works fine, and has the benefit of returning foo.bar if it exists at all, not just if it's a truthy value.
foo = bar.x || 3;
rather than foo = bar.hasOwnProperty('x') ? bar.x : 3;
is nice. foo = ('x' in bar) ? bar.x : 3;
instead. The problem with your code is that if the property bar.x exists, but is one of any number of values, like 0 or false, your code will still set foo to 3. Requiring properties to be explicitly created means that you're separating existence from value, which are two very different things in my book.I mean, very many times (for example) you're pulling in some JSON from a server app, which will have types properly enforced at the database and/or application level. There are still gotchas around defaulting to true, whether or not an empty string is a valid value and so on, but there are many cases where foo || bar is safe enough.
let x = {
keyA: "valueA"
};
x.keyB = "valueB";[Maps]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
For what it's worth, you can still use "options hashes" in your argument APIs in strong mode using objects; you just write a library function something like:
function options(options, defaults) {
// the final args start off as a clone of the default args
let args = Object.clone(defaults);
// we then loop through the keys and copy in any overrides
for(let key of Object.keys(args)) {
// ignore inherited properties and skip missing ones
if(args.hasOwnProperty(key) && options.hasOwnProperty(key)) {
args[key] = options[key];
}
}
// args now has all of the overrides from options
return args;
}
And then in all your functions that take options objects: function bakeBread(ingredientOverrides) {
let ingredients = options(ingredientOverrides, {
flourType: 'whole wheat',
sugarAmount: '3 tbsp',
waterAmount: '1 cup',
milkAmount: '0.3 cups',
flourAmount: '4 cups'
});
let batter = mix(ingredients);
return bake(batter);
}
JS is still quite dynamic, even in strong mode — you can define arbitrary objects and types at runtime, and easily inspect/reflect on them — it's just a little harder to silently corrupt data.The neat thing about using named arguments with objects is that in typed variants of JS — for example, TypeScript, or perhaps someday SoundScript — you can actually typecheck them! Maps can't do that in any language I know of: by design they can contain anything.
Map<String, Integer> = new HashMap<String, Integer>();
That declares a Map with a String key and Integer value. Is this what you're thinking of? interface BreadIngredientOptions {
flourType?: String; // this is the syntax for optional strings
sugarAmount?: String; // ditto: it's the ? that makes it optional
// ...
}
function bakeBread(ingredientOverrides: BreadIngredientOptions) {
// ...
}
// callers don't need to explicitly inherit or implement to be type checked
// however, since all properties are optional, this is less interesting
bakeBread({
flourType: 'white'
});
But you can do even better than that example shows. One common problem with maps-as-named-arguments is that you can't easily determine which arguments are required and which are optional. With typed optional properties and structural subtyping you can enforce that at compile time, as follows: interface MyArgumentInterface {
requiredArg: number;
optionalArg?: number;
}
function f(args: MyArgumentInterface) {
// ...
}
// This works:
f({
requiredArg: 10,
optionalArg: 5
});
// This also works:
f({ requiredArg: 50 });
// This statically throws at compile time:
f({ optionalArg: 10 });
It's a combination of the simple object literal syntax from raw JS that makes it easy to create objects of arbitrary types, with structural subtyping. I'm not aware of any language with the same features (but would love to be corrected!). let foo = opts.foo || 'default'
Which is nice. On the other hand, if you look at how V8 does its JIT compiling, it seems like there's just some things you can't optimize around, and they've gotten as far as they can reasonably be expected to get there. Having object schema that can change on the fly is just really hard to JIT efficiently.My worry is that Strong Mode becomes the defacto standard, and we end up losing the flexibility and expressiveness of well-written JS, and end up with static typing all over. If your JS is compact and otherwise well-formed, you can probably afford the compiler hit sometimes, knowing that code is a lot easier to write.
I understand that there are performances issues to doing things this way, but it seems like you could have the strong typed base while still allowing expando properties that may be slower? In .Net the added Dynamic but I find it of limited use because it must be explicitly used rather than just Object allowing it which is what you are going to get from most libraries.
Silent property failures and the resulting proliferation of 'undefined' are the most prominent mistake in JavaScript, and can be rather tedious to debug (very much like null pointer exceptions in other languages, but much more common).
Having different objects (that were created in the same way and represent the same thing) potentially have different properties at different parts of their lifetime can easily lead to a codebase where you can never be certain of anything about such objects and have to do loads of explicit checking every time you use them. Especially in a larger codebase developed by more than one person.
It's a nice feature for whipping up some quick prototypes though.
The undefined issue seems no different to the null reference as you mentioned. I run into it constantly in C#, in fact everyone does that why they are adding the null lifting operator (?.), javascript could do the same for both undefined and null.
I may be weird but I actually like the fact that javascript has both undefined and null. It allows an extra state over just null in C# which I constantly wish I had, typically for things like data/domain objects. With undefined and null it's trivial to encoded the fact that a property is not loaded say from a db or sent from a client vs it being loaded but the value is null or sent over the network with a null value.
My biggest problem with this proposal is that it will force a full-blown code-style on you. Many, perhaps most, of the features will be part of the common consent how to write good apps but I am sure lots of people have a problem with feature x or y. I wonder if a "selecting a subset" would be practicable.
Yes, just like "use strict" did, and thank goodness! The fact of the matter is that Javascript wasn't a carefully designed language from top to bottom and we're still working to fix some of its pitfalls. It's not that some of the ill-posed features don't have valid uses; they do. But we know a lot about the best practices, and with ES6, there are objectively safer alternatives to old patterns.
This isn't just for today's experienced JS dev. Javascript is here to stay, and there's no reason the person learning it today should have to know all the language quirks I had to work around. "use strong" will lead to more performant and more resilient JS coding practices, period. Let's stop fetishizing the missteps of the original language.
More and more mission critical software is being written in javascript, affecting the livelihood of ordinary people. I think there is a significant audience that can benefit from a less dynamic, more static javascript.
What I'm missing here?
It is a SyntaxError to use the identifier ‘eval’.
SyntaxError? "use strong"; should probably do something like:
let eval = void 0;
rather than making eval a SyntaxError.What if I want a variable?!
It's important to note that the modes are on a per-function basis, so opting into the new mode doesn't break existing libraries, etc even if you call those libraries from a newer mode.
It's also worth mentioning that strong mode doesn't introduce anything new. All that strong mode does is remove features. `let` and `var` both already exist; in strong mode, only `let` does.
People who are new to FORTRAN get told "if it doesn't say "implicit none" at the top then don't try to read it yet".
let result = 0;
for (let i = 0; i < arr.length; i++) {
result += arr[i];
}