Home > Web Front-end > JS Tutorial > body text

In-depth understanding of JavaScript series (10) JavaScript core (a must-read for advanced experts)_javascript skills

WBOY
Release: 2016-05-16 17:56:59
Original
970 people have browsed it

Suitable readers: experienced developers, professional front-end personnel.

Original author: Dmitry A. Soshnikov
Release time: 2010-09-02
Original text: http://dmitrysoshnikov.com/ecmascript/javascript-the-core/
Reference 1 : http://ued.ctrip.com/blog/?p=2795
Reference 2: http://www.cnblogs.com/ifishing/archive/2010/12/08/1900594.html
Main It combines the Chinese translations of the two experts above and combines the best parts of the two articles together.

Let’s first take a look at the concept of object [Object], which is also the most basic concept in ECMASript.

Object Object
ECMAScript is a highly abstract object-oriented language, used to deal with Objects objects. Of course, there are also basic types, but when necessary, they also need to be converted into object objects. use.

An object is a collection of properties and has a single prototype object. The prototype may be either an object or the null value. The prototype object [prototype object]. This prototype object [prototype object] can be an object or null value.
Copy code
Let us take an example of a basic Object. First, we must understand that the prototype of an Object is a reference to the internal [[prototype]] attribute.

But generally speaking, we will use ____ underscores instead of double brackets, such as __proto__ (this is the specific implementation of the prototype concept of some script engines such as SpiderMonkey, although not standard).


var foo = {
x: 10,
y: 20
};


The above code foo object has two explicit properties [explicit own properties] and an implicit __proto__ property [implicit __proto__ property] , pointing to the prototype of foo.


Figure 1. A basic object with a prototype

Why do we need prototypes? Let us consider the concept of prototype chain to answer this question.

Prototype chain
Prototype objects are also ordinary objects, and may also have their own prototypes. If the prototype of a prototype object is not null, we call it a prototype. Chain (prototype chain).
A prototype chain is a finite chain of objects which is used to implemented inheritance and shared properties.

Imagine a situation like this, 2 objects, most of the content is the same, only a small part is different. Obviously, in a good design pattern, we will need to reuse that part of the same, and Do not define the same methods or properties repeatedly in every object. In a class-based system, these reused parts are called inheritance of classes - the same parts are put into class A, then class B and class C inherit from A and can claim something unique of each.

ECMAScript has no concept of classes. However, the concept of reuse is no different (in some respects, even more flexible than class-) and can be implemented by the prototype chain. This kind of inheritance is called delegation based inheritance, or more popularly, prototypal inheritance.

Similar to classes "A", "B", "C", in ECMAScript you create object classes "a", "b", "c", accordingly, object "a" owns object " The common part of b" and "c". At the same time objects "b" and "c" only contain their own additional properties or methods.



var a = { x: 10, calculate : function (z) { return this.x this.y z }}; var b = { y: 20, __proto__: a}; var c = { y: 30, __proto__: a}; // Call the inherited method b .calculate(30); // 60c.calculate(40); // 80


Doesn’t this look very simple? b and c can use the calculate method defined in a, which is implemented by the prototype chain [prototype chain].

The principle is very simple: if the calculate method cannot be found in object b (that is, there is no calculate attribute in object b), then the search will start along the prototype chain. If this calculate method is not found in the prototype of b, then the prototype of a will be found along the prototype chain, and the entire prototype chain will be traversed. Remember, once found, the first property or method found is returned. Therefore, the first property found becomes the inherited property. If the entire prototype chain is traversed and still not found, undefined will be returned.

Note that in an inheritance mechanism, the value of this still points to the object it originally belongs to, rather than the object it belongs to when it is found from the prototype chain. For example, in the above example, this.y is obtained from b and c, not a. Of course, you also discovered that this.x is taken from a, because it is found through the prototype chain mechanism.

If the prototype of an object has not been explicitly declared or defined, then the default value of __prototype__ is object.prototype, and object.prototype will also have a __prototype__, which is the end of the prototype chain. , is set to null.

