When programming JavaScript, we cannot avoid declaring functions and variables in order to successfully build our system, but how and where does the interpreter find these functions and variables? What exactly happens when we reference these objects?
Original release: Dmitry A. Soshnikov
Published time: 2009-06-27
Russian address: http://dmitrysoshnikov.com/ecmascript/ru-chapter-2-variable-object/
English Translation: Dmitry A. Soshnikov
Release time: 2010-03-15
English address: http://dmitrysoshnikov.com/ecmascript/chapter-2-variable-object/
Partially difficult to translate The sentence refers to the Chinese translation of justinw
Most ECMAScript programmers should know that variables are closely related to execution context:
var a = 10; // Variables in the global context
(function () {
var b = 20; // Local variables in the function context
})();
alert(a); // 10
alert(b); // Global variable "b" is not declared
Also, many programmers As we all know, the current ECMAScript specification states that independent scopes can only be created through an execution context of the "function" code type. In other words, compared to C/C, the for loop in ECMAScript cannot create a local context.
for (var k in {a: 1, b: 2}) {
alert(k);
}
alert(k); // Although the loop has ended, the variable k is still in the current scope
Let’s see Take a look at what details we discovered when we declared the data.
Data declaration
If the variable is related to the execution context, the variable itself should know where its data is stored and know how to access it. This mechanism is called a variable object.
Variable object (abbreviated as VO) is a special object related to the execution context. It stores the following content declared in the context:
Variable (var, variable declaration);
FunctionDeclaration (FunctionDeclaration, Abbreviated as FD);
Formal parameters of function
For example, we can use ordinary ECMAScript objects to represent a variable object:
VO = {};
As we said, VO is the property of the execution context:
activeExecutionContext = {
VO: {
//Context data (var, FD, function arguments)
}
};
Only global context variable objects are allowed to pass VO Property name to access indirectly (because in the global context, the global object itself is a variable object, which will be introduced in detail later), the VO object cannot be directly accessed in other contexts, because it is only an implementation of the internal mechanism.
When we declare a variable or a function, there is no other difference from when we create a new attribute of VO (ie: there is a name and a corresponding value).
For example:
var a = 10;
function test(x) {
var b = 20;
};
test(30);
The corresponding variable object is:
// Global context variable object
VO(globalContext) = {
a: 10,
test:
};
// variable object of test function context
VO(test functionContext) = {
x: 30,
b: 20
};
At the specific implementation level (and in the specification) the variable object is just an abstract concept. (Essentially, in a specific execution context, the VO name is different, and the initial structure is also different.
Variable objects in different execution contexts
For all types of execution contexts, variable objects Some operations (such as variable initialization) and behaviors are common. From this perspective, it is easier to understand variable objects as abstract basic things and also define additional content related to variable objects in the context of functions.
Abstract variable object VO (general behavior of variable initialization process)
║
╠══> Global context variable object GlobalContextVO
║ (VO === this === global)
║
╚══> Function context variable object FunctionContextVO
(VO === AO, and added
and )
Let’s take a closer look:
Variable objects in the global context
First, we need to give the global object a clear definition:
The global object (Global object) is created before entering any execution context. Object;
There is only one copy of this object, its properties can be accessed anywhere in the program, and the life cycle of the global object ends at the moment the program exits.
Copy code
In the initial creation phase of the global object, Math, String, Date, parseInt are used as its own attributes, and other attributes are initialized. It can also have other additional objects created as attributes (which can point to the global object itself). For example, in the DOM, the window attribute of the global object can refer to the global object itself (of course, not all specific implementations are like this):
global = {
Math: <...>,
String: <...>
...
...
window: global //Reference itself
};
The prefix is usually ignored when accessing the properties of the global object, because the global Objects cannot be accessed directly by name. However, we can still access the global object through this of the global context, and we can also refer to itself recursively. For example, window in DOM. To sum up, the code can be abbreviated as:
String( 10); // It is global.String(10);
// With prefix
window.a = 10; // === global.window.a = 10 === global.a = 10 ;
this.b = 20; // global.b = 20;
So, back to the variable object in the global context - in this case, the variable object is the global object itself:
VO(globalContext) === global;
It is very necessary to understand the above conclusion. Based on this principle, only when the correspondence is declared in the global context, we can access it indirectly through the properties of the global object (for example, in advance don't know the variable name).
var a = new String('test');
alert(a); // Direct access, found in VO(globalContext): "test"
alert(window['a']); // Indirect access through global: global === VO(globalContext) ): "test"
alert(a === this.a); // true
var aKey = 'a';
alert(window[aKey]); // indirectly through the dynamic property name Access: "test"
Variable object in function context
In the function execution context, VO cannot be accessed directly. At this time, it is played by the activation object (abbreviated as AO) The role of VO.
VO(functionContext) === AO;
The active object is created when entering the function context, and it is initialized through the arguments attribute of the function. The value of the arguments attribute is the Arguments object:
The Arguments object is an attribute of the active object, which includes the following attributes:
callee — a reference to the current function
length — The number of parameters actually passed
properties-indexes (integer of string type) The value of the property is the parameter value of the function (arranged from left to right in the parameter list). The number of elements inside properties-indexes is equal to arguments.length. The value of properties-indexes is shared with the parameters actually passed in.
For example:
function foo(x, y, z) {
// Number of declared function parameters arguments (x, y, z)
alert(foo.length); // 3
// The number of parameters actually passed in (only x, y)
alert(arguments.length); // 2
// The callee of the parameter is the function itself
alert(arguments.callee === foo); // true
// Parameter sharing
alert(x === arguments[0]); // true
alert(x); // 10
arguments[0] = 20;
alert(x); // 20
x = 30;
alert(arguments[0]); // 30
// However, there is no parameter z and parameter passed in The 3rd index value is not shared
z = 40;
alert(arguments[2]); // undefined
arguments[2] = 50;
alert(z); / / 40
}
foo(10, 20);
The code for this example has a bug in the current version of Google Chrome — even if the parameters z, z are not passed and arguments[2] are still shared.
Two stages of processing context code
Now we have finally reached the core point of this article. The execution context code is divided into two basic stages for processing:
Entering the execution context
Executing code
Modifications of variable objects are closely related to these two stages.
Note: The processing in these two stages is a general behavior and has nothing to do with the type of context (that is, the performance is the same in global context and function context).
Enter the execution context
When entering the execution context (before code execution), VO already contains the following attributes (as mentioned earlier):
All formal parameters of the function (if we are in the function execution context Medium)
—A property of a variable object consisting of a name and a corresponding value is created; if no corresponding parameters are passed, a property of a variable object consisting of a name and an undefined value will also be created.
All function declarations (FunctionDeclaration, FD)
—A property of a variable object consisting of a name and a corresponding value (function-object) is created; if the variable object already has a property with the same name, it is completely Replace this attribute.
All variable declarations (var, VariableDeclaration)
—The properties of a variable object consisting of a name and a corresponding value (undefined) are created; if the variable name is the same as a declared formal parameter or function, the variable declaration will not Interfering with existing properties of this type.
Let’s look at an example:
function test( a, b) {
var c = 10;
function d() {}
var e = function _e() {};
(function x() {});
}
test(10); // call
When entering the test function context with parameter 10, AO behaves as follows:
AO(test) = {
a: 10,
b: undefined,
c : undefined,
d:
e: undefined
};
Note that AO does not contain function "x". This is because "x" is a function expression (FunctionExpression, abbreviated as FE) rather than a function declaration, and function expressions will not affect VO. Anyway, the function "_e" is also a function expression, but as we will see below, because it is assigned to the variable "e", it can be accessed by the name "e". The difference between function declaration FunctionDeclaration and function expression FunctionExpression will be discussed in detail in Chapter 15 Functions. You can also refer to Chapter 2 of this series to learn about named function expressions.
After this, you will enter the second stage of processing the context code - executing the code.
Code execution
During this cycle, AO/VO already has attributes (however, not all attributes have values, and the values of most attributes are still the system default initial value of undefined).
Still with the previous example, AO/VO was modified as follows during code interpretation:
AO['c'] = 10;
AO['e'] = ;
Note again, because FunctionExpression "_e" is saved to the declared variable "e", so it still exists in memory. FunctionExpression "x" does not exist in AO/VO, which means that if we try to call the "x" function, no matter before or after the function is defined, an error "x is not defined" will appear, and the function is not saved. An expression can only be called within its own definition or recursively.
Another classic example:
alert(x); // function
var x = 10;
alert(x); // 10
x = 20;
function x() {};
alert(x); // 20
Why is the return value of the first alert “x” a function, and it accesses “x” before “x” is declared? Why not 10 or 20? Because, according to the specification, the function declaration is filled in when entering the context; agreeing with the cycle, there is also a variable declaration "x" when entering the context, then as we said in the previous stage, the variable declaration follows in sequence After the function declaration and formal parameter declaration, and during this context entry stage, the variable declaration will not interfere with the function declaration or formal parameter declaration of the same name that already exists in VO. Therefore, when entering the context, the structure of VO is as follows:
VO = {};
VO['x'] = < reference to FunctionDeclaration "x">
// Find var x = 10;
// If function "x" has not been declared
// At this time the value of "x" should be undefined
// But the variable declaration in this case does not affect the value of the function with the same name
VO['x'] =
Next, in During the code execution phase, VO makes the following modifications:
VO[' x'] = 10;
VO['x'] = 20;
We can see this effect in the second and third alerts.
In the example below we can see again that the variables are put into VO during the context phase. (Because, although the else part of the code will never be executed, the variable "b" still exists in VO anyway.)
if (true) {
var a = 1;
} else {
var b = 2;
}
alert (a); // 1
alert(b); // undefined, not that b is not declared, but that the value of b is undefined
About variables
Usually, various articles Books related to JavaScript all claim: "You can declare a variable whether using the var keyword (in the global context) or not using the var keyword (anywhere)." Remember, this is a misconception:
At any time, a variable can only be declared by using the var keyword.
The above assignment statement:
a = 10;
This just creates a new property for the global object (but it is not a variable). "Not a variable" does not mean that it cannot be changed, but that it does not conform to the variable concept in the ECMAScript specification, so it is "not a variable" (the reason why it can become a property of the global object is entirely because of VO(globalContext) = == global, do you still remember this? ).
Let’s take a look at the specific differences through the following examples:
alert(a); // undefined
alert(b); // "b" is not declared
b = 10;
var a = 20;
All roots are still VO and enter context phase and code execution phase:
Enter context phase:
VO = {
a: undefined
};
We can see that because "b" is not a variable, at this stage There is no "b" at all, "b" will only appear during the code execution phase (but in our case, the error will occur before that).
Let’s change the example code:
alert( a); // undefined, everyone knows this,
b = 10;
alert(b); // 10, created during the code execution phase
var a = 20;
alert(a) ; // 20, Modification during code execution phase
There is another important knowledge point about variables. Compared with simple attributes, variables have an attribute: {DontDelete}. The meaning of this attribute is that the variable attribute cannot be directly deleted using the delete operator.
a = 10;
alert(window.a); // 10
alert(delete a); // true
alert(window.a); >var b = 20;
alert(window.b); // 20
alert(delete b); // false
alert(window.b); // 여전히 20 >
그러나 이 규칙은 하나의 컨텍스트, 즉 eval 컨텍스트에서는 왜곡될 수 없으며 변수에는 {DontDelete} 속성이 없습니다.
alert(window.a); // 10
alert(delete a); // true
alert(window.a) // 정의되지 않음
이 인스턴스를 테스트할 때 일부 디버깅 도구(예: Firebug) 콘솔을 사용하는 경우 Firebug도 eval을 사용하여 콘솔에서 코드를 실행한다는 점에 유의하세요. 따라서 변수 속성에도 {DontDelete} 속성이 없으므로 삭제가 가능합니다.
특수 구현: __parent__ 속성
앞서 언급했듯이 표준 사양에 따르면 활성 개체에 직접 액세스할 수 없습니다. 그러나 SpiderMonkey 및 Rhino와 같은 일부 특정 구현은 이 규정을 완전히 준수하지 않습니다. 구현 시 함수에는 특수 속성 __parent__이 있으며 이를 통해 함수가 생성한 활성 개체 또는 전역 변수 개체를 직접 참조할 수 있습니다.
예(SpiderMonkey, Rhino):
var a = 10;
function foo() {}
alert(foo.__parent__); // global VO = foo.__parent__; Alert(VO.a); // 10
alert(VO === global); // true
위의 예에서 foo 함수가 전역에서 생성되는 것을 볼 수 있습니다. 따라서 __parent__ 속성은 전역 컨텍스트의 변수 객체, 즉 전역 객체를 가리킵니다.
그러나 SpiderMonkey에서는 동일한 방식으로 활성 개체에 액세스할 수 없습니다. 다른 버전의 SpiderMonkey에서는 내부 함수의 __parent__가 때때로 null을 가리키기도 하고 때로는 전역 개체를 가리키기도 합니다.
Rhino에서는 동일한 방식으로 활성 개체에 액세스하는 것이 완전히 가능합니다.
예(Rhino):
코드 복사
var y = 20;
// "foo" 컨텍스트의 활성 개체
var AO = (함수 () { }).__parent__;
print(AO.y); // 20
// 현재 활성 객체의 __parent__는 기존 전역 객체입니다.
// 변수 객체의 특수 체인
// 그래서 이를 범위 체인이라고 부릅니다.
print(AO.__parent__ === global); // true
print(AO.__parent__.x) // 10
})() ;
요약
이번 글에서는 실행 컨텍스트와 관련된 객체를 심층적으로 살펴보았습니다. 이 지식이 귀하에게 도움이 되기를 바라며 귀하가 직면한 몇 가지 문제나 혼란을 해결할 수 있기를 바랍니다. 계획대로 다음 장에서는 범위 체인, 식별자 확인 및 클로저를 살펴보겠습니다.
궁금한 점이 있으시면 아래 댓글로 답변해 드리겠습니다.
기타 참고자료
10.1.3 –
변수 인스턴스화
;
10.1.5 –