Lexical Scope JavaScript eval() function and with() function
Lexical Scope stage
Lexical Scope:- the first working phase of most standard language compilers is called lexicalization (also called lexicalization). Back to Recall that the lexicalization process will check the characters in the source code. If it is a stateful parsing process, it will also assign Give the word semantics.
This concept is the basis for understanding the lexical scope and the origin of its name. Simply put, the lexical scope is the scope defined in the lexical stage. In other words, lexical scope is written by you The code determines where the variable and block scope are written, so the lexical analyzer will maintain the scope when processing the code No change (in most cases this is the case). Later, some methods to deceive the lexical scope will be introduced. These methods will depend on the lexical analyzer after processing. It is possible to modify the scope, but this mechanism may be a little difficult to understand. In fact, let lexical work It is a very good best practice to keep the natural relationship in writing according to the lexical relationship. Consider the following code:
1 2 3 4 5 6 7 8 |
function foo(a) { var b = a * 2; function bar(c) { console.log( a, b, c ); } bar( b * 3 ); } foo( 2 ); // 2, 4, 12 |
Contains the entire global scope, of which there is only one identifier: foo.
Contains the scope created by foo, which has three identifiers: a, bar, and b.
Contains the scope created by bar, which has only one identifier: c.
Scope bubbles are determined by where their corresponding scope block codes are written, and they are included level by level. The same type of scope, but now just assume that every function will create a new scope bubble. The bubble of bar is completely contained in the bubble created by foo. The only reason is that we want to define the function. The position of bar.
Find in Lexical Scope:
The structure of the scope bubble and the positional relationship between each other provide enough position information for the engine, and the engine uses this information To find the location of the identifier. In the previous code snippet, the engine executes the console.log(..) statement and finds the references of the three variables a, b, and c use. It starts from the innermost scope, which is the scope bubble of the bar(..) function. Engine cannot be A is found here, so it will go to the upper level to the scope of the nested foo(..) to continue searching. Found a here, Therefore the engine uses this reference. The same is true for b. For c, the engine is found in bar(..) Got it. If both a and c exist in bar(..) and foo(..), console.log(..) can directly use bar(..) Without looking for the outside foo(..). The scope search will stop when the first matching identifier is found. The same name can be defined in multiple nested scopes Identifiers, this is called the “masking effect” (the internal identifier “masks” the external identifier). Set aside the shadowing effect, Scope lookup always starts from the innermost scope at runtime, and proceeds step by step outwards or upwards until it meets Up to the first matching identifier. Global variables will automatically become properties of global objects (such as the window object in a browser), so It is possible not directly through the lexical name of the global object, but indirectly through the reference to the attributes of the global object Used to access it.
window.a
Through this technique, you can access those global variables that are obscured by variables with the same name. But non-global variables If it is obscured, it cannot be accessed anyway. No matter where the function is called, and no matter how it is called, its lexical scope is only determined by the function in which it was declared. The location is determined. Lexical scope search only finds first-level identifiers, such as a, b, and c. If foo.bar.baz is referenced in the code, Lexical scope search will only try to find the foo identifier. After finding this variable, the object attribute access rules will be followed separately Control access to the bar and baz attributes.
lexical scope of change:
If the lexical scope is completely defined by the position where the function is declared during code writing, how can it be “fixed” at runtime What about the lexical scope of “change” (or cheating)?
There are two mechanisms in JavaScript to achieve this. The community generally believes that using these two mechanisms in code is not What a good note. But the most important point is often overlooked in the debate about them: deceptive lexical scope leads to performance decline. Before explaining the performance issues in detail, let’s take a look at the principles of these two mechanisms.
eval() Lexical Scope Function
The eval(..) function in JavaScript can accept a string as a parameter and treat the content as if it were in the book The code that exists at this position in the program when it is written. In other words, you can use the program to generate code in the code you write and It runs as if the code was written at that location. According to this principle to understand eval(..), how it deceives and pretends to be writing through code (that is, the lexical period) The code is there, to modify the lexical scope environment, the principle becomes clear and easy to understand. When executing the code after eval(..), the engine does not “know” or “care” that the preceding code is inserted in a dynamic form
Enter and modify the environment of the lexical scope. The engine will only perform lexical scope lookups as usual. Consider the following code:
1 2 3 4 5 6 |
function foo(str, a) { eval( str ); console.log( a, b ); } var b = 2; foo( "var b = 3;", 1 ); // 1, 3 |
The “var b = 3;” code in the eval(..) call will be treated as if it were there. Because of that generation The code declares a new variable b, so it modifies the lexical scope of the existing foo(..). fact Above, the same principle as mentioned earlier, this code actually creates a variable b inside foo(..) and masks it A variable with the same name in the external (global) scope is deleted. When console.log(..) is executed, both a and b will be found inside foo(..), but they will never be found External b. Therefore, “1, 3” will be output instead of “1, 2” that would be output under normal conditions. In this example, for the convenience and brevity of the display, the “code” string we passed in is consistent. In actual situations, it is very easy to dynamically change the characters After splicing together, pass it in. eval(..) is usually used to execute dynamically created code, because In order to dynamically execute a code composed of fixed characters as in the example, it is no better than directly It is more beneficial to write the code there. By default, if the code executed in eval(..) contains one or more declarations (whether variable or function Number), the lexical scope of eval(..) will be modified. Technically, through some skills (beyond my The scope of our discussion) can indirectly call eval(..) to make it run in the global scope and perform modify. But in any case, eval(..) can modify the lexical scope of the writing period at runtime. In strict mode programs, eval(..) has its own lexical scope at runtime, which means its The declaration in cannot modify the scope in which it is located.
1 2 3 4 5 6 |
function foo(str) { "use strict"; eval( str ); console.log( a ); // ReferenceError: a is not defined } foo( "var a = 2" ); |
There are other functions in JavaScript that are similar to eval(..). setTimeout(..) and The first parameter of setInterval(..) can be a string, and the content of the string can be interpreted as a dynamically generated Function code. These features are outdated and not promoted. Don’t use them! The behavior of the new Function(..) function is also very similar. The last parameter can accept a code string and convert it to Converted into a dynamically generated function (the previous parameter is the formal parameter of this newly generated function). The syntax of this construction function is eval(..) is slightly safer, but it should be avoided if possible. The use of dynamic code generation in the program is very rare, because the benefits it brings cannot offset the performance loss Lost.
Lexical Scope with() function:
Another feature in JavaScript that is difficult to master (and is not recommended now) to deceive lexical scope is with keyword. There are many ways to explain with, here I choose to explain it from this perspective: how it is the same The lexical scope affected by it interacts. with is usually used as a shortcut to repeatedly refer to multiple properties in the same object, and there is no need to refer to the object repeatedly itself.
such as:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
var obj = { a: 1, b: 2, c: 3 }; obj.a = 2; obj.b = 3; obj.c = 4; // simple shortcut with (obj) { a = 3; b = 4; c = 5; } |
But in fact this is not just for convenient access to object properties. Consider the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function foo(obj) { with (obj) { a = 2; } } var o1 = { a: 3 }; var o2 = { b: 3 }; foo( o1 ); console.log( o1.a ); // 2 foo( o2 ); console.log( o2.a ); // undefined console.log( a ); // 2——No, a is leaked to the global scope! |
In this example, two objects, o1 and o2, are created. One of them has the a attribute and the other does not. foo(..) letter The number accepts an obj parameter, which is an object reference, and executes with(obj) {..} on this object reference. Inside the with block, the code we wrote seems to be a simple lexical reference to the variable a, which is actually a LHS reference, and assign 2 to it. When we pass in o1, a=2 assignment operation finds o1.a and assigns 2 to it, which is in the console later. It can be reflected in log(o1.a). When o2 is passed in, o2 does not have a attribute, so this attribute will not be created. o2.a remains undefined. But you can notice a strange side effect, in fact a = 2 assignment operation creates a global variable a. This what’s going on? with can treat an object with no or multiple attributes as a completely isolated lexical scope, so this pair The attributes of the image will also be treated as lexical identifiers defined in this scope. If the eval(..) function accepts code containing one or more declarations, it will modify the lexical scope in which it is located, and The with statement actually creates a new lexical scope out of thin air based on the object you pass to it. It can be understood that when we pass o1 to with, the scope declared by with is o1, and this scope contains There is an identifier that matches the o1.a attribute. But when we take o2 as the scope, there is no a identifier in it, Therefore, a normal LHS identifier lookup was performed
The identifier a is not found in the scope of o2, the scope of foo(..), and the global scope, so when a=2 execute When, a global variable is automatically created (because it is in non-strict mode). The behavior of putting objects and their properties into a scope and assigning identifiers at the same time is very puzzling. But to say This is the most straightforward explanation I can give. Another reason why eval(..) and with are not recommended is that they will be affected by strict mode (limited system). with is completely forbidden, and indirectly or unsafely used under the premise of retaining the core function eval(..) is also banned.
performance
eval(..) and with will modify or create a new scope at runtime to deceive other words defined at the time of writing Law scope. You may ask, so what? If they can implement more complex functions and the code is more extensible, would it Isn’t it a very good feature? the answer is negative. The JavaScript engine will perform several performance optimizations during the compilation phase. Some of these optimizations rely on being able to Lexical analysis is performed statically, and the definition positions of all variables and functions are determined in advance, so that they can be quickly found during execution Identifier. But if the engine finds eval(..) or within the code, it can only simply assume the judgment about the position of the identifier Are invalid, because it is impossible to know exactly what codes eval(..) will receive during the lexical analysis stage. These codes will How to modify the scope, and it is impossible to know the content of the object passed to with to create the new lexical scope What is it. The most pessimistic situation is that if eval(..) or with appears, all optimizations may be meaningless, so the simplest The single approach is to not do any optimization at all. If you use eval(..) or with a lot in your code, it will definitely become very slow. No matter how much the engine is Obviously, trying to limit the side effects of these pessimistic situations to a minimum, it is unavoidable that without these optimizations, generation The fact that the code will run slower.
Summary of Lexical Scope:
Lexical scope means that the scope is determined by the position of the function declaration when the code is written. Lexical analysis stage of compilation Basically know where and how all the identifiers are declared, so as to be able to predict how to treat it during execution We searched. There are two mechanisms in JavaScript to “cheat” the lexical scope: eval(..) and with. The former can be a package The “code” string containing one or more declarations is calculated and used to modify the existing lexical scope (in Runtime). The latter essentially treats the reference of an object as a scope and treats the properties of the object as Use the identifier in the domain to process, thus creating a new lexical scope (also at runtime). The side effect of these two mechanisms is that the engine cannot optimize the scope lookup at compile-time, because the engine can only recognize For such optimization is ineffective. Using any of these mechanisms will cause the code to run slower. Don’t use them.
Related Article:
https://programmingdigest.com/math-objects-in-javascript-porperties-and-methods-with-examples/