前言
过年期间在家喝酒有点厉害,刚刚来公司这边就变成“歪脖子”了,整个肩膀很疼啊,所以程序员平时要多运动才行
开年后也很忙,一方面然后又搬了个家,另一方面最近就算有所得,都是零零碎碎,不然就是不够深入,于是就没有什么总结了
最近在工作中碰到几个闭包的问题,虽然我对闭包了解不是太深,但是觉得还是可以拿出来说下,如果有何问题,请指正
闭包的产生
复制代码
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可能实际会遇到
结语
今天过来的主要原因其实是研究虚拟键盘与fixed相关的问题,不想一个同事问到了闭包和事件委托相关东西,这里便做一点记录,希望对各位有帮助
如果文中有何问题,请一并提出
本文转自叶小钗博客园博客,原文链接:http://www.cnblogs.com/yexiaochai/p/3561797.html,如需转载请自行联系原作者

【小贴士】工作中的”闭包“与事件委托的”阻止冒泡“相关推荐

  1. 小程序组件中的监听事件

    小程序组件中的监听事件 需求: 微信小程序中,如果进行使用了component级的组件的话,在一些情况下,父组件中使页面中的数据进行变化,子组件中 的数据不会一起变化,由此可以使用该方法 方法: 使用 ...

  2. 128-Vue中的事件修饰符-阻止冒泡事件

    128-Vue中的事件修饰符 .stop 阻止事件冒泡(*) .prevent 阻止默认事件(*) .prevent.stop 阻止默认事件的同时阻止冒泡 .once 阻止事件重复触发(once与st ...

  3. Event事件-1:addEventListener事件监听 / 事件冒泡事件捕获 / 事件委托 / preventDefault 阻止默认行为 / cancelBubble、stopPropa...

    addEventListener 事件监听器 target.addEventListener(type, listener[, options|useCapture])     添加事件监听 参数: ...

  4. vue 事件上加阻止冒泡 阻止默认事件

    重点 vue事件修饰符 <!-- 阻止单击事件冒泡 --> <a v-on:click.stop="doThis"></a><!-- 提交 ...

  5. 用vue的事件修饰符阻止冒泡

    用mousemove事件举例 1.传统做法: 定义一个阻止冒泡的函数stop,形参为事件e,执行e.stopPropagation(), 在标签上添加v-on:mousemove="stop ...

  6. 详解js中EventListener监听器(事件委托/事件代理)

    监听器: 语法: addEventListener(event,function,userCapture) 方法 第一个参数写事件句柄,不需要加"on",直接写时间名就可以(如:c ...

  7. 【小贴士】虚拟键盘与fixed带给移动端的痛!

    前言 今天来公司的主要目的就是研究虚拟键盘与fixed的问题,期间因为同事问起闭包与事件委托(阻止冒泡)相关问题,便穿插了一篇别的: [小贴士]工作中的"闭包"与事件委托的&quo ...

  8. python常见加密方式总结踩坑小贴士

    本文是向大家介绍python中常见的一些加密方式,在使用python的时候遇到数据加密的情况时,可以根据实际场景来选择加密的方式对数据进行加密,加强数据传输的安全性. 一.前言 日常工作中经常会看到各 ...

  9. 微信小程序开发中的二三事之网易云信IMSDK DEMO

    本文由作者邹永胜授权网易云社区发布. 简介 为了更好的展示我们即时通讯SDK强悍的能力,网易云信IM SDK微信小程序DEMO的开发就提上了日程.用产品的话说就是: 云信 IM 小程序 SDK 的能力 ...

  10. 一图讲清:如何将招聘管理流程化,让HR从重复工作中解放出来

    小A是上海某家科技初创公司负责招聘管理的HR,帮公司每个岗位挖掘到合适的人才是她每天的工作内容.但随着公司从5个人.20个人,到现在的100多人,招聘任务越来越多,招聘流程也变得复杂起来,仅凭她人工整 ...

最新文章

  1. 用.Net Reactor5打包加密dll文件和exe程序
  2. LeetCode 2. Add Two Numbers--C++,Python解法--面试算法题
  3. web.xml配置错误页面,及输出错误信息
  4. 多形态MVC式Web架构:完成实时响应
  5. 東方茸回廊 汉化补丁
  6. Caffe代码导读(0):路线图
  7. java.lang.NoClassDefFoundError: org/springframework/context/ApplicationContextAware
  8. 《3D Math Primer for Graphics and Game Development》读书笔记1
  9. firefox input file宽度失效
  10. python内存技巧_使用__slots__节省python内存技巧
  11. 上海一公司向苹果索赔100亿,要求停售iPhone
  12. 通过Nginx反向代理,实现远程调试本机代码
  13. 爬虫项目之爬取页面并按界面样式导入excel表格
  14. 【Android游戏开发二十一】Android os设备谎言分辨率的解决方案!
  15. LoadRunner如何建立关联
  16. CCNA--路由器常用命令
  17. vue 实现12个月的平铺式日历插件
  18. 部署VC2008应用程序时不能运行解决办法
  19. 证件照尺寸大小收集整理
  20. 【转载】树莓派 Raspberry Pi Pico windows7 串口驱动

热门文章

  1. Python数据库操作——连接MySQL
  2. ios 点击出现另外一套tabbar_iOS 点击UITabBar触发刷新
  3. 软硬件联合调试步骤_PLC与SCADA或第三方软件的联合仿真调试—NetToPLCSIM
  4. mysql backup 使用_mysqlbackup (官方使用)
  5. 传统emmc所用的sdio接口_MMC/SD/SDIO介绍
  6. c语言游戏源码_【C语言/C++】益智游戏开发:2048(思路+源码详解)
  7. vivaldi浏览器_两款可以提升效率的网络浏览器
  8. j2ee 现在已经改名为java ee_Java EE 已经正式更名为 Jakarta EE(雅加达)
  9. Java数组--数组常用的办法;
  10. java实体类生成mysql表_自己简单写的JDBCUtils,可以根据当前数据库下面的表生成java实体类。...