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 相关文章推荐
优秀js开源框架-jQuery使用手册(1)
Mar 10 Javascript
jquery 简短右键菜单 多浏览器兼容
Jan 01 Javascript
JS实现闪动的title消息提醒效果
Jun 20 Javascript
非jQuery实现照片散落桌子上,单击放大的LightBox效果
Nov 28 Javascript
基于jQuery实现带动画效果超炫酷的弹出对话框(附源码下载)
Feb 22 Javascript
jquery实现一个简单的表单验证实例
Mar 30 Javascript
JavaScript ES6的新特性使用新方法定义Class
Jun 28 Javascript
JavaScript制作简单分页插件
Sep 11 Javascript
js判断数组是否包含某个字符串变量的实例
Nov 24 Javascript
Vue-cli项目获取本地json文件数据的实例
Mar 07 Javascript
JS获取url参数,JS发送json格式的POST请求方法
Mar 29 Javascript
jQuery插件Validation表单验证详解
May 26 jQuery
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
php程序的国际化实现方法(利用gettext)
2011/08/14 PHP
php异常处理技术,顶级异常处理器
2012/06/13 PHP
wordpress自定义url参数实现路由功能的代码示例
2013/11/28 PHP
ThinkPHP CURD方法之where方法详解
2014/06/18 PHP
PHP产生不重复随机数的5个方法总结
2014/11/12 PHP
PHP基于ip2long实现IP转换整形
2020/12/11 PHP
JQuery 学习技巧总结
2010/05/21 Javascript
JavaScript常用对象的方法和属性小结
2012/01/24 Javascript
javascript:FF/Chrome与IE动态加载元素的区别说明
2014/01/26 Javascript
10条建议帮助你创建更好的jQuery插件
2015/05/18 Javascript
JS插件overlib用法实例详解
2015/12/26 Javascript
js实现异步循环实现代码
2016/02/16 Javascript
ionic由于使用了header和subheader导致被遮挡的问题的两种解决方法
2016/09/22 Javascript
网络传输协议(http协议)
2016/11/18 Javascript
bootstrap实现图片自动轮播
2016/12/21 Javascript
vue 组件高级用法实例详解
2018/04/11 Javascript
在vue 中使用 less的教程详解
2018/09/26 Javascript
nodejs实现用户登录路由功能
2019/05/22 NodeJs
[05:08]第一届“网鱼杯”DOTA2比赛精彩集锦
2014/09/05 DOTA
python3.3实现乘法表示例
2014/02/07 Python
Python求算数平方根和约数的方法汇总
2016/03/09 Python
深入理解python中的atexit模块
2017/03/07 Python
利用Hyperic调用Python实现进程守护
2018/01/02 Python
为什么选择python编程语言入门黑客攻防 给你几个理由!
2018/02/02 Python
python SMTP实现发送带附件电子邮件
2018/05/22 Python
Python3单行定义多个变量或赋值方法
2018/07/12 Python
Python简易版停车管理系统
2019/08/12 Python
wxPython多个窗口的基本结构
2019/11/19 Python
使用python将微信image下.dat文件解密为.png的方法
2020/11/30 Python
卡骆驰新加坡官网:Crocs新加坡
2018/06/12 全球购物
荷兰鞋类购物网站:Donelli
2019/05/24 全球购物
幼儿园教师辞职信
2014/01/18 职场文书
解除劳动合同协议书范本
2014/04/14 职场文书
2014年幼儿园学期工作总结
2014/12/05 职场文书
文员岗位职责范本
2015/04/16 职场文书
python爬虫之利用selenium模块自动登录CSDN
2021/04/22 Python