参考 高性能javascript Tom大叔深入理解javascript系列
相关概念
1.执行上下文
当控制器转到ecmascript可执行代码的时候,就会进入一个执行上下文,执行上下文是以堆栈的方式进行管理的,也就是最底层是全局的上下文,最顶层是当前的执行上下文,每当进入function(包括递归调用)或者eval,都会产生执行上下文压入堆栈,随着函数或者eval的结束,对应的执行上下文被弹出.每当遇到return语句的时候就会推出当前的执行上下文,代码执行完毕后,管理执行上下文的堆栈只会包含全局的上下文
eval('var x = 10'); (function foo() { eval('var y = 20'); })(); alert(x); // 10 alert(y); // "y" 提示没有声明
eval在执行的时候会产生调用上下文,而eval的执行会影响到这个调用上下文的状态(数据)
上面的例子中 第一个eval执行的时候会产生调用上下文,它的调用上下文是全局上下文,也就是这个eval操作会对全局上下文产生影响,第二个eval的调用上下文是函数foo的执行上下文,这个eval操作只会影响到foo的指向上下文,当foo函数执行完毕,foo的执行上下文弹出堆栈,所以在全局上下文中y并没有定义
2.变量对象
变量对象是管理执行上下文相关变量的一种机制,通过变量对象我们能知道如何访问上下文对象中的变量,它是一个与执行上下文相关的特殊对象,它储存着执行上下文中的变量 函数声明 函数形参
在全局的上下文中全局对象自身就是变量对象(理解 因为全局上下文中的变量对象就是全局对象,声明在全局的变量相当于向全局上下文的变量对象中添加属性,也就是向全局对象中添加属性 )
在函数上下文中,变量对象这个概念被活动对象所替换,在进入函数上下文的时刻活动对象通过arguments属性初始化
function test(x) { console.log(x); console.log(arguments[0]); arguments[0] = 100; console.log(x); } test(1);//形参 和 arguments 之间的数据共享
处理上下文代码分为两个阶段,进入执行上下文 执行代码
进入执行上下文阶段 变量对象包含下列的值 函数所有的形式参数(键值对) 所有函数声明 所有的变量声明(不会干扰在变量对象中存在的同名字的形式参数和函数声明)
代码执行阶段 其实就是对进入执行上下文阶段初始化值的赋值过程
理解函数声明的提升
alert(x); // function var x = 10; alert(x); // 10 x = 20; function x() { console.log(1); };
在进入执行上下文的阶段,变量的声明是在函数的形式参数声明和函数声明之后的,并且变量的声明不会影响函数的声明,所以在进入执行上下文阶段的变量对象中x的值是指向声明的函数 在执行阶段在对变量的值进行修改
3.变量
变量只能通过var关键字进行声明
a = 10; var b = 10;
在上面的代码中进入执行上下文阶段 变量对象中只包含b undefined 不存在a 之所以存在一种观点任何不通过var声明的变量都会转变成全局变量 是因为在代码执行阶段 比如执行到 a = 10的时候,由于此时的变量对象就是全局对象,并且我们访问全局对象的属性的时候是可以省略全局对象的前缀的(也就是this或者window),所以这句话相当于在全局对象中添加属性,并没有声明一个变量 属性是可以删除的 但是变量是不可以删除的 特例
eval("var a = 10;"); console.log(delete a);//true
var b = 10;
console.log(delete b);//false
3 作用链域
在每个执行上下文中都有一个变量对象(函数执行上下文活动对象),作用链域正是存储这些变量对象(包括父级的变量对象的列表),通过作用链域进行标示符的解析,函数的作用链域在函数调用时进行创建
如何进行标示符解析
var x = 10; function test() { var y = 20; console.log(x+y); } test();
上面的例子中在全局上下文中的变量对象中是存在变量X 函数test 在函数test的活动对象中存在着变量y 但是函数执行的过程中能到查找到x,也就是在test的执行上下文中能够查找到x 这个机制是什么呢? 就是通过[[scope]]属性,这个属性在函数创建的时候被创建,包含父级上下文的所有变量对象加上自身的变量对象(活动对象)构成作用链域(也就是存在当前的变量对象的变量或者函数声明屏蔽父级变量对象的变量或者函数声明),[[scope]]在函数创建创建的时候被存储,它是静态的属性
var x = 10; function test(){ var x = 10; console.log(x); } test();
上面的例子中在test的作用链域中是有test的活动对象 + [[scope]]组成,当前test的上下文中[[scope]]属性值只包含全局上下文的变量对象也就是全局对象,在标示符解析的过程中优先从作用链域的顶端test的活动对象中查找,在通过[[scope]]属相沿着父级的变量对象一级一级进行查找,知道找到相应的变量或者函数声明,未找到返回undefined
例外 通过Function创建的函数的[[scope]]中只包含全局对象
正文
在执行链域中处于深层的标示符解析的时候,因为要通过作用链域进行查找,位置越深的读取速度也就越慢,以下是提供标示符解析性能的一些方法
(1)缓存跨作用域的值
function test() { var doc = document; var getId = doc.getElementById("aa"); }
在函数中缓存全局变量的document属性,在函数test中进行多次使用的时候,就能减少变量查找的过程
(2)在通过原型链进行查找的过程中,对象成员的层次越深,查找的性能越慢,多次进行读写的话也就更加降低性能,所以涉及到对对象成员的查找的时候也进行一定的缓存
(3)避免使用with语句 with会在作用链域的开头加入新的对象对象从而导致原来的变量对象处于作用链域的第二位置,降低了访问的性能