上一篇:《javascript高级程序设计》笔记:内存与执行环境javascript
上篇文章中说到:
(1)当执行流进入函数时,对应的执行环境就会生成
(2)执行环境建立时会生成变量对象,肯定做用域链,肯定this指向
(2)每一个执行环境都有一个与之关联的变量对象,环境中定义的全部变量和函数都保存在这个对象中java
变量对象是在进入执行环境就肯定下来的,顾名思义,变量对象是用于存储在这个执行环境中的全部变量和函数的一个对象。只是这个对象是用于解析器处理数据时使用,咱们没法直接调用segmentfault
下图描述了执行流在执行环境中的执行过程(执行环境的生命周期)函数
(1)创建arguments对象。检查当前上下文中的参数,创建该对象下的属性与属性值。this
(2)检查当前上下文的函数声明,也就是使用function关键字声明的函数。在变量对象中以函数名创建一个属性,属性值为指向该函数所在内存地址的引用。若是函数名的属性已经存在,那么该属性将会被新的引用所覆盖。spa
(3)检查当前上下文中的变量声明,每找到一个变量声明,就在变量对象中以变量名创建一个属性,属性值为undefined。若是该变量名的属性已经存在,为了防止同名的函数被修改成undefined,则会直接跳过,原属性值不会被修改。设计
总之:function声明会比var声明优先级更高一点
code
下面经过具体的例子来看变量对象:对象
function test() { console.log(a); console.log(foo()); var a = 1; function foo() { return 2; } } test();
当执行到test()时会生成执行环境testEC,具体形式以下:blog
// 建立过程 testEC = { // 变量对象(variable object) VO: {}, // 做用域链 scopeChain: [], // this指向 this: {} }
仅针对变量对象来具体展开:
VO = { // 传参对象 arguments: {}, // 在testEC中定义的function foo: "<foo reference>", // 在testEC中定义的var a: undefined }
未进入执行阶段以前,变量对象中的属性都不能访问!可是进入执行阶段以后,变量对象转变为了活动对象,里面的属性都能被访问了,而后开始进行执行阶段的操做
// 执行阶段 VO -> AO // Active Object AO = { arguments: {...}, foo: function(){return 2}, a: 1 }
最后咱们将变量对象建立时的VO和执行阶段的AO整合到一块儿就能够获得整个执行环境中代码的执行顺序:
function test() { function foo() { return 2; } var a; console.log(a);// undefined console.log(foo());// 2 a = 1; } test();
这个就是分步变量对象的建立和执行阶段来解读预解析
预解析分为变量提高和函数提高
变量提高:提高的是当前变量的声明,赋值还保留在原来的位置
函数提高:函数声明,能够认为是把整个函数体声明了
函数表达式方式定义函数至关于变量声明 var fn = function(){}
一个简单的变量提高的例子
!function(){ console.log(a); var a = 1; }() // 实际执行顺序 !function(){ var a; console.log(a); a = 1; }()
技巧:
将执行过程手动拆分红两个步骤
1.建立阶段:也称做编译阶段,主要是变量的声明和函数的定义(找var和function)
2.执行阶段:变量赋值和函数执行
这样看预解析仍是比较容易的,可是涉及到命名冲突时,又是怎样的状况呢?
(1)一个函数和一个变量出现同名
状况一:变量只声明了,但没有赋值
!function(){ var f; function f() {}; console.log(f); // f(){} }()
状况二:变量只声明且赋值
!function(){ console.log(f); // f(){} var f = 123; function f() {}; console.log(f); // 123 }()
结论:
1.一个函数和一个变量出现同名,若是是变量只声明了,但没有赋值,变量名会被忽略
2.一个函数和一个变量出现同名,变量声明且赋值,赋值前值为函数,赋值后为变量的值
(2)两个变量出现同名
!function(){ console.log(f); // undefined var f = 123; var f = 456; console.log(f); // 456 }()
结论:
两个变量出现同名,重复的var声明无效,会被忽略,只会起到赋值的做用,前面赋值会被后面的覆盖
(3)两个函数出现同名
!function(){ console.log(f); // function f(){return 456}; function f(){return 123}; function f(){return 456}; console.log(f); // function f(){return 456}; }()
结论:
两个函数出现同名,因为javascript中函数没有重载,前面的同名函数会被后面的覆盖
(4)变量名与参数名相同
函数参数的本质是什么?函数参数也是变量,至关于在该函数的执行环境内最顶部声明了实参
状况一:参数为变量,与变量命名冲突
(function (a) { console.log(a); // 100 var a = 10; console.log(a); // 10 })(100); // 至关于 (function (a) { var a = 100; var a; // 重复声明的var没有意义,忽略 console.log(a); // 100 a = 10; console.log(a); // 10 })(100);
状况二:参数为函数,与变量命名冲突
(function (a) { console.log(a); // function(){return 2} var a = 10; console.log(a); // 10 })(function(){return 2}); // 至关于 (function (a) { var a = function(){return 2}; var a; // 重复声明的var没有意义,忽略 console.log(a); // function(){return 2} a = 10; console.log(a); // 10 })(function(){return 2});
状况三:参数为空,与变量命名冲突
(function (a) { console.log(a); // undefined var a = 10; console.log(a); // 10 })(); // 至关于 (function (a) { var a; console.log(a); // undefined a = 10; console.log(a); // 10 })();
结论:
1.重名的是一个变量,传入了参数(变量或函数):
若是是在变量赋值以前,此时获取的是:参数的值!
若是是在变量赋值以后,此时获取的是:变量的值!
2.重名的是一个变量,可是没有传入参数:
若是是在变量赋值以前,此时获取的是:undefined! 若是是在变量赋值以后,此时获取的是:变量的值!
(5)函数名与参数名相同
状况一:参数为变量,与函数命名冲突
(function (a) { console.log(a); // a(){} function a(){}; console.log(a); // a(){} })(100); // 至关于 (function (a) { var a = 100; function a(){}; console.log(a); // a(){} console.log(a); // a(){} })(100);
状况二:参数为函数,与函数命名冲突
(function (a) { console.log(a); // function a(){return 1} function a(){return 1} console.log(a); // function a(){return 1} })(function(){return 2}); // 至关于 (function (a) { var a = function(){return 2}; function a(){return 1}; console.log(a); // function a(){return 1} console.log(a); // function a(){return 1} })(function(){return 2});
状况三:参数为空,与函数命名冲突
(function (a) { console.log(a); // function a(){}; function a(){}; console.log(a); // function a(){}; })(); // 至关于 (function (a) { var a; function a(){}; console.log(a); // function a(){}; console.log(a); // function a(){}; })();
注意:函数表达式声明方式var f = function(){}
,在预解析中与普通的变量声明无异,由于预解析阶段主要是经过var
和function
来区分函数与变量的
结论:
传参为函数时,赋值前均为参数的值,赋值后为函数的值,传空时均为函数的值
如今,咱们回到变量对象中,在变量对象的建立阶段(执行流编译阶段),分别肯定了arguments对象、函数声明和变量声明,其优先级也是arguments>function>var
,arguments就是参数,反推上面变量提高的结论,一样是成立的