摘要:检查当前上下文中的参数,建立该对象下的属性与属性值。检查当前上下文的函数声明,也就是使用关键字声明的函数。如果该变量名的属性已经存在,为了防止同名的函数被修改为,则会直接跳过,原属性值不会被修改。
上一篇:《javascript高级程序设计》笔记:内存与执行环境
上篇文章中说到:
(1)当执行流进入函数时,对应的执行环境就会生成
(2)执行环境创建时会生成变量对象,确定作用域链,确定this指向
(2)每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中
变量对象是在进入执行环境就确定下来的,顾名思义,变量对象是用于存储在这个执行环境中的所有变量和函数的一个对象。只是这个对象是用于解析器处理数据时使用,我们无法直接调用
下图描述了执行流在执行环境中的执行过程(执行环境的生命周期)
(1)建立arguments对象。检查当前上下文中的参数,建立该对象下的属性与属性值。
(2)检查当前上下文的函数声明,也就是使用function关键字声明的函数。在变量对象中以函数名建立一个属性,属性值为指向该函数所在内存地址的引用。如果函数名的属性已经存在,那么该属性将会被新的引用所覆盖。
(3)检查当前上下文中的变量声明,每找到一个变量声明,就在变量对象中以变量名建立一个属性,属性值为undefined。如果该变量名的属性已经存在,为了防止同名的函数被修改为undefined,则会直接跳过,原属性值不会被修改。
总之:function声明会比var声明优先级更高一点
下面通过具体的例子来看变量对象:
function test() { console.log(a); console.log(foo()); var a = 1; function foo() { return 2; } } test();
当执行到test()时会生成执行环境testEC,具体形式如下:
// 创建过程 testEC = { // 变量对象(variable object) VO: {}, // 作用域链 scopeChain: [], // this指向 this: {} }
仅针对变量对象来具体展开:
VO = { // 传参对象 arguments: {}, // 在testEC中定义的function foo: "", // 在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();
这个就是分步变量对象的创建和执行阶段来解读预解析
2. 预解析预解析分为变量提升和函数提升
变量提升:提升的是当前变量的声明,赋值还保留在原来的位置
函数提升:函数声明,可以认为是把整个函数体声明了
函数表达式方式定义函数相当于变量声明 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就是参数,反推上面变量提升的结论,同样是成立的
经典推荐:《javascript高级程序设计》笔记:原型图解
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/89969.html
摘要:因此,所有在方法中定义的变量都是放在栈内存中的当我们在程序中创建一个对象时,这个对象将被保存到运行时数据区中,以便反复利用因为对象的创建成本通常较大,这个运行时数据区就是堆内存。 上一篇:《javascript高级程序设计》笔记:继承近几篇博客都会围绕着图中的知识点展开 showImg(https://segmentfault.com/img/bVY0C4?w=1330&h=618);...
摘要:建筑的顶层代表全局作用域。实际的块级作用域远不止如此块级作用域函数作用域早期盛行的立即执行函数就是为了形成块级作用域,不污染全局。这便是闭包的特点吧经典面试题下面的代码输出内容答案个如何处理能够输出闭包方式方式下一篇你不知道的笔记 下一篇:《你不知道的javascript》笔记_this 写在前面 这一系列的笔记是在《javascript高级程序设计》读书笔记系列的升华版本,旨在将零碎...
摘要:数据类型中有种简单数据类型也称为基本数据类型和。在中非空字符串,非零数字,任意对象,都被认为。而空字符串,和,,认为是。用于表示整数和浮点数。标识符由数字字母下划线美元符组成,但首字母不能是数字。变量方法对象命名推荐驼峰法。 JavaScript语法 一.语法简介 因为JavaScript语法和Java等语法非常类似。所以只是简单介绍一下。 大小写 JavaScript是大小写敏感的语...
摘要:一写在前面最近重读高级程序设计,总结下来,查漏补缺。但这种影响是单向的修改命名参数不会改变中对应的值。这是因为对象的长度是由传入的参数个数决定的,不是由定义函数时的命名参数的个数决定的。实际改变会同步,改变也会同步 一、写在前面 最近重读《JavaScript高级程序设计》,总结下来,查漏补缺。 二、JS简介 2.1 JS组成 ECMAscript:以ECMA-262为基础的语言,由...
摘要:用于把对象序列化字符串,在序列化对象时,所有函数及原型成员都会被有意忽略,不体现在结果中。对第步返回的每个值进行相应的序列化。参考文档高级程序设计作者以乐之名本文原创,有不当的地方欢迎指出。 showImg(https://segmentfault.com/img/bVburW1?w=658&h=494); JSON与JavaScript对象 JSON是一种表示结构化数据的存储格式,语...
阅读 5262·2021-09-22 15:50
阅读 1866·2021-09-02 15:15
阅读 1166·2019-08-29 12:49
阅读 2545·2019-08-26 13:31
阅读 3461·2019-08-26 12:09
阅读 1212·2019-08-23 18:17
阅读 2738·2019-08-23 17:56
阅读 2930·2019-08-23 16:02