上一篇:《javascript高级程序设计》笔记:内存与执行环境

上篇文章中说到:
(1)当执行流进入函数时,对应的执行环境就会生成
(2)执行环境创建时会生成变量对象,确定作用域链,确定this指向
(2)每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中

1. 变量对象

变量对象是在进入执行环境就确定下来的,顾名思义,变量对象是用于存储在这个执行环境中的所有变量和函数的一个对象。只是这个对象是用于解析器处理数据时使用,我们无法直接调用

下图描述了执行流在执行环境中的执行过程(执行环境的生命周期)

(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中定义的functionfoo: "<foo reference>",// 在testEC中定义的vara: 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);// undefinedconsole.log(foo());// 2a = 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); // undefinedvar 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); // 100var a = 10;console.log(a); // 10
})(100);
// 相当于
(function (a) {var a = 100;var a; // 重复声明的var没有意义,忽略console.log(a); // 100a = 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); // undefinedvar a = 10;console.log(a); // 10
})();
// 相当于
(function (a) {var a;console.log(a); // undefineda = 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(){},在预解析中与普通的变量声明无异,因为预解析阶段主要是通过varfunction来区分函数与变量的

结论
传参为函数时,赋值前均为参数的值,赋值后为函数的值,传空时均为函数的值

现在,我们回到变量对象中,在变量对象的创建阶段(执行流编译阶段),分别确定了arguments对象、函数声明和变量声明,其优先级也是arguments>function>var,arguments就是参数,反推上面变量提升的结论,同样是成立的

经典推荐:《javascript高级程序设计》笔记:原型图解

《javascript高级程序设计》笔记:变量对象与预解析相关推荐

  1. JavaScript高级程序设计笔记 - 第四章 变量 作用域 内存问题

    4.1 基本类型和引用类型的值 基本类型: 简单的数据段 引用类型: 指那些可能有多个值构成的对象, 指保存在内存中的对象 4.1.2 复制变量值 除了保存的方式不同之外,在从一个变量向另一个变量复制 ...

  2. JavaScript高级程序设计笔记01 | 第一章到第四章 | 关键字与保留字 | 数据类型 | 操作符 | 作用域

    观前提示:大部分都是书上的内容,个人理解的部分的较少,看我的笔记还不如去看书 第二章 async:可选.表示应该立即下载脚本,但不应妨碍页面中的其他操作,比如下载其他资源或 等待加载其他脚本.只对外部 ...

  3. javascript高级程序设计之变量、作用域和内存问题

    1.基本类型和引用类型的值 1.1 动态属性 对于引用类型的值,可以为其添加属性和方法,也可以改变和删除其属性和方法 1.2 复制变量值 基本类型的复制的值是相互独立的 引用类型的复制,其值的副本是指 ...

  4. javascript高级程序设计--笔记01

    概述 JavaScript的实现包含三个部分: 1  核心(ECMAScript)   提供核心语言功能 2  文档对象模型(DOM)  一套提供了访问以及操作网页内容的API 3  浏览器对象模型( ...

  5. JavaScript高级程序设计 第八章---对象,类与面向对象编程

    第八章(一) 内容 理解对象 理解对象创建过程 ECMA-262 将对象定义为一组属性的无序集合.严格来说,这意味着对象就是一组没有特定顺序的值.对象的每个属性或方法都由一个名称来标识,这个名称映射到 ...

  6. javascript高级程序设计笔记

    1.要讲一个值转换成其对应的Boolean类型 ,可以调用转型函数Boolean(). var message="hello world!"; var messageAsBoole ...

  7. 读javascript高级程序设计06-面向对象之继承

    原型链是实现继承的主要方法,通过原型能让一个引用类型继承另一个引用类型. 1.原型链实现继承 function SuperType(){ this.superprop=1; } SuperType.p ...

  8. JavaScript高级程序设计笔记 事件冒泡和事件捕获

    1.事件冒泡 要理解事件冒泡,就得先知道事件流.事件流描述的是从页面接收事件的顺序,比如如下的代码: <body><div> click me!</div> < ...

  9. 《javascript高级程序设计》笔记:内存与执行环境

    上一篇:<javascript高级程序设计>笔记:继承 近几篇博客都会围绕着图中的知识点展开 由于javascript是一门具有自动垃圾收集机制的编程语言,开发者不必担心内存的分配和回收的 ...

最新文章

  1. 如何使用RS-232发射器和接收器?
  2. Qt学习笔记5-信号槽机制
  3. SAP Spartacus B2cStorefrontModule里提供的默认配置
  4. leetcode104 二叉树的最大深度
  5. 蔚来辅助驾驶致死,副总裁沈斐或遇更大危机
  6. 打工人的健康修炼记:2021卷里求生(附报告下载)
  7. 190329每日一句
  8. 【重新挂载磁盘空间】Linux系统/home的磁盘空间重新挂载给/root
  9. 小米手机无限重启怎么办?
  10. 大学计算机实践教程raptor,大学计算机实践教程.docx
  11. 贪心 汽车加油 java_贪心算法解汽车加油站问题
  12. arcmap怎么保存相对路径_如何将arcgis的mxd文档存储为相对路径
  13. 在Ubuntu 8.10 中安装使用新一代输入法ibus Deb包下载
  14. python统计人物出现次数_python实例:利用jieba库,分析统计金庸名著《倚天屠龙记》中人物名出现次数并排序...
  15. 平板app尺寸html5,app界面设计尺寸规范大全
  16. Docker Nginx 如何重新加载配置
  17. python中get函数是什么意思_详解python中get函数的用法(附代码)_后端开发
  18. 服务器运行时将杀毒软件关掉,教你快速关闭禁用Windows 10自带的杀毒软件Windows Defender...
  19. 【总结】北大2018冬令营题目总结
  20. 苹果掉出全球手机市场前三

热门文章

  1. Win8兼容ARM十大意义:打破垄断拉低价格
  2. Symbian的内存管理机制
  3. 【Vegas原创】解决System.Web.Extensions版本冲突方法
  4. confluence 常见问题处理
  5. 设计模式——装饰者模式
  6. 《深入浅出Mysql》學習筆記--日志
  7. @SkipValidation跳过Struts2validation校验器
  8. CCF NOI1040 除法游戏
  9. 基本数据类型与表达式2 - 零基础入门学习Delphi03
  10. 135.137.138.139.445端口分析