关于执行上下文--整理
JS执行上下文,也可以理解为代码的运行环境或者作用域,分别为以下三种
1,全局上下文,引擎最先进入的就是这个环境
2,函数级别的代码,当执行函数(就是函数调用),运行内代码
3,Eval代码,在Eval函数中运行的代码
每当调用一个函数(不管是否重复),一个新的执行上下文会被创建出来
具体分为两个阶段
第一个阶段:建立阶段(1,建立 arguments对象(检查当前上下文的参数),
2,参数,
3,函数声明,
4,变量声明;
,5建立作用域链
6,确定this的值
)
这个时刻,引擎会检查函数中的参数,声明的变量以及内部函数,然后基于这些信息建立执行上下文对象(executionContextObj)。在这个阶段,variableObject对象,作用域链,以及this所指向的对象都会被确定。
第二个阶段:代码执行阶段(变量赋值,函数引用,执行其它代码)
var a = 10; var b = 30; function fn(x){ var a = 100; var c = 20; function f1(x){ var a = 1000; var d = 10; console.log(x); } f1(100); // 100 f1(200); // 200 } fn(250);
函数上下文中的变量对象
Arguments对象是活动对象的一个属性,它包括如下属性
1,callee--指向当前函数的引用
2,length --真正传递的参数个数(arguments.lenth是真正的传参个数,foo.length是参数)
3,properties-indexes (字符串类型的整数) 属性的值就是函数的参数值(按参数列表从左到右排列)。
properties-indexes内部元素的个数等于arguments.length. properties-indexes 的值和实际传递进来的参数之间是共享的。
function foo(x, y, z) { // 声明的函数参数数量arguments (x, y, z) alert(foo.length); // 3 // 真正传进来的参数个数(only x, y) alert(arguments.length); // 2 // 参数的callee是函数自身 alert(arguments.callee === foo); // true // 参数共享 alert(x === arguments[0]); // true alert(x); // 10 arguments[0] = 20; alert(x); // 20 x = 30; alert(arguments[0]); // 30 // 不过,没有传进来的参数z,和参数的第3个索引值是不共享的 z = 40; alert(arguments[2]); // undefined arguments[2] = 50; alert(z); // 40 } foo(10, 20);
这个例子的代码,在当前版本的Google Chrome浏览器里有一个bug — 即使没有传递参数z,z和arguments[2]仍然是共享的。
几个例子
var a = 10; function fn() { console.log(a); } function bar(f){ var a = 20; f(); } bar(fn); // 10 // 因为fn()函数虽然在调用之前就进行了预编译状态。所以a=10
function fn() { console.log(a); } function bar(f){ a = 20; //没加var 这是全局变量 f(); } bar(fn); // 20
1 var b; 2 console.log(b); //b是undefined 3 (function b(){ 4 b = 20; 5 console.log("b:"+b); 6 // b:function b(){b = 20;console.log("b:"+b);} 7 })(); 8 console.log(b); //b是undefined
//加上自己的理解吧(并不是一定正确)
// 在这个自调用函数之中,函数名和一个属性的赋值都是b的名称。 b=20 是全局变量,自调用函数相当于函数的声明,函数声明的提升
顺序大于 变量的提升,所以b赋值为 function b(){}
// 当 b=20 改为var b = 20;时 结果就是 b:20
1 var b; 2 console.log(b); //b是undefined 3 (function a(){ 4 b = 20; 5 console.log("b:"+b);// b:20 6 })(); 7 console.log(b); // 20 8 //以下是我自己的理解, 这次函数名字和赋值b 并不冲突。所以 b=20是全局变量,
var b; function b(){}; console.log(b) //function b(){} 因为变量b没赋值。所以函数优先 //另一个例子 var b = 20; function b(){}; console.log(b) //20 变量赋值 则函数声明不会影响到值。因为函数声明比变量声明的等级高,都会提到最前面。变量的赋值是最后面
var scope = "global scope"; function checkscope(){ var scope = "local scope"; function f(){ console.log(scope); //local scope } return f(); } checkscope(); var scope = "global scope"; function checkscope(){ var scope = "local scope"; function f(){ console.log(scope); //local scope } return f; } checkscope()(); //区别在哪呢 //第二个的入栈顺序为 /* ECStack.push(checkscope> functionContext); ECStack.pop(); ECStack.push(f> functionContext); ECStack.pop(); */
function test(a, b) { var c = 10; function d() {} var e = function _e() {}; (function x() {}); } test(10); // call // AO(test) = { // a: 10, // b: undefined, // c: undefined, // d: <reference to FunctionDeclaration "d"> // e: undefined // }; // AO['c'] = 10; // AO['e'] = <reference to FunctionExpression "_e">; // 关于变量,还有一个重要的知识点。变量相对于简单属性来说,变量有一个特性(attribute):{DontDelete},这个特性的含义就是不能用delete操作符直接删除变量属性。 a = 10; alert(window.a); // 10 alert(delete a); // true alert(window.a); // undefined var b = 20; alert(window.b); // 20 alert(delete b); // false alert(window.b); // still 20 // // 但是这个规则在有个上下文里不起走样,那就是eval上下文,变量没有{DontDelete}特性。 eval('var a = 10;'); alert(window.a); // 10 alert(delete a); // true alert(window.a); // undefined function test(a,b){ var c = 10; function d(){} var e = function _e(){}; (function x(){}); b = 20; } test(10); // 提示:因为此函数中有形参b,所以在变量初始化阶段会b:undefined,如果没有形参b,会报错 b is not defined。
以上有参考其他文献