JS工作中的小贴士之”闭包“与事件委托的”阻止冒泡“


Posted in Javascript onJune 16, 2016

说下闭包的由来

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 id="input" value="input" type="button" />
<div id="div">
我是div</div>
<span id="span">我是span</span>
<div id="wrapper">
<input id="inner" 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');
});

这段代码是最简单的实现,首先我们无论点击页面什么地方都会执行click事件,当然这显然不是我们想要看到的情况,于是我们做处理,让每次点击时候触发他应有的事件

这里有几个问题比较尖锐:

① 既然我们事件是绑定到document上面,那么我怎么知道我现在是点击的什么元素呢

② 就算我能根据e.target获取当前点击元素,但是我怎么知道是哪个元素具有事件呢

③ 就算我能根据selector确定当前点击的哪个元素需要执行事件,但是我怎么找得到是哪个事件呢

如果能解决以上问题的话,我们后面的流程就比较简单了

确定点击元素是否触发事件

首先,我们点击时候可以使用e.target获取当前点击元素,然后再根据selector依次寻找其父DOM,如果找得到就应该触发事件

因为这些都是要在触发时候才能决定,所以我们需要重写其fn回调函数,于是简单操作后:

var arr = [];
var slice = arr.slice;
var extend = function (src, obj) {
var o = {};
for (var k in src) {
o[k] = src[k];
}
for (var k in obj) {
o[k] = obj[k];
}
return o;
};
function delegate(selector, type, fn) {
var callback = fn;
var handler = function (e) {
//选择器找到的元素
var selectorEl = document.querySelector(selector);
//当前点击元素
var el = e.target;
//确定选择器找到的元素是否包含当前点击元素,如果包含就应该触发事件
/*************
注意,此处只是简单实现,实际应用会有许多判断
*************/
if (selectorEl.contains(el)) {
var evt = extend(e, { currentTarget: selectorEl });
evt = [evt].concat(slice.call(arguments, 1));
callback.apply(selectorEl, evt);
var s = '';
}
var s = '';
};
document.addEventListener(type, handler, false);
}

于是我们可以展开调用了:

delegate('#input', 'click', function () {
console.log('input');
});
delegate('#div', 'click', function () {
console.log('div');
});
delegate('#wrapper', 'click', function () {
console.log('wrapper');
});
delegate('#span', 'click', function () {
console.log('span');
});
delegate('#inner', 'click', function () {
console.log('inner');
});

我们这里来简单解析下整个程序

① 我们调用delegate为body增加事件

② 在具体绑定时候,我们将其中的回调给重写了

③ 在具体点击时候(绑定几次事件实际就会触发几次click),会获取当前元素,查看其选择器搜索的元素是否包含他,如果包含的话便触发事件

④ 由于这里每次注册时候都会形成一个闭包,传入的callback被维护起来了,所以每次调用便能找到自己的回调函数(这里对闭包理解很有帮助)

⑤ 最后重写event句柄的currentTarget,于是一次事件委托就结束了

PS:我这里实现还有问题的,比如在event的处理上就有问题,但是作为demo的话我便不去关注了,有兴趣的朋友自己去看zepto实现吧

事件委托的问题

事件委托可以提高效率但是有一个比较烦的事情就是阻止冒泡没用

拿上面代码来说,有一个inner元素和一个wrapper元素,他们是互相包裹关系

但是其执行顺序并不是先内再外的事件冒泡顺序,因为事件全部绑定到了document上面,所以这里执行顺序便是以其注册顺序所决定

这里有一个问题便是如何“阻止冒泡”

在inner处完了执行

e.stopImmediatePropagation()

是可以达到目的的,但是仍然要求inner元素必须注册到之前

除此之外,就只给这种会嵌套的元素绑定一个事件,又e.target决定到底执行哪个事件,具体各位自己斟酌

以上问题在使用backbone可能实际会遇到