The diagram below shows the inheritance relationship of the above a, b, c

Figure 2. Prototype chain

Prototype chains are usually used in situations where objects have the same or similar state structure (i.e. the same set of properties) and different state values. In this case, we can use Constructor to create objects in a specified pattern.
Constructor
In addition to creating objects, the constructor also does another useful thing - automatically sets the prototype object for the new object created. . The prototype object is stored in the ConstructorFunction.prototype property.

For example, if we rewrite the previous example and use the constructor to create objects "b" and "c", then object "a" plays the role of "Foo.prototype":

Copy code The code is as follows:

//Constructor
function Foo(y) {
//Construction The function will create objects in a specific mode: the created objects will have the "y" attribute
this.y = y;
}

// "Foo.prototype" stores the new object's Prototype reference
// So we can use it to define inheritance and shared properties or methods
// So, like the above example, we have the following code:

// Inherited properties" x"
Foo.prototype.x = 10;

// Inherited method "calculate"
Foo.prototype.calculate = function (z) {
return this.x this.y z ;
};

// Create "b" and "c" using foo pattern
var b = new Foo(20);
var c = new Foo(30);

// Call inherited method
b.calculate(30); // 60
c.calculate(40); // 80

// Let’s see if Used expected properties

console.log(

b.__proto__ === Foo.prototype, // true
c.__proto__ === Foo.prototype, // true

// "Foo.prototype" automatically creates a special attribute "constructor"
// Points to the constructor of a itself
// Instances "b" and "c" can be authorized through Find it and use it to test your own constructor

b.constructor === Foo, // true
c.constructor === Foo, // true
Foo.prototype.constructor == = Foo // true

b.calculate === b.__proto__.calculate, // true
b.__proto__.calculate === Foo.prototype.calculate // true

);

The above code can be expressed as the following relationship:

Figure 3. The relationship between constructors and objects

As can be seen from the above diagram, each object has a prototype. The constructor Foo also has its own __proto__, which is Function.prototype, and the __proto__ of Function.prototype points to Object.prototype. Let me reiterate , Foo.prototype is just an explicit attribute, that is, the __proto__ attribute of b and c.
A complete and detailed explanation of this issue can be found in Chapters 18 and 19 that Uncle will soon translate. There are two parts: Object-oriented programming. The general theory (OOP. The general theory), which describes different object-oriented paradigms and stylistics (OOP paradigms and stylistics), and comparison with ECMAScript. Object-oriented programming. ECMAScript implementation (OOP) . ECMAScript implementation), which specifically talks about object-oriented programming in ECMAScript.

Now that we have understood the basic object principles, let’s take a look at the program execution environment [runtime program execution] in ECMAScript. This is commonly called the "execution context stack" [execution context] stack]. Each element can be understood abstractly as an object. You may have discovered that, yes, in ECMAScript, you can see objects almost everywhere.

Execution Context Stack
There are three types of code in ECMASscript: global, function and eval.

The execution of each type of code depends on its own context. Of course, the global context may cover many function and eval instances. Every time a function is called, it will enter the context of function execution and calculate the values ​​of variables in the function. Each execution of the eval function will also enter the context of the eval execution to determine where the value of the variable should be obtained.

Note that a function may generate unlimited contexts, because a function call (even recursion) generates a new context.

Copy code The code is as follows:

function foo(bar) {}
// Calling the same function will generate 3 different contexts each time
//(containing different states, such as the value of parameter bar)

foo(10);
foo(20);
foo(30);

An execution context can activate another context, just like a function calls another function (or the global context calls a global function), and then calls it layer by layer. Logically speaking, this implementation is a stack, which we can call a context stack.

A context that activates other contexts is called a caller. The activated context is called the callee. The callee may also be the caller (for example, a function called in the global context calls some of its own internal methods).

