说下闭包的由来
function a() { var i = 0; function b() { console.log(i); } return b; } var c = a(); c();
一般来说,当一个函数内部匿名函数用到了自己的变量,并且这个匿名函数被返回了,这就建立了一个闭包,比如上面的代码
这个时候,就算a调用结束被销毁,i也会存在不会消失当a定义时,js解释器会将函数a的作用域链设置为定义a时所在环境当执行a时,a会进入相应的执行环境,执行环境创建后才会有作用域scope属性,然后创建一个活动对象,然后将其置为作用域链的顶端
现在a的作用域链就有a的活动对象以及window
然后为活动对象加入arguments属性
这个时候a的返回函数b的引用给了c,b的作用域链包含a的活动对象引用,所以c可以访问到a的活动对象,这个时候a返回后不会被GC
以上便是对闭包的简单介绍,说多了就容易绕进去了,我们这里简单结束,然后进入实际的场景加以说明
实际场景
同事的疑惑
之前一个同事让我去看一个代码:
var User = function (opts) { var scope = this; for (var k in opts) { scope['get' + k] = function () { return opts[k]; }; scope['set' + k] = function (v) { return opts[k] = v; }; } }; var u = new User({ name: '测试', age: 11 });
代码本意很简单,希望对传入的对象生成get/set方法,但是他这里就遇到一个闭包问题:
导致这个问题的原因就是返回值内部使用的k永远是“age”,这个k便是由于getXXX函数共享的活动对象,这里修改也比较简单
var User = function (opts) { var scope = this; for (var k in opts) { (function (k) { scope['get' + k] = function () { return opts[k]; }; scope['set' + k] = function (v) { return opts[k] = v; }; })(k); } }; var u = new User({ name: '测试', age: 11 });
在for循环内部创建一个立即执行函数,将k传入,这个时候getXXX函数共享的就是各个匿名函数的“k”了
生成唯一ID
生成唯一ID也是闭包一个经典的使用方式
function getUUID() { var id = 0; return function () { return ++id; } } var uuid = getUUID();
这段代码其实非常有意义,我们在浏览器中不停的执行uuid()确实会得到不同的值,但是如果我们只使用getUUID()()的话每次值仍然一样
导致这个问题的原因是,我们将getUUID执行后的结果赋予uuid,这个时候uuid就保存对其中匿名函数的引用,而匿名函数保存着getUUID的活动对象,所以id一直未销毁
而直接调用的话,每次都会重新生成活动对象,所以id是不能保存的
一段有意思的代码
Util.tryUrl = function (url) { var iframe = document.createElement('iframe'); iframe.height = 1; iframe.width = 1; iframe.frameBorder = 0; iframe.style.position = 'absolute'; iframe.style.left = '-9999px'; iframe.style.top = '-9999px'; document.body.appendChild(iframe); Util.tryUrl = function (url) { iframe.src = url; }; U.tryUrl(url); };
这段代码十分有意思,当我们第一次调用时候会创建一个iframe对象,而第二次调用时候iframe对象就存在了,我们这里将代码做一定简化后
var getUUID = function () { var i = 0; getUUID = function () { return i++; }; return getUUID(); };
这样调整后,其实并不存在返回函数,但是我们其实依然形成了闭包
事件委托与闭包
我们都知道jquery的on是采用的事件委托,但是真正了解什么事事件委托仍然要花一定功夫,于是我们这里来试试
闭包是事件委托实现的基石,我们最后就以事件委托深入学习下闭包结束今天闭包的学习吧
加入我们页面下有如下dom结构
<input value="input" type="button" /> <div> 我是div</div> <span>我是span</span> <div> <input value="我是inner" type="button"/> </div>
我们使用zepto的话是使用如下方式绑定事件
$.on('click', 'selector', fn)
我们这里没有zepto就自己简单实现吧
事件委托原理
首先事件委托实现的基石是事件冒泡,我们在页面的每次点击最终都会冒泡到其父元素,所以我们在document处可以捕捉到所有的事件
知道了这个问题后,我们可以自己实现一个简单的delegate事件绑定方式:
function delegate(selector, type, fn) { document.addEventListener(type, fn, false); } delegate('#input', 'click', function () { console.log('ttt'); });