Specialized javascript addressing, closures, object models and related issues

Source: Internet
Author: User

It is precisely because JS is a dynamic language that JS addressing is field addressing, rather than just like C. Compile and confirm. In addition, JS introduces the this pointer, which is very troublesome because it is "implicitly" passed to the function as a parameter. Let's first look at the example in the "Scope chain" topic:
Var testvar = 'window properties ';
Var o1 = {testvar: '1', fun: function () {alert ('o1: '+ this. testvar );}};
Var o2 = {testvar: '2', fun: function () {alert ('o2: '+ this. testvar );}};
O1.fun (); // '1'
O2.fun (); // '2'
O1.fun. call (o2); // The three alert results of '2' are different. Isn't it interesting? In fact, all interesting and strange concepts can end up with addressing.
Addressing simple variables
Is JS static or dynamic scope?
It tells you an unfortunate message that JS uses static scopes, or variable addressing is much more complex than dynamic scope languages such as perl. The following code is an example of the principle of the programming language:
01 | function big (){
02 | var x = 1;
03 | eval ('f1 = function () {echo (x )}');
04 | function f2 () {var x = 2; f1 ()};
05 | f2 ();
06 | };
07 | big ();
The output is 1, which is exactly the same as pascal and ada, although f1 is dynamically defined using eval. Another example also comes from the programming language principle:
Function big2 (){
Var x = 1;
Function f2 () {echo (x)}; // use the value of x to generate an output.
Function f3 () {var x = 3; f4 (f2 )};
Function f4 (f) {var x = 4; f ()};
F3 ();
}
Big2 (); // output 1: Deep binding; Output 4: Light binding; Output 3: special binding
The output is still 1, indicating that JS is not only a static scope, but also a deep binding. This is a big deal ......
ARI Concept
To explain complex addressing issues during the running of a function (especially in languages that allow function nesting, such as Ada), the program design language principles book defines ARI ": it is a number of records on the stack, including:
Function address
Local variable
Return address
Dynamic Link
Static Link
Here, the dynamic link always points to the caller of a function (for example, if B calls a during execution, then the dynamic link in ARI of a points to B ); static links describe the parent element when a is defined. Because the function is organized by a root tree, all static links will be directed to the host (such as window) after summary ), let's look at the example (output after annotation ):
Var x = 'x in host ';
Function a () {echo (x )};
Function B () {var x = 'x inside B '; echo (x )};
Function c () {var x = 'x inside C'; ()};
Function d (){
Var x = 'x inside d, a closure-made function ';
Return function () {echo (x )}};
A (); // x in host
B (); // x inside B
C (); // x in host
D (); // x inside d, a closure-made function when calling the First sentence, we can regard it as "stack" with the following content (top stack on the left):
[ARI] → [host] a's static chain is directly directed to the host, because x is not defined in A. When the interpreter looks for x, x is found in the host along the static chain; the call to B is because x is recorded in the local variable of B, so the final echo is x in B: 'X inside B ';
Now c is much more interesting. When Calling c, you can write the stack information as follows:
Dynamic Link: [a] → [c] → [host]
Static link: [c] → [host]; [a] → [host]
Because the addressing of x is only performed after calling a, static links are directly pushed to the host. Naturally, x is still 'x in host!
D is even more interesting. d creates a function as the return value, which is then called ~ Because the return value of d is created within the lifecycle of d, the static link stamp of the return value of d is directed to d. Therefore, when calling the function, the x: 'x inside d in d is output, a closure-made function '.
Creation Time of static Link
Yueying and amingoo said that "closure" is the "reference during call" of the function. "The Principle of programming language" is simply called ARI, but what's different is that, ARI in the principle of programming language is stored in the stack, and once the lifecycle of the function ends, ARI will be destroyed. But the closure of JS is not like this, and the closure will be destroyed, if and only if there is no reference pointing to it and its members (or, no code can find it ). We can simply think that the function ARI is an object, but it is simply put on the "Clothes" of the function.
The static Link described in the program design language principles is created during call. However, the static link is determined during code compilation. For example, the following code:
PROCEDURE;
PROCEDURE B;
END
PEOCEDURE c;
END
END
The static link timestamp of B and c to. If B is called and a variable in B is not a local variable in B, the compiler generates a piece of code that searches the stack up the static chain until the variable or RTE is found.
Unlike compiled languages such as ada, JS is a fully explanatory language and functions can be dynamically created, which leads to the "static link maintenance" problem. Fortunately, JS functions cannot be directly modified. They are like symbols in erl, and changes are equivalent to redefinition. Therefore, the static link only needs to be updated each time it is defined. No matter whether the defined method is function () {} Or eval assignment, the static link is fixed after the function is created.
Let's return to the big example. When the interpreter runs to function big (){......} ", it creates a function instance in the memory and connects to the host statically. However, when calling the last line, the interpreter draws an area in the memory as ARI. We may wish to become ARI [big]. Execute the pointer to move to row 2nd.
When the 3rd row is executed, the interpreter creates an f1 instance, stores it in ARI [big], and connects the static link to ARI [big]. Next line. The interpreter creates an f2 instance and connects to the static link. Next, in the second row, call f2 to create ARI [f1]; f2 to call f1 to create ARI [f1]; f1 to output x, you need to address x.
Addressing simple variables
Let's continue. Now we want to address x, but x does not appear in the local variable of f1. Therefore, the interpreter must search for x along the stack and view it from the output, the interpreter does not search for the "stack" layer, but jumps because the "stack" is:
| F1 | bytes thread pointer
| F2 | x = 2
| Big | x = 1
| HOST |
If the interpreter finds it along the layer of the stack, the output is 2. This involves the essence of Js variable addressing: search along the static chain.
Continue to the above problem, execute the pointer to search along the f1 static chain, find the big, just big contains x = 1, so output 1, everything is fine.
So Will static links be connected to loops, resulting in an "endless loop" of addressing? Don't worry, because do you still remember that functions are nested with each other? In other words, the function is composed of a root tree, and all the static link pointers can be summarized to the host at last. Therefore, it is ridiculous to worry about "turning pointers into loops. (Instead, dynamic scope language addressing is prone to endless loops .)
Now, we can summarize the method of simple variable addressing: The Interpreter searches for the variable name in the local variable of the current function. If the variable name is not found, it will be traced along the static chain, until the variables are found or traced back to the host, they are still not found.
ARI's life
Now let's look at ARI. ARI records the local variables (including parameters), this pointer, dynamic links, and the most important address of the function instance during function execution. We can assume that ARI has the following structure:
ARI ::{
Variables: * variableTable, // variable table
DynamicLink: * ARI, // Dynamic Link
Instance: * funtioninst // function instance
}
Variables includes all local variables, parameters, and this pointers. dynamicLink points to ARI's callers, and instance points to function instances. Function instances include:
Functioninst ::{
Source: * jsOperations, // FUNCTION command
StaticLink: * ARI, // static Link
......
}
When a function is called, the following "form code" is actually executed ":
* ARI p;
P = new ARI ();
P-> dynamicLink = thread. currentARI;
P-> instance = called Function
P-> variables. insert (parameter table, this reference)
Thread. transfer (p-> instance-> operations [0])
Have you seen it? Create ARI, press the parameter and this into the variable table, and then transfer the thread pointer to the first instruction of the function instance.
What happens when a function is created? After assigning values to function commands, you must also:
NewFunction-> staticLink = thread. currentARI;
Now the problem is clear. We created a static link in the function definition, which directly stamps the current ARI of the thread. In this way, almost all simple variable addressing problems can be explained. For example, the following code:
Function test (){
For (I = 0; I <5; I ++ ){
(Function (t) {// this anonymous function is called f
SetTimeout (function () {echo (''+ t)}, 1000) // The anonymous function here is g
}) (I)
}
}
Test ()
The effect of this Code is to delay 1 second and then output in the order of 0 1 2 3 4. We will focus on the function used by setTimeout. When it is created, the static link points to the anonymous function f. The variable table of f (a ARI) contains I (the parameter is regarded as a local variable ), therefore, when setTimeout arrives, the anonymous function g searches for the variable t, which is found in ARI of the anonymous function f. Therefore, 0 1 2 3 4 is output one by one according to the creation sequence.
There are five ARI function instances for the public anonymous function f (Do you still remember that ARI is created every time the function is called ?), Correspondingly, g also "created" five times. Before the first setTimeout is reached, the stack contains the following records (I write the g into five records separately ):
+ ARI of test [I = 5 at the end of the loop]
| ARI of f; t = 0 bytes ------ static link of g0
| ARI of f; t = 1 bytes ------ static link of g1
| ARI of f; t = 2 bytes ------ static link of g2
| ARI of f; t = 3 bytes ------ static link of g3
| ARI of f; t = 4 bytes ------ static link of g4
\------
While the "stack" is shown below during the g0 call:
+ ARI of test [I = 5 at the end of the loop]
| ARI of f; t = 0 bytes ------ static link of g0
| ARI of f; t = 1 bytes ------ static link of g1
| ARI of f; t = 2 bytes ------ static link of g2
| ARI of f; t = 3 bytes ------ static link of g3
| ARI of f; t = 4 bytes ------ static link of g4
\------
+ G0 ARI
| T addressing is required here, so ...... T = 0
\------
The ARI of g0 may not be in ARI of the f series and can be directly stored in the host. However, the static link concerned with addressing is still pushed to ARI of each f, so there is no error ~ Because setTimeout is sequentially pushed into the waiting queue, It is output in the order of 0, 1, 2, 3, and 4.
Will the static link be modified when the function is redefined?
Now let's look at the next question: Will a static link be created when the function is defined, so will another static link be created when the function is redefined? First look at the example:
Var x = "x in host ";
F = function () {echo (x )};
F ();
Function big (){
Var x = 'x in big ';
F ();
F = function () {echo (x )};
F ()
}
Big ()
Output:
X in host
X in host
X in big
This example may be better understood. During big running, the host f is redefined, and the static link of "new" f points to big, so the last line outputs 'x in big '.
However, the following example is much more interesting:
Var x = "x in host ";
F = function () {echo (x )};
F ();
Function big (){
Var x = 'x in big ';
F ();
Var f1 = f;
F1 ();
F = f;
F ()
}
Big ()
Output:
X in host
X in host
X in host
X in host
Does it mean that the static link will be modified after the definition is redefined? However, here the two values are only assigned values, and only the pointers of f1 and f are modified. (Do you still remember that the JS function is of the reference type ?), F static links remain unchanged in real instances !. Therefore, the four outputs are actually x in the host.
Composition (attribute) Addressing in structure (object)
Please forgive me for using this strange name from the Christian (java) School and the modemical (csh) school, but the JS object is too much like a Hash table. Let's consider this addressing problem:
A. B compiled language will generate code that finds a and offsets B back to find B. However, JS is a fully dynamic language, and the members of the object can be freely increased or decreased, as well as prototype problems, it makes addressing of JS object members very interesting.
The object is a hash table.
In addition to several special methods (and prototype members), the object is similar to the hash table, because the methods and attributes can be stored in the "lattice" of the "hash table. The monthly version implements a HashTable class in his return of the JS king.
Attribute addressing of an object
The "own" attribute refers to the attributes whose hasOwnProperty is true. From the implementation point of view, it is a member of the object's own "hash table. For example:
Function Point (x, y ){
This. x = x;
This. y = y;
}
Var a = new Point (1, 2 );
Echo ("a. x:" + a. x)
The Point constructor creates the "Point" Object a and sets the x and y attributes. Therefore, in the member table of a, there are:
| X | ---> 1
| Y | ---> 2
When searching for a. x, the interpreter first finds a and then searches for x in the member table of a to get 1.
The constructor does not set a good policy for the object because it will cause two similar object methods:
Function Point (x, y ){
This. x = x;
This. y = y;
This. abs = function () {return Math. sqrt (this. x * this. x + this. y * this. y )}
}
Var a = new Point (1, 2 );
Var B = new Point (1, 2 );
Echo ("a. abs = B. abs? "+ (A. abs = B. abs ));
Echo ("a. abs === B. abs? "+ (A. abs = B. abs ));
Both outputs are false, because in the fourth row, an abs member (method) of the object is created each time, so. abs and B. abs actually points to two completely different function instances. Therefore, two seemingly equal methods actually do not.
Addressing of Prototype
A prototype is a function (class) attribute that points to an object (not a class ). The idea of "prototype" can be analogous to "photo and image Tiger": "tiger" and "CAT" do not have the relationship that inherits, but "tiger" is like "CAT. The prototype focuses on similarity. In js, code estimation can be written:
The prototype of Tiger. prototype = new Cat () function can also be a blank object:
SomeClass. prototype = {} Let's get back to addressing. Suppose we use. To get an attribute, what should we do if it is an attribute in the prototype? The phenomenon is: It does get, but how does it get it? What if the object's attributes and prototype attributes have the same names? Fortunately, the property of the object takes priority.
Defining a method in a prototype is a good design strategy. Let's change the example above:
Function Point (x, y ){
This. x = x;
This. y = y;
}
Point. prototype. abs = function () {return Math. sqrt (this. x * this. x + this. y * this, y )}
Var a = new Point (1, 2 );
Var B = new Point (1, 2 );
Echo ("a. abs = B. abs? "+ (A. abs = B. abs ));
Echo ("a. abs === B. abs? "+ (A. abs = B. abs ));
Now, the output is finally equal. The reason is that a. abs and B. abs Point to the abs Member of the Point prototype, so the output is equal. However, we cannot directly access Point. prototype. abs. An error occurs during the test. Correct: After a retest, the problem "Point. prototype. abs cannot be accessed" is the problem of my JSCOnsole. The reply is correct. Thank you for your correction!
The prototype chain can be long, long, or even round. Consider the following code:
A = function (x) {this. x = x };
B = function (x) {this. y = x };
A. prototype = new B (1 );
B. prototype = new A (1 );
Var a = new A (2 );
Echo (a. x + ',' + a. y );
Var B = new B (2 );
Echo (B. x + ',' + B. y );
The relationship described here is probably "I am like you, and you are like me ". The prototype pointer causes the following output:
2, 1
1, 2
When searching for a. y, I found "a. prototype" along the prototype chain and Output 1. B. x is the same principle. Now, we need to output the unregistered attribute "a. z:
Echo (tyoeof a. z) we are surprised that there is no endless loop here. It seems that the interpreter has a mechanism to handle the problem of prototype chain forming a ring. At the same time, the prototype can either form a tree or form a single ring without a multi-ring structure. This is a very simple graph theory.
This: Potential rules in the function
This is the most annoying potential rule for method (function) calls. In principle, this is a pointer that is pushed to the caller (an object ). But if this always points to the caller, the world would be wonderful. But this nasty Pointer "kicks your dog" from time to time ". Possible changes include call, apply, asynchronous call, and "window. eval ".
I prefer to use this as a parameter, just like self in lua. The self of lua can be passed explicitly or called using a colon:
A: f (x, y, z) = a. f (a, x, y, z) in JS, the call of the "plain" method is also like this:
A. f (x, y, z) =. f. call (a, x, y, z) f. call is the form of a truly "clean" call, which is like a clean call in lua. Many people say that lua is a clear version of js. lua simplifies many js things and exposes many of js's hidden rules.
Corrected the "this" Principle
The "use closure to correct this" mentioned in Return of the King, first look at the Code:
Button1.onclick = (
Function (e) {return function () {button_click.apply (e, arguments )}}
) (Button1) Don't underestimate this line of code. In fact, it creates an ARI, binds button1 to this, and returns a function. The function forces the caller (subject) to call button_click, therefore, this that is passed to button_click is e, that is, button1! After the event binding is completed, the environment looks like the following:
Button1.onclick = _ F _; // set a name for the returned anonymous Function
_ F _. staticLink = _ ARI _; // ARI of the anonymous function called after creation
_ ARI _ [e] = button1 // e in the anonymous ARI parameter table, which is also found by _ F _
Therefore, when we click the button, we will call the _ F _, _ F _ initiate a caller called e's button_click function. According to our previous analysis, e equals to button1, therefore, we get an insurance "specify callers" method. Maybe we can continue to use this idea to make it a common interface:
BindFunction = function (f, e) {// we are good people, don't change the prototype, don't change ......
Return function (){
F. apply (e, arguments)
}
}
Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.