When a caller activates a callee, the caller will suspend its own execution and then transfer control to the callee. So the callee is put on the stack, which is called the running context [running context] /active execution context]. When the context of this callee ends, control will be handed over to its caller again, and then the caller will continue execution where it was paused. After this caller ends, other contexts will continue to be triggered. A callee can end its own context by returning or throwing an exception.

As shown below, all ECMAScript program execution can be regarded as an execution context stack [execution context (EC) stack]. The top of the stack is the active context.

Figure 4. Execution context stack

When a program starts, it will first enter the global execution context [global execution context], which is also the bottom element in the stack. This global program will start to initialize and generate necessary objects and functions. During the execution of this global context, it may activate some methods (already initialized of course) and then enter their context. environment and then push the new element onto the stack. After these initializations are completed, the system will wait for some events (such as user mouse clicks, etc.), trigger some methods, and then enter a new context.

See Figure 5. There is a function context "EC1" and a global context "Global EC". The following figure shows the changes in the stack when entering and exiting "EC1" from "Global EC":

Figure 5. Changes in execution context stack

This is how the ECMAScript runtime system manages code execution.

For information about the ECMAScript execution context stack, please refer to Chapter 11 Execution context (Execution context) of this series of tutorials.

As mentioned above, each execution context in the stack can be represented as an object. Let's look at the structure of the context object and the state required to execute its code.
Execution Context
An execution context can be understood abstractly as an object. Each execution context has a set of properties (called context states) that are used to track the execution progress of the associated code. This diagram is the structure of a context.

Figure 6. Context structure

In addition to these three required attributes (variable object (variable object), this pointer (this value), scope chain (scope chain)), the execution context depends on the specific Implementations can also have any additional properties. Next, let's take a closer look at these three properties.

Variable Object
A variable object is a scope of data related with the execution context.
It's a special object associated with the context and which stores variables and function declarations are being defined within the context.

A variable object is the scope of data related to the execution context.
It is a special object associated with the context and is used to store variables and function declarations defined in the context.
Copy code
Note: Function expressions [function expressions] (rather than function declarations [function declarations, please refer to Chapter 2 of this series for the difference]) are not included in VO[variable object].

Variable Object is an abstract concept. In different contexts, it represents the use of different objects. For example, in the global context, the variable object is also the global object itself. (This is why we can point to global variables through the properties of the global object).

Let’s take a look at the global execution context in the following example:

Copy the code The code is as follows:

var foo = 10;

function bar() {} // // Function declaration
(function baz() {}); // Function expression

console.log(
this.foo == foo, // true
window.bar == bar // true
);

console.log(baz); // Reference error, baz is not defined

The variable object (VO) in the global context will have the following attributes:

Figure 7. Global variable object

As shown above, the function "baz" is not included in the variable object if it is used as a function expression. This is why trying to access outside a function produces a ReferenceError. Please note that in ECMAScript, compared to other languages ​​(such as C/C++), only functions can create new scopes. Variables and internal functions defined inside the function are not directly visible outside and do not pollute the global object. When using eval, we will also use a new (eval-created) execution context. eval will use the global variable object or the caller's variable object (the source of eval's call).

What about functions and their own variable objects? In the context of a function, variable objects are represented as activation objects.
Activation object
When a function is activated by the caller, this special activation object is created. It contains normal parameters (formal parameters) and special parameter (arguments) objects (parameter mapping table with indexed attributes). Active objects are used in function context as variable objects.

That is: the variable object of the function remains unchanged, but in addition to the storage variables and function declarations, it also contains special object arguments.

Consider the following situation:

Copy code The code is as follows:

function foo(x, y) {
var z = 30;
function bar() {} // Function declaration
(function baz() {}); // Function expression
}
foo(10, 20);

The next activation object (AO) of the "foo" function context is as shown below:

Figure 8. Activating objects

For the same reason, function expression is not included in AO.

Details of this AO can be found in Chapter 9 of this series of tutorials.

