execution context 执行环境(环境),是JavaScript中特别重要的一个概念,它定义了变量或函数有权访问的其他数据,决定了他们各自的行为, 每个函数都有自己的执行环境
-
变量对象:每个执行环境都有一个与之关联的变量对象,环境中定义的变量和函数都保存在这个对象中,不过我们无法操作这个对象,只有解析器在处理数据时,在后台访问它。
-
全局执行环境:全局执行环境是最外层的执行环境,也即是window。
-
销毁:某个环境中的所有代码执行完之后,它之中的函数和变量都会被自动销毁,但是全局环境只有在关闭浏览器窗口的时候才会被销毁。
-
执行流:程序在运行的时候,我们可以将程序的运行看成是水流在流动,从window开始,向下流动,当它进入一个函数时,函数的环境会被推入一个环境栈中, 在函数执行完毕后,函数的环境将会被弹出栈,并且销毁此函数环境中的变量和作用域,然后将控制权返回给之前的执行环境
-
作用域链:代码在环境中执行的时候,会创建一条作用域链,它的作用是保证执行环境对有权访问的变量和对象的有序访问。在作用域链的前端,始终都是当前 代码所在执行环境的变量对象。当执行环境是一个函数时,则将它的活动对象作为变量对象。活动对象一开始只包含一个arguments对象(也就是传入的参数)。 作用域链中的后一个变量对象,来自于上一个执行环境,在后一个,来自于上上个执行环境,再后一个。。。。以此规则一直推到全局环境。因为它永远都是作用域链中的 最后一个变量对象。所以我们在查找标示符的时候,会从当前执行环境的变量对象开始查找,一层一层向后,直至全局环境的变量对象,若还没查找到,通常会报错
let test = [];
function test2(obj) {
if(obj instanceof Array){
console.log(1231231);
}
}
test2(test)
在此函数中,test2执行环境的作用域链中,包含两个变量对象,一个是它本身的变量对象,一个是全局变量对象,所以它能够获取到全局变量对象中的test
-
延长作用域链:with和try-catch,都能够延长作用域链,with会将其中包裹的东西加到作用域的最前对,try-catch会创建一个新的变量对象,其中包含 被抛出的错误对象声明,但是with在严格模式下不可用
-
没有块级作用域:看一下一下代码
if (true) {
var color = 'blue';
}
alert(color)
如果有块级作用域的话,以上代码会报错,因为在花括号外围无法找到color,所以js是没有块级作用域的,还有一种情况需要特别注意、
for(var i=0; i<10; i++){
var y = i
}
console.log(i) //10
console.log(y) //9
for循环中定义的i是可以被访问到的!所以如果我们的执行环境或者上级执行环境中的变量对象包含i,那么我们的循环就会出错!
-
垃圾收集:相较于c与c++,js有自带的垃圾回收机制,所以我们不用再手动清除内存中不需要的变量,它一般有两种清除方式
-
标记清除:垃圾回收机制运行的时候,会把所有变量加上标记,然后清除环境中的变量和被引用的变量的标记。下次再运行时,被加上标记的就是准备回收删除的
-
引用计数:跟踪记录每个值被引用的计数,如果生命了一个变量,并将一个引用类型的值赋给了变量,那么这个值的计数就是1,如果这个变量又被赋值给了其他变量, 那么这个值的引用计数加1,相反的,如果这个变量被赋了其他值,那么这个值的应用计数减1,如果引用计数为零,那么这个值将会被垃圾回收机制清除
//假设一个引用类型的值为 {}
let test = {}; //第一次引用,计数+1
let test2 = test; //第二次被引用,计数+1,现在为2
test = []; //计数-1
test2 = []; //计数-1 计数为0 {} 被垃圾回收机制清除
但是这个机制存在一个问题,如果存在循环引用的情况,垃圾回收机制就无法正确清除
function problem() {
let test = {};
let test2 = {};
test.test2 = test2;
test2.test = test;
}
以上例子中,test对象中的一个属性引用了test2,test2中的一个属性引用了test,这样他们的引用次数都是2,那么他们就不会被回收,永远的留在内存中
- 性能问题:现代浏览器中的垃圾回收机制,一般能很好的回收我们不再使用的数据,但是比较好的做法是,当不再使用的时候,手动将变量指向null,这样 垃圾回收机制可能会自动回收,这也引发了我的一个思考,我们在大量使用全局变量的情况下,会导致性能问题,所以性能优化的一个点,就是减少全局变量的使用