JavaScript goes deep from prototype to prototype chain
Constructor to create an object
Let's first create an object using the constructor:
function Person() { } let person = new Person(); person.name = 'name'; console.log(person.name) // name
In this example, person is a constructor. We use new to create an instance object person.
It's very simple. Let's get to the point:
prototype
Each function has a prototype attribute (prototype is the only attribute of the function), such as:
function Person() { } Person.prototype.name = 'name'; var person1 = new Person(); var person2 = new Person(); console.log(person1.name) // name console.log(person2.name) // name
The prototype attribute of the function points to an object, which is the prototype of the instance created by calling the constructor, that is, the prototypes of person1 and person2 in this example.
So what is a prototype? You can understand it this way: every JavaScript object (except null) will be associated with another object when it is created. This object is what we call the prototype, and each object will "inherit" properties from the prototype.
Let's use a diagram to show the relationship between the constructor and the instance prototype:
In this picture, we use person Prototype represents an instance prototype
So how do we represent instances and instance prototypes, that is, person and person At this time, we will talk about the second attribute:
proto
This is a property that every JavaScript object (except null) has, called__ proto__, This property points to the prototype of the object.
function Person() { } var person = new Person(); console.log(person.__proto__ === Person.prototype); //true
So we update the following diagram:
Since both the instance object and the constructor can point to the prototype, does the prototype have properties pointing to the constructor or instance?
constructor
There is no instance pointing, because a constructor can generate multiple instances, but there are prototypes pointing to constructors. This involves the third attribute: constructor. Each prototype has a constructor attribute pointing to the associated constructor
function Person() {} console.log(Person === Person.prototype.constructor); //true
So update the following diagram:
To sum up, we have come to the following conclusion:
function Person() {} var person = new Person(); console.log(person.__proto__ == Person.prototype) // true console.log(Person.prototype.constructor == Person) // true
ES5 method can obtain the prototype of the object
console.log(Object.getPrototypeOf(person) === Person.prototype) //true
After understanding the relationship among constructors, instance prototypes, and instances, let's talk about the relationship between instances and prototypes:
Examples and prototypes
When reading the properties of an instance, if it cannot be found, it will find the properties in the prototype associated with the object. If it cannot be found, it will find the prototype of the prototype until the top level is found.
for instance:
function Person() {} Person.prototype.name = 'name'; var person = new Person(); person.name = 'name of this person'; console.log(person.name) // name of this person delete person.name; console.log(person.name) // name copy code
In this example, we set the name attribute of person, so we can read it as' name of this person '. When we delete the name attribute of person, we read person Name, if you can't find the prototype of person, that is, person__ proto__ == Person. Fortunately, we found it as' name ', but what if we haven't found it yet? What is the prototype of the prototype?
Earlier, we have said that the prototype is also an object. Since it is an object, we can create it in the most primitive way, that is
var obj = new Object(); obj.name = 'name' console.log(obj.name) // name
Therefore, the prototype Object is generated through the Object constructor, combined with the examples mentioned earlier__ proto__ Point to the prototype of the constructor, so let's update the following diagram:
Prototype chain
That's object What about the prototype?
Null, well, it's null, so I found object Prototype can stop searching
So the last diagram is
By the way, the chain structure composed of interrelated prototypes in the figure is the prototype chain, that is, the blue line.
supplement
Finally, supplement and correct some imprecision in this paper:
The first is the constructor,
function Person() {}var person = new Person();console.log(person.constructor === Person); // true
When getting person When using the constructor, in fact, there is no constructor attribute in person. When the constructor attribute cannot be read, it will be retrieved from the prototype of person, that is, person Read from the prototype. It happens that the attribute exists in the prototype, so
person.constructor === Person.prototype.constructor
The second is__ proto__, Most browsers support this non-standard method access prototype, but it does not exist with person In prototype, in fact, it comes from object Prototype is not so much a property as a getter/setter when using obj__ proto__ It can be understood as returning object getPrototypeOf(obj)
Finally, about inheritance, we mentioned earlier that "every object" inherits "properties from the prototype". In fact, inheritance is a very confusing statement. Quoting the words in JavaScript you don't know, that is: inheritance means copy operation. However, JavaScript does not copy object properties by default. On the contrary, JavaScript only creates an association between two objects, so that one object can access the properties and functions of another object through a delegate. Therefore, the term delegate is more accurate than inheritance.
Lexical scope and dynamic scope of JavaScript
Scope
Scope is the area in the program source code where variables are defined.
The scope specifies how to find variables, that is, to determine the access rights of the currently executing code to variables.
Before ECMAScript6, there were only global scope and function scope.
JavaScript uses lexical scope, that is, static scope.
Static scope and dynamic scope
Because the lexical scope is adopted, the scope of the function is determined when the function is defined.
The lexical scope is opposite to the dynamic scope. The scope of a function is determined only when the function is called.
Let's take a closer look at an example to see the difference:
var value = 1;function foo() { console.log(value);}function bar() { var value = 2; foo();}bar();
When the static scope is used, execute the foo function, and first find out whether there is a local variable value from within the foo function. If not, find the code of the upper layer according to the writing position. Here is the global scope, that is, value is equal to 1, so 1 will be printed at last
When the dynamic scope is adopted, the foo function is still executed to find out whether there is a local variable value from within the foo function. If not, find the value variable from the scope of the calling function, that is, inside the bar function, so 2 will be printed at last
dynamic scope
You may wonder what language is dynamic scope?
Bash is a dynamic scope. If you don't believe it, save the following script as, for example, scope Bash, then enter the corresponding directory and execute bash with the command line/ scope.bash, see what the printed value is
value=1function foo () { echo $value;}function bar () { local value=2; foo; }bar
This file can also be found in demos/scope/ Found in.
Thinking questions
Finally, let's look at an example in the JavaScript authority Guide:
var scope = "global scope";function checkscope(){ var scope = "local scope"; function f(){ return scope; } return f();}checkscope();
var scope = "global scope";function checkscope(){ var scope = "local scope"; function f(){ return scope; } return f;}checkscope()();
Guess what the execution results of the two pieces of code are?
Here we will tell you the result directly. Both pieces of code will print 'local scope'.
The answer to quoting authoritative guidelines is:
The execution of JavaScript functions uses the scope chain, which is created when the function is defined. The nested function f() is defined in this scope chain, and the variable scope must be a local variable. No matter when and where the function f() is executed, this binding is still valid when f() is executed.
JavaScript deep execution context stack
Sequential execution?
If you want to ask about the execution sequence of JavaScript code, it must be an intuitive impression for developers who have written JavaScript, that is, sequential execution. After all:
var foo = function () { console.log('foo1');}foo(); // foo1var foo = function () { console.log('foo2');}foo(); // foo2
However, look at this Code:
function foo() { console.log('foo1');}foo(); // foo2function foo() { console.log('foo2');}foo(); // foo2
The result of printing is two foo2.
Those who have brushed the face-to-face test questions know that this is because the JavaScript engine does not analyze and execute the program line by line, but analyzes and executes it paragraph by paragraph. When a piece of code is executed, a "preparatory work" will be carried out, such as variable promotion in the first example and function promotion in the second example.
But what this article really wants us to think about is: how to divide the "paragraph" in this "paragraph by paragraph"?
What kind of code does the JavaScript engine do when it encounters?
Executable code
This is about the types of executable code in JavaScript?
In fact, it is very simple. There are three kinds: global code, function code and eval code.
For example, when a function is executed, preparations will be made. Let's use a more professional term for "preparations", which is called "execution contexts".
Execution Context Stack
Next, the question comes. We write more functions. How to manage so many execution contexts?
Therefore, the JavaScript engine creates an Execution context stack (ECS) to manage the execution context
To simulate the behavior of the execution context stack, let's define that the execution context stack is an array:
ECStack = [];
Imagine that when JavaScript starts to interpret and execute code, the first thing it encounters is the global code, so when initializing, it will first press a global execution context into the execution context stack. We use globalContext to represent it, and ECStack will be cleared only when the whole application ends, Therefore, there is always a globalContext at the bottom of ECStack:
ECStack = [ globalContext];
Now JavaScript encounters the following code:
function fun3() { console.log('fun3')}function fun2() { fun3();}function fun1() { fun2();}fun1();
When a function is executed, an execution context will be created and pushed into the execution context stack. When the function is executed, the execution context of the function will pop up from the stack. Knowing how this works, let's see how to handle the above code:
// Pseudo code / / fun1() ecstack push(<fun1> functionContext);// Fun2 is called in fun1, and the execution context ecstack of fun2 is created push(<fun2> functionContext);// Wipe, fun2 also called fun3! ECStack. push(<fun3> functionContext);// Fun3 finished ecstack pop();// Fun2 finished ecstack pop();// Fun1 finished ecstack pop();// JavaScript then executes the following code, but there is always a globalContext at the bottom of ecstack
Solve thinking problems
Well, now that we know how the execution context stack handles execution context, let's take a look at the previous article JavaScript deep lexical scope and dynamic scope Final question:
var scope = "global scope";function checkscope(){ var scope = "local scope"; function f(){ return scope; } return f();}checkscope();
var scope = "global scope";function checkscope(){ var scope = "local scope"; function f(){ return scope; } return f;}checkscope()();
The execution results of the two pieces of code are the same, but what are the differences between the two pieces of code?
The answer is that the execution context stack changes differently.
Let's simulate the first code:
ECStack.push(<checkscope> functionContext);ECStack.push(<f> functionContext);ECStack.pop();ECStack.pop();
Let's simulate the second code:
ECStack.push(<checkscope> functionContext);ECStack.pop();ECStack.push(<f> functionContext);ECStack.pop();
Is it a little different?
JavaScript deep variable object
preface
In the first part JavaScript deep execution context stack As mentioned in, when JavaScript code executes an executable code, the corresponding execution context will be created.
For each execution context, there are three important attributes:
- Variable object (VO)
- Scope chain
- this
Today we will focus on the process of creating variable objects.
Variable object
A variable object is a data scope related to the execution context and stores variable and function declarations defined in the context.
Because variable objects in different execution contexts are slightly different, let's talk about variable objects in global context and variable objects in function context.
Global Context
Let's first understand a concept called global object. stay W3C school It is also introduced in:
Global objects are predefined objects that serve as placeholders for JavaScript's global functions and global properties. By using global objects, you can access all other predefined objects, functions, and properties.
In the top-level JavaScript code, you can reference global objects with the keyword this. Because the global object is the head of the scope chain, this means that all unqualified variables and function names will be queried as the properties of the object.
For example, when JavaScript code refers to the parseInt() function, it refers to the parseInt attribute of the global object. The global object is the head of the scope chain, which also means that all variables declared in the top-level JavaScript code will become the properties of the global object.
If you don't understand it very well, let me introduce the global object again:
1. You can use this reference. In client JavaScript, the global object is the Window object.
console.log(this);
2. The global Object is an Object instantiated by the Object constructor.
console.log(this instanceof Object);
3. Predefined a bunch of, uh, a bunch of functions and attributes.
// Can work, console log(Math.random()); console. log(this.Math.random());
4. As the host of global variables.
var a = 1;console.log(this.a);
5. In the client JavaScript, the global object has a window attribute pointing to itself.
var a = 1;console.log(window.a);this.window.b = 2;console.log(this.b);
I spent a lot of time introducing global objects. In fact, I just want to say:
The variable object in the global context is the global object!
Function context
In the function context, we use the activation object (AO) to represent the variable object (VO).
Active objects and variable objects are actually the same thing, but variable objects are standardized or engine implemented and cannot be accessed in the JavaScript environment. Only when entering an execution context, the variable object of the execution context will be activated, so it is called activation object, and only the activated variable object, That is, various properties on the active object can be accessed.
The active object is created when it enters the function context. It is initialized by the arguments property of the function. The arguments property value is the arguments object.
Execution process
The code of execution context will be processed in two stages: analysis and execution, which can also be called:
- Enter execution context
- Code execution
Enter execution context
When entering the execution context, the code has not been executed at this time,
Variable objects include:
- All formal parameters of the function (if it is a function context)
- The properties of a variable object consisting of a name and a corresponding value are created
- There are no arguments, and the property value is set to undefined
- Function declaration
- The properties of a variable object consisting of a name and a corresponding value (function object) are created
- If a variable object already has a property with the same name, replace the property completely
- Variable declaration
- The attribute of a variable object composed of name and corresponding value (undefined) is created;
- If the variable name is the same as the declared formal parameter or function, the variable declaration will not interfere with the existing properties
for instance:
function foo(a) { var b = 2; function c() {} var d = function() {}; b = 3;}foo(1);
After entering the execution context, the AO at this time is:
AO = { arguments: { 0: 1, length: 1 }, a: 1, b: undefined, c: reference to function c(){}, d: undefined}
Code execution
In the code execution phase, the code will be executed sequentially, and the value of the variable object will be modified according to the code
As in the above example, when the code is executed, the AO is:
AO = { arguments: { 0: 1, length: 1 }, a: 1, b: 3, c: reference to function c(){}, d: reference to FunctionExpression "d"}
Here we have finished the process of creating variable objects. Let's briefly summarize what we said above:
- The variable object initialization of the global context is a global object
- The variable object initialization of the function context includes only the Arguments object
- When entering the execution context, initial attribute values such as formal parameters, function declarations and variable declarations will be added to the variable object
- During the code execution phase, the attribute value of the variable object is modified again
Thinking questions
Finally, let's look at a few examples:
1. Question 1
function foo() { console.log(a); a = 1;}foo(); // ???function bar() { a = 1; console.log(a);}bar(); // ???
An error will be reported in the first paragraph: Uncaught ReferenceError: a is not defined.
The second paragraph will print: 1.
This is because "a" in the function is not declared through the var keyword, and all will not be stored in AO.
When the console is executed in the first paragraph, the value of AO is:
AO = { arguments: { length: 0 }}
If there is no value of a, it will go to the global search, and there is no global, so an error will be reported.
When the console is executed in the second paragraph, the global object has been given the a attribute. At this time, the value of a can be found from the global, so 1 will be printed.
2. Question 2
console.log(foo);function foo(){ console.log("foo");}var foo = 1;
Functions are printed instead of undefined.
This is because when entering the execution context, the function declaration will be processed first, and the variable declaration will be processed second. If the variable name is the same as the declared formal parameter or function, the variable declaration will not interfere with the existing properties.
JavaScript deep scope chain
The fifth part of JavaScript in-depth series describes the creation process of action chain. Finally, combined with variable objects and execution context stack, let's take a look at what happened in the process of function creation and execution?
preface
stay JavaScript deep execution context stack As mentioned in, when JavaScript code executes an executable code, the corresponding execution context will be created.
For each execution context, there are three important attributes:
- Variable object (VO)
- Scope chain
- this
Today we will focus on scope chain.
Scope chain
stay JavaScript deep variable object As mentioned in, when searching for variables, you will first search from the variable object of the current context. If it is not found, you will search from the variable object of the execution context of the parent (the parent at the lexical level), and always find the variable object of the global context, that is, the global object. In this way, the linked list composed of variable objects of multiple execution contexts is called scope chain.
Next, let's explain how the scope chain is created and changed in two periods: the creation and activation of a function.
Function creation
stay JavaScript deep lexical scope and dynamic scope As mentioned in, the scope of a function is determined when the function is defined.
This is because the function has an internal attribute [[scope]]. When the function is created, all parent variable objects will be saved into it. You can understand that [[scope]] is the hierarchical chain of all parent variable objects, but note that [[scope]] does not represent a complete scope chain!
for instance:
function foo() { function bar() { // ... }}
When the function is created, the respective [[scope]] is:
foo.[[scope]] = [ globalContext.VO];bar.[[scope]] = [ fooContext.AO, globalContext.VO];
Function activation
When the function is activated, enter the function context, and after creating VO/AO, the active object will be added to the front end of the action chain.
At this time, the Scope chain of the execution context is named Scope:
Scope = [AO].concat([[Scope]]);
At this point, the scope chain is created.
Stroke it
Taking the following example as an example, combined with the variable object and execution context stack mentioned earlier, let's summarize the creation process of scope chain and variable object in function execution context:
var scope = "global scope";function checkscope(){ var scope2 = 'local scope'; return scope2;}checkscope();
The execution process is as follows:
1. The checkscope function is created to save the scope chain to the internal attribute [[scope]]
checkscope.[[scope]] = [ globalContext.VO];
2. Execute the checkscope function and create the execution context of the checkscope function. The execution context of the checkscope function is pushed into the execution context stack
ECStack = [ checkscopeContext, globalContext];
3. The checkscope function is not executed immediately. Start preparations. Step 1: copy the function [[scope]] attribute to create the scope chain
checkscopeContext = { Scope: checkscope.[[scope]],}
4. Step 2: create an active object with arguments, then initialize the active object and add formal parameters, function declarations and variable declarations
checkscopeContext = { AO: { arguments: { length: 0 }, scope2: undefined }}
5. Step 3: press the active object into the top of the checkscope scope chain
checkscopeContext = { AO: { arguments: { length: 0 }, scope2: undefined }, Scope: [AO, [[Scope]]]}
6. After the preparation work is completed, start to execute the function. With the execution of the function, modify the attribute value of AO
checkscopeContext = { AO: { arguments: { length: 0 }, scope2: 'local scope' }, Scope: [AO, [[Scope]]]}
7. Find the value of scope2. After returning, the function execution is completed, and the function context pops up from the execution context stack
ECStack = [ globalContext];
JavaScript in-depth interpretation of this from ECMAScript specification
JavaScript goes deep into the sixth part of the series. In this article, we trace back to the source and interpret how this is determined during function call from ECMAScript5 specification.
preface
stay JavaScript deep execution context stack As mentioned in, when JavaScript code executes an executable code, the corresponding execution context will be created.
For each execution context, there are three important properties
- Variable object (VO)
- Scope chain
- this
Today I'll focus on this, but it's not easy to talk about it.
......
Because we want to start with the ECMASciript5 specification.
Please send us ECMAScript 5.1 specification address:
English version: es5.github.io/#x15.1
Chinese version: yanhaijing.com/es5/#115
Let's start to understand the specification!
Types
The first is Chapter 8 Types:
Types are further subclassified into ECMAScript language types and specification types.
An ECMAScript language type corresponds to values that are directly manipulated by an ECMAScript programmer using the ECMAScript language. The ECMAScript language types are Undefined, Null, Boolean, String, Number, and Object.
A specification type corresponds to meta-values that are used within algorithms to describe the semantics of ECMAScript language constructs and ECMAScript language types. The specification types are Reference, List, Completion, Property Descriptor, Property Identifier, Lexical Environment, and Environment Record.
Let's simply translate:
ECMAScript is divided into language type and specification type.
ECMAScript language types can be operated by developers directly using ECMAScript. In fact, they are often referred to as Undefined, Null, Boolean, String, Number, and Object.
The specification type is equivalent to meta values, which is used to describe ECMAScript language structure and ECMAScript language type by algorithm. Specification types include: reference, list, completion, property descriptor, property identifier, legal environment, and Environment Record.
Don't you understand? It doesn't matter. We just need to know that there is another type only existing in the ECMAScript specification, which is used to describe the underlying behavioral logic of the language.
Today we will focus on the Reference type. It is closely related to the direction of this.
Reference
What is Reference?
Let's look at chapter 8.7 The Reference Specification Type:
The Reference type is used to explain the behaviour of such operators as delete, typeof, and the assignment operators.
Therefore, the Reference type is used to explain operations such as delete, typeof, and assignment.
Copying you Yuxi dada's words is:
The Reference here is a Specification Type, that is, "an abstract type that exists only in the specification". They exist to better describe the underlying behavioral logic of the language, but they do not exist in the actual js code.
Let's look at the following paragraph to introduce the content of Reference:
A Reference is a resolved name binding.
A Reference consists of three components, the base value, the referenced name and the Boolean valued strict reference flag.
The base value is either undefined, an Object, a Boolean, a String, a Number, or an environment record (10.2.1).
A base value of undefined indicates that the reference could not be resolved to a binding. The referenced name is a String.
This paragraph describes the composition of Reference, which consists of three parts:
- base value
- referenced name
- strict reference
But what are these?
We simply understand:
base value is the object or EnvironmentRecord where the attribute is located. Its value can only be one of undefined, an Object, a Boolean, a String, a Number, or an environment record.
referenced name is the name of the attribute.
for instance:
var foo = 1;// The corresponding Reference is: VAR fooreference = {base: environmentrecord, name: 'foo', strict: false};
Another example:
var foo = { bar: function () { return this; }};foo.bar(); // Foo / / the Reference corresponding to bar is: VAR barreference = {base: foo, propertyname: 'bar', strict: false};
Moreover, the specification also provides methods to obtain Reference components, such as GetBase and IsPropertyReference.
These two methods are very simple. Take a simple look:
1.GetBase
GetBase(V). Returns the base value component of the reference V.
Returns the base value of the reference.
2.IsPropertyReference
IsPropertyReference(V). Returns true if either the base value is an object or HasPrimitiveBase(V) is true; otherwise returns false.
Simple understanding: if base value is an object, it returns true.
GetValue
In addition, immediately after 8.7 The specification in Chapter 1 describes a method for obtaining the corresponding value from the Reference type: GetValue.
Simply simulate the use of GetValue:
var foo = 1;var fooReference = { base: EnvironmentRecord, name: 'foo', strict: false};GetValue(fooReference) // 1;
GetValue returns the true value of the object property, but note:
Call GetValue and return a specific value instead of a Reference
This is important, this is important, this is important.
How to determine the value of this
So much has been said about Reference, why should we talk about Reference? How does Reference relate to this topic? If you can patiently read the previous content, the following begin to enter the high-energy stage:
See specification 11.2 3 Function Calls:
Here is how to determine the value of this when the function is called.
Just look at step 1, step 6 and step 7:
1.Let ref be the result of evaluating MemberExpression.
6.If Type(ref) is Reference, then
a.If IsPropertyReference(ref) is true, then i.Let thisValue be GetBase(ref).b.Else, the base of ref is an Environment Record i.Let thisValue be the result of calling the ImplicitThisValue concrete method of GetBase(ref).
7.Else, Type(ref) is not Reference.
a. Let thisValue be undefined.
Let's describe:
1. Assign the result of calculating MemberExpression to ref
2. Judge whether ref is a Reference type
2.1 If ref yes Reference,also IsPropertyReference(ref) yes true, that this The value of is GetBase(ref)2.2 If ref yes Reference,also base value Value is Environment Record, that this The value of is ImplicitThisValue(ref)2.3 If ref no Reference,that this The value of is undefined
make a concrete analysis
Let's look at it step by step:
- The result of calculating MemberExpression is assigned to ref
What is MemberExpression? See specification 11.2 left hand side expressions:
MemberExpression :
- PrimaryExpression / / for the original expression, see Chapter 4 of the JavaScript authority guide
- FunctionExpression / / function definition expression
- Memberexpression [expression] / / property access expression
- MemberExpression . IdentifierName / / property access expression
- new MemberExpression Arguments / / object creation expressions
for instance:
function foo() { console.log(this)}foo(); // MemberExpression is foofunction foo() {return function() {console. Log (this)}} foo() ()// MemberExpression is foo() var foo = {bar: function() {return this;}} foo.bar(); // MemberExpression is foo bar
So simply understand that MemberExpression is actually the left part of ().
2. Judge whether ref is a Reference type.
The key is to see how the specification handles various memberexpressions and whether the returned result is a Reference type.
give an example:
var value = 1;var foo = { value: 2, bar: function () { return this.value; }}//Example 1console log(foo.bar());// Example 2console log((foo.bar)());// Example 3console log((foo.bar = foo.bar)());// Example 4console log((false || foo.bar)());// Example 5console log((foo.bar, foo.bar)());
foo.bar()
In example 1, the result of the MemberExpression calculation is foo Bar, then foo Is bar a Reference?
See specification 11.2 1. Property accessors. Here is a calculation process. No matter what, just look at the last step:
Return a value of type Reference whose base value is baseValue and whose referenced name is propertyNameString, and whose strict mode flag is strict.
We know that the expression returns a Reference type!
According to the previous content, we know that the value is:
var Reference = { base: foo, name: 'bar', strict: false};
Next, follow the judgment process in 2.1:
2.1 if ref is Reference and IsPropertyReference(ref) is true, the value of this is GetBase(ref)
If the value is of type Reference, what is the result of IsPropertyReference(ref)?
We have foreshadowed the IsPropertyReference method. If base value is an object, the result returns true.
base value is foo, which is an object, so the IsPropertyReference(ref) result is true.
At this time, we can determine the value of this:
this = GetBase(ref)
GetBase has also paved the way to obtain the base value. In this example, it is foo, so the value of this is foo, and the result of example 1 is 2!
Oh, my God, I'm so tired to prove that this points to foo! But knowing the principle, the rest is faster.
(foo.bar)()
See example 2:
console.log((foo.bar)());
foo.bar is enclosed by (), see specification 11.1 6 The Grouping Operator
Look directly at the results:
Return the result of evaluating Expression. This may be of type Reference.
NOTE This algorithm does not apply GetValue to the result of evaluating Expression.
In fact, () does not calculate MemberExpression, so the result is the same as that in example 1.
(foo.bar = foo.bar)()
See example 3. There is an assignment operator. See specification 11.13 1 Simple Assignment ( = ):
The third step of calculation:
3.Let rval be GetValue(rref).
Because GetValue is used, the returned value is not of type Reference,
According to the judgment logic mentioned earlier:
2.3 if ref is not a Reference, the value of this is undefined
This is undefined. In non strict mode, when the value of this is undefined, its value will be implicitly converted to a global object.
(false || foo.bar)()
See example 4, logic and algorithm, and see specification 11.11 Binary Logical Operators:
Step 2 of calculation:
2.Let lval be GetValue(lref).
Because GetValue is used, the returned is not of Reference type, and this is undefined
(foo.bar, foo.bar)()
Look at example 5, comma operator, and see specification 11.14 Comma Operator (,)
Step 2 of calculation:
2.Call GetValue(lref).
Because GetValue is used, the returned is not of Reference type, and this is undefined
Announce the results
So the result of the last example is:
var value = 1;var foo = { value: 2, bar: function () { return this.value; }}//Example 1console log(foo.bar()); // 2 / / example 2console log((foo.bar)()); // 2 / / example 3console log((foo.bar = foo.bar)()); // 1 / / example 4console log((false || foo.bar)()); // 1 / / example 5console log((foo.bar, foo.bar)()); // one
Note: the above results are in the non strict mode. In the strict mode, because this returns undefined, example 3 will report an error.
supplement
Finally, forget the most common case:
function foo() { console.log(this)}foo();
MemberExpression is foo. Resolve the identifier and see specification 10.3 1 identifier resolution, a value of Reference type will be returned:
var fooReference = { base: EnvironmentRecord, name: 'foo', strict: false};
Next, make a judgment:
2.1 if ref is Reference and IsPropertyReference(ref) is true, the value of this is GetBase(ref)
Because base value is an EnvironmentRecord, not an Object type, remember the possibility of the value of base value mentioned earlier? It can only be one of undefined, an Object, a Boolean, a String, a Number, and an environment record.
If the result of IsPropertyReference(ref) is false, enter the next judgment:
2.2 if ref is Reference and base value is Environment Record, the value of this is ImplicitThisValue(ref)
base value is exactly Environment Record, so ImplicitThisValue(ref) will be called
See specification 10.2 1.1. 6. Introduction to ImplicitThisValue method: this function always returns undefined.
So the last value of this is undefined.
One more word
Although we can simply understand this as the object calling the function, if so, how to explain the following example?
var value = 1;var foo = { value: 2, bar: function () { return this.value; }}console.log((false || foo.bar)()); // 1
In addition, how to determine who is the object calling the function? At the beginning of writing this article, I was faced with these problems. Finally, I gave up the idea of explaining the direction of this to you from multiple situations, but went back to the source to explain the direction of this from the ECMAScript specification. Although it is difficult to write and read from this perspective, once I read it several times and understand the principle, it will definitely give you a new perspective on this. You can also understand that although the final results of foo() and (foo.bar = foo.bar)() point to undefined, they are essentially different from each other from a normative point of view.
JavaScript deep execution context
The seventh part of the JavaScript in-depth series, combined with the four articles mentioned earlier, takes the demo of the authoritative guide as an example to explain how the execution context stack, variable object and scope chain change when a function is executed.
preface
stay JavaScript deep execution context stack As mentioned in, when JavaScript code executes an executable code, the corresponding execution context will be created.
For each execution context, there are three important attributes:
- Variable object (VO)
- Scope chain
- this
Then separately JavaScript deep variable object,JavaScript deep scope chain,"JavaScript in depth: interpreting this from ECMAScript specification" These three properties are explained in.
Before reading this article, if you are not very clear about the above concepts, I hope to read these articles first.
Because in this article, we will combine all the contents and talk about the specific processing process of execution context.
Thinking questions
stay JavaScript deep lexical scope and dynamic scope This paper puts forward such a thinking problem:
var scope = "global scope";function checkscope(){ var scope = "local scope"; function f(){ return scope; } return f();}checkscope();
var scope = "global scope";function checkscope(){ var scope = "local scope"; function f(){ return scope; } return f;}checkscope()();
Both pieces of code will print 'local scope'. Although the execution results of the two pieces of code are the same, what are the differences between the two pieces of code?
Then in the next article JavaScript deep execution context stack The difference between the two is that the changes of the execution context stack are different. However, if such a general answer is still not detailed enough, this article will analyze the specific change process of the execution context stack and the execution context in detail.
Specific implementation analysis
We analyze the first code:
var scope = "global scope";function checkscope(){ var scope = "local scope"; function f(){ return scope; } return f();}checkscope();
The execution process is as follows:
1. Execute the global code and create the global execution context. The global context is pushed into the execution context stack
ECStack = [ globalContext];
2. Global context initialization
globalContext = { VO: [global, scope, checkscope], Scope: [globalContext.VO], this: globalContext.VO}
2. While initializing, the checkscope function is created to save the internal attribute [[scope]] of the scope chain to the function
checkscope.[[scope]] = [ globalContext.VO ];
3. Execute the checkscope function and create the execution context of the checkscope function. The execution context of the checkscope function is pushed into the execution context stack
ECStack = [ checkscopeContext, globalContext ];
4. The checkscope function performs context initialization:
- Copy the function [[scope]] attribute to create a scope chain,
- Create an active object with arguments,
- Initialize the active object, that is, add formal parameters, function declarations and variable declarations,
- Push the active object into the top of the checkscope scope chain.
At the same time, the f function is created to save the internal attribute [[scope]] of the scope chain to the f function
checkscopeContext = { AO: { arguments: { length: 0 }, scope: undefined, f: reference to function f(){} }, Scope: [AO, globalContext.VO], this: undefined }
5. Execute the f function, create the f function execution context, and the f function execution context is pushed into the execution context stack
ECStack = [ fContext, checkscopeContext, globalContext ];
6. The F function performs context initialization, which is the same as step 4:
- Copy function [[scope]] attribute to create scope chain
- Creating an active object with arguments
- Initialize the active object, that is, add formal parameters, function declarations and variable declarations
- Push the active object into the top of the f scope chain
fContext = { AO: { arguments: { length: 0 } }, Scope: [AO, checkscopeContext.AO, globalContext.VO], this: undefined }
7.f function execution, find the scope value along the scope chain and return the scope value
8. After the f function is executed, the f function context pops up from the execution context stack
ECStack = [ checkscopeContext, globalContext ];
9. After the checkscope function is executed, the checkscope execution context pops up from the execution context stack
ECStack = [ globalContext ];
The second piece of code is left for you to try to simulate its execution process.
var scope = "global scope";function checkscope(){ var scope = "local scope"; function f(){ return scope; } return f;}checkscope()();
However, the execution process of this code will also be mentioned in the next JavaScript in-depth closure.
JavaScript deep closure
JavaScript is the eighth in-depth series, which introduces closures in theory and practice, and analyzes classic closure problems from the perspective of scope chain.
definition
MDN defines closure as:
Closures are functions that can access free variables.
So what is a free variable?
A free variable is a variable that is used in a function but is neither a function parameter nor a local variable of the function.
From this, we can see that closure consists of two parts:
Closure = function + free variable that function can access
for instance:
var a = 1;function foo() { console.log(a);}foo();
Foo function can access variable a, but a is neither a local variable nor a parameter of foo function, so a is a free variable.
Then, the free variable a accessed by the function foo + foo constitutes a closure
That's true!
Therefore, it is mentioned in the authoritative guide to javascript: from a technical point of view, all JavaScript functions are closures.
Eh, why is this different from the closure we usually see!?
Don't worry. This is a closure in theory. In fact, there is a closure in practice. Let's take a look at the definition in the article on closure translated by Uncle Tom:
In ECMAScript, closure refers to:
- From a theoretical point of view: all functions. Because they all save the data of the upper context when they are created. Even simple global variables are the same, because accessing global variables in a function is equivalent to accessing free variables. At this time, the outermost scope is used.
- From a practical point of view, the following functions are considered closures:
- Even if the context in which it was created has been destroyed, it still exists (for example, an inner function returns from a parent function)
- Free variables are referenced in the code
Next, let's talk about practical closures.
analysis
Let's write an example first. The example is still from the authoritative guide to JavaScript, with some changes:
var scope = "global scope";function checkscope(){ var scope = "local scope"; function f(){ return scope; } return f;}var foo = checkscope();foo();
First, we will analyze the changes of execution context stack and execution context in this code.
Another example similar to this code, in JavaScript deep execution context There is a very detailed analysis in. If you don't understand the following implementation process, it is recommended to read this article first.
Here is a brief implementation process:
- Enter the global code, create the global execution context, and press the global execution context into the execution context stack
- Global execution context initialization
- Execute the checkscope function, create the checkscope function execution context, and the checkscope execution context is pushed into the execution context stack
- checkscope performs context initialization and creates variable objects, scope chains, this, etc
- After the checkscope function is executed, the checkscope execution context pops up from the execution context stack
- Execute the F function, create the F function execution context, and the f execution context is pushed into the execution context stack
- f execute context initialization, create variable object, scope chain, this, etc
- After the f function is executed, the f function context pops up from the execution context stack
To understand this process, we should think about a problem, that is:
When the f function is executed, the checkscope function context has been destroyed (that is, it has been ejected from the execution context stack). How can the scope value under the checkscope scope scope be read?
If the above code is converted to PHP, an error will be reported, because in PHP, the f function can only read the values in its own scope and global scope, so it can't read the scope value under checkscope.
However, JavaScript is OK!
After we understand the specific execution process, we know that f execution context maintains a scope chain:
fContext = { Scope: [AO, checkscopeContext.AO, globalContext.VO],}
Yes, because of this scope chain, the f function can still read the checkscopeContext The value of Ao, indicating that when the f function references checkscopeContext When the value in Ao is, even if the checkscopeContext is destroyed, JavaScript will still make the checkscopeContext Ao lives in memory, and f function can still find it through the scope chain of f function. It is precisely because JavaScript does this, thus realizing the concept of closure.
Therefore, let's take a look at the definition of closure from a practical point of view:
- Even if the context in which it was created is destroyed, it still exists (for example, the inner function returns from the parent function)
- Free variables are referenced in the code
Here is another definition of closure in the original English version of the JavaScript authority Guide:
This combination of a function object and a scope (a set of variable bindings) in which the function's variables are resolved is called a closure in the computer science literature.
Closure is just a common concept in computer science. Don't think too complicated.
Required questions
Next, let's look at the closing question that must be brushed and tested in the interview:
var data = [];for (var i = 0; i < 3; i++) { data[i] = function () { console.log(i); };}data[0]();data[1]();data[2]();
The answer is 3. Let's analyze the reasons:
Before the data[0] function is executed, the VO of the global context is:
globalContext = { VO: { data: [...], i: 3 }}
When the data[0] function is executed, the scope chain of the data[0] function is:
data[0]Context = { Scope: [AO, globalContext.VO]}
AO of data[0]Context has no i value, so it will be from globalcontext Search in VO, i is 3, so the print result is 3.
data[1] is the same as data[2].
So let's change it to closure:
var data = [];for (var i = 0; i < 3; i++) { data[i] = (function (i) { return function(){ console.log(i); } })(i);}data[0]();data[1]();data[2]();
Before the data[0] function is executed, the VO of the global context is:
globalContext = { VO: { data: [...], i: 3 }}
It's as like as two peas before changing.
When the data[0] function is executed, the scope chain of the data[0] function changes:
data[0]Context = { Scope: [AO, Anonymous function Context.AO, globalContext.VO]}
AO of anonymous function execution context is:
Anonymous function Context = { AO: { arguments: { 0: 0, length: 1 }, i: 0 }}
AO of the data[0]Context has no i value, so it will start from anonymous function context When searching in AO, you will find that i is 0. If you find it, you will not go to globalcontext Found in VO, even if globalcontext VO also has the value of i (the value is 3), so the print result is 0.
data[1] is the same as data[2].
JavaScript depth parameters are passed by value
JavaScript goes deep into the ninth part of the series. In addition to passing by value and reference, there is a third way of passing - passing by share
definition
In the third edition of JavaScript advanced programming, 4.1 3. About transfer parameters:
The parameters of all functions in ECMAScript are passed by value.
What is pass by value?
In other words, copying values outside the function to parameters inside the function is the same as copying values from one variable to another.
Pass by value
Take a simple example:
var value = 1;function foo(v) { v = 2; console.log(v); //2}foo(value);console.log(value) // 1
It is well understood that when passing value to the function foo, it is equivalent to copying a copy of value, assuming that the copy is called_ Value, which is modified in the function_ Value without affecting the original value value.
Reference passing
Although copying is easy to understand, it will cause performance problems when the value is a complex data structure.
So there is another way to pass by reference.
The so-called pass by reference is to pass the reference of the object. Any change to the parameter inside the function will affect the value of the object, because they refer to the same object.
for instance:
var obj = { value: 1};function foo(o) { o.value = 2; console.log(o.value); //2}foo(obj);console.log(obj.value) // 2
Ah, no, even our red book says that the parameters of all functions in ECMAScript are passed by value. How can they be passed successfully by reference?
And is this reference passing?
The third delivery mode
No hurry, let's take another example:
var obj = { value: 1};function foo(o) { o = 2; console.log(o); //2}foo(obj);console.log(obj.value) // 1
If JavaScript adopts reference passing, the value of the outer layer will also be modified. Why hasn't it been changed? So isn't it really reference passing?
In fact, there is a third delivery method, which is called shared delivery.
Shared transfer refers to the transfer of a copy of the object's reference when the object is transferred.
Note: passing by reference is the reference of the passing object, and passing by share is the copy of the reference of the passing object!
Therefore, modifying o.value can find the original value by reference, but modifying o directly will not modify the original value. So the second and third examples are actually passed by sharing.
Finally, you can understand this:
Parameters are passed by value if they are basic types and by sharing if they are reference types.
However, because the copy copy is also a copy of the value, it is also directly considered as passed by value in the elevation.
So, elevation, who calls you a red treasure book!
Simulation Implementation of call and apply in JavaScript
JavaScript goes deep into the tenth part of the series. Through the simulation of call and apply, it takes you to uncover the truth that call and apply change this
call
call in one sentence:
The call() method calls a function or method on the premise of using a specified this value and several specified parameter values.
for instance:
var foo = { value: 1};function bar() { console.log(this.value);}bar.call(foo); // 1
Note two points:
- call changes the direction of this to foo
- The bar function executes
The first step of Simulation Implementation
So how do we simulate these two effects?
Imagine changing the foo object into the following when calling call:
var foo = { value: 1, bar: function() { console.log(this.value) }};foo.bar(); // 1
At this time, this points to foo. Is it very simple?
But this adds an attribute to the foo object itself, which is not good!
But don't worry, let's use delete to delete it~
Therefore, our simulation steps can be divided into:
- Set the function as the property of the object
- Execute the function
- Delete the function
The above examples are:
// Step 1 foo FN = bar / / step 2 foo Fn() / / step 3: delete foo fn
fn is the attribute name of the object. It should be deleted anyway, so it doesn't matter what it becomes.
According to this idea, we can try to write the call2 function in the first version:
// First edition function prototype. Call2 = function (context) {/ / first get the function calling call. Use this to get context. FN = this; context. Fn(); delete context. FN;}// Test var foo = {value: 1}; function bar() { console.log(this.value);} bar. call2(foo); // one
Just can print 1 Hey! Are you happy! (~ ̄▽ ̄)~
Simulation implementation step 2
At the beginning, I also said that the call function can also execute the function with given parameters. for instance:
var foo = { value: 1};function bar(name, age) { console.log(name) console.log(age) console.log(this.value);}bar.call(foo, 'kevin', 18);// kevin// 18// 1
Note: the parameters passed in are uncertain. What can I do?
No hurry. We can take values from the Arguments object, take the second to last parameters, and then put them into an array.
For example:
// For the above example, the arguments are: / / arguments = {/ / 0: foo, / / 1: 'Kevin', / / 2: 18, / / length: 3 / /} / / because arguments is a class array object, you can use the for loop var args = []; for(var i = 1, len = arguments.length; i < len; i++) { args.push('arguments[' + i + ']');}// After execution, args is [foo, 'kevin', 18]
After the indefinite length parameter problem is solved, we will then put the parameter array into the parameters of the function to be executed.
// Put the elements in the array as multiple parameters into the formal parameters of the function context fn(args.join(','))// (O_o)??// This method must not work!!!
Some people may think of using the ES6 method, but call is the ES3 method. In order to simulate the implementation of an ES3 method, we need to use the ES6 method. It seems that... Well, it's OK. But this time we use eval method to assemble a function, which is similar to this:
eval('context.fn(' + args +')')
Here args will automatically call array Tostring() this method.
Therefore, our second edition overcomes two major problems. The code is as follows:
// Second edition function prototype. call2 = function(context) { context.fn = this; var args = []; for(var i = 1, len = arguments.length; i < len; i++) { args.push('arguments[' + i + ']'); } eval('context.fn(' + args +')'); delete context. fn;}// Test var foo = {value: 1}; function bar(name, age) { console.log(name) console.log(age) console.log(this.value);} bar. call2(foo, 'kevin', 18); // kevin// 18// 1
(๑•̀ㅂ•́)و✧
Simulation implementation step 3
The simulation code has been completed by 80%. There are two points to note:
1.this parameter can be null. When null, it is regarded as pointing to window
for instance:
var value = 1;function bar() { console.log(this.value);}bar.call(null); // 1
Although the example itself does not use call, the result is the same.
2. Functions can have return values!
for instance:
var obj = { value: 1}function bar(name, age) { return { value: this.value, name: name, age: age }}console.log(bar.call(obj, 'kevin', 18));// Object {// value: 1,// name: 'kevin',// age: 18// }
But it's all very easy to solve. Let's look directly at the code of the third and last version:
// Third edition function prototype. call2 = function (context) { var context = context || window; context.fn = this; var args = []; for(var i = 1, len = arguments.length; i < len; i++) { args.push('arguments[' + i + ']'); } var result = eval('context.fn(' + args +')'); delete context. fn return result;}// Test var value = 2;var obj = { value: 1}function bar(name, age) { console.log(this.value); return { value: this.value, name: name, age: age }}bar.call(null); // 2console.log(bar.call2(obj, 'kevin', 18));// 1// Object {// value: 1,// name: 'kevin',// age: 18// }
So far, we have completed the simulation implementation of call and give ourselves a praise B ( ̄▽  ̄) d
Simulation Implementation of apply
The implementation of apply is similar to that of call. The code here is directly from the implementation of Zhihu @ Zheng hang:
Function.prototype.apply = function (context, arr) { var context = Object(context) || window; context.fn = this; var result; if (!arr) { result = context.fn(); } else { var args = []; for (var i = 0, len = arr.length; i < len; i++) { args.push('arr[' + i + ']'); } result = eval('context.fn(' + args + ')') } delete context.fn return result;}
Simulation and implementation of JavaScript deep bind
JavaScript goes deep into the eleventh part of the series. Through the simulation and implementation of bind function, we can really understand the characteristics of bind
bind
One sentence introduction bind:
The bind() method creates a new function. When this new function is called, the first parameter of bind() will be the this of its runtime, and the following sequence of parameters will be passed in as its parameters before the passed arguments. (from MDN)
From this, we can first get two characteristics of the bind function:
- Returns a function
- Parameters can be passed in
Simulation Implementation of return function
Starting with the first feature, let's take an example:
var foo = { value: 1};function bar() { console.log(this.value);}// Returned a function var bindfoo = bar bind(foo); bindFoo(); // one
For specifying the direction of this, we can use call or apply to implement it. For the simulation implementation of call and apply, you can see JavaScript in depth: Simulation Implementation of call and apply . Let's write the first version of the code:
// First edition function prototype. bind2 = function (context) { var self = this; return function () { self.apply(context); }}
Simulation of parameter transmission
Next, let's look at the second point. You can pass in parameters. This is a little puzzling. Can I pass parameters when I'm binding? Can I pass parameters when I execute the function returned by bind? Let's take an example:
var foo = { value: 1};function bar(name, age) { console.log(this.value); console.log(name); console.log(age);}var bindFoo = bar.bind(foo, 'daisy');bindFoo('18');// 1// daisy// 18
The function needs to pass two parameters: name and age. Unexpectedly, it can only pass one name when bind ing and another parameter age when executing the returned function!
What can I do? It's not urgent. We use arguments:
// Second edition function prototype. Bind2 = function (context) {var self = this; / / get the bind2 function from the second parameter to the last parameter var args = array.prototype.slice.call (arguments, 1); return function() {/ / the arguments at this time refer to the parameters passed in by the function returned by bind. Var bindArgs = array.prototype.slice.call (arguments); self.apply (context, args.concat(bindArgs)); }}
Simulation Implementation of constructor effect
Having completed these two points, the hardest part is here! Because another feature of bind is
A binding function can also create objects using the new operator: this behavior is like using the original function as a constructor. The value of this provided is ignored, and the parameters at the time of call are provided to the analog function.
That is, when the function returned by bind is used as the constructor, the this value specified during bind will become invalid, but the passed in parameters will still take effect. for instance:
var value = 2;var foo = { value: 1};function bar(name, age) { this.habit = 'shopping'; console.log(this.value); console.log(name); console.log(age);}bar.prototype.friend = 'kevin';var bindFoo = bar.bind(foo, 'daisy');var obj = new bindFoo('18');// undefined// daisy// 18console.log(obj.habit);console.log(obj.friend);// shopping// kevin
Note: Although the value value is declared in both global and foo, undefind is returned in the end, indicating that the bound this is invalid. If you understand the simulation implementation of new, you will know that this has pointed to obj at this time.
(ha ha, this is for my next article JavaScript in-depth series: Simulation Implementation of new Advertising).
So we can modify the prototype of the returned function. Let's write:
// Third edition function prototype. bind2 = function (context) { var self = this; var args = Array.prototype.slice.call(arguments, 1); var fbound = function () { var bindArgs = Array.prototype.slice.call(arguments) ; // When used as a constructor, this points to the instance and self points to the binding function, because the following sentence ` fbound prototype = this. prototype;`, Fbound. Has been modified Prototype is the prototype of the binding function. At this time, the result is true. When the result is true, this points to the instance// When it is a normal function, this points to the window and self points to the binding function. At this time, the result is false. When the result is false, this points to the bound context. self.apply(this instanceof self ? this : context, args.concat(bindArgs)); } // Modify the prototype of the return function to the prototype of the binding function, and the instance can inherit the value fbound. In the prototype of the function prototype = this. prototype; return fbound;}
If you are a little confused about the prototype chain, you can view it JavaScript in depth: from prototype to prototype chain.
Optimized implementation of constructor effect
But in this way, we directly put fbound prototype = this. Prototype, we directly modify fbound When using the prototype, the prototype of the function will also be modified directly. At this time, we can transit through an empty function:
// Fourth edition function prototype. bind2 = function (context) { var self = this; var args = Array.prototype.slice.call(arguments, 1); var fNOP = function () {}; var fbound = function () { var bindArgs = Array.prototype.slice.call(arguments); self.apply(this instanceof self ? this : context, args.concat(bindArgs)); } fNOP. prototype = this. prototype; fbound. prototype = new fNOP(); return fbound;}
So far, the big problems have been solved. Give yourself a praise! o( ̄▽ ̄)d
Three small problems
Let's deal with some small problems:
1. The code of apply is slightly different from that on MDN
When the MDN Chinese version talks about the simulation implementation of bind, the code here is:
self.apply(this instanceof self ? this : context || this, args.concat(bindArgs))
There is one more judgment about whether the context exists, but this is wrong!
for instance:
var value = 2;var foo = { value: 1, bar: bar.bind(null)};function bar() { console.log(this.value);}foo.bar() // 2
The above code will normally print 2. If you change to context | this, this code will print 1!
Therefore, we should not judge the context here. If you check the English version of the same content of MDN, this judgment does not exist!
2. What if the function calling bind is not a function?
No, we have to report mistakes!
if (typeof this !== "function") { throw new Error("Function.prototype.bind - what is trying to be bound is not callable");}
3. I want to use it online
Then don't forget to be compatible:
Function.prototype.bind = Function.prototype.bind || function () { // ......};
Of course, it's best to use it es5-shim la
Final code
So the last code is:
Function.prototype.bind2 = function (context) { if (typeof this !== "function") { throw new Error("Function.prototype.bind - what is trying to be bound is not callable"); } var self = this; var args = Array.prototype.slice.call(arguments, 1); var fNOP = function () {}; var fbound = function () { self.apply(this instanceof self ? this : context, args.concat(Array.prototype.slice.call(arguments))); } fNOP.prototype = this.prototype; fbound.prototype = new fNOP(); return fbound;}
Simulation Implementation of JavaScript in-depth new
JavaScript goes deep into the twelfth part of the series. Through the simulated implementation of new, it takes you to uncover the truth of using new to obtain constructor instances
new
Introduce new in one sentence:
The new operator creates an instance of a user-defined object type or one of the built-in object types with a constructor
Maybe it's a little difficult to understand. Before we simulate new, let's see what functions new implements.
for instance:
// Otaku otaku (name, age) {this. Name = name; this. Age = age; this. Hat = 'Games';}// Because of lack of exercise, physical strength is worrying otaku prototype. strength = 60; Otaku. prototype. sayYourName = function () { console.log('I am ' + this.name);} var person = new Otaku('Kevin', '18'); console. log(person.name) // Kevinconsole. log(person.habit) // Gamesconsole. log(person.strength) // 60person. sayYourName(); // I am Kevin
From this example, we can see that the instance person can:
- Access the properties in the Otaku constructor
- Visit otaku Properties in prototype
Next, we can try to simulate it.
Because new is a keyword, it cannot be directly overridden like the bind function, so we write a function named objectFactory to simulate the effect of new. It's like this:
function Otaku () { ......}// Use newvar person = new Otaku(...)// Use objectFactoryvar person = objectFactory(Otaku,...)
Preliminary implementation
analysis:
Because the result of new is a new object, we also need to create a new object when simulating the implementation. Suppose this object is called obj, because obj will have the attributes in the Otaku constructor. Think of the classic inheritance example, we can use Otaku Apply (obj, arguments) to add new attributes to obj.
In the first part of the JavaScript in-depth series, we talked about the prototype and prototype chain. We know that the proto attribute of the instance will point to the prototype of the constructor. It is precisely because of this relationship that the instance can access the attributes on the prototype.
Now we can try to write the first edition:
// Code of the first version: function objectfactory() {var obj = new object(), constructor = []. Shift. Call (arguments); obj. _proto_ = constructor. Prototype; constructor. Apply (obj, arguments); return obj;};
In this edition, we:
- Create a new object obj in the way of new Object()
- Take out the first parameter, which is the constructor we want to pass in. In addition, because shift will modify the original array, the first parameter of arguments will be removed
- Point the obj prototype to the constructor so that OBJ can access the properties in the constructor prototype
- Using apply, change the constructor this to point to the new object, so that obj can access the properties in the constructor
- Return obj
More about:
Prototype and prototype chain, you can see JavaScript in depth: from prototype to prototype chain
apply, you can see JavaScript in depth: Simulation Implementation of call and apply
Classic inheritance, you can see JavaScript deep inheritance
Copy the following code into the browser, and we can do the following tests:
function Otaku (name, age) { this.name = name; this.age = age; this.habit = 'Games';}Otaku.prototype.strength = 60;Otaku.prototype.sayYourName = function () { console.log('I am ' + this.name);}function objectFactory() { var obj = new Object(), Constructor = [].shift.call(arguments); obj.__proto__ = Constructor.prototype; Constructor.apply(obj, arguments); return obj;};var person = objectFactory(Otaku, 'Kevin', '18')console.log(person.name) // Kevinconsole.log(person.habit) // Gamesconsole.log(person.strength) // 60person.sayYourName(); // I am Kevin
[]( ̄▽ ̄)**
Return value effect implementation
Next, let's look at another case. If the constructor has a return value, for example:
function Otaku (name, age) { this.strength = 60; this.age = age; return { name: name, habit: 'Games' }}var person = new Otaku('Kevin', '18');console.log(person.name) // Kevinconsole.log(person.habit) // Gamesconsole.log(person.strength) // undefinedconsole.log(person.age) // undefined
In this example, the constructor returns an object, and only the properties in the returned object can be accessed in the instance person.
Also note that here we return an object. What if we just return a value of a basic type?
Another example:
function Otaku (name, age) { this.strength = 60; this.age = age; return 'handsome boy';}var person = new Otaku('Kevin', '18');console.log(person.name) // undefinedconsole.log(person.habit) // undefinedconsole.log(person.strength) // 60console.log(person.age) // 18
The result is completely reversed. Although there is a return value this time, it is equivalent to no return value for processing.
Therefore, we also need to judge whether the returned value is an object. If it is an object, we will return this object. If not, we will return what we should return.
Let's look at the second version of the code, which is also the last version of the code:
// Code of the second version function objectfactory() {var obj = new object(), constructor = []. Shift. Call (arguments); obj. _ proto_ = constructor. Prototype; VAR RET = constructor. Apply (obj, arguments); return typeof RET = = = 'object'? RET: Obj;};
JavaScript array objects and arguments
Part 13 of the JavaScript in-depth series explains the similarities and differences between class array objects and objects, as well as the key points for attention of arguments
Class array object
The so-called class array object:
An object that has a length attribute and several index attributes
for instance:
var array = ['name', 'age', 'sex'];var arrayLike = { 0: 'name', 1: 'age', 2: 'sex', length: 3}
Even so, why is it called a class array object?
Let's look at these two objects from three aspects: reading and writing, obtaining length and traversal.
Reading and writing
console.log(array[0]); // nameconsole.log(arrayLike[0]); // namearray[0] = 'new name';arrayLike[0] = 'new name';
length
console.log(array.length); // 3console.log(arrayLike.length); // 3
ergodic
for(var i = 0, len = array.length; i < len; i++) { // ......}for(var i = 0, len = arrayLike.length; i < len; i++) { // ......}
Isn't it very similar?
Can that kind of array object use array methods? For example:
arrayLike.push('4');
However, the above code will report an error: arraylike push is not a function
So it's still a class array
Call array method
What if the class array is willful and wants to use the array method?
Since we can't call it directly, we can use function Call indirect call:
var arrayLike = {0: 'name', 1: 'age', 2: 'sex', length: 3 }Array.prototype.join.call(arrayLike, '&'); // name&age&sexArray. prototype. slice. call(arrayLike, 0); // ["name", "age", "sex"] / / slice can convert class array to array array prototype. map. call(arrayLike, function(item){ return item.toUpperCase();}); // ["NAME", "AGE", "SEX"]
Class array to object
In the above example, a method of converting an array to an array has been mentioned, and three more are added:
var arrayLike = {0: 'name', 1: 'age', 2: 'sex', length: 3 }// 1. sliceArray.prototype.slice.call(arrayLike); // ["name", "age", "sex"] // 2. spliceArray.prototype.splice.call(arrayLike, 0); // ["name", "age", "sex"] // 3. ES6 Array.fromArray.from(arrayLike); // ["name", "age", "sex"] // 4. applyArray.prototype.concat.apply([], arrayLike)
So why talk about class array objects? And what are the applications of class arrays?
When it comes to class array objects, the Arguments object is a class array object. In client-side JavaScript, some DOM methods (document.getElementsByTagName(), etc.) also return class array objects.
Arguments object
Next, we will focus on the Arguments object.
The Arguments object is only defined in the function body, including the parameters and other properties of the function. In the function body, Arguments refers to the Arguments object of the function.
for instance:
function foo(name, age, sex) { console.log(arguments);}foo('name', 'age', 'sex')
The printing results are as follows:
arguments
We can see that in addition to the index attribute and length attribute of the class array, there is also a callee attribute. Next, we will introduce it one by one.
length attribute
The length attribute of the Arguments object indicates the length of the argument, for example:
function foo(b, c, d){ console.log("The length of the argument is:" + arguments.length)}console.log("The length of the formal parameter is:" + foo.length)foo(1)// Parameter length: 3 / / argument length: 1
callee attribute
The callee attribute of the Arguments object, through which you can call the function itself.
Let's talk about a closure classic interview question. Use callee's solution:
var data = [];for (var i = 0; i < 3; i++) { (data[i] = function () { console.log(arguments.callee.i) }).i = i;}data[0]();data[1]();data[2]();// 0// 1// 2
Next, let's talk about some important points of the arguments object:
Binding of arguments and corresponding parameters
function foo(name, age, sex, hobbit) { console.log(name, arguments[0]); // Name / / change the formal parameter name = 'new name'; console. log(name, arguments[0]); // New name new name / / change arguments arguments [1] = 'new age'; console. log(age, arguments[1]); // New age new age / / test whether the unaccepted data will be bound to the console log(sex); // undefined sex = 'new sex'; console. log(sex, arguments[2]); // new sex undefined arguments[3] = 'new hobbit'; console. log(hobbit, arguments[3]); // undefined new hobbit}foo('name', 'age')
The values of the passed in parameters, arguments and arguments will be shared. When they are not passed in, the values of arguments and arguments will not be shared
In addition, the above is in non strict mode. If it is in strict mode, the arguments and arguments will not be shared.
Transfer parameters
Pass arguments from one function to another
// Use apply to pass foo parameters to barfunction foo() {bar. Apply (this, arguments);} function bar(a, b, c) { console.log(a, b, c);} foo(1, 2, 3)
Powerful ES6
Using the... Operator of ES6, we can easily turn it into an array.
function func(...arguments) { console.log(arguments); // [1, 2, 3]}func(1, 2, 3);
application
In fact, there are many applications of arguments. In the next series, that is, the JavaScript special series, we will see the figure of arguments in the scenarios such as the extension implementation of jQuery, function coritization, recursion and so on. This article will not be expanded in detail.
If you want to summarize these scenarios, you can think of the following for the time being:
- Parameter indefinite length
- Function coritization
- Recursive call
- function overloading
JavaScript delves into a variety of ways to create objects and their advantages and disadvantages
JavaScript is the fourteenth in-depth series, which explains various ways to create objects, as well as their advantages and disadvantages.
Write in front
This article explains various ways to create objects, as well as their advantages and disadvantages.
But note:
This article is more like a note, because "JavaScript advanced programming" is really well written!
1. Factory mode
function createPerson(name) { var o = new Object(); o.name = name; o.getName = function () { console.log(this.name); }; return o;}var person1 = createPerson('kevin');
Disadvantages: the object cannot be recognized because all instances point to a prototype
2. Constructor mode
function Person(name) { this.name = name; this.getName = function () { console.log(this.name); };}var person1 = new Person('kevin');
Advantage: instances can be identified as a specific type
Disadvantages: each method is created once every time an instance is created
2.1 constructor pattern optimization
function Person(name) { this.name = name; this.getName = getName;}function getName() { console.log(this.name);}var person1 = new Person('kevin');
Advantages: it solves the problem that each method has to be recreated
Disadvantages: what is this called encapsulation
3. Prototype mode
function Person(name) {}Person.prototype.name = 'keivn';Person.prototype.getName = function () { console.log(this.name);};var person1 = new Person();
Advantage: the method is not recreated
Disadvantages: 1 All properties and methods are shared 2 Unable to initialize parameter
3.1 prototype mode optimization
function Person(name) {}Person.prototype = { name: 'kevin', getName: function () { console.log(this.name); }};var person1 = new Person();
Advantages: better encapsulation
Disadvantages: the prototype is rewritten and the constructor attribute is lost
3.2 prototype mode optimization
function Person(name) {}Person.prototype = { constructor: Person, name: 'kevin', getName: function () { console.log(this.name); }};var person1 = new Person();
Advantages: the instance can find its constructor through the constructor attribute
Disadvantages: what are the disadvantages of the prototype pattern
4. Combination mode
Constructor mode and prototype mode are a combination of two swords.
function Person(name) { this.name = name;}Person.prototype = { constructor: Person, getName: function () { console.log(this.name); }};var person1 = new Person();
Advantages: the shared, the private, the most widely used way
Disadvantages: some people just want to write it all together, that is, better encapsulation
4.1 dynamic prototype mode
function Person(name) { this.name = name; if (typeof this.getName != "function") { Person.prototype.getName = function () { console.log(this.name); } }}var person1 = new Person();
Note: when using dynamic prototyping mode, you cannot rewrite the prototype with object literals
Explain why:
function Person(name) { this.name = name; if (typeof this.getName != "function") { Person.prototype = { constructor: Person, getName: function () { console.log(this.name); } } }}var person1 = new Person('kevin');var person2 = new Person('daisy');// The error report does not contain the method person1 getName();// Comment out the above code, this sentence is executable. person2.getName();
To explain this, suppose you start executing var person1 = new Person('kevin ').
If you are not familiar with the underlying execution process of new and apply, you can read the article in the relevant link at the bottom.
Let's review the implementation steps of new:
- First, create a new object
- Then point the prototype of the object to person prototype
- Then person apply(obj)
- Return this object
Note that at this time, review the implementation steps of apply and execute obj The person method will execute the contents of the if statement at this time. Note that the prototype attribute of the constructor points to the prototype of the instance and directly overwrites person in a literal way Prototype does not change the prototype value of the instance. person1 still points to the previous prototype, not person prototype. The previous prototype had no getName method, so it reported an error!
If you just want to write code literally, try this:
function Person(name) { this.name = name; if (typeof this.getName != "function") { Person.prototype = { constructor: Person, getName: function () { console.log(this.name); } } return new Person(name); }}var person1 = new Person('kevin');var person2 = new Person('daisy');person1.getName(); // kevinperson2.getName(); // daisy
5.1 parasitic constructor mode
function Person(name) { var o = new Object(); o.name = name; o.getName = function () { console.log(this.name); }; return o;}var person1 = new Person('kevin');console.log(person1 instanceof Person) // falseconsole.log(person1 instanceof Object) // true
Parasitic constructor mode, I personally think it should be read as follows:
Parasitism constructor pattern, that is, a method of parasitism in the constructor.
In other words, under the guise of a constructor, you can't point to the constructor when you create an instance using instanceof!
This method can be used in special cases. For example, if we want to create a special Array with additional methods, but do not want to directly modify the Array constructor, we can write as follows:
function SpecialArray() { var values = new Array(); for (var i = 0, len = arguments.length; i < len; i++) { values.push(arguments[i]); } values.toPipedString = function () { return this.join("|"); }; return values;}var colors = new SpecialArray('red', 'blue', 'green');var colors2 = SpecialArray('red2', 'blue2', 'green2');console.log(colors);console.log(colors.toPipedString()); // red|blue|greenconsole.log(colors2);console.log(colors2.toPipedString()); // red2|blue2|green2
You will find that the so-called parasitic constructor mode uses one more new when creating objects than the factory mode. In fact, the results of the two are the same.
However, the author may hope to use SpecialArray like ordinary Array. Although it can also be used as a function, this is not the author's original intention and becomes inelegant.
Do not use this mode when other modes can be used.
However, it is worth mentioning that the loop in the above example:
for (var i = 0, len = arguments.length; i < len; i++) { values.push(arguments[i]);}
Can be replaced by:
values.push.apply(values, arguments);
5.2 safe constructor mode
function person(name){ var o = new Object(); o.sayName = function(){ console.log(name); }; return o;}var person1 = person('kevin');person1.sayName(); // kevinperson1.name = "daisy";person1.sayName(); // kevinconsole.log(person1.name); // daisy
The so-called secure object refers to an object that has no public attributes and its methods do not reference this.
There are two differences from the parasitic constructor mode:
- The newly created instance method does not reference this
- Call the constructor without the new operator
Secure objects are best suited for some secure environments.
The safe constructor pattern, like the factory pattern, does not recognize the type of object.
There are many ways and advantages and disadvantages of JavaScript in-depth inheritance
Part 15 of the JavaScript in-depth series explains various inheritance methods, advantages and disadvantages of JavaScript.
Write in front
This article explains various inheritance methods, advantages and disadvantages of JavaScript.
But note:
This article is more like a note. Hey, let me sigh again: "JavaScript advanced programming" is really well written!
1. Prototype chain inheritance
function Parent () { this.name = 'kevin';}Parent.prototype.getName = function () { console.log(this.name);}function Child () {}Child.prototype = new Parent();var child1 = new Child();console.log(child1.getName()) // kevin
Question:
1. The attribute of reference type is shared by all instances, for example:
function Parent () { this.names = ['kevin', 'daisy'];}function Child () {}Child.prototype = new Parent();var child1 = new Child();child1.names.push('yayu');console.log(child1.names); // ["kevin", "daisy", "yayu"]var child2 = new Child();console.log(child2.names); // ["kevin", "daisy", "yayu"]
2. When creating an instance of Child, you cannot pass parameters to the Parent
2. Borrow constructor (classic inheritance)
function Parent () { this.names = ['kevin', 'daisy'];}function Child () { Parent.call(this);}var child1 = new Child();child1.names.push('yayu');console.log(child1.names); // ["kevin", "daisy", "yayu"]var child2 = new Child();console.log(child2.names); // ["kevin", "daisy"]
advantage:
1. Avoid the attribute of reference type being shared by all instances
2. You can pass parameters to the Parent in the Child
for instance:
function Parent (name) { this.name = name;}function Child (name) { Parent.call(this, name);}var child1 = new Child('kevin');console.log(child1.name); // kevinvar child2 = new Child('daisy');console.log(child2.name); // daisy
Disadvantages:
Methods are defined in the constructor, and methods are created every time an instance is created.
3. Combination inheritance
Prototype chain inheritance and classic inheritance are combined.
function Parent (name) { this.name = name; this.colors = ['red', 'blue', 'green'];}Parent.prototype.getName = function () { console.log(this.name)}function Child (name, age) { Parent.call(this, name); this.age = age;}Child.prototype = new Parent();var child1 = new Child('kevin', '18');child1.colors.push('black');console.log(child1.name); // kevinconsole.log(child1.age); // 18console.log(child1.colors); // ["red", "blue", "green", "black"]var child2 = new Child('daisy', '20');console.log(child2.name); // daisyconsole.log(child2.age); // 20console.log(child2.colors); // ["red", "blue", "green"]
Advantages: integrating the advantages of prototype chain inheritance and constructor, it is the most commonly used inheritance mode in JavaScript.
4. Prototype inheritance
function createObj(o) { function F(){} F.prototype = o; return new F();}
Es5 object The simulation implementation of create takes the incoming object as the prototype of the created object.
Disadvantages:
Property values containing reference types always share corresponding values, just like prototype chain inheritance.
var person = { name: 'kevin', friends: ['daisy', 'kelly']}var person1 = createObj(person);var person2 = createObj(person);person1.name = 'person1';console.log(person2.name); // kevinperson1.firends.push('taylor');console.log(person2.friends); // ["daisy", "kelly", "taylor"]
Note: modify person1 Value of name, person2 The value of name has not changed, not because person1 and person2 have independent name values, but because person1 Name = 'person1', adding a name value to person1 is not modifying the name value on the prototype.
5. Parasitic inheritance
Create a function that is only used to encapsulate the inheritance process. The function internally enhances the object in some form, and finally returns the object.
function createObj (o) { var clone = object.create(o); clone.sayName = function () { console.log('hi'); } return clone;
Disadvantages: like borrowing the constructor pattern, the method is created every time an object is created.
6. Parasitic combinatorial inheritance
For your convenience, repeat the code of composite inheritance here:
function Parent (name) { this.name = name; this.colors = ['red', 'blue', 'green'];}Parent.prototype.getName = function () { console.log(this.name)}function Child (name, age) { Parent.call(this, name); this.age = age;}Child.prototype = new Parent();var child1 = new Child('kevin', '18');console.log(child1)
The biggest disadvantage of composite inheritance is that it calls the parent constructor twice.
The first is to set the prototype of subtype instances:
Child.prototype = new Parent();
When creating a subtype instance:
var child1 = new Child('kevin', '18');
Recall the simulation implementation of new. In fact, in this sentence, we will execute:
Parent.call(this, name);
Here, we will call the Parent constructor again.
So, in this example, if we print the child1 object, we will find child Both prototype and child1 have an attribute of colors and attribute values of ['Red ',' Blue ',' green '].
So how can we keep improving and avoid this repeated call?
If we don't use child Prototype = new parent(), but indirectly let child Prototype access to parent What about prototype?
See how:
function Parent (name) { this.name = name; this.colors = ['red', 'blue', 'green'];}Parent.prototype.getName = function () { console.log(this.name)}function Child (name, age) { Parent.call(this, name); this.age = age;}// The key three steps var F = function () {};F.prototype = Parent.prototype;Child.prototype = new F();var child1 = new Child('kevin', '18');console.log(child1);
Finally, we encapsulate this inheritance method:
function object(o) { function F() {} F.prototype = o; return new F();}function prototype(child, parent) { var prototype = object(parent.prototype); prototype.constructor = child; child.prototype = prototype;}// When we use: prototype(Child, Parent);
The praise of parasitic combinatorial inheritance in JavaScript advanced programming is quoted as follows:
The efficiency of this method shows that it only calls the Parent constructor once, and therefore avoids Create unnecessary and redundant attributes on prototype. At the same time, the prototype chain can remain unchanged; Therefore, instanceof and isPrototypeOf can also be used normally. Developers generally believe that parasitic composite inheritance is the most ideal inheritance paradigm for reference types.