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 相关文章推荐
使用prototype.js进行异步操作
Feb 07 Javascript
对采用动态原型方式无法展示继承机制得思考
Dec 04 Javascript
用AJAX返回HTML片段中的JavaScript脚本
Jan 04 Javascript
锋利的jQuery 要点归纳(三) jQuery中的事件和动画(下:动画篇)
Mar 24 Javascript
JavaScript从0开始构思表情插件
Jul 26 Javascript
JavaScript DOM节点操作方法总结
Aug 23 Javascript
AngularJS学习笔记(三)数据双向绑定的简单实例
Nov 08 Javascript
浅谈Javascript中的Label语句
Dec 14 Javascript
Bootstrap modal 多弹窗之叠加引起的滚动条遮罩阴影问题
Feb 27 Javascript
jquery+ajaxform+springboot控件实现数据更新功能
Jan 22 jQuery
解决vue单页使用keep-alive页面返回不刷新的问题
Mar 13 Javascript
vue实现滚动鼠标滚轮切换页面
Dec 13 Vue.js
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开发工具ZendStudio下Xdebug工具使用说明详解
2013/11/11 PHP
PHP中替换键名的简易方法示例详解
2014/01/07 PHP
php使用pdo连接并查询sql数据库的方法
2014/12/24 PHP
PHP Streams(流)详细介绍及使用
2015/05/12 PHP
php数据库操作model类(使用__call方法)
2016/11/16 PHP
浅谈PHP匿名函数和闭包
2019/03/08 PHP
CSS中一些@规则的用法小结
2021/03/09 HTML / CSS
如何让您的中波更粗更长 - 中波框形天线制作
2021/03/10 无线电
slice函数的用法 之不错的应用
2006/12/29 Javascript
js 获取服务器控件值的代码
2010/03/05 Javascript
jquery判断浏览器后退时候弹出消息的方法
2014/08/11 Javascript
jquery实现在页面加载的时自动为日期插件添加当前日期
2014/08/20 Javascript
easyui Droppable组件实现放置特效
2015/08/19 Javascript
Backbone.js框架中Model与Collection的使用实例
2016/05/07 Javascript
JS键盘版计算器的制作方法
2016/12/03 Javascript
Vuejs实现带样式的单文件组件新方法
2017/05/02 Javascript
node.js利用socket.io实现多人在线匹配联机五子棋
2018/05/31 Javascript
使用RxJS更优雅地进行定时请求详析
2019/06/02 Javascript
如何通过shell脚本自动生成vue文件详解
2019/09/10 Javascript
TypeScript之调用栈的实现
2019/12/31 Javascript
js找出5个数中最大的一个数和倒数第二大的数实现方法示例小结
2020/03/04 Javascript
jQuery实现倒计时功能完整示例
2020/06/01 jQuery
element-ui tree结构实现增删改自定义功能代码
2020/08/31 Javascript
vue+vant实现购物车全选和反选功能
2020/11/17 Vue.js
[02:39]DOTA2英雄基础教程 极限穿梭编织者
2013/12/05 DOTA
python lambda表达式在sort函数中的使用详解
2019/08/28 Python
python连接PostgreSQL过程解析
2020/02/09 Python
大学自主招生自荐信
2013/12/16 职场文书
出生证明公证书
2014/04/09 职场文书
办公室主任四风问题对照检查材料思想汇报
2014/09/28 职场文书
2014副镇长民主生活会个人对照检查材料思想汇报
2014/09/30 职场文书
青年文明号创建口号大全
2015/12/25 职场文书
2016个人先进事迹材料范文
2016/03/01 职场文书
入党申请书格式
2019/06/20 职场文书
HTML通过表单实现酒店筛选功能
2021/05/18 HTML / CSS
python ansible自动化运维工具执行流程
2021/06/24 Python