What we are going to talk about next is the third main object. As we all know, in ECMAScript, we will use inner functions [inner functions]. In these inner functions, we may refer to its parent function variables or global variables. We call these variable objects the scope object of the context. Similar to the prototype chain discussed above, we call it the scope chain here.
Scope Chains
A scope chain is a list of objects that are searched for identifiers appear in the code of the context.
A scope chain is a list of objects (list of objects) to retrieve identifiers that appear in the context code.
Copy code
The principle of scope chain is very similar to prototype chain. If the variable does not exist in its own scope, then it will look for the parent, all the way to the top level.

Identifiers [Identifiers] can be understood as variable names, function declarations and ordinary parameters. For example, when a function needs to reference a variable within its own function body, but the variable is not declared inside the function (or is not a parameter name), then the variable can be called a free variable [free variable]. Then we need to use the scope chain to search for these free variables.

In general, a scope chain includes the parent variable object (the top of the scope chain), the function's own variable VO and the activation object. However, in some cases other objects may also be included, such as dynamically added to the scope chain during execution - such as with or catch statements. [Annotation: with-objects refers to the temporary scope object generated by the with statement; catch-clauses refers to the catch clause, such as catch(e), which will generate exception objects and cause scope changes].

When looking for an identifier, it starts at the active object part of the scope chain, then (if the identifier is not found in the active object) searches at the top of the scope chain, and so on, just like the action Like a domain chain.

Copy code The code is as follows:

var x = 10;

( function foo() {
var y = 20;
(function bar() {
var z = 30;
// "x" and "y" are free variables
// Will be found in the next object in the scope chain (after the interactive object of function "bar")
console.log(x y z);
})();
})();

We assume that the object linkage of the scope chain is through an attribute called __parent__, which points to the next object in the scope chain. You can test this process in Rhino Code. This technology is indeed implemented in the ES5 environment (there is a called outer link). Of course, you can also use a simple data to simulate this model. Using the concept of __parent__, we can demonstrate the above code into the following situation. (Therefore, the parent variable is stored in the [[Scope]] attribute of the function).

Figure 9. Scope chain

During code execution, using the with or catch statement will change the scope chain. These objects are simple objects, and they will also have prototype chains. In this case, the scope chain will be searched in two dimensions.

  1. First, in the original scope chain
  2. The scope chain of each link point (if this link point has a prototype)

Let’s look at the following example:

Copy the code The code is as follows:

Object. prototype.x = 10;

var w = 20;
var y = 30;

// In the SpiderMonkey global object
// For example, the variable object of the global context It is inherited from "Object.prototype"
// So we can get "undeclared global variables"
// Because we can get it from the prototype chain

console.log(x) ; // 10

(function foo() {

// "foo" is a local variable
var w = 40;
var x = 100;

// "x" can be obtained from "Object.prototype", note that the value is 10
// Because {z: 50} is inherited from it

with ({z: 50 }) {
console.log(w, x, y, z); // 40, 10, 30, 50
}

// The "with" object is deleted from the scope chain After that
// x can be obtained from the context of foo again. Note that this time the value has returned to 100.
// "w" is also a local variable
console.log(x, w); / / 100, 40

// In the browser
// We can get the global w value through the following statement
console.log(window.w); // 20

})();

We will have the following structural diagram. This means that before we search for __parent__, we first go to the link to __proto__.

Figure 10. withincreased scope chain

Note that not all global objects are inherited from Object.prototype. The situation illustrated above can be tested in SpiderMonkey.

