# 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 ```