执行环境和作用域
每个函数都有自己的执行环境,当代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链的用途,就是保证对执行环境有权访问的所有变量和函数进行访问。
内部环境可以通过作用域链访问所有的外部环境,但是外部环境不能够访问内部环境中的任何变量和函数。
延长作用域链
有些语句可以在作用域链的前端临时增加一个变量对象,该变量对象会在代码执行后被移除。比如try-catch语句的catch块,还有with语句。
查询标识符
查找标识符的过程从作用域链的前端开始,向上逐级查询与给定名字匹配的标示符。如果在局部环境中找到了该标示符,搜索过程停止,变量就绪。如果在局部环境中没有搜索到该变量名,则会继续沿作用域链向上搜索,直到搜到全局环境变量。
在这个搜索过程中,如果存在一个局部的变量定义,那么搜索就会停止,不再进入下一个变量对象。也就是如果局部环境中存在着同名标示符,就不会再去父环境中找了。
var color = 'blue';
function getColor(){
var color = 'red';
return color;
}
console.log(color); // red
垃圾收集
JavaScript具有自定垃圾收集机制,执行环境会负责管理代码执行过程中使用的内存。
标记清除:JavaScript最常用的垃圾收集方式就是标记清除,当变量进入环境,就会把这个变量标记为进入环境,当变量离开环境,就标记为离开环境。垃圾收集器在运行的时候会给变量都加上标记,然后它会去掉环境中变量的标记和被环境中变量引用的变量标记,而在此后,还拥有标记的变量就会被视为准备删除的变量,最后,垃圾收集起完成内存清除的工作,销毁那些带标记的值并回收它们所占的内存空间。
引用计数:另一种不太常见的垃圾收集策略叫引用计数,通过跟踪每个值被引用的次数,当声明了一个变量并将一个引用类型的值赋给变量时,该值的引用次数就加一,相反,如果这个变量之后又引用了其他的值来赋值,那么那个值的引用次数剪一。当引用次数为0时,说明这个值已经没有用了,那就可以把它占用的内存空间回收回来。 但是这种方法有一个问题,如果两个变量循环引用,那么引用次数就不会为0,内存也就得不到回收了。
管理内存
为了确保占用最少的内存让页面有更好的性能,一旦数据不再有用,最好通过使用把其值设为null来解除引用。这个做法适用于大多数全局变量和全局对象的属性,局部变量会在它们离开执行环境的时候自动解除引用。
不过,解除一个值的引用并不意味着会立马自动回收该值所占有的内存,而是让值脱离执行环境,等待垃圾收集器下次运行时将其回收。
闭包
之前作用域链中有说到,作用域链的前端可以访问包含它的作用域(外部)中的变量,所以,如果我们在一个函数内部创建一个函数,那么内部的那个函数可以访问外部函数中的变量。闭包就是指有权访问另一个函数作用域中变量的函数。(就是内部创建的那个函数)。
闭包与变量
不过闭包也会有一些问题,因为它能取得父作用域的值,有的时候会有这样的问题。
function create(){
var result = new Array();
for(var i=0; i< 10; i++){
result[i] = function(){
return i;
};
}
return result;
}
这样的结果是,数组里每个值都是10,这是因为i保留着对creat函数的i的引用,所以i都是10。不过我们可以通过再创建一个匿名函数来达到预期。
function create(){
var result = new Array();
for(var i=0; i< 10; i++){
result[i] = function(num){
return function(){
return num;
}(i);
};
}
return result;
}
this
在全局函数中,this等于window,而当函数被作为某个对象方法调用时,this等于那个对象。
var name = "window";
var object ={
name:"object",
getNameFunc: function(){
return function(){
return this.name;
};
}
};
console.log(object.getNameFunc()); //window
这样相当于直接在全局作用域下去取得this.name,所以如果我们想要取得object的作用域,可以把object的外部作用域保存在一个闭包能访问到的变量里,这样闭包就能访问到该对象了。
var name = "window";
var object ={
name:"oject",
getNameFunc: function(){
var that = this;
return function(){
return that.name;
};
}
};
console.log(object.getNameFunc()); //object
模仿块级作用域
(function(){
//这里是块级作用域
})();
通过一层封装创造一个单独的作用域,这样用在全局中,其他的全局函数和变量就访问不到我们块级作用域中的变量了,通过创建私有作用域,每个开发者既可以拥有自己的变量,又不比担心污染全局作用域。而且也可以减少闭包占用的内存问题,因为没有指向匿名函数的引用,只要函数执行完毕,就可以立即销毁其作用域链了。
模块模式
道格拉斯所说的模块模式则是为单例创建瓷釉变量和特权方法,所谓单例,指的是只有一个实例的对象。
var single = function(){
//私有
var privateV = 10;
var privateF = function(){
return flase;
};
//特权 公有方法和属性
return{
publicv : 20,
publicF : function(){
return privateV;
}
};
}();
增强的模块模式
有人进一步改进了模块模式,这种增强的模块模式适合那些单例必须是某种类型的实例。同时还必须添加某些属性和方法对其加以增强的情况。
var single = function(){
//私有
var privateV = 10;
var privateF = function(){
return flase;
};
//特权 公有方法和属性
var object = new Object();
object.publicv = 20;
object.publicF = function(){
return privateV;
};
return object;
}();