第七章、匿名函数
本章中比较重要的几个概念:闭包、匿名函数,作用域链,this对象等。在js中碰到的很多坑都来自本章。另外,在很多类库中如jQuery中都会大量使用闭包和匿名函数,它们都是非常有用的特性。
1、函数定义和函数表达式的区别:对于函数定义,在代码执行以前会被加载到内存中,而对于函数表达式:只有在代码执行到那一行的时候才有定义:
function func(){
}
var func = function(){
}
2、可以用arguments.callee实现递归,比直接使用函数名更加保险。即使你的函数更改为另外的名字,使用arguments.callee可以保证递归正确执行。
3、闭包和匿名函数的区别:
闭包是有权访问另一个函数作用域中的变量的函数。创建闭包的最常见方式,就是在一个函数内部创建另外一个函数。理解如何创建作用域链,以及作用域链的细节,对于理解闭包非常重要。作用域链本质是一个指向变量对象的指针列表,它只引用而不实际包含对象。
作用域链:当函数第一次被调用时,会创建一个执行环境以及相应的作用域链,并把作用域链复制给一个特殊的内部属性[[Scope]],然后,利用this,arguments和其他命名参数的值来初始化函数的活动对象。
后台的每个一执行环境都有一个表示变量的对象-变量对象。全局环境的变量对象始终存在。而对于函数的局部环境的变量对象,只在函数执行的过程中存在。
在另一个函数中定义的函数会将包含函数(外部函数)的活动对象添加到它的作用域链中。
function createCompare( proper ){
returnfunction( obj1, obj2) {
varval1 = obj1[proper];
varval2 = obj2[proper];
returnval1 - val2;
}
}
var obj = [
{id: 10, age : 20},
{id: 12, age : 16},
{id: 15, age : 22},
];
var compare = createCompare('age');
obj.sort(compare);
console.log(obj);
在匿名函数从createCompare函数返回之后,它的作用域链被初始化为包含createCompare函数的活动对象和全局变量对象。这样匿名函数就可以访问在createCompare函数中定义的所有变量。更为重要的是,createCompare函数在执行完毕之后,它的活动对象也不会销毁,因为匿名函数的作用域链依然在引用这个活动对象。换句话说:在createCompare函数返回后,其执行环境的作用域链会被销毁,但是它的活动对象依然会保留在内存中,直到匿名函数被销毁后,createCompare的活动对象才会被销毁。
compare = null;
由于闭包会携带包含它的函数的作用域,因此会比其他的函数占用更多的内存。
闭包与变量:
由于闭包的特性,导致闭包只能取得包含函数中任意变量的最后一个值。闭包中保存的是整个变量对象,而不是某个特殊的变量:
function create(){
var result = [];
for( var i = 0; i < 10; i++ ){
result[i] = function(){
return i;
};
}
return result;
}
由于闭包引用的外部函数的变量i,因此每个函数都保存着同一个变量对象,所以在每个函数内部i的值都是10.可以通过创建另一个匿名函数强制让闭包的行为符合预期:
function create(){
var result = [];
for( var i = 0; i < 10; i++ ){
result[i] = function(num){
return function(){
return num;
}
}(i);
}
return result;
}
关于this对象:
this对象是在运行时给予函数的执行环境绑定的,在全局函数中,this等于window,而当函数作为某个对象的方法调用时,this等于那个对象。不过匿名函数的执行具有全局性,因此其this对象通常指向window.在通过call()或者apply()改变函数执行环境的情况下,this指向其他对象。
var name = "the window";
var obj = {
name : "the obj",
getFunc: function(){
return function(){
return this.name;
}
}
}
console.log( obj.getFunc()() );
可以把外部作用域的this变量包含在一个闭包能够访问的变量里,就可以让闭包访问该对象了:
var name = "the window";
var obj = {
name : "the obj",
getFunc: function(){
var that = this;
return function(){
return that.name;
}
}
}
console.log( obj.getFunc()() );
this和arguments也存在同样的问题。如果想要访问作用域中的arguments对象,必须将对该对象引用保存到另一个闭包能够访问的变量中。
闭包与内存泄露:
由于IE对于JScript对象和COM对象使用不同的垃圾收集机制(IE对Javascript采用标记清除的垃圾收集机制,而DOM和BOM对象并非原生的Javascript对象,是通过引用计数机制来进行垃圾收集的),因此闭包在IE中会导致一些特殊的问题。如果闭包的作用域链中包含了一个HTML元素,那么就意味着该元素无法被销毁:
function setHandler(){
var elem = document.getElementById("someElement");
elem.οnclick= function(){
console.log(elem.id );
}
}
以上代码创建了一个elem元素的时间处理程序的闭包,而这个闭包又创建了一个循环引用。由于匿名函数保存了一个对assignHandler的活动对象引用,因此在assignHandler执行完毕退出时,并不会销毁assignHandler的活动对象。因此只要有匿名函数存在,elem的引用数至少也是1. 解决方法:
functionsetHandler(){
var elem =document.getElementById("someElement");
var id = elem.id;
elem.onclick = function(){
console.log( id );
}
elem = null;
}
块级作用域:javascript中没有块级作用域的概念,要模拟块级作用域,可以用下面的语法:
(function(){
})();
也就是定义一个匿名函数并立即调用它。
这个技术最常在全局作用域中被用于函数外部,从而限制向全局作用域中添加过多的变量和函数。这种方法可以减少闭包占用的内存问题,因为没有指向匿名函数的引用。
私有变量:
function MyObject(){
var privateVar = 10;
function privateFunc(){
console.log(privateVar );
}
this.publicFunc= function(){
privateVar++;
privateFunc();
}
}