# MCMAScript 文章
- Ref: http://dmitrysoshnikov.com/
---
章節
- ECMA-262-3 in detail
Chapter 1. Execution Contexts
Chapter 2. Variable object
Chapter 3. This
Chapter 4. Scope chain
Chapter 5. Functions
Chapter 6. Closures
Chapter 7.1. OOP: The general theory
Chapter 7.2. OOP: ECMAScript implementation
Chapter 8. Evaluation strategy
- ECMA-262-5 in detail
Chapter 1. Properties and Property Descriptors
Chapter 2. Strict Mode
Chapter 3.1. Lexical environments: Common Theory
Chapter 3.2. Lexical environments: ECMAScript implementation
---
這部分是 JavaScript 比較底層的部分,作者的文章也稍為貼近規格書的內容,算是還不錯的規格書導讀,第一次看還不是很懂,之後再回來看看,底下筆記列出文章中的一些名詞,方便之後對照查閱
筆記 :
#### ECMA-262-3 in detail
- Variable Object :
- VO, AO
```javascript
AbstractVO (generic behavior of the variable instantiation process)
║
╠══> GlobalContextVO
║ (VO === this === global)
║
╚══> FunctionContextVO
(VO === AO, <arguments> object and <formal parameters> are added)
```
- AO :
- Regarding the execution context of functions — there VO is inaccessible directly, and its role plays so-called an activation object (in abbreviated form — AO).
```javascript
VO(functionContext) === AO;
```
- Global context :
- Global object is the object which is created before entering any execution context
```javascript
global = {
Math: <...>,
String: <...>
...
...
window: global
};
```
- \_\_parent\_\_
- which is the reference to the activation object (or the global variable object) in which these functions have been created.
```javascript
var global = this;
var a = 10;
function foo() {}
alert(foo.__parent__); // global
var VO = foo.__parent__;
alert(VO.a); // 10
alert(VO === global); // true
```
- This : Function call and non-Reference type
- when on the left hand side of call parentheses there is a value not of Reference type but any another type, this value is automatically set to null and, as consequence, to the global object.
```javascript
(function () {
console.log(this); // null => global
})();
```
```javascript
var foo = {
bar: function () {
console.log(this);
}
};
foo.bar(); // Reference, OK => foo
(foo.bar)(); // Reference, OK => foo
// ----------------------------------
(foo.bar = foo.bar)(); // global?
(false || foo.bar)(); // global?
(foo.bar, foo.bar)(); // global?
```
- In the third case, assignment operator, unlike the grouping operator, calls ==GetValue== method (see step 3 of 11.13.1). As a result at return there is already function object (but not a value of Reference type) which means that this value set to null and, as consequence, to global.
- Similarly with the fourth and fifth cases — the comma operator and logical OR expression ==call the GetValue method and accordingly we lose value of type Reference== and get value of type function; and again this value is set to global.
- 早期的瀏覽器 : SpiderMonkey, JScript
- Algorithm of function creation
```javascript
F = new NativeObject();
// property [[Class]] is "Function"
F.[[Class]] = "Function"
// a prototype of a function object
F.[[Prototype]] = Function.prototype
// reference to function itself
// [[Call]] is activated by call expression F()
// and creates a new execution context
F.[[Call]] = <reference to function>
// built in general constructor of objects
// [[Construct]] is activated via "new" keyword
// and it is the one who allocates memory for new
// objects; then it calls F.[[Call]]
// to initialize created objects passing as
// "this" value newly created object
F.[[Construct]] = internalConstructor
// scope chain of the current context
// i.e. context which creates function F
F.[[Scope]] = activeContext.Scope
// if this functions is created
// via new Function(...), then
F.[[Scope]] = globalContext.Scope
// number of formal parameters
F.length = countParameters
// a prototype of created by F objects
__objectPrototype = new Object();
__objectPrototype.constructor = F // {DontEnum}, is not enumerable in loops
F.prototype = __objectPrototype
return F
```
- ==F.[[Prototype]] is a prototype of the function (constructor) and F.prototype is a prototype of objects created by this function== (because often there is a mess in terminology, and F.prototype in some articles is named as a “prototype of the constructor” that is incorrect).
- first-class functions
- Functions which can participate as normal data, i.e. be passed as arguments, receive functional arguments or be returned as functional values, are called first-class functions. In ECMAScript ==all functions are first-class==.
- In ECMAScript all functions are first-class.
- A ==free variable== is a variable which is used by a function, but is neither a parameter, nor a local variable of the function.
```javascript
function testFn() {
let localVar = 10;
function innerFn(innerParam) {
console.log(innerParam + localVar);
}
return innerFn;
}
let someFn = testFn();
someFn(20); // 30
```
```javascript
let z = 10;
function foo() {
console.log(z);
}
foo(); // 10 – with using both static and dynamic scope
(function () {
let z = 20;
// NOTE: always 10 in JS!
foo(); // 10 – with static scope, 20 – with dynamic scope
})();
// the same with passing foo
// as an arguments
(function (funArg) {
let z = 30;
funArg(); // 10 – with static scope, 30 – with dynamic scope
})(foo);
```
- Closure
- A closure is a combination of a code block and data of a context in which this code block is created.
- [[Scope]] : all inner functions share the same parent environment.
```javascript
var data = [];
for (var k = 0; k < 3; k++) {
data[k] = function () {
console.log(k);
};
}
data[0](); // 3, but not 0
data[1](); // 3, but not 1
data[2](); // 3, but not 2
```
```javascript
activeContext.Scope = [
... // higher variable objects
{data: [...], k: 3} // activation object
];
data[0].[[Scope]] === Scope;
data[1].[[Scope]] === Scope;
data[2].[[Scope]] === Scope;
```
- Creation of additional enclosing context
```javascript
var data = [];
for (var k = 0; k < 3; k++) {
data[k] = (function _helper(x) {
return function () {
console.log(x);
};
})(k); // pass "k" value
}
// now it is correct
data[0](); // 0
data[1](); // 1
data[2](); // 2
```
```javascript
data[0].[[Scope]] === [
... // higher variable objects
AO of the parent context: {data: [...], k: 3},
AO of the _helper context: {x: 0}
];
data[1].[[Scope]] === [
... // higher variable objects
AO of the parent context: {data: [...], k: 3},
AO of the _helper context: {x: 1}
];
data[2].[[Scope]] === [
... // higher variable objects
AO of the parent context: {data: [...], k: 3},
AO of the _helper context: {x: 2}
];
```
- OOP
- ECMAScript is the object-oriented programming language with the ==prototype== based implementation.
- Polymorphism
- one function can be applied to different objects, just like it would be the native characteristic of an object (because this value determinate on entering the execution context)
```javascript
function test() {
alert([this.a, this.b]);
}
test.call({a: 10, b: 20}); // 10, 20
test.call({a: 100, b: 200}); // 100, 200
var a = 1;
var b = 2;
test(); // 1, 2
```
- Encapsulation
- well known modifiers: private, protected and public which also are called as access levels (or access modifiers) to characteristics of objects.
- encapsulation is an increasing of abstraction, but ==not a paranoid hiding from “malicious hackers”== which, “want to write something directly into fields of your classes”.
```javascript
function A() {
var _a; // "private" a
this.getA = function _getA() {
return _a;
};
this.setA = function _setA(a) {
_a = a;
};
}
var a = new A();
a.setA(10);
alert(a._a); // undefined, "private"
alert(a.getA()); // 10
```
- Data type ( ES3rd )
- Undefined
- Null
- Boolean
- String
- Number
- Object
- Reference ( implementation level )
- List ( implementation level )
- Completion ( implementation level )
- Dynamic nature
```javascript
var foo = {x: 10};
// add new property
foo.y = 20;
console.log(foo); // {x: 10, y: 20}
// change property value to function
foo.x = function () {
console.log('foo.x');
};
foo.x(); // 'foo.x'
// delete property
delete foo.x;
console.log(foo); // {y: 20}
```
- freeze ( ES5 )
```javascript
var foo = {x: 10};
// freeze the object
Object.freeze(foo);
console.log(Object.isFrozen(foo)); // true
// can't modify
foo.x = 100;
// can't extend
foo.y = 200;
// can't delete
delete foo.x;
console.log(foo); // {x: 10}
```
- Boolean, String and Number objects
- Such objects are created with corresponding built in constructors and contain primitive value as one of internal properties. Object representation can be converted into primitive values and vice-versa.
```javascript
var c = new Boolean(true);
var d = new String('test');
var e = new Number(10);
// converting to primitive
// conversion: ToPrimitive
// applying as a function, without "new" keyword
с = Boolean(c);
d = String(d);
e = Number(e);
// back to Object
// conversion: ToObject
с = Object(c);
d = Object(d);
e = Object(e);
```
- there are also objects created by special built in constructors:
- Function (function objects constructor) Array (arrays constructor)
- RegExp (regular expressions constructor)
- Math (the mathematical module)
- Date (the constructor of dates)
- Associative arrays? ( Hash table )
- Really, they are similar enough, and in respect of ==“key-value”== pairs storage completely correspond to the theoretical “associative array” or “hash tables” data structures.
- there is no separated special term (hash or other) for that. Because any object regardless its internal properties can store these pairs:
```javascript
var a = {x: 10};
a['y'] = 20;
a.z = 30;
var b = new Number(1);
b.x = 10;
b.y = 20;
b['z'] = 30;
var c = new Function('');
c.x = 10;
c.y = 20;
c['z'] = 30;
// etc. – with any object "subtype"
```
- objects in ECMAScript because of ==delegation can be nonempty==, therefore the term “hash” also can be improper:
```javascript
Object.prototype.x = 10;
var a = {}; // create "empty" "hash"
console.log(a["x"]); // 10, but it's not empty
console.log(a.toString); // function
a["y"] = 20; // add new pair to "hash"
console.log(a["y"]); // 20
Object.prototype.y = 20; // and property into the prototype
delete a["y"]; // remove
console.log(a["y"]); // but key and value are still here – 20
```
- Also in ECMAScript concept of a “property” semantically is not separated into a ==“key”, “array index”, “method” or “property”==
- Type conversion
- To convert an object into a primitive value the method ==valueOf== can be used. As we noted, the call of the constructor (for certain types) as a function
- aaa
- ==valueOf== method allows objects to participate in various operations, for example, in addition:
```javascript
var a = new Number(1);
var b = new Number(2);
console.log(a + b); // 3
// or even so
var c = {
x: 10,
y: 20,
valueOf: function () {
return this.x + this.y;
}
};
var d = {
x: 30,
y: 40,
// the same .valueOf
// functionality as "с" object has,
// borrow it:
valueOf: c.valueOf
};
console.log(c + d); // 100
```
- For this ==toString== method is responsible, which in some operations is also applied automatically:
```javascript
var a = {
valueOf: function () {
return 100;
},
toString: function () {
return '__test';
}
};
// in this operation
// toString method is
// called automatically
console.log(a); // "__test" ( 實測會映出 "a" 這個物件 )
// but here - the .valueOf() method
console.log(a + 10); // 110
// but if there is no
// valueOf method, it
// will be replaced with the
//toString method
delete a.valueOf;
console.log(a + 10); // "_test10"
```
- with the new and without new
- ==Array or Function== constructors produce the same results when are called as a constructor (with new) and as a simple function (without new):
```javascript
var a = Array(1, 2, 3); // [object Array]
var b = new Array(1, 2, 3); // [object Array]
var c = [1, 2, 3]; // [object Array]
var d = Function(''); // [object Function]
var e = new Function(''); // [object Function]
```
- Constructor
- Constructor is a function that creates and initializes the newly created object.
- Though, even from initialization we can return different object ignoring this object
```javascript
function A() {
// update newly created object
this.x = 10;
// but return different object
return [1, 2, 3];
}
var a = new A();
console.log(a.x, a); undefined, [1, 2, 3]
```
- Algorithm of objects creation
```javascript
F.[[Construct]](initialParameters):
O = new NativeObject();
// property [[Class]] is set to "Object", i.e. simple object
O.[[Class]] = "Object"
// get the object on which
// at the moment references F.prototype
var __objectPrototype = F.prototype;
// if __objectPrototype is an object, then:
O.[[Prototype]] = __objectPrototype
// else:
O.[[Prototype]] = Object.prototype;
// where O.[[Prototype]] is the prototype of the object
// initialization of the newly created object
// applying the F.[[Call]]; pass:
// as this value – newly created object - O,
// arguments are the same as initialParameters for F
R = F.[[Call]](initialParameters); this === O;
// where R is the returned value of the [[Call]]
// in JS view it looks like:
// R = F.apply(O, initialParameters);
// if R is an object
return R
// else
return O
```
- the prototype of the created object is taken from the prototype property of a function on the current moment
- if at object initialization the [[Call]] has returned an object, exactly it is used as the result of the whole new expression:
```javascript
function A() {}
A.prototype.x = 10;
var a = new A();
console.log(a.x); // 10 – by delegation, from the prototype
// set .prototype property of the
// function to new object; why explicitly
// to define the .constructor property,
// will be described below
A.prototype = {
constructor: A,
y: 100
};
var b = new A();
// object "b" has new prototype
console.log(b.x); // undefined
console.log(b.y); // 100 – by delegation, from the prototype
// however, prototype of the "a" object
// is still old (why - we will see below)
console.log(a.x); // 10 - by delegation, from the prototype
function B() {
this.x = 10;
return new Array();
}
// if "B" constructor had not return
// (or was return this), then this-object
// would be used, but in this case – an array
var b = new B();
console.log(b.x); // undefined
console.log(Object.prototype.toString.call(b)); // [object Array]
```
- Prototype
- Communication with a prototype is organized via the internal, implicit and inaccessible directly [[Prototype]] property. A prototype can be either an object, or the null value.
- Via the inherited constructor property instances can indirectly get the reference to the prototype object:
```javascript
function A() {}
A.prototype.x = new Number(10);
var a = new A();
console.log(a.constructor.prototype); // [object Object]
console.log(a.x); // 10, via delegation
// the same as a.[[Prototype]].x
console.log(a.constructor.prototype.x); // 10
console.log(a.constructor.prototype.x === a.x); // true
```
- if we change function’s prototype property completely (via assigning a new object), the reference to the original constructor (as well as to the original prototype) is lost
```javascript
function A() {}
A.prototype = {
x: 10
};
var a = new A();
console.log(a.x); // 10
console.log(a.constructor === A); // false!
```
- Therefore, this reference should be restored manually:
```javascript
function A() {}
A.prototype = {
constructor: A,
x: 10
};
var a = new A();
console.log(a.x); // 10
console.log(a.constructor === A); // true
```
- ES5 introduced Object.getPrototypeOf(O) method, which directly returns the [[Prototype]] property of an object — the original prototype of the instance. However, in contrast with __proto__, being only a getter, it does not allow to set the prototype.
```javascript
var foo = {};
Object.getPrototypeOf(foo) == Object.prototype; // true
```
- instanceof operator
- This operator works exactly with the prototype chain of an object but not with the constructor itself.
- instanceof operator does is only ==takes the value of the Foo.prototype property== and checks its presence in the prototype chain of foo, starting from the foo.[[Prototype]].
```javascript
function A() {}
A.prototype.x = 10;
var a = new A();
console.log(a.x); // 10
console.log(a instanceof A); // true
// if set A.prototype
// to null...
A.prototype = null;
// ...then "a" object still
// has access to its
// prototype - via a.[[Prototype]]
console.log(a.x); // 10
// however, instanceof operator
// can't work anymore, because
// starts its examination from the
//prototype property of the constructor
console.log(a instanceof A); // error, A.prototype is not an object
```
- Prototype as a storage for methods and shared properties
- The most useful application of the prototype in ECMAScript is the storage of methods, default state and shared properties of objects.
```javascript
function A(x) {
this.x = x || 100;
}
A.prototype = (function () {
// initializing context,
// use additional object
var _someSharedVar = 500;
function _someHelper() {
console.log('internal helper: ' + _someSharedVar);
}
function method1() {
console.log('method1: ' + this.x);
}
function method2() {
console.log('method2: ' + this.x);
_someHelper();
}
// the prototype itself
return {
constructor: A,
method1: method1,
method2: method2
};
})();
var a = new A(10);
var b = new A(20);
a.method1(); // method1: 10
a.method2(); // method2: 10, internal helper: 500
b.method1(); // method1: 20
b.method2(); // method2: 20, internal helper: 500
// both objects are use
// the same methods from
// the same prototype
console.log(a.method1 === b.method1); // true
console.log(a.method2 === b.method2); // true
```
- Reading and writing properties
- As we mentioned, reading and writing of properties are managed by the internal methods ==[[Get]]== and ==[[Put]]==.
```javascript
Object.prototype.x = 100;
var foo = {};
console.log(foo.x); // 100, inherited
foo.x = 10; // [[Put]]
console.log(foo.x); // 10, own , [[Get]]
delete foo.x;
console.log(foo.x); // again 100, inherited
```
- Property accessors
- The ==dot notation== is used when the property name is a **valid identifier** name and in advance known, ==bracket notation== allows forming names of **properties dynamically**.
```javascript
var a = {testProperty: 10};
console.log(a.testProperty); // 10, dot notation
console.log(a['testProperty']); // 10, bracket notation
var propertyName = 'Property';
console.log(a['test' + propertyName]); // 10, bracket notation with dynamic property
```
- So, why in this example “primitive” value a has access to the toString method, but has no to the newly created test property?
```javascript
var a = 10; // primitive value
// but, it has access to methods,
// just like it would be an object
console.log(a.toString()); // "10"
// moreover, we can even
// (try) to create a new
// property in the "а" primitive calling [[Put]]
a.test = 100; // seems, it even works
// but, [[Get]] doesn't return
// value for this property, it returns
// by algorithm - undefined
console.log(a.test); // undefined
```
- First, as we said, after the property accessor is applied, it is already not a primitive, but the intermediate object.
```javascript
[[Put]]
// Algorithm of evaluating a.toString():
1. wrapper = new Number(a);
2. wrapper.toString(); // "10"
3. delete wrapper;
// Algorithm of evaluating a.test = 100:
1. wrapper = new Number(a);
2. wrapper.test = 100;
3. delete wrapper;
```
- We see that in step 3 the wrapper is removed and its newly created test property is of course also — with removing the object itself.
```javascript
[[Get]]
// Algorithm of evaluating a.test:
1. wrapper = new Number(a);
2. wrapper.test; // undefined
```
- Inheritance
- If you completely understand the simple algorithm of the ==[[Get]]== method, the question on inheritance in JavaScript will disappear by itself and the answer to it will become clear.
- we can do not create any constructors or objects but ==the whole language is already full of inheritance==. The line of code is very simple:
```javascript
console.log(1..toString()); // "1"
```
- Notice the subtle case of the syntax. Two dots in the example above is not an error. The first dot is used for **fractional part** of a number, and the second one is already a **property accessor**:
```javascript
1.toString(); // SyntaxError!
(1).toString(); // OK
1 .toString(); // OK (space after 1)
1..toString(); // OK
1['toString'](); // OK
```
- Prototype chain
```javascript
function A() {
console.log('A.[[Call]] activated');
this.x = 10;
}
A.prototype.y = 20;
var a = new A();
console.log([a.x, a.y]); // 10 (own), 20 (inherited)
function B() {}
// the easiest variant of prototypes
// chaining is setting child
// prototype to new object created,
// by the parent constructor
B.prototype = new A();
// fix .constructor property, else it would be А
B.prototype.constructor = B;
var b = new B();
console.log([b.x, b.y]); // 10, 20, both are inherited
// [[Get]] b.x:
// b.x (no) -->
// b.[[Prototype]].x (yes) - 10
// [[Get]] b.y
// b.y (no) -->
// b.[[Prototype]].y (no) -->
// b.[[Prototype]].[[Prototype]].y (yes) - 20
// where b.[[Prototype]] === B.prototype,
// and b.[[Prototype]].[[Prototype]] === A.prototype
```
- class
- In contrast, in the class based model, all properties are copied to the class-descendant.
- ES3
```javascript
Object.create ||
Object.create = function (parent, properties) {
function F() {}
F.prototype = parent;
var child = new F;
for (var k in properties) {
child[k] = properties[k].value;
}
return child;
}
```
- ES5
```javascript
var foo = {x: 10};
var bar = Object.create(foo, {y: {value: 20}});
console.log(bar.x, bar.y); // 10, 20
```
- ES6
```javascript
class Foo {
constructor(name) {
this._name = name;
}
getName() {
return this._name;
}
}
class Bar extends Foo {
getName() {
return super.getName() + ' Doe';
}
}
var bar = new Bar('John');
console.log(bar.getName()); // John Doe
```
---
#### ECMA-262-5 in detail
- Named data property
```javascript
var defaultDataPropertyAttributes = {
[[Value]]: undefined,
[[Writable]]: false,
[[Enumerable]]: false,
[[Configurable]]: false
};
```
- Unfortunately, in ES3 we had no control of property attributes what caused well known issues with augmentation of built-in prototypes.
```javascript
// ES3
Array.prototype.sum = function () {
// sum implementation
};
var a = [10, 20, 30];
// works fine
console.log(a.sum()); // 60
// but because of for-in examines the
// prototype chain as well, the new "sum"
// property is also enumerated, because has
// {DontEnum} == false
// iterate over properties
for (var k in a) {
console.log(k); // 0, 1, 2, sum
}
```
```javascript
// ES5
Object.defineProperty(Array.prototype, "sum", {
value: function arraySum() {
// sum implementation
},
enumerable: false
});
// now with using the same example this "sum"
// is no longer enumerable
for (var k in a) {
console.log(k); // 0, 1, 2
}
```
- Named accessor property
- A named accessor property associates a name (also — only a string) with one or two accessor functions: a getter and a setter.
- An accessor property can be defined either using already mentioned above meta-method ==Object.defineProperty==:
```javascript
var foo = {};
Object.defineProperty(foo, "bar", {
get: function getBar() {
return 20;
},
set: function setBar(value) {
// setting implementation
}
});
foo.bar = 10; // calls foo.bar.[[Set]](10)
// independently always 20
console.log(foo.bar); // calls foo.bar.[[Get]]()
```
- Strict mode
- Strict mode syntax and semantics of ECMAScript is explicitly made at the level of individual ECMAScript code units.
```javascript
"use strict";
// implementation of a module
```
- Strictness scope
```javascript
var eval = 10; // OK
function foo() {
"use strict";
alert(eval); // 10
eval = 20; // SyntaxError
}
foo();
```
- Assignment to an undeclared identifier
```javascript
// non-strict mode
(function foo() {
// local vars
var x = y = 20;
})();
// unfortunately, "y" wasn't local
// for "foo" function
alert(y); // 20
alert(x); // "x" is not defined
// ---------------
"use strict";
a = 10; // ReferenceError
var x = y = 20; // also a ReferenceError
```
- Scope
- Scope is an enclosing context in which a variable is associated with a value.
```javascript
// global "x"
int x = 10;
void foo() {
// local "x" of "foo" function
int x = 20;
if (true) {
// local "x" of if-block
int x = 30;
printf("%d", x); // 30
}
printf("%d", x); // 20
}
foo();
printf("%d", x); // 10
```
- Previously the “block-scope” could be implemented as an immediately-invoked function expression (IIFE):
```javascript
var x = 10;
if (true) {
(function (x) {
console.log(x); // 20
})(20);
}
console.log(x); // 10
```
- Static (lexical) scope
```javascript
var x = 10;
var y = 20;
function foo() {
console.log(x, y);
}
foo(); // 10, 20
function bar() {
var y = 30;
console.log(x, y); // 10, 30
foo(); // 10, 20
}
bar();
```
- Rebinding
- Often (and in ECMAScript in particular) rebinding is implemented via a simple operation of assignment.
```javascript
// bind "foo" to {x: 10} object
var foo = {x: 10};
console.log(foo.x); // 10
// bind "bar" to the *same* object
// as "foo" identifier is bound
var bar = foo;
console.log(foo === bar); // true
console.log(bar.x); // OK, also 10
// and now rebind "foo"
// to the new object
foo = {x: 20};
console.log(foo.x); // 20
// and "bar" still points
// to the old object
console.log(bar.x); // 10
console.log(foo === bar); // false
```
- First-class functions
- A first-class function is one that may participate as a normal data, i.e. be created literally at runtime, be passed as an argument, or be returned as a value from another function.
- Funargs and higher-order functions
- In turn, a function which accepts the “funarg” is called a higher-order function (HOF) or, closely to mathematics, an operator.
- Free variable
- A free variable is a variable which is used by a function, but is neither a parameter, nor a local variable of the function.
```javascript
// Global environment (GE)
var x = 10;
function foo(y) {
// environment of "foo" function (E1)
var z = 30;
function bar(q) {
// environment of "bar" function (E2)
return x + y + z + q;
}
// return "bar" to the outside
return bar;
}
var bar = foo(20);
bar(40); // 100
```