Fine-grained JavaScript addressing, closures, object modeling and related issues _javascript tips

Source: Internet
Author: User
Tags abs anonymous closure function definition lua
It is because JS is a dynamic language, so the address of JS is in-situ addressing, rather than as C, compiled after the confirmation. In addition, JS introduced the this pointer, which is a very troublesome thing, because it "implicitly" as a parameter to the inside of the function. Let's look at the examples in the "Scope Chain" topic:
var testvar = ' Window property ';
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); ' 2 ' three alert results are not the same, it's interesting, isn't it? In fact, all the interesting, bizarre concepts can finally be summed up in one problem, that is addressing.
Addressing a simple variable
Is JS a static or dynamic scope?
To tell you the unfortunate news that JS is static, or that variable addressing is much more complex than a dynamic scope language such as Perl. The following code is an example of a programming language principle:
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, the same as Pascal and Ada, although F1 is dynamically defined with Eval. Another example comes from the principle of programming languages:
function Big2 () {
var x = 1;
function F2 () {echo (x)}; Produces an output with the value of X
Function F3 () {var x = 3;f4 (F2)};
function F4 (f) {var x = 4;f ()};
F3 ();
}
BIG2 ()//Output 1: Deep binding, Output 4: Shallow binding, Output 3: Special binding
The output is still 1, stating that JS is not only a static scope, or deep binding, this is a big thing ...
The concept of Ari
To explain the complex addressing problem of functions (especially in languages that allow function nesting, such as ADA), the principle of programming language defines "ARI": It is some record on the stack, including:
function address
Local variables
return address
Dynamic Link
Static links
Here, the dynamic link always points to the caller of a function (such as B when execution calls a, in Ari of a, the dynamic link points to B; The static link describes the parent element when a is defined, because the function's organization has a root tree, so all of the static links are aggregated and must point to the host (such as window). We can look at the example (after the annotation is the output):
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 '; a ()};
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 the first call is made, we can see the following on the stack (the top of the stack on the left):
[A's ari]→[host]a the static chain straight to the host, because there is no X defined in a, the interpreter finds X in the host, along the static chain, and the call to B, because the B's local variable records x, so the last echo is x in B: ' x inside B ';
Now, C is a much more interesting situation, and when you call C, you can write stack information like this:
Dynamic chain: [a]→[c]→[host]
Static chain: [c]→[host];[a]→[host]
Because the addressing of X is done after a call to a, the static link still sticks straight to the host, and the natural x is the ' X in Host '.
D is more interesting, D creates a function as the return value, and it is then called ~ because the return value of D is created within the life cycle of D, so D returns the static link of the value to D, so when called, the X in the output D: ' X inside D,a Closure-made function '.
The opportunity to create a static link
Moon Shadow and Amingoo said, "closures are" call-time references "to functions, and the principles of programming languages are simply called Ari, but the difference is that Ari in the programming language principle is stored on the stack, and once the life cycle of the function is over, Ari destroys it. While JS closure is not the case, the closure is destroyed when and only if there is no reference to it and its members (or no code can find it). We can simply assume that the function Ari is an object, just put on the "clothes" of the function.
The static chain described in the principle of programming language is created when invoked, but the relationship of the static chain is determined when the code is compiled. For example, the following code:
PROCEDURE A;
PROCEDURE b;
End
Peocedure C;
End
End
, the static chain of B and C is stamped to a. If B is called, and a variable in B is not in B's local variable, the compiler generates a piece of code that wants to search the stack up the static chain until the variable or RTE is found.
Unlike a compiled language such as Ada, JS is a fully explanatory language, and functions can be created dynamically, creating a "static chain Maintenance" challenge. Fortunately, JS functions can not be directly modified, it is like erl inside the symbol, change is equal to redefine. So the static chain just needs to be updated each time it is defined. The static chain is fixed when the function is created, whether the defined method is function () {} or eval assignment.
We go back to Big's example when the interpreter runs to "function big () {...}" , it creates an instance of the function in memory and joins the static link to the host. However, when the last line is invoked, the interpreter draws an area in memory, as Ari. We may as well become ari[big]. The execution pointer moves to line 2nd.
When executed to line 3rd, the interpreter creates a "F1" instance, which is saved in Ari[big], and the connection is statically linked to Ari[big]. The next line. The interpreter creates a "F2" instance that connects the static chain. Next, to line 5th, call F2, create ARI[F1];F2 call F1, create ari[f1];f1 to output x, you need to address X.
Addressing a simple variable
We go on, now we're going to address X, but X does not appear in the F1 local variable, so the interpreter has to search for x along the stack, and from the output, the interpreter does not go along the stack layer, but jumps, because the stack is now:
|f1 | ← Thread pointer
|f2 | x = 2
|big | x = 1
| Host|
If the interpreter really looked up and down the stack, the output would be 2. This touches the nature of JS variable addressing: Search along the static chain.
Continue the above problem, execute the pointer along F1 static chain search, find big, just big inside there is x=1, so output 1, all right.
Then, whether the static chain will connect cyclization, resulting in addressing the "dead loop"? Don't worry, because you remember that functions are nested with each other? In other words, the function is composed of a root tree, all the static chain pointers must eventually be aggregated to the host, therefore, it is absurd to worry about "pointer into loop". Instead, dynamic domain language addressing tends to cause dead loops. )
Now, we can summarize the simple variable addressing method: The interpreter now looks for the variable name in the local variable of the current function and, if not found, goes back along the static chain until it finds or traces back to the host and still does not find the variable.
Ari's life
Now, look at this. Ari,ari records local variables (including parameters), this pointer, dynamic chain, and most importantly-the address of the function instance, when the function executes. We can imagine 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 pointer; dynamiclink points to Ari by its caller; instance points to the function instance. In the function instance, there are:
Functioninst:: {
Source:: *jsoperations,//function instruction
Staticlink:: *ari,//Static link
......
}
When a function is invoked, it actually executes the following "formal code":
*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])
Did you see that? Create Ari, press the argument and this to 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 the function instruction assigns a value, you also:
Newfunction->staticlink = Thread.currentari;
Now that the question is clear, we created a static link in the function definition that directly poked at the thread's current ARI. This will explain almost all of the simple variable addressing problems. 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 called G
}) (i)
}
}
Test ()
The effect of this code is to delay 1 seconds and then output in the order of 0 1 2 3 4. We focus on the function of settimeout, when it is created, the static link to the anonymous function f,f (some ARI's) variable table contains I (parameter as local variable), so, settimeout to time, anonymous function g search variable t, It was found in Ari, an anonymous function f. As a result, output 0 1 2 3 4, in the order in which they were created.
The function instance of the public anonymous function f has a total of 5 Ari (remember that when the function is invoked every time, Ari creates it once?) , accordingly, G was also "created" 5 times. Before the first settimeout, the stack is equivalent to the following record (I write g separately for 5):
+test Ari [i=5 at end of cycle]
| F's ari;t=0← —————— G0 static link
| Ari of F; t=1← —————— G1 Static link
| Ari of F; t=2← —————— g2 Static link
| Ari of F; t=3← —————— g3 static link
| Ari of F; t=4← —————— G4 static link
\------
And, when G0 calls, "stack" is the following:
+test Ari [i=5 at end of cycle]
| Ari of F; t=0← —————— G0 static link
| Ari of F; t=1← —————— G1 Static link
| Ari of F; t=2← —————— g2 Static link
| Ari of F; t=3← —————— g3 static link
| Ari of F; t=4← —————— G4 static link
\------
+g0 Ari.
| Here to address T, so ... t=0
\------
G0 Ari may not be in the F-series of Ari, can be regarded as directly in the host, but the static link of the address is still poking to each F ari, naturally will not be wrong ~ because the settimeout is in the order of the waiting queue, so the last in accordance with 0 1 2 3 4 in the order of output.
Does a static link change when a function is redefined?
Now look at the next question: When a function is defined, a static link is created, and then another static link is 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 might be quite understandable, when big runs to redefine F in the host, and the static link to "new" F points to big, so the last line prints ' x in '.
However, the following examples are 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
Is it not that redefining will modify the static link? However, here the two assignments are only assigned, modifying only the F1 and F pointers (remember that the JS function is a reference type?). , f Real example, static link does not change!. So, four outputs are actually x in the host.
Component (attribute) addressing problems in structures (objects)
Ask the Christian (Java) and Mormon (CSH) people to forgive me for using this strange salutation, but the JS object is too much like a hash table, we consider this addressing problem:
A.B compiled language will generate a to find a backward offset a distance to find B code, but, JS is a full dynamic language, the members of the object can be arbitrarily added or subtracted, there are prototypes of the problem, so that JS object members addressing is very interesting.
object is a hash table
Apart from a few special methods (and prototype members), objects are simply indistinguishable from hash tables, because both methods and properties can be stored in the "grid" of the hash table. The month edition in his "JS King returns" Inside realizes a Hashtable class.
Property addressing for the object itself
The attribute of "itself" is the attribute that hasOwnProperty is true. From an implementation point of view, is the object of their own "hash table" inside the members. Like what:
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 properties; So, 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, getting 1.
Setting a method from a constructor to an object is not a good strategy because it can result in two homogeneous 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));
Two output is false because the ABS member (method) of the object creates one at a time, so that A.abs and B.abs actually point to two completely different instances of the function. Thus, two seemingly equal methods actually ranged.
On the addressing problem of the prototype
A prototype is a property of a function (class) that points to an object (not a class). The "archetype" thought can be likened to "suit": "Tiger" and "cat" do not inherit that relationship, only "tiger" like "cat" relationship. Prototype looks at similarity, in JS, code estimates can be written:
Tiger.prototype = The prototype of the new Cat () function can also be just a blank object:
Someclass.prototype = {} We go back to addressing and assume that. To get a property, what happens to the properties of the prototype? The phenomenon is: it did take, but how did this get? What if the property of the object itself and the name of the stereotype attribute are duplicate? Fortunately, the properties of the object itself are preferred.
It is a good design strategy to define the method in the prototype. If we change the above example:
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));
This, the output is finally equal, investigate the reason, because A.abs and B.abs point is the type of a member of the prototype of the ABS, so the output is equal. However, we can not directly access Point.prototype.abs, the test when the direct error. Correction: After the test, the "Point.prototype.abs" problem is the jsconsole problem I used. The reply is right, thank you for correcting me!
The prototype chain can be very long and can even be wrapped around a loop. 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);
This description of the relationship is probably "I like you, you are like me." The prototype pointer refers to the following output:
2, 1
1, 2
Search A.Y, along the prototype chain to find the "A.prototype", output 1;b.x is the same principle. Now, we're going to output the "A.z" attribute that is not registered:
Echo (tyoeof a.z) We are surprised that there is no dead loop here, and it seems that the interpreter has a mechanism to deal with the problem of the prototype chain into loops. At the same time, the prototype is either a tree or a single ring, and there will be no polycyclic structure, which is a very simple graph theory.
This: Unspoken rules in a function
The most annoying unspoken rule in a method (function) Call is the this question. In principle, this is a pointer to the caller (an object). But if this always points to the caller, the world would be wonderful. But this hateful pointer occasionally "kicks your dog". Possible modifications include call, apply, asynchronous invocation, and "Window.eval".
I'd rather take this as an argument, like self in Lua. Lua self can be passed explicitly, or it can be invoked with a colon:
A:f (x,y,z) = = = A.F (a,x,y,z) JS in the "element" method call is the same way:
A.F (x,y,z) = = A.f.call (a,x,y,z) F.call is really a "clean" form of invocation, as is the clean call in Lua. Many people say that Lua is a clear version of JS, Lua simplifies a lot of JS, exposing a lot of JS unspoken rules, really true.
Fix the principle of "this"
"Return of the King" mentioned above, "fix this with closures", 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 the button1 to this, and then returns a function that forces the invocation of E as the caller (subject) Button_Click, so this is the e that is passed to Button_Click, which is button1! After the event binding is over, the environment is probably the following:
Button1.onclick = _f_; Set a name for the returned anonymous function
_f_.staticlink = _ari_; Ari for anonymous functions that are called after creation
_ari_[e] = button1//anonymous ARI parameter table inside of E, also is _f_ looking for that E
So, when we click the button, we invoke _f_,_f_ to initiate a button_click function of the caller E, according to our previous analysis, E equals button1, so we get an insurance "specify Caller" method. Perhaps we can continue to use this idea as a common interface:
Bindfunction = function (f,e) {//We are good people, do not change the prototype, do not change ...
return function () {
F.apply (e,arguments)
}
}

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.