Javascript 相关文章推荐
jquery写个checkbox——类似邮箱全选功能
Mar 19 Javascript
jquery实现加载等待效果示例
Sep 25 Javascript
JQuery设置获取下拉菜单某个选项的值(比较全)
Aug 05 Javascript
JavaScript函数定义的常见注意事项小结
Sep 16 Javascript
深入浅析JavaScript的API设计原则
Jun 14 Javascript
最全面的百度地图JavaScript离线版开发
Sep 10 Javascript
通过npm引用的vue组件使用详解
Mar 02 Javascript
Vue2.0 UI框架ElementUI使用方法详解
Apr 14 Javascript
Angular4如何自定义首屏的加载动画详解
Jul 26 Javascript
解决vue页面刷新或者后退参数丢失的问题
Mar 13 Javascript
浅谈vue3中effect与computed的亲密关系
Oct 10 Javascript
vue如何在用户要关闭当前网页时弹出提示的实现
May 31 Javascript
JS阻止事件冒泡行为和闭包的方法
Jun 16 #Javascript
jquery实现简单Tab切换菜单效果
Jul 17 #Javascript
特殊日期提示功能的实现方法
Jun 16 #Javascript
JS代码实现根据时间变换页面背景效果
Jun 16 #Javascript
基于JS代码实现图片在页面中旋转效果
Jun 16 #Javascript
客户端验证用户名和密码的方法详解
Jun 16 #Javascript
检查表单元素的值是否为空的实例代码
Jun 16 #Javascript
You might like
在smarty模板中使用PHP函数的方法
2011/04/23 PHP
php将金额数字转化为中文大写
2015/07/09 PHP
比较全面的event对像在IE与FF中的区别 推荐
2009/09/21 Javascript
JavaScript 学习笔记(四)
2009/12/31 Javascript
js下获得客户端操作系统的函数代码(1:vista,2:windows7,3:2000,4:xp,5:2003,6:2008)
2011/10/31 Javascript
JavaScript高级程序设计 阅读笔记(二十一) JavaScript中的XML
2012/09/14 Javascript
JS解决url传值出现中文乱码的另类办法
2013/04/08 Javascript
js 表单提交后按钮变灰的实例代码
2013/08/16 Javascript
jquery中get和post的简单实例
2014/02/04 Javascript
9款2014最热门jQuery实用特效推荐
2014/12/07 Javascript
基于JavaScript实现网页倒计时自动跳转代码
2015/12/28 Javascript
JS实现为排序好的字符串找出重复行的方法
2016/03/02 Javascript
node.js实现爬虫教程
2020/08/25 Javascript
使用jQuery的toggle()方法对HTML标签进行显示、隐藏的方法(示例)
2016/09/01 Javascript
基于JS实现移动端向左滑动出现删除按钮功能
2017/02/22 Javascript
JS编写兼容IE6,7,8浏览器无缝自动轮播
2018/10/12 Javascript
javascript中this的用法实践分析
2019/07/29 Javascript
wx-charts 微信小程序图表插件的具体使用
2019/08/18 Javascript
Vue-CLI 项目在pycharm中配置方法
2019/08/30 Javascript
Vue 自定义指令功能完整实例
2019/09/17 Javascript
Vue中el-form标签中的自定义el-select下拉框标签功能
2020/04/20 Javascript
jQuery 函数实例分析【函数声明、函数表达式、匿名函数等】
2020/05/19 jQuery
Python中用startswith()函数判断字符串开头的教程
2015/04/07 Python
python使用socket连接远程服务器的方法
2015/04/29 Python
Python使用爬虫猜密码
2016/02/19 Python
浅谈Python2.6和Python3.0中八进制数字表示的区别
2017/04/28 Python
浅谈Python处理PDF的方法
2017/11/10 Python
bluepy 一款python封装的BLE利器简单介绍
2019/06/25 Python
Flask框架实现的前端RSA加密与后端Python解密功能详解
2019/08/13 Python
阿联酋最好的手机、电子产品和家用电器网上商店:Eros Digital Home
2020/08/09 全球购物
出售房屋协议书范本
2014/10/06 职场文书
停水通知
2015/04/16 职场文书
2015年初三班主任工作总结
2015/05/21 职场文书
90行Python代码开发个人云盘应用
2021/04/20 Python
Python pandas求方差和标准差的方法实例
2021/08/04 Python
nginx静态资源的服务器配置方法
2022/07/07 Servers