6. Execution environment and scope (1) Execution context: All JavaScript code runs in an execution environment. When control is transferred to the JavaScript executable When you execute code, you enter an execution environment. The active execution environment logically forms a stack. The global execution environment is always the bottom element of this stack, and the top element of the stack is the currently running execution environment. Each function has its own execution environment. When the execution flow enters a function, the execution environment of this function is pushed onto the top of the stack. After the function is executed, the execution environment is popped up and control is returned to the previous execution environment.
(2) Variable object: Each execution environment has a corresponding variable object. All variables and functions defined in the execution environment are stored in this variable object. This variable object is an object in the background implementation and we cannot access it in the code, but it helps us understand the concepts related to execution environment and scope.
(3) Scope chain: When code is run in an execution environment, a scope chain consisting of variable objects is created. The front end of this chain is the variable object of the environment where the current code is located, and the end of the chain is the variable object of the global environment. When parsing an identifier in an execution environment, it will search in the corresponding variable object of the current execution environment. If it is found, it will be returned. If it is not found, it will be searched level by level along the scope chain until the variable object of the global environment. If it has been If not found, a reference exception is thrown.
(4) Activation object: If an execution environment is a function execution environment, the variable object is also called an activation object. The active object initially contains only one variable, the arguments object (this object does not exist in the variable object of the global environment).
Although these four concepts are somewhat abstract, they are still relatively natural. You can experience it in detail with an example in "JavaScript Advanced Programming (3rd Edition)":
// Enter the global scope and create a global variable object
var color = " blue";
function changeColor(){
// Enter the changeColor scope and create a variable object corresponding to changeColor
var anotherColor = "red";
function swapColors(color1 , color2){
// Enter the swapColors scope and create the corresponding variable object of swapColors
var tempColor = anotherColor;
anotherColor = color;
color = tempColor;
/*
* The objects accessible within the swapColors scope are:
* color, changeColor of the global variable object
* anotherColor, swapColors of the corresponding variable object of the changeColor function
* tempColor of the corresponding variable object of the swapColors function
*/
}
swapColors('white');
/*
* The objects accessible within the changeColor scope are:
* The color, changeColor of the global variable object
* The corresponding changeColor function anotherColor, swapColors of variable objects
*/
}
changeColor();
/*
* The objects that can be accessed in the global scope are:
* global variable object color,changeColor
*/
The whole process here is: (1) Enter the global environment, create a global variable object, and push the global environment to the top of the stack (this is also the bottom of the stack). According to the previous conclusion about declaration promotion, a possible process for creating a global variable object here is to first create the global variable object, then process the function declaration and set the attribute changeColor to the corresponding function, and then process the variable declaration and set the attribute color to undefined.
(2) Execute code in the global environment. First perform the color variable initialization, assign the value to 'blue', and then call the changeColor() function.
(3) Call the changeColor() function, enter the changeColor function execution environment, create the corresponding variable object (that is, the active object) of this environment, and push this environment onto the top of the stack. A possible process for creating an active object is to first create the active object, process the internal function declaration and set the attribute swapColors to the corresponding function, process the function parameters to create the attribute arguments object of the active object, and process the internal variable declaration to set the attribute anotherColor to undefined.
(4) Execute the changeColor() function code. First execute anotherColor to initialize it to 'red', and then call the swapColors() function.
(5) Call the swapColors() function, enter the swapColors function execution environment, create the corresponding variable object (active object), and push the swapColors execution environment onto the top of the stack. A possible process for creating an active object here is to first create the active object, process the function parameters, use the formal parameters as attributes of the active object and assign them to undefined, create the attribute arguments object of the active object, and initialize the formal parameters and arguments according to the actual parameters. The values and attributes (initialize the attributes color1 and arguments[0] to 'white'. Since there is no second actual parameter, the value of color2 is undefined, and the length of arguments is only 1). After processing the function parameters, Then process the internal variable declaration of the function, use tempColor as a property of the active object and assign it to undefined.
(6) Execute the swapColors() function code. First initialize and assign a value to tempColor, and then implement the value exchange function (here the values of color and anotherColor are read along the scope chain).
(7) After the swapColors() function code is executed, it returns undefined, pops the corresponding execution environment from the stack and destroys it (note that the execution environment will be destroyed here, but the corresponding active objects of the execution environment will not necessarily destroyed), the current execution environment is restored to the execution environment of the changeColor() function. As the swapColor() function completes execution and returns, changeColor() also completes execution, also returns undefined, and the execution environment of the changeColor() function is popped off the stack and destroyed, and the current execution environment is restored to the global environment. The entire processing process ends, and the global environment is destroyed until the page exits.
The scope chain also explains why a function can call itself recursively internally: the function name is an attribute of the corresponding variable object in the execution environment where the function is defined, and then in the internal execution environment of the function, you can follow the scope The chain goes up one level to access the function object pointed to by the function name. If the function name points to a new function inside the function, the recursive call will be incorrect:
function fn(num){
if(1 == num){
return 1;
}else{
fn = function(){
return 0;
};
return num * fn(num - 1);
}
}
console.info(fn(5));//0
Regarding scope and declaration hoisting, let’s look at another example:
var name = 'linjisong';
function fn(){
console.info(name);//undefined
var name = 'oulinhai';
console.info( name);//oulinhai
}
fn();
console.info(name);//linjisong
The most unintuitive thing here may be the 3rd line of output undefined, because name has been defined globally, but if you parse it once according to the above parsing steps, you can get the correct result. In addition, I would like to emphasize that in ECMAScript there are only global execution environment and function execution environment, and correspondingly there are only global scope and function scope, and there is no block scope - although there are block statements.
function fn(){
var fnScope = ' a';
{
var blockScope = 'b';
blockScope = fnScope;
}
console.info(blockScope);//There is no block scope, so ok Access blockScope within the entire function scope
console.info(fnScope);
}
fn();//ba,a
console.info(blockScope);//ReferenceError, Outside the function scope, internally defined variables cannot be accessed
console.info(fnScope);//ReferenceError
For the scope chain, you can also use the catch block of the with and try-catch statements to extend:
•When using the with(obj){} statement, add the obj object to the current scope chain The front end.
•When using the try{}catch(error){} statement, add the error object to the front of the current scope chain.
I have inserted a more abstract concept, hoping it will not affect the smoothness of the entire reading. In fact, I have quietly bypassed a concept called "closure" here. Regarding functions and closures, below Described in detail in this article.
7. Function internal objects and this
For users of object-oriented languages, this is very familiar. It just points to the new creation of the constructor. The object! However, in ECMAScript, don't take it lightly, things are not that simple. Although when using the new operator to call a function, this does point to the newly created object, but this is just a way to specify the value of this object. There are more ways to specify the value of this object. In other words, this is dynamic and can be freely specified by ourselves.
(1) this in the global environment
In the global environment, this points to the global object itself, which is the window in the browser. Here, this in the global environment can also be understood as The variable object corresponding to the global execution environment. The variables and functions defined in the global environment are attributes of this variable object:
var vo = 'a';
vo2 = 'b';
function fn(){
return 'fn';
}
console.info(this === window);//true
console.info(this.vo);//a
console.info(this.vo2);//b
console .info(this.fn());//fn
If you want to reference the global object in a custom function, although you can use window directly, a better way is to use the global object as Parameters are passed into the function, which is a very common way in JS libraries:
(function(global){
console.info(global === window);//You can use global instead of window internally
})(this);
This method has better compatibility (global objects in ECMAScript implementation may not all be window). When compressing, global can also be simplified to g instead of using window.
(2) Function internal attribute this
In the function environment, this is an internal attribute object, which can be understood as an attribute of the active object corresponding to the function, and the value of this internal attribute is Dynamic. How is this value dynamically determined?
•When called using new, the function is also called a constructor. At this time, this inside the function is designated as the newly created object.
function fn(){
var name = ' oulinhai';//Attributes of the active object corresponding to the function
this.name = 'linjisong';//When calling the function using new, specify this as the newly created object, that is, add attributes to the newly created object
}
var person = new fn();
console.info(person.name);//linjisong
var arr = [fn];
console.info(arr[ 0]());//undefined
You need to pay attention to distinguish the properties defined in the function execution environment (that is, the properties of the active object) and the properties of this object. When calling the function using array elements When, this inside the function points to the array itself, so the above example finally outputs undefined.
•When called as a general function, this points to the global object.
•When called as a method of an object, this points to the object calling this method.
Look at the following example:
var name = ' oulinhai';
var person = {
name:'linjisong',
getName:function(){
return this.name;
}
};
console. info(person.getName());//linjisong
var getName = person.getName;
console.info(getName());//oulinhai
The function object itself is anonymous and is used as an attribute of the person object. When called as an object attribute, this points to the object. When this function is assigned to another function and then called, it is called as a general function. , this points to the global object. This example fully illustrates that "when a function is called as a method of an object, the internal attribute this points to the calling object, and when a function is called as a general function, the internal attribute this points to the global object." It also shows that the designation of this is dynamic and is specified at the time of call. , regardless of whether the function is defined individually or as an object method. It is precisely because when a function is called as a method of an object, this points to the calling object, so only when this is returned inside the function can the next method of the calling object be continued - that is, chain operation (a major feature of jQuery).
•When calling a function using apply(), call() or bind(), this points to the first parameter object. If no parameters are passed in or null or undefined are passed in, this points to the global object (will be set to null in ES5 strict mode). If the first parameter passed in is a simple type, this will be set to the corresponding simple type wrapper object.
var name = 'linjisong';
function fn (){
return this.name;
}
var person = {
name:'oulinhai',
getName:fn
};
var person2 = {name :'hujinxing'};
var person3 = {name:'huanglanxue'};
console.info(fn());//linjisong, general function call, the internal attribute this points to the global object, so this. name returns linjisong
console.info(person.getName()); //oulinhai, called as an object method, this points to this object, so here returns person.name
console.info(fn.apply(person2) );//hujinxing, use apply, call or bind to call the function and execute the first parameter object passed in, so person2.name
console.info(fn.call(person2));//hujinxing
var newFn = fn.bind(person3);//The new method in ES5 will create a new function instance and return it. The internal this value is specified as the passed parameter object
console.info(newFn()); //huanglanxue
The examples listed above are all common situations. The situations where the first parameter is null or undefined are not listed. Interested friends can test it by themselves. Regarding the determination of this value, there is another example in the original book:
var name = 'The Window';
var object = {
name : 'My Object',
getName:function(){
return this.name;
},
getNameFunc:function(){
return function(){
return this.name;
}
}
};
console.info(object.getName ());//My Object
console.info((object.getName)());//My Object
console.info((object.getName = object.getName)());// The Window
console.info(object.getNameFunc()());//The Window
第1个是正常输出,第2个(object.getName)与object.getName的效果是相同的,而第3个(object.getName=object.getName)最终返回的是函数对象本身,也就是说第3个会作为一般函数来调用,第4个则先是调用getNameFunc这个方法,返回一个函数,然后再调用这个函数,也是作为一般函数来调用。
8、函数属性和方法 函数是一个对象,因此也可以有自己的属性和方法。不过函数属性和方法与函数内部属性很容易混淆,既然容易混淆,就把它们放一起对照着看,就好比一对双胞胎,不对照着看,不熟悉的人是区分不了的。
先从概念上来区分一下:
(1)函数内部属性:可以理解为函数相应的活动对象的属性,是只能从函数体内部访问的属性,函数每一次被调用,都会被重新指定,具有动态性。
(2)函数属性和方法:这是函数作为对象所具有的特性,只要函数一定义,函数对象就被创建,相应的属性和方法就可以访问,并且除非你在代码中明确赋为另一个值,否则它们的值不会改变,因而具有静态性。有一个例外属性caller,表示调用当前函数的函数,也是在函数被调用时动态指定,在《JavaScript高级程序设计(第3版)》中也因此将caller属性和函数内部属性arguments、this一起讲解,事实上,在ES5的严格模式下,不能对具有动态特性的函数属性caller赋值。
光从概念上区分是非常抽象的,也不是那么容易理解,再把这些属性列在一起比较一下(没有列入一些非标准的属性,如name):
类别 |
名称 |
继承性 |
说明 |
备注 |
函数内部属性 |
this |
- |
函数据以执行的环境对象 |
和一般面向对象语言有很大区别 |
arguments |
- |
表示函数实际参数的类数组对象
arguments本身也有自己的属性:length、callee和caller
|
1、length属性表示实际接收到的参数个数
2、callee属性指向函数对象本身,即有:
fn.arguments.callee === fn
3、caller属性主要和函数的caller相区分,值永远都是undefined
|
函数属性 |
caller |
否 |
调用当前函数的函数 |
虽然函数一定义就可访问,但是不在函数体内访问时永远为null,在函数体内访问时返回调用当前函数的函数,在全局作用域中调用函数也会返回null |
length |
否 |
函数形式参数的长度 |
就是定义函数时命名的参数个数 |
prototype |
否 |
函数原型对象 |
原型对象是ECMAScript实现继承的基础 |
constructor |
是 |
继承自Object,表示创建函数实例的函数,也就是Function() |
值永远是Function,也就是内置的函数Function() |
函数方法 |
apply |
否 |
调用函数自身,以(类)数组方式接受参数 |
这三个方法主要作用是动态绑定函数内部属性this
1、apply和call在绑定之后会马上执行
2、bind在绑定之后可以在需要的时候再调用执行
|
call |
否 |
调用函数自身,以列举方式接受参数 |
bind |
否 |
绑定函数作用域,ES5中新增 |
toLocalString |
覆盖 |
覆盖了Object类型中的方法,返回函数体
不同浏览器实现返回可能不同,可能返回原始代码,也可能返回去掉注释后的代码
|
toString |
覆盖 |
valueOf |
覆盖 |
hasOwnProperty |
是 |
直接继承自Object类型的方法,用法同Object |
propertyIsEnumerable |
是 |
isPropertyOf |
是 |
Function properties and methods, in addition to those inherited from Object, also include properties and methods unique to the function itself. The most commonly used methods are naturally the apply() and call() mentioned in the previous section. This Both methods are used to set the internal attribute this of the function to expand the function scope. However, when apply() expands the function scope, it accepts the parameters of the function in the form of a (class) array, while call() expands the function scope. You need to list and pass the function parameters one by one, see the following example:
function sum(){
var total = 0,
l = arguments.length;
for(; l; l--){
total = arguments[l -1];
}
return total;
}
console.info(sum.apply(null,[1,2,3,4]));//10
console.info(sum.call(null,1,2,3,4));//10
However, it needs to be emphasized that the main role of apply and call is to extend the function Scope. apply and call will call the function immediately when expanding the scope, which puts great restrictions on the application. Therefore, a new bind() function is added in ES5. This function is also used to expand the scope, but it does not need to be executed immediately. Function, which returns a function instance and takes the first parameter passed to it as the scope of the original function. A possible implementation of it is as follows:
function bind(scope ){
var that = this;
return function(){
that.apply(scope, arguments);
}
}
Function.prototype.bind = bind;
This involves a concept of closure, we will continue tomorrow.