As long as the variable objects of all external functions exist, there is nothing special about referencing external data from internal functions - we just traverse the scope list and find the required variables. However, as mentioned above, when a context terminates, its state and itself will be destroyed, and the inner function will return from the outer function. Furthermore, the returned function may later be activated in other contexts, so what happens if a previously terminated context with some free variables is activated again? Generally speaking, the concept of solving this problem is similar to that in ECMAScript. Scope chains are directly related and are called (lexical) closures.
Closures
In ECMAScript, functions are "first-class" objects. This term means that functions can be passed as arguments to other functions (in this case, the function is called "funargs" - short for "functional arguments" [Annotation: I don't know if it is translated as functional arguments appropriately]) . Functions that accept "funargs" are called higher-order functions, or, more mathematically, operators. The runtime of other functions will also return functions, and these returned functions are called function valued functions (functions with functional value).

There are two conceptual problems between "funargs" and "functional values". These two sub-problems are called the "Funarg problem" ("functional parameter problem"). To accurately solve the problem of functional parameters, the concept of closure needs to be introduced. Let us describe these two problems in detail (as we can see, the [[Scope]] attribute of functions is used in ECMAScript to solve this problem).

A sub-problem of the "funarg problem" is the "upward funarg problem" [Annotation: It may be translated as: the upward search function parameter problem]. This problem will arise when a function returns to the outside from other functions. To be able to access the variables of the external context when the external context ends, the internal function needs to store it in the scope of the parent element of the [[Scope]] attribute at the time of creation (at creation moment). Then when the function is activated, the context's scope chain appears as the activation object combined with the [[Scope]] attribute (in fact, you can see it in the picture above):

Scope chain = Activation object [[ Scope]]
Scope chain = active object [[Scope]]

Please note that the main thing is - the function saves the outer scope when it is created, because of this saved role The saved scope chain will be used for variable lookup in future function calls.

Copy code The code is as follows:

function foo() {
var x = 10 ;
return function bar() {
console.log(x);
};
}

// "foo" also returns a function
// And this returned function can freely use the internal variable x

var returnedFunction = foo();

// global variable "x"
var x = 20;

// Support returned function
returnedFunction(); // The result is 10 instead of 20

This form of scope is called static scope [static/lexical scope]. The x variable above is found in [[Scope]] of function bar. Theoretically, there will also be dynamic scope [dynamic scope], that is, the above x is interpreted as 20, not 10. But EMCAScript does not use dynamic scope.

Another type of "funarg problem" is the top-down ["downward funarg problem"]. In this case, the parent's top and bottom will exist, but there will be when judging the value of a variable Ambiguity. That is, which scope should this variable use? Is it the scope when the function is created, or the scope when it is executed? To avoid this ambiguity, you can use closures, that is, use static scope.

Please see the example below:


Copy the code The code is as follows:

// Global variable "x"
var x = 10;

// Global function
function foo() {
console.log(x);
}

(function (funArg) {

// Local variable "x"
var x = 20;

// This will not be ambiguous
// Because we use the global variable "x" saved in [[Scope]] of the "foo" function,
// is not the "x" of the caller scope

funArg(); // 10, not 20

})(foo); // Pass foo as a "funarg"

From the above situation, we seem to be able to conclude that using static scope is a mandatory requirement for closures in the language. However, in some languages, a combination of dynamic and static scoping is provided, allowing the developer to choose which scope to use. But in ECMAScript, only static scope is used. So ECMAScript fully supports using the [[Scope]] attribute. We can get the following definition for closure:

A closure is a combination of a code block (in ECMAScript this is a function) and statically/lexically saved all parent scopes.
Thus, via these saved scopes a function may easily refer free variables.
A closure is a sequence of blocks of code (functions in ECMAScript) and statically holds the scopes of all parents. Free variables in the function are searched through these saved scopes.
Copy code
Please note that because every ordinary function saves [[Scope]] when it is created, theoretically, all functions in ECMAScript are closures.

Another very important point is that several functions may have the same parent scope (this is a very common situation, for example, there are several internal or global functions). In this case, variables existing in [[Scope]] are shared. Changes to variables in one closure will also affect the other closure.
Copy code The code is as follows:

function baz() {
var x = 1 ;
return {
foo: function foo() { return x; },
bar: function bar() { return --x; }
};
}

var closures = baz();

console.log(
closures.foo(), // 2
closures.bar() // 1
);

The above code can be represented by this picture:

Figure 11. Shared [[Scope]]

The above picture can cause confusion when creating multiple functions in a loop. If you use a loop variable (such as "k") in the created function, then all functions use the same loop variable, causing some programmers to often not get the expected value. Now it is clear why such a problem occurs - because all functions share the same [[Scope]], where the loop variable is the last complex assignment.

Copy code The code is as follows:

var data = [];

for (var k = 0; k < 3; k ) {
data[k] = function () {
alert(k);
};
}

data[0](); // 3, but not 0
data[1](); // 3, but not 1
data[2](); // 3, but not 2

There are some techniques to solve this kind of problem. One technique is to provide an additional object in the scope chain, such as adding a function:

Copy code The code is as follows:

var data = [];

for (var k = 0; k < 3; k ) {
data[k] = (function ( x) {
return function () {
alert(x);
};
})(k); // Pass k as a parameter
}

// The result is correct
data[0](); // 0
data[1](); // 1
data[2](); // 2


In-depth research and specific practice of closure theory can be found in Chapter 16 of this series of tutorials, Closures. If you want to get more information about scope chain, you can refer to Chapter 14 of this series of tutorials: Scope chain (Scope chain).

The next chapter will discuss the last attribute of an execution context - the concept of this pointer.

This pointer
A this value is a special object which is related with the execution context.
Therefore, it may be named as a context object (i.e. an object in which context the execution context is activated).
This is a special object that is closely related to the execution context. Therefore, it can also be called a context object (the context that activates the execution context).
Copy code
Any object can be used as the this value of the context. I would like to once again clarify some misunderstandings regarding execution context in ECMAScript - specifically this. Often, this is incorrectly described as a property of a variable object. I discovered it recently, for example in this book (although the chapter in the book that mentions this is not bad). Please remember:

a this value is a property of the execution context, but not a property of the variable object.
this is a property of the execution context, but not a property of the variable object. 🎜>Copy code
This feature is very important, because unlike variables, this does not have a process similar to searching for variables. When you use this in your code, the value of this is obtained directly from the execution context without searching in the scope chain. The value of this only depends on the situation when entering the context.

By the way, unlike ECMAScript, Python has a self parameter, which is similar to this, but can be changed during execution. In ECMAScript, you cannot assign a value to this, because, again, this is not a variable.

In the global context (global context), the value of this refers to the global object, which means that the value of this is the variable itself.

Copy code The code is as follows:
var x = 10;

console .log(
x, // 10
this.x, // 10
window.x // 10
);

in function context [function context ], this may become a different value according to each function call. This will be provided by each caller, and the caller is generated by calling the expression [call expression] (that is, how this function is activated and called) . For example, in the following example foo is a callee, activated in the global context. The following example shows the difference in this caused by different callers.

Copy code The code is as follows:
// The alert in the "foo" function has not changed
// But this is different every time it is activated

function foo() {
alert(this);
}

// caller activates "foo "This callee,
// And provide "this" to this callee

foo(); // Global object
foo.prototype.constructor(); // foo.prototype

var bar = {
baz: foo
};

bar.baz(); // bar

(bar.baz)(); // also bar
(bar.baz = bar.baz)(); // This is a global object
(bar.baz, bar.baz)(); // It is also a global object
(false || bar.baz)(); // Also a global object

var otherFoo = bar.baz;
otherFoo(); // Still a global object

If you want to think more deeply You can read Chapter 10 This of this series of tutorials about how the value of this changes (more importantly, how it changes) in each function call. The situations mentioned above will be discussed in detail in this chapter.

Conclusion
Here we have completed a brief overview. Although it may not seem so brief, a complete treatment of these topics would require an entire book. There are two important topics we didn't touch on: functions (and the differences between different types of functions, such as function declarations and function expressions) and ECMAScript's evaluation strategy. These two topics can be found in Chapter 15 Functions and Chapter 19 Evaluation Strategy (Evaluation strategy) of this series of tutorials respectively.

If you have any comments, questions or additions, I welcome discussing them in the article comments.

I wish everyone good luck in learning ECMAScript.
Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template