闭包应用实例

潘魏增@美团网

封面是一道鱼翅龙虾,两个意思,首先闭包是javascript中的一道美味,其次美团网上有很多这样的美食。

目标

(function(){
    var elForm;
    function getAttr() { ... }
    MT.app.Deal = {
        init:function() { ... }
    };
    MT.app.Deal.address = (function(){
        return {
            check:function() { ... }
        };
    })();
})();
            
elForm可以在init和check函数中共享,而不用保存在全局变量中。 这个是从实际代码中抽出来的,前面说过远古森林的一条条小溪,这个就是小溪的原型,也叫prototype。 我的目标就是让以前从未听说过闭包或者对闭包不熟悉的同学,在听完后能理解这段代码。

 

8.8. Function Scope and Closures

[*] This section covers advanced material that you may want to skip on your first reading.

理解闭包是成为高级前端工程师的必经之路

闭包的定义

闭包是函数和执行它的作用域组成的综合体
——《JavaScript权威指南》
所有的函数都是闭包
1.这里的作用域是指词法作用域,JavaScript中的函数是通过词法来划分作用域的,当一个函数进行定义的时候,作用域链就固定了。 2.这种定义比较宽泛,而只有当函数被导出到外部作用域时,这种闭包才是真正意义上的闭包。

闭包的定义

函数可以访问它被创建时的上下文环境,称为闭包
——《JavaScript语言精粹》
内部函数比它的外部函数具有更长的生命周期
综合体? 词法作用域? 上下文环境?

更简单的定义

闭包是引用了自由变量的函数
JavaScript中的函数就是对象

闭包 in action

function closure(name){
	var status = 1;
	return { 
		getName:function(){ return name; },
		getStatus:function(){ return status++; }
	}
}
var a = closure('w3ctech');
alert(a.getName());
alert(a.getStatus());
alert(a.getStatus());
            
1.这个示例中,参数name和变量status就是自由变量。 2.其中getName访问的不是name这个参数的拷贝,而是name本身。参数name生命周期延长了。 3.普通的函数,执行完,就会把变量和参数从内存中回收,而闭包不会。

作用域

函数作用域
  • 变量和参数在函数外不可见
  • 变量可以在函数内任何位置定义,并在函数内任何地方可见
  • 嵌套函数可以访问外部函数的参数和变量
在编程语言中,作用域控制着变量和参数的可见性以及生命周期。

作用域示例一

function func_scope(){
    var x = 1;
    if (true) {
        var x = 2;
        alert(x);
    }   
    alert(x);
}
func_scope();

作用域示例二

function a(){
    var name = 'w3ctech';
    return b();
    function b(){ return name; }
}
alert(a());
嵌套函数可以访问外部函数的所有参数和变量

自由变量

自由变量是作用域可以导出到外部作用域的变量
  • 函数内部变量和函数参数都可以是自由变量
  • 函数参数不包含this和arguments
JavaScript通过词法来划分作用域,即函数定义了的时候,作用域链就固定了。 函数调用时,除了声明时定义的形式参数,每个函数接受两个附加参数:this和arguments。 而参数this在面向对象编程中非常重要,它必须在调用的时候才知道自己是谁。 四种调用模式,具体参见《JavaScript语言精粹》。

自由变量示例一

var name = "The Window";
var object = {
	name : "My Object",
	getNameFunc : function(){
		return function(){
			return this.name;
		};
	}
};
alert(object.getNameFunc()());
            

自由变量示例二

var name = "The Window";
var object = {
	name : "My Object",
	getNameFunc : function(){
		var that = this;
		return function(){
			return that.name;
		};
	}
};
alert(object.getNameFunc()());
            

闭包 in action

function closure(name){
	var status = 1;
	return { 
		getName:function(){ return name; },
		getStatus:function(){ return status++; }
	}
}
var a = closure('w3ctech');
alert(a.getName());
alert(a.getStatus());
alert(a.getStatus());
            

闭包应用场景

全局变量是evil,是JavaScrip里面最糟糕的设计。 计数、状态

闭包与匿名函数的关系

匿名函数示例一

<img id="captcha" src="/account/captcha" />
<span onclick="
(function(){
	var img = document.getElementById('captcha');
	var	src = img.src.replace(/(\?0\.\d+)?$/, ''); 
	img.src = src + '?' + math.random();
})();
">
看不清楚?换一张
</span>
这个不是真正意义上的闭包,因为它没有自由变量。

匿名函数示例二

(function(){
var $E = YAHOO.util.Event;
var elPayForm;

MT.app.Order = {
    pay:function(){ 
        $E.on('order-pay-button', 'click', function(){
            elPayForm.submit();
        });
    }
};
})();
MT.app.Order.pay,说实话,我最喜欢美团网的用户执行这个函数了。 用户执行这个函数越多,我的工资和奖金就有了保证。应该在里面加一个salary和bonus的自由变量。

闭包 in real world

美团网代码分析演示

首先从core.js看namespace是如何管理的 app-signup.js 看闭包是如何实现的 w-rating.js 看类在闭包中的实现 app-kaixin.js 看所有变量都是闭包的情况

注意事项一

闭包与函数作用域
(function(){
var els = document.getElementsByTagName('li');
for(var i = 0, len = els.length; i < len; i++){
	var el = els[i];
	el.onclick = function(){
		alert(el.innerHTML); 
	}; 
}
})();

注意事项一

第一种修改方法
(function(){
var els = document.getElementsByTagName('li');
for(var i = 0, len = els.length; i < len; i++){
	var el = els[i];
	el.onclick = function(){ 
		alert(this.innerHTML); 
	}; 
}
})();

注意事项一

第二种修改方法
(function(){
var els = document.getElementsByTagName('li');
for(var i = 0, len = els.length; i < len; i++){
	(function(el){
		el.onclick = function(){ 
			alert(el.innerHTML);
		} 
	})(els[i])
}
})();

注意事项一

第三种修改方法
(function(){
var els = document.getElementsByTagName('li');
for(var i = 0, len = els.length; i < len; i++){
	var el = els[i];
	el.onclick = (function(x){
		return function(){ alert(x.innerHTML); } 
	})(el);
}
})();

注意事项二

IE内存泄漏问题
function test(el){
	el.attachEvent("onmouseover", handler);
	function handler(){
		// 这里有操作el的代码
	}
}
test(document.getElementById('menu'));

更多:Understanding and Solving Internet Explorer Leak Patterns

也有三种修改方法 1.不使用闭包 2.document.body.onunload时dettachEvent 3.使用0级DOM的事件监听方式,比如用el.onclick代替el.attachEvent,销毁el时先设置el.onclick=null 一般情况也不用太在意,有些交互比较复杂如私信选择好友,推荐好友列表

注意事项三

闭包无法杜绝全局变量污染
  • 祭出神器JSLint
  • 打开Option Explicit选项

推荐阅读

  1. 维基百科:闭包 (计算机科学)
  2. 阮一峰:学习Javascript闭包(Closure)
  3. 深入理解JavaScript闭包(Closure)

THANK YOU

meituan.com is hiring