diff --git a/es6 & beyond/ch2.md b/es6 & beyond/ch2.md index 915174a61..7ea1727c2 100644 --- a/es6 & beyond/ch2.md +++ b/es6 & beyond/ch2.md @@ -1103,6 +1103,8 @@ var o = { } ``` +**Warning:** While `x() { .. }` seems to just be shorthand for `x: function(){ .. }`, concise methods have special behaviors that their older counterparts don't; specifically, the allowance for `super` (see "Object `super`" later in this chapter). + #### Concisely Unnamed While that convenience shorthand is quite attractive, there's a subtle gotcha to be aware of. To illustrate, let's examine pre-ES6 code like the following, which you might try to refactor to use concise methods: @@ -1359,7 +1361,39 @@ var o2 = Object.assign( ); ``` -`Object.assign(..)` is a new ES6 utility -- basically it copies object properties -- and is covered in Chapter 6. `Object.create(..)` is the ES5 standard utility that creates an empty object that is `[[Prototype]]`-linked. +`Object.assign(..)` is a new ES6 utility -- it copies object properties -- and is covered in Chapter 6. `Object.create(..)` is the ES5 standard utility that creates an empty object that is `[[Prototype]]`-linked. + +### Object `super` + +`super` is typically thought of as being only related to classes. However, due to JS's classless-objects-with-prototypes nature, `super` is equally effective, and nearly the same in behavior, with plain objects' concise methods. + +Consider: + +```js +var o1 = { + foo() { + console.log( "o1:foo" ); + } +}; + +var o2 = { + foo() { + super.foo(); + console.log( "o2:foo" ); + } +}; + +Object.setPrototypeOf( o2, o1 ); + +o2.foo(); // o1:foo + // o2:foo +``` + +**Warning:** `super` is only allowed in concise methods, not regular function expression properties. It also is only allowed in `super.XXX` form (for property/method access), not in `super()` form. + +The `super` reference in the `o2.foo()` method is locked statically to `o2`, and specifically to the `[[Prototype]]` of `o2`. `super` here would basically be `Object.getPrototypeOf(o2)` -- resolves to `o1` of course -- which is how it finds and calls `o1.foo()`. + +For complete details on `super`, see "Classes" in Chapter 3. ## Template Literals diff --git a/es6 & beyond/ch3.md b/es6 & beyond/ch3.md index 5ec877f9e..1b4234962 100644 --- a/es6 & beyond/ch3.md +++ b/es6 & beyond/ch3.md @@ -1661,11 +1661,11 @@ f.y; // 15 f.gimmeXY(); // 75 ``` -**Warning:** Though `class Foo` is much like `function Foo`, there are important differences. The `Foo(..)` call *must* be made with `new` -- a pre-ES6 approach `Foo.call( obj )` will *not* work. Also, while `function Foo` is "hoisted" (see the *Scope & Closures* title of this series), `class Foo` is not; the `extends ..` clause specifies an expression that must not be "hoisted". +**Warning:** Though `class Foo` is much like `function Foo`, there are important differences. The `Foo(..)` call *must* be made with `new` -- a pre-ES6 approach of `Foo.call( obj )` will *not* work. Also, while `function Foo` is "hoisted" (see the *Scope & Closures* title of this series), `class Foo` is not; the `extends ..` clause specifies an expression that cannot be "hoisted". So, you must declare a `class` before you can instantiate it. ### `extends` and `super` -ES6 classes also have syntax sugar for establishing the `[[Prototype]]` delegation link between two function prototypes, using the class-oriented familiar terminology `extends`: +ES6 classes also have syntax sugar for establishing the `[[Prototype]]` delegation link between two function prototypes -- commonly mislabeled "inheritance" or confusingly labeled "prototype inheritance" -- using the class-oriented familiar terminology `extends`: ```js class Bar extends Foo { @@ -1689,17 +1689,19 @@ b.gimmeXYZ(); // 1875 A significant new addition is `super`, which is actually something not directly possible (without some unfortunate hack tradeoffs). In the constructor, `super` automatically refers to the "parent constructor", which in the previous example is `Foo(..)`. In a method, it refers to the "parent object", such that you can then make a property/method access off it, such as `super.gimmeXY()`. -`Bar extends Foo` of course means to `[[Prototype]]` link `Bar.prototype` to `Foo.prototype`. So, `super` in a method like `gimmeXYZ()` specifically means `Foo.prototype`, whereas `super` means `Foo` when used in the constructor. +`Bar extends Foo` of course means to link the `[[Prototype]]` of `Bar.prototype` to `Foo.prototype`. So, `super` in a method like `gimmeXYZ()` specifically means `Foo.prototype`, whereas `super` means `Foo` when used in the `Bar` constructor. + +**Note:** `super` is not limited to `class` declarations. It also works in object literals, in much the same way we're discussing here. #### There Be `super` Dragons It is not insignificant to note that `super` behaves differently depending on where it appears. In fairness, most of the time, that won't be a problem. But surprises await if you deviate from a narrow norm. -There may be cases where in the constructor you would want to reference the `Foo.prototype`, such as to directly access one of its properties/methods. However, `super` in the constructor cannot be used in that way; `super.prototype` will not work. `super(..)` means roughly to call `new Foo(..)`, but isn't actually a reference to `Foo` itself. +There may be cases where in the constructor you would want to reference the `Foo.prototype`, such as to directly access one of its properties/methods. However, `super` in the constructor cannot be used in that way; `super.prototype` will not work. `super(..)` means roughly to call `new Foo(..)`, but isn't actually a usable reference to `Foo` itself. -Symmetrically, you may want to reference the `Foo(..)` function from inside a non-constructor method. `super.constructor` will point at `Foo(..)` the function, but beware that this function can *only* be invoked with `new`. `new super.constructor(..)` would be valid, but it wouldn't be terribly useful in most cases, because you can't make that use/reference the current `this` object context, which is likely what you'd want. +Symmetrically, you may want to reference the `Foo(..)` function from inside a non-constructor method. `super.constructor` will point at `Foo(..)` the function, but beware that this function can *only* be invoked with `new`. `new super.constructor(..)` would be valid, but it wouldn't be terribly useful in most cases, because you can't make that call use or reference the current `this` object context, which is likely what you'd want. -Also, `super` looks like it might be related to a function's context just like `this` -- that is, that they'd both be dynamically bound. However, `super` is not dynamic like `this` is. When a constructor or method makes a `super` reference inside it at declaration time, that `super` is statically bound to that specific class heirarchy, and cannot be overriden (at least in ES6). +Also, `super` looks like it might be driven by a function's context just like `this` -- that is, that they'd both be dynamically bound. However, `super` is not dynamic like `this` is. When a constructor or method makes a `super` reference inside it at declaration time (in the `class` body), that `super` is statically bound to that specific class heirarchy, and cannot be overriden (at least in ES6). What does that mean? It means that if you're in the habit of taking a method from one "class" and "borrowing" it for another class by overriding its `this`, say with `call(..)` or `apply(..)`, that may very well create surprises if the method you're borrowing has a `super` in it. Consider this class heirarchy: