关于event:事件的阻止冒泡和阻止默认行为

阻止冒泡w3c的办法是event.stopPropagation(),IE则是应用event.cancelBubble = true; 兼容写法<div class="parent"> <div class="child"></div></div><script> const child = document.querySelector(".child"); child .addEventListener("click",function(event){ event = window.event || event if(event.stopPropagation){ event.stopPropagation() }else{ event.cancelBubble = true } }) </script>阻止默认行为w3c的办法是event.preventDefault(),IE则是应用event.returnValue = false;如果事件是能够冒泡的,在冒泡过程中也能够阻止默认行为,举个例子,咱们在body标签的click事件中阻止默认行为,那么页面上所有的a标签点击事件的默认行为都无奈执行,也就是都无奈跳转。 兼容写法 <input id="div" value="123"> <script> const div = document.getElementById("div") div.addEventListener("copy",function(event){ event = window.event || event; if(event.preventDefault){ event.preventDefault() }else{ event.returnValue = false; } }) </script>return falsejavascript的return false只会阻止默认行为,且只能在dom0事件中失效。而用jQuery的话则既阻止默认行为又避免对象冒泡。 <a href="www.baidu.com" id="a"></a>const a = document.getElementById("a")a.onclick = function(){ return false;};<a href="www.baidu.com" id="a"></a>$("#a").on('click',function(){return false;});

June 19, 2021 · 1 min · jiezi

利用eventemitter2实现Vue组件通信

概述当两个组件之间没有任何父子关系时,利用Vue标准的props传值和emit触发事件无法解决他们之间通信的问题。最近做的项目使用的是eventemitter2,来实现不相关组件之间的通信。这篇文章分享的是我对eventemitter2使用的总结和体会。 eventemitter2的npm文档大家可以看 eventemitter2介绍。它是node.js提供的事件接口。安装如下 npm install --save eventemitter2模块的EventEmitter2属性是一个构造函数,可以生成eventemitter2实例。实例定义了绑定、触发、移除事件的方法。本文涉及到的实例方法有on,off,emit,removeAllListeners,更多方法,大家可以去npm文档自行学习。 和其他事件处理机制一样,eventemitter2事件处理,必须包含三部分:绑定事件 =》 触发事件 =》 移除事件 本次分享,主要讨论在vue项目中使用eventemitter2: 方法一:结合class模块化编程,对实例、事件名、绑定事件方法和移除事件方法做封装方法二:结合vue插件开发,全局添加事件实例方法一:结合class开发步骤: 添加模块 event_confg.js,用于存储eventEmitter2实例和事件名添加模块 event_manager.js,封装实例绑定事件的方法和移除事件的方法按照 绑定事件 =》 触发事件 =》 移除事件的步骤,使用eventeitter2第一步:创建 event_confg.jsvar EventEmitter2 = require('eventemitter2').EventEmitter2;// emiter中定义的是eventemitter2实例,config中定义的是事件名const eventConfig = {}eventConfig.emitter = new EventEmitter2()eventConfig.config = { "CHECK_CHANGES": "checkChanges" // 更多的事件名,往这里添加}export default eventConfig第二步: 创建event_manager.js此模块创建的是一个类,传入eventemitter2实例后,对实例的添加和移除事件的方法做了封装。这里绑定和移除事件,分别用了on,off方法。 export default class { constructor(evtInst){ this.evtInst = evtInst this.listeners = {} // {eventName: [callback1,callback2...]} } // 将事件名evtName的回调绑定为callback,同时将事件名和回调存到listeners,方便后面移除事件使用 addListener(evtName, callback){ this.evtInst.on(evtName, callback) if(!this.listeners[evtName]){ this.listeners[evtName] = [callback] }else{ this.listeners[evtName].push(callback) } } removeListeners(){ Object.keys(this.listeners).forEach(evtName => { this.listeners[evtName].forEach((callback, index) => { this.evtInst.off(evtName,callback) }) }) }}第三步: 在组件中使用eventemitter2绑定事件同一个事件名,可以绑定多个事件回调,当事件被触发时,会顺序执行同名的回调函数 ...

November 3, 2019 · 1 min · jiezi

源码解读一文彻底搞懂Events模块

前言为什么写这篇文章? 清楚的记得刚找node工作和面试官聊到了事件循环,然后面试官问事件是如何产生的?什么情况下产生事件。。。Events 在哪些场景应用到了?之前封装了一个 RxJava 的开源网络请求框架,也是基于发布-订阅模式,语言都是相通的,挺有趣。表情符号Events 模块是我公众号 Node.js 进阶路线的一部分面试会问说一下 Node.js 哪里应用到了发布/订阅模式Events 模块在实际项目开发中有使用过吗?具体应用场景是? Events 监听函数的执行顺序是异步还是同步的? 说几个 Events 模块的常用函数吧? 模拟实现 Node.js 的核心模块 Events 文章首发Github 博客开源项目 https://github.com/koala-codi... 发布/订阅者模式发布/订阅者模式应该是我在开发过程中遇到的最多的设计模式。发布/订阅者模式,也可以称之为消息机制,定义了一种依赖关系,这种依赖关系可以理解为 1对N (注意:不一定是1对多,有时候也会1对1哦),观察者们同时监听某一个对象相应的状态变换,一旦变化则通知到所有观察者,从而触发观察者相应的事件,该设计模式解决了主体对象与观察者之间功能的耦合。 生活中的发布/订阅者模式警察抓小偷在现实生活中,警察抓小偷是一个典型的观察者模式「这以一个惯犯在街道逛街然后被抓为例子」,这里小偷就是被观察者,各个干警就是观察者,干警时时观察着小偷,当小偷正在偷东西「就给干警发送出一条信号,实际上小偷不可能告诉干警我有偷东西」,干警收到信号,出击抓小偷。这就是一个观察者模式 订阅了某个报社的报纸生活中就像是去报社订报纸,你喜欢读什么报就去报社去交钱订阅,当发布了新报纸的时候,报社会向所有订阅了报纸的每一个人发送一份,订阅者就可以接收到。 你订阅了我的公众号我这个微信公号作者是发布者,您这些微信用户是订阅者「我发送一篇文章的时候,关注了【程序员成长指北】的订阅者们都可以收到文章。 实例的代码实现与分析以大家订阅公众号为例子,看看发布/订阅模式如何实现的。(以订阅报纸作为例子的原因,可以增加一个type参数,用于区分订阅不同类型的公众号,如有的人订阅的是前端公众号,有的人订阅的是 Node.js 公众号,使用此属性来标记。这样和接下来要讲的 EventEmitter 源码更相符,另一个原因是这样你只要打开一个订阅号文章是不是就想到了发布-订阅者模式呢。) 代码如下: let officeAccounts ={ // 初始化定义一个存储类型对象 subscribes:{ 'any':[] }, // 添加订阅号 subscribe:function(type='any',fn){ if(!this.subscribes[type]){ this.subscribes[type] = []; } this.subscribes[type].push(fn);//将订阅方法存在数组中 }, // 退订 unSubscribe:function(type='any',fn){ this.subscribes[type] = this.subscribes[type].filter((item)=>{ return item!=fn;// 将退订的方法从数组中移除 }); }, // 发布订阅 publish:function(type='any',...args){ this.subscribes[type].forEach(item => { item(...args);// 根据不同的类型调用相应的方法 }); }}以上就是一个最简单的观察者模式的实现,可以看到代码非常的简单,核心原理就是将订阅的方法按分类存在一个数组中,当发布时取出执行即可 ...

October 17, 2019 · 3 min · jiezi

浏览器常用监听事件

广告:Fundebug错误监控插件,及时发现Bug,提高Debug效率!页面//初始化页面监听document.addEventListener(“DOMContentLoaded”, ready);// 页面跳转hashdocument.addEventListener(“hashchange”, navigation);// 监听pop和push需要自定义document.addEventListener(“popstate”, navigation);document.addEventListener(“pushState”, navigation);//离开页面监听document.addEventListener(“beforeunload”, leave);自定义监听popstate和pushStatehistory.pushState = this.resetHistory(“pushState”);history.replaceState = this.resetHistory(“replaceState”); resetHistory(type) { let orig = history[type]; return function() { let rv = orig.apply(this, arguments); let e = new Event(type); e.arguments = arguments; window.dispatchEvent(e); return rv; }; }errorwindow.onerror = function (errorMsg, url, lineNumber) { alert(errorMsg + lineNumber);//错误信息+lineNumber }; window.addEventListener(‘unhandledrejection’, event => { console.log(‘unhandledrejection:’ + event);//打印event查看所有报错信息 });

April 17, 2019 · 1 min · jiezi

JFinal-event v2.3.0 发布,简化 Event 事件类

说明JFinal-event 2.x 参考 Spring 4.2.x 中 Event 的使用方式而生,为 JFinal 用户带来更多方便。其核心目标是深层次解耦,为您节约更多时间,去陪恋人、家人和朋友 :)年初 @idoz 提议实现类似 Spring 4.2 里的 PayloadApplicationEvent,其实我早就有此想法,一直没有抽出时间来折腾。顺便在 码云上添加了 issues 3.0优化,有2点功能:第一点,实现 PayloadApplicationEvent 不难。扩展个 EventTypeclass1 为 ApplicationEvent 类 或者 PayloadApplicationEvent类信息class2 为 ApplicationEvent 泛型,或者 Payload 中的类信息第二点,将类扫描改为 annotation Processor 编译期处理(避免容器环境差异)由于 @EventListener 注解可以在任何方法中,annotation Processor 需要扫描 *,然后 找出 对应的含有 @EventListener 方法的类,然后写入 META-INF/dream.events 文件中。本次版本升级完成了第一点,第二点由于觉得更加适合底层框架去实现,从而方便扩展,对于 annotation Processor 感兴趣的朋友可以了解我的开源 mica-auto更新说明2019-04-08 v2.3.0升级到 jfinal 3.8。支持 @EventListener 方法可以无参数。ObjenesisBeanFactory 和 jfinal Aop inject 冲突,去掉对 JFinal inject 支持,标记为弃用。新增 SourceClass 作为 event 事件源(同 Spring PayloadApplicationEvent),event模型不再需要实现 ApplicationEvent。@EventListener 注解新增 value 变量,功能同 events。修复 @EventListener events 参数类型判断bug。初始化插件// 初始化插件EventPlugin plugin = new EventPlugin();// 设置为异步,默认同步,或者使用threadPool(ExecutorService executorService)自定义线程池。plugin.async();// 设置扫描jar包,默认不扫描plugin.scanJar();// 设置监听器默认包,多个包名使用;分割,默认全扫描plugin.scanPackage(“net.dreamlu”);// bean工厂,默认为DefaultBeanFactory,可实现IBeanFactory自定义扩展// 对于将@EventListener写在不含无参构造器的类需要使用ObjenesisBeanFactory(2.3.0 已经不推荐使用)plugin.beanFactory(new ObjenesisBeanFactory());// 手动启动插件,用于main方法启动,jfinal中不需要,添加插件即可。plugin.start();// 停止插件,用于main方法测试plugin.stop();新建事件类(任意模型即可 2.3.0支持)public class AccountEvent { private Integer id; private String name; private Integer age; // 省略 get set}编写监听@EventListenerpublic void listenTest1Event(AccountEvent event) { System.out.println(“AccountEvent:” + event);}发送事件AccountEvent event = new AccountEvent();event.setId(1);event.setName(“张三”);event.setAge(18);EventKit.post(event);@EventListener注解说明示例@EventListener(events = Test1Event.class, order = 1, async = true, condition = “event.isExec()")说明value 或 events支持的事件类型数组,用于将事件方法定义为ApplicationEvent或者自定义父类。public class Test { // Test1Event, Test2Event 为 TestEvent 子类 @EventListener({Test1Event.class, Test2Event.class}) public void applicationEvent(TestEvent event) { System.out.println(Thread.currentThread().getName() + “\tevent:” + event); }}order排序,数值越小越先执行,默认为Integer.MAX_VALUEasync异步执行,需要插件开启async()或者自定义线程池。condition表达式条件,使用event.xxxx,event.isExec() == true判定event的属性或者方法。jar依赖jar包下载http://central.maven.org/mave…以上版本均已上传到maven仓库~<dependency> <groupId>net.dreamlu</groupId> <artifactId>JFinal-event</artifactId> <version>2.3.0</version></dependency>开源推荐mica Spring boot 微服务核心组件集:https://gitee.com/596392912/micaAvue 一款基于vue可配置化的神奇框架:https://gitee.com/smallweigit/avuepig 宇宙最强微服务(架构师必备):https://gitee.com/log4j/pigSpringBlade 完整的线上解决方案(企业开发必备):https://gitee.com/smallc/SpringBladeIJPay 支付SDK让支付触手可及:https://gitee.com/javen205/IJPay关注我们扫描上面二维码,更多精彩内容每天推荐! ...

April 8, 2019 · 1 min · jiezi

「前端面试题系列7」Javascript 中的事件机制(从原生到框架)

前言这是前端面试题系列的第 7 篇,你可能错过了前面的篇章,可以在这里找到:理解函数的柯里化ES6 中箭头函数的用法this 的原理以及用法伪类与伪元素的区别及实战如何实现一个圣杯布局?今日头条 面试题和思路解析最近,小伙伴L 在温习 《JavaScript高级程序设计》中的 事件 这一章节时,产生了困惑。他问了我这样几个问题:了解事件流的顺序,对日常的工作有什么帮助么?在 vue 的文档中,有一个修饰符 native ,把它用 . 的形式 连结在事件之后,就可以监听原生事件了。它的背后有什么原理?事件的 event 对象中,有好多的属性和方法,该如何使用?浏览器中的事件机制,也经常在面试中被提及。所以这回,我们共同探讨了这些问题,并最终整理成文,希望帮到有需要的同学。事件流的概念先从概念说起,DOM 事件流分为三个阶段:捕获阶段、目标阶段、冒泡阶段。先调用捕获阶段的处理函数,其次调用目标阶段的处理函数,最后调用冒泡阶段的处理函数。网景公司提出了 事件捕获 的事件流。这就好比采矿的小游戏,每次都会从地面开始一路往下,抛出抓斗,捕获矿石。在上图中就是,某个 div 元素触发了某个事件,最先得到通知的是 window,然后是 document,依次往下,直到真正触发事件的那个目标元素 div 为止。而 事件冒泡 则是由微软提出的,与之顺序相反。还是刚才的采矿小游戏,命中目标后,抓斗再沿路收回,直到冒出地面。在上图中就是,事件会从目标元素 div 开始依次往上,直到 window 对象为止。w3c 为了制定统一的标准,采取了折中的方式:先捕获在冒泡。同一个 DOM 元素可以注册多个同类型的事件,通过 addEventListener 和 removeEventListener 进行管理。addEventListener 的第三个参数,就是为了捕获和冒泡准备的。注册事件(addEventListener) 有三个参数,分别为:“事件名称”, “事件回调”, “捕获/冒泡”(布尔型,true代表捕获事件,false代表冒泡事件)。target.addEventListener(type, listener[, useCapture]);type 表示事件类型的字符串。listener 是一个实现了 EventListener 接口的对象,或者是一个函数。当所监听的事件类型触发时,会接收到一个事件通知对象(实现了 Event 接口的对象)。capture 表示 listener 会在该类型的事件捕获阶段,传播到该 EventTarget 时触发,它是一个 Boolean 值。解除事件(removeEventListener) 也有三个参数,分别为:“事件名称”, “事件回调”, “捕获/冒泡”(Boolean 值,这个必须和注册事件时的类型一致)。target.removeEventListener(type, listener[, useCapture]);要想注册过的事件能够被解除,必须将回调函数保存起来,否则无法解除。例如这样:const btn = document.getElementById(“test”);//将回调存储在变量中const fn = function(e){ alert(“ok”);};//绑定btn.addEventListener(“click”, fn, false);//解除btn.removeEventListener(“click”, fn, false);事件捕获和冒泡的5个注意点当有多层交互嵌套时,事件捕获和冒泡的先后顺序,似乎不是那么好理解。接下来,将分 5 种情况讨论它们的顺序,以及如何规避意外情况的发生。1.在外层 div 注册事件,点击内层 div 来触发事件时,捕获事件总是要比冒泡事件先触发(与代码顺序无关)假设,有这样的 html 结构:<div id=“test” class=“test”> <div id=“testInner” class=“test-inner”></div></div>然后,我们在外层 div 上注册两个 click 事件,分别是捕获事件和冒泡事件,代码如下:const btn = document.getElementById(“test”); //捕获事件btn.addEventListener(“click”, function(e){ alert(“capture is ok”);}, true); //冒泡事件btn.addEventListener(“click”, function(e){ alert(“bubble is ok”);}, false);点击内层的 div,先弹出 capture is ok,后弹出 bubble is ok。只有当真正触发事件的 DOM 元素是内层的时候,外层 DOM 元素才有机会模拟捕获事件和冒泡事件。2.当在触发事件的 DOM 元素上注册事件时,哪个先注册,就先执行哪个html 结构同上,js 代码如下:const btnInner = document.getElementById(“testInner”);//冒泡事件btnInner.addEventListener(“click”, function(e){ alert(“bubble is ok”);}, false); //捕获事件btnInner.addEventListener(“click”, function(e){ alert(“capture is ok”);}, true);本例中,冒泡事件先注册,所以先执行。所以,点击内层 div,先弹出 bubble is ok,再弹出 capture is ok。3.当外层 div 和内层 div 同时注册了捕获事件时,点击内层 div 时,外层 div 的事件一定会先触发js 代码如下:const btn = document.getElementById(“test”);const btnInner = document.getElementById(“testInner”);btnInner.addEventListener(“click”, function(e){ alert(“inner capture is ok”);}, true);btn.addEventListener(“click”, function(e){ alert(“outer capture is ok”);}, true);虽然外层 div 的事件注册在后面,但会先触发。所以,结果是先弹出 outer capture is ok,再弹出 inner capture is ok。4.同理,当外层 div 和内层 div 都同时注册了冒泡事件,点击内层 div 时,一定是内层 div 事件先触发。const btn = document.getElementById(“test”);const btnInner = document.getElementById(“testInner”);btn.addEventListener(“click”, function(e){ alert(“outer bubble is ok”);}, false);btnInner.addEventListener(“click”, function(e){ alert(“inner bubble is ok”);}, false);先弹出 inner bubble is ok,再弹出 outer bubble is ok。5.阻止事件的派发通常情况下,我们都希望点击某个 div 时,就只触发自己的事件回调。比如,明明点击的是内层 div,但是外层 div 的事件也触发了,这是就不是我们想要的了。这时,就需要阻止事件的派发。事件触发时,会默认传入一个 event 对象,这个 event 对象上有一个方法:stopPropagation。MDN 上的解释是:阻止 捕获 和 冒泡 阶段中,当前事件的进一步传播。所以,通过此方法,让外层 div 接收不到事件,自然也就不会触发了。btnInner.addEventListener(“click”, function(e){ //阻止冒泡 e.stopPropagation(); alert(“inner bubble is ok”);}, false);事件代理我们经常会遇到,要监听列表中多项 li 的情况,假设我们有一个列表如下:<ul id=“list”> <li id=“item1”>item1</li> <li id=“item2”>item2</li> <li id=“item3”>item3</li> <li id=“item4”>item4</li></ul>如果我们要实现以下功能:当鼠标点击某一 li 时,输出该 li 的内容,我们通常的写法是这样的:window.onload=function(){ const ulNode = document.getElementById(“list”); const liNodes = ulNode.children; for(var i=0; i<liNodes.length; i++){ liNodes[i].addEventListener(‘click’,function(e){ console.log(e.target.innerHTML); }, false); }}在传统的事件处理中,我们可能会按照需要,为每一个元素添加或者删除事件处理器。然而,事件处理器将有可能导致内存泄露,或者性能下降,用得越多这种风险就越大。JavaScript 的事件代理,则是一种简单的技巧。用法及原理事件代理,用到了在 JavaSciprt 事件中的两个特性:事件冒泡 和 目标元素。使用事件代理,我们可以把事件处理器添加到一个元素上,等待一个事件从它的子级元素里冒泡上来,并且可以得知这个事件是从哪个元素开始的。改进后的 js 代码如下:window.onload=function(){ const ulNode=document.getElementById(“list”); ulNode.addEventListener(‘click’, function(e) { /判断目标事件是否为li/ if(e.target && e.target.nodeName.toUpperCase()==“LI”){ console.log(e.target.innerHTML); } }, false);};一些常用技巧回到文章开头的问题:了解事件流的顺序,对日常的工作有什么帮助呢?我总结了以下几个注意点。1. 阻止默认事件比如 href 的链接跳转,submit 的表单提交等。可以在方法的最后,加上一行 return false;。它会阻止通过 on 的方式绑定的事件的默认事件。ele.onclick = function() { …… // 通过返回 false 值,阻止默认事件行为 return false;}另外,重写 onclick 会覆盖之前的属性,所以解绑事件可以这么写:// 解绑事件,将 onlick 属性设为 null 即可ele.onclick = null;2. stopPropagation 和 stopImmediatePropagation前面说过 stopPropagation 的定义是:终止事件在传播过程的捕获、目标处理或起泡阶段进一步传播。事件不再被分派到其他节点上。// 事件捕获到 ele 元素后,就不再向下传播了ele.addEventListener(‘click’, function (event) { event.stopPropagation();}, true);// 事件冒泡到 ele 元素后,就不再向上传播了ele.addEventListener(‘click’, function (event) { event.stopPropagation();}, false);但是,stopPropagation 只会阻止当前元素 同类型的 事件冒泡或捕获的传播,并不会阻止该元素上 其他类型 事件的监听。以 click 事件为例:ele.addEventListener(‘click’, function (event) { event.stopPropagation(); console.log(1);});ele.addEventListener(‘click’, function(event) { // 仍然可以触发 console.log(2);});如果想禁用之后所有的 click 事件,就要用到 stopImmediatePropagation 了。但是,需要注意的是,stopImmediatePropagation 只会禁用之后注册的同类型的监听事件。就比如阻止了之后的 click 事件监听函数,但别的事件类型如 mousedown、dblclick 之类,还是可以监听到的。ele.addEventListener(‘click’, function (event) { event.stopImmediatePropagation(); console.log(1);});ele.addEventListener(‘click’, function(event) { // 不会触发 console.log(2);});ele.addEventListener(‘mousedown’, function(event) { // 会触发 console.log(3);});3. jquery 中的 return false;jquery 中的 on 是事件冒泡。当用 return false; 阻止浏览器的默认行为时,会做下面这 3 件事:event.preventDefault();event.stopPropagation();停止回调函数执行并立即返回。这 3 件事中,只有 preventDefault 是用来阻止默认行为的。除非你还想阻止事件冒泡,否则直接用 return false; 会埋下隐患。4. angular 中的 $eventangular 是个包罗万象的框架,似乎学完它的一整套之后,就能玩转世界了。它加工封装了许多原生的东西,其中就包括了 event,只是前面需要加一个 $,表示这是 angular 中的特有对象。// template<div> <button (click)=“doSomething($event)">Click me</button></div>// jsdoSomething($event: Event) { $event.stopPropagation(); …}$event 在这里作为一个变量,显式地 传入回调函数,之后就可以将 $event 当做原生的事件对象来用了。5. vue 中的 native 修饰符在 vue 的自定义组件中绑定原生事件,需要用到修饰符 native。那是因为,我们的自定义组件,最终会渲染成原生的 html 标签,而非类似于 这样的自定义组件。如果想让一个普通的 html 标签触发事件,那就需要对它做事件监听(addEventListener)。修饰符 native 的作用就在这里,它可以在背后帮我们绑定了原生事件,进行监听。一个常用的场景是,配合 element-ui 做登录界面时,输完账号密码,想按一下回车就能登录。就可以像下面这样用修饰符:<el-input class=“input” v-model=“password” type=“password” @keyup.enter.native=“handleSubmit”></el-input>el-input 就是自定义组件,而 keyup 就是原生事件,需要用 native 修饰符进行绑定才能监听到。6. react 中的合成事件想要在 react 的事件回调中使用 event 对象,会产生困扰,会发现不少原生的属性都是 null。那是因为在 react 中的事件,其实是合成事件(SyntheticEvent),并不是浏览器的原生事件,但它也符合 w3c 规范。举一个简单的例子,我们要实现一个组件,它有一个按钮,点击按钮后会显示一张图片,点击这张图片之外的任意区域,可以隐藏这张图片,但是点击该图片本身时,不会隐藏。代码如下:class ShowImg extends Component { constructor(props) { super(props); this.state = { active: false }; } componentDidMount() { document.addEventListener(‘click’, this.hideImg.bind(this)); } componentWillUnmount() { document.removeEventListener(‘click’, this.hideImg); } hideImg () { this.setState({ active: false }); } handleClickBtn() { this.setState({ active: !this.state.active }); } handleClickImg (e) { e.stopPropagation(); } render() { return ( <div className=“img-wrapper”> <button className=“showImgBtn” onClick={this.handleClickBtn.bind(this)}> 显示图片 </button> <div className=“img” style={{ display: this.state.active ? ‘block’ : ’none’ }} onClick={this.handleClickImg.bind(this)}> <img src=”@/assets/avatar.jpg" > </div> </div> ); }}按照之前说的原生事件机制,我们会错误地认为通过:handleClickImg (e) { e.stopPropagation();}就可以阻止事件的派发了,但其实没法这么做。想要解决这个问题,当然也不复杂,就把 react 的事件和原生事件分开即可。componentDidMount() { document.addEventListener(‘click’, this.hideImg.bind(this)); document.addEventListener(‘click’, this.imgStopPropagation.bind(this));}componentWillUnmount() { document.removeEventListener(‘click’, this.hideImg); document.removeEventListener(‘click’, this.imgStopPropagation);}hideImg () { this.setState({ active: false });}imgStopPropagation (e) { e.stopPropagation();}7. 事件对象 event当对一个元素进行事件监听的时候,它的回调函数里就会默认传递一个参数 event,它是一个对象,包含了许多属性。我列出了一些比较常用的属性:event.target:指的是触发事件的那个节点,也就是事件最初发生的节点。event.target.matches:可以对关键节点进行匹配,来执行相应操作。event.currentTarget:指的是正在执行的监听函数的那个节点。event.isTrusted:表示事件是否是真实用户触发。event.preventDefault():取消事件的默认行为。event.stopPropagation():阻止事件的派发(包括了捕获和冒泡)。event.stopImmediatePropagation():阻止同一个事件的其他监听函数被调用。总结事件机制在浏览器中非常有用,所有用户的交互型操作,都依赖于它。现代 JavaScript 框架应用中,我们也都离不开与原生事件的交互。所以,在理解了事件流的概念,清楚了事件捕获与冒泡的顺序,掌握了一些原生事件的技巧之后,相信下次再遇到坑的时候,可以少走一些弯路了。PS:欢迎关注我的公众号 “超哥前端小栈”,交流更多的想法与技术。 ...

February 24, 2019 · 3 min · jiezi

JavaScript事件循环(Event Loop)

1、为什么要有事件循环?因为js是单线程的,事件循环是js的执行机制,也是js实现异步的一种方法。既然js是单线程,那就像只有一个窗口的银行,客户需要排队一个一个办理业务,同理js任务也要一个一个顺序执行。如果一个任务耗时过长,那么后一个任务也必须等着。那么问题来了,假如我们想浏览新闻,但是新闻包含的超清图片加载很慢,难道我们的网页要一直卡着直到图片完全显示出来?因此聪明的程序员将任务分为两类:同步任务异步任务当我们打开网站时,网页的渲染过程就是一大堆同步任务,比如页面骨架和页面元素的渲染。而像加载图片音乐之类占用资源大耗时久的任务,就是异步任务。2、宏任务与微任务JavaScript中除了广泛的同步任务和异步任务,我们对任务有更精细的定义:macro-task(宏任务): 包括整体代码script,setTimeout,setIntervalmicro-task(微任务): Promise,process.nextTick不同的类型的任务会进入不同的Event Queue(事件队列),比如setTimeout、setInterval会进入一个事件队列,而Promise会进入另一个事件队列。一次事件循环中有宏任务队列和微任务队列。事件循环的顺序,决定js代码执行的顺序。进入整体代码(宏任务-<script>包裹的代码可以理解为第一个宏任务),开始第一次循环,接着执行所有的微任务。然后再次从宏任务开始,找到其中一个任务队列的任务执行完毕,再执行所有的微任务。如:<script> setTimeout(function() { console.log(‘setTimeout’); }) new Promise(function(resolve) { console.log(‘promise’); }).then(function() { console.log(’then’); }) console.log(‘console’); /* —————————-分析 start——————————— / 1、&lt;script&gt;中的整段代码作为第一个宏任务,进入主线程。即开启第一次事件循环 2、遇到setTimeout,将其回调函数放入Event table中注册,然后分发到宏任务Event Queue中 3、接下来遇到new Promise、Promise,立即执行;将then函数分发到微任务Event Queue中。输出: promise 4、遇到console.log,立即执行。输出: console 5、整体代码作为第一个宏任务执行结束,此时去微任务队列中查看有哪些微任务,结果发现了then函数,然后将它推入主线程并执行。输出: then 6、第一轮事件循环结束 开启第二轮事件循环。先从宏任务开始,去宏任务事件队列中查看有哪些宏任务,在宏任务事件队列中找到了setTimeout对应的回调函数,立即执行之。此时宏任务事件队列中已经没有事件了,然后去微任务事件队列中查看是否有事件,结果没有。此时第二轮事件循环结束;输出:setTimeout / —————————-分析 end——————————— */</script>3、分析更复杂的代码<script> console.log(‘1’); setTimeout(function() { console.log(‘2’); process.nextTick(function() { console.log(‘3’); }) new Promise(function(resolve) { console.log(‘4’); resolve(); }).then(function() { console.log(‘5’) }) }) process.nextTick(function() { console.log(‘6’); }) new Promise(function(resolve) { console.log(‘7’); resolve(); }).then(function() { console.log(‘8’) }) setTimeout(function() { console.log(‘9’); process.nextTick(function() { console.log(‘10’); }) new Promise(function(resolve) { console.log(‘11’); resolve(); }).then(function() { console.log(‘12’) }) })</script>一、第一轮事件循环a)、整段<script>代码作为第一个宏任务进入主线程,即开启第一轮事件循环b)、遇到console.log,立即执行。输出:1c)、遇到setTimeout,将其回调函数放入Event table中注册,然后分发到宏任务事件队列中。我们将其标记为setTimeout1d)、遇到process.nextTick,其回调函数放入Event table中注册,然后被分发到微任务事件队列中。记为process1e)、遇到new Promise、Promise,立即执行;then回调函数放入Event table中注册,然后被分发到微任务事件队列中。记为then1。输出: 7f)、遇到setTimeout,将其回调函数放入Event table中注册,然后分发到宏任务事件队列中。我们将其标记为setTimeout2此时第一轮事件循环宏任务结束,下表是第一轮事件循环宏任务结束时各Event Queue的情况-宏任务事件队列微任务事件队列第一轮事件循环(宏任务已结束)process1、then1第二轮事件循环(未开始)setTimeout1 第三轮事件循环(未开始)setTimeout2 可以看到第一轮事件循环宏任务结束后微任务事件队列中还有两个事件待执行,因此这两个事件会被推入主线程,然后执行g)、执行process1。输出:6h)、执行then1。输出:8第一轮事件循环正式结束!二、第二轮事件循环a)、第二轮事件循环从宏任务setTimeout1开始。遇到console.log,立即执行。输出: 2b)、遇到process.nextTick,其回调函数放入Event table中注册,然后被分发到微任务事件队列中。记为process2c)、遇到new Promise,立即执行;then回调函数放入Event table中注册,然后被分发到微任务事件队列中。记为then2。输出: 5此时第二轮事件循环宏任务结束,下表是第二轮事件循环宏任务结束时各Event Queue的情况-宏任务事件队列微任务事件队列第一轮事件循环(已结束) 第二轮事件循环(宏任务已结束)process2、then2第三轮事件循环(未开始)setTimeout2 可以看到第二轮事件循环宏任务结束后微任务事件队列中还有两个事件待执行,因此这两个事件会被推入主线程,然后执行d)、执行process2。输出:3e)、执行then2。输出:5第二轮事件循环正式结束!三、第三轮事件循环a)、第三轮事件循环从宏任务setTimeout2开始。遇到console.log,立即执行。输出: 9d)、遇到process.nextTick,其回调函数放入Event table中注册,然后被分发到微任务事件队列中。记为process3c)、遇到new Promise,立即执行;then回调函数放入Event table中注册,然后被分发到微任务事件队列中。记为then3。输出: 11此时第三轮事件循环宏任务结束,下表是第三轮事件循环宏任务结束时各Event Queue的情况-宏任务事件队列微任务事件队列第一轮事件循环(已结束) 第二轮事件循环(已结束) 第三轮事件循环(未开始)(宏任务已结束)process3、then3可以看到第二轮事件循环宏任务结束后微任务事件队列中还有两个事件待执行,因此这两个事件会被推入主线程,然后执行d)、执行process3。输出:10e)、执行then3。输出:124、参考文章https://juejin.im/post/59e85e… ...

January 21, 2019 · 1 min · jiezi

CloudEvents 0.2版本发布

在2018年5月,CNCF宣布将开源规范CloudEvents纳入CNCF Sandbox。从那时起,工作组已经取得了大量进展,概述了如何抽象事件,并在生态系统中实现互操作,允许在堆栈的不同部分之间进行通信,以及不同的编程语言。今天,CloudEvents团队很高兴地宣布该规范的0.2版本,其中包括许多新的细节以及SDK规范。这里我们将介绍新版本的亮点,但有关更多信息,请参阅0.2规格。该规范的一些主要新更新是各种传输映射(mapping)和绑定(binding),特别是对于AMQP 1.0、MQTT 3.1.1和5.0、以及NATS。这创建了一个新的抽象层,允许开发者连接到任何主要的传输消息协议,并符合此规范定义的事件转换标准,从而提供更大的灵活性。0.2版本的另一个主要补充是对protobuf(或Protocol Buffers)的支持,这是一种语言中立、平台中立、可扩展的序列化结构化数据的方式,用于通信协议、数据存储和其他软件开发场景。有关0.2中protobuf支持的更多信息,请参阅protobuf格式。CloudEvents团队已经定义了几个SDK,包括Go、Java、Python、C#和JavaScript,但还有许多其他语言,新的CloudEvents SDK规范概述了团队添加和支持新SDK的最低要求。如果你有兴趣参与此项工作,请参阅SDK指南,立即开始成为贡献者或维护者。如果你有兴趣但不确定从哪里开始,或者这将如何适合你的项目,0.2版本提供CloudEvents Primer,它解释这项工作背后的概念和驱动因素,以及如何开始。此外,对于那些希望利用这项工作或想要参与并做出贡献的人来说,路线图是了解这个沙箱工作的下一个阶段以及规范的目标的好方法。项目的TOC赞助人包括Ken Owens和Brian Grant。CNCF沙箱是早期项目的基地,为了进一步了解CNCF项目成熟度水平,请访问我们的毕业标准。2019年KubeCon + CloudNativeCon中国论坛提案征集(CFP)现已开放KubeCon + CloudNativeCon 论坛让用户、开发人员、从业人员汇聚一堂,面对面进行交流合作。与会人员有 Kubernetes、Prometheus 及其他云原生计算基金会 (CNCF) 主办项目的领导,和我们一同探讨云原生生态系统发展方向。2019年中国开源峰会提案征集(CFP)现已开放在中国开源峰会上,与会者将共同合作及共享信息,了解最新和最有趣的开源技术,包括 Linux、容器、云技术、网络、微服务等;并获得如何在开源社区中导向和引领的信息。大会日期:提案征集截止日期:太平洋标准时间 2 月 15 日,星期五,晚上 11:59提案征集通知日期:2019 年 4 月 1 日会议日程通告日期:2019 年 4 月 3 日幻灯片提交截止日期:6 月 17 日,星期一会议活动举办日期:2019 年 6 月 24 至 26 日2019年KubeCon + CloudNativeCon + Open Source Summit China赞助方案出炉啦

January 21, 2019 · 1 min · jiezi

Javascript 时间循环event loop

都知道javascript是单线程,那么问题来了,既然是单线程顺序执行,那怎么做到异步的呢?我们理解的单线程应该是这样的,排着一个个来,是同步执行。现实中js是这样的 setTimeout(function() { console.log(1); }); new Promise(function(resolve, reject) { console.log(2) resolve(3) }).then(function(val) { console.log(val); }) console.log(4) //执行结果为 2、4、3、1结果告诉我们,js是单线程没错,不过不是逐行同步执行。那我们就来解析一下既然有异步,那顺序是怎样的?这些执行顺序规则就是理解eventLoop的要点,继续往下。上图为我录制的chrome控制代码台执行顺序,虽然能看出执行顺序但我们还是懵逼的,我们不知道规则,不懂就要问。搜索了很多官方、个人博客得到了一堆词:js引擎、主线程、事件表、事件队列、宏任务、微任务,彻底懵逼。。。不急不急一个个来,我们进入刨根问底状态js引擎总结一句话就是解析优化代码 **制定执行规则 具体规则往下看主线程总结一句话执行js引擎优化并排列顺序后的代码事件表(event table)执行代码过程中,异步的回调,例如(setTimeout,ajax回调)注册回调事件到event table事件队列当事件回调结束,事件表(event table)会将事件移入到事件队列(event queue)宏任务和微任务宏任务包含的事件事件浏览器nodeI/O✅✅setTimeout✅✅setInterval✅✅setImmediate❌✅requestAnimationFrame✅❌微任务包含的事件事件浏览器nodeI/O✅✅process.nextTick❌✅MutationObserver✅❌Promise.then catch finally✅✅很多博客是这样说的:浏览器会不断从task队列中按顺序取task执行,每执行完一个task都会检查microtask队列是否为空(执行完一个task的具体标志是函数执行栈为空),如果不为空则会一次性执行完所有microtask。然后再进入下一个循环去task队列中取下一个task执行说实话不是太理解,那么我就以自己的方式去学习和理解为了更好的理解我们再看代码 console.log(‘1’); setTimeout(function() { console.log(‘2’); new Promise(function(resolve) { console.log(‘3’); resolve(); }).then(function() { console.log(‘4’) }) }) new Promise(function(resolve) { console.log(‘5’); resolve(); }).then(function() { console.log(‘6’) }) setTimeout(function() { console.log(‘7’); new Promise(function(resolve) { console.log(‘8’); resolve(); }).then(function() { console.log(‘9’) }) }) //执行结果:1、5、6、2、3、4、7、8、9有图为证我没骗你再来个动图我们具体看看浏览器的执行顺序首先js引擎,区分是直接执行(同步代码),再执行异步代码,如果是异步再区分是宏任务还是微任务,分别放入两个任务队列,然后开始执行,每执行完一个宏任务,扫一遍微任务队列并全部执行,此时形成一次eventLoop循环。以此规则不停的执行下去就是我们所听到的事件循环。我再补充一点,可以理解js引擎一开始把整个script当做一个宏任务,这样里边的就更容易理解了,开始就执行script宏任务,解析到宏任务里边又包含同步代码和异步代码(宏任务和微任务)依次执行顺序形成eventLoop。欢迎吐槽点赞评论!文章参考学习:https://www.jianshu.com/p/12b…https://juejin.im/post/59e85e...https://segmentfault.com/a/11…

January 15, 2019 · 1 min · jiezi

Apache 工作的三种模式:Prefork、Worker、Event

Apache 的三种工作模式(Prefork、Worker、Event)Web服务器Apache目前一共有三种稳定的MPM(Multi-Processing Module,多进程处理模块)模式。它们分别是prefork,worker、event,它们同时也代表这Apache的演变和发展。本文原文转自米扑博客:Apache 工作的三种模式:Prefork、Worker、Event如何查看我们的Apache的工作模式呢?可以使用httpd -V 命令查看,如我安装的Apache 2.4版本。# httpd -VServer version: Apache/2.4.34 (Unix)Server built: Aug 2 2018 19:44:29Server’s Module Magic Number: 20120211:79Server loaded: APR 1.6.3, APR-UTIL 1.6.1Compiled using: APR 1.6.3, APR-UTIL 1.6.1Architecture: 64-bitServer MPM: event threaded: yes (fixed thread count) forked: yes (variable process count)或者,更直接的命令 httpd -l 或 apachectl -V | grep -i mpm# httpd -lCompiled in modules: core.c mod_so.c http_core.c event.c# apachectl -V | grep -i mpmServer MPM: event这里使用的是event模式,在apache的早期版本2.0默认prefork,2.2版本是worker,2.4版本是event,详见米扑博客:Apache 服务器负载低访问慢的原因分析和优化方案在configure配置编译参数的时候,可以使用–with-mpm=prefork|worker|event 来指定编译为那一种MPM,当然也可以用编译为三种都支持:–enable-mpms-shared=all,这样在编译的时候会在modules目录下自动编译出三个MPM文件的so,然后通过修改httpd.conf配置文件更改MPM1、Prefork MPM Prefork MPM实现了一个非线程的、预派生的web服务器。它在Apache启动之初,就先预派生一些子进程,然后等待连接;可以减少频繁创建和销毁进程的开销,每个子进程只有一个线程,在一个时间点内,只能处理一个请求。这是一个成熟稳定,可以兼容新老模块,也不需要担心线程安全问题,但是一个进程相对占用资源,消耗大量内存,不擅长处理高并发的场景。图片描述如何配置在Apache的配置文件httpd.conf的配置方式:<IfModule mpm_prefork_module> StartServers 5 MinSpareServers 5 MaxSpareServers 10 MaxRequestWorkers 250 MaxConnectionsPerChild 1000 </IfModule> StartServers 服务器启动时建立的子进程数量,prefork默认是5,MinSpareServers 空闲子进程的最小数量,默认5;如果当前空闲子进程数少于MinSpareServers ,那么Apache将以最大每秒一个的速度产生新的子进程。此参数不要设的太大。MaxSpareServers 空闲子进程的最大数量,默认10;如果当前有超过MaxSpareServers数量的空闲子进程,那么父进程会杀死多余的子进程。次参数也不需要设置太大,如果你将其设置比MinSpareServers 小,Apache会自动将其修改为MinSpareServers +1的数量。MaxRequestWorkers 限定服务器同一时间内客户端最大接入的请求数量,默认是256;任何超过了MaxRequestWorkers限制的请求都要进入等待队列,一旦一个个连接被释放,队列中的请求才将得到服务,如果要增大这个数值,必须先增大ServerLimit。在Apache2.3.1版本之前这参数MaxRequestWorkers被称为MaxClients。MaxConnectionsPerChild 每个子进程在其生命周期内允许最大的请求数量,如果请求总数已经达到这个数值,子进程将会结束,如果设置为0,子进程将永远不会结束。在Apache2.3.9之前称之为MaxRequestsPerChild。这里建议设置为非零,注意原因: 1)能够防止(偶然的)内存泄漏无限进行,从而耗尽内存。 2)给进程一个有限寿命,从而有助于当服务器负载减轻的时候减少活动进程的数量(重生的机会)。2、Worker MPM 和prefork模式相比,worker使用了多进程和多线程的混合模式,worker模式也同样会先预派生一些子进程,然后每个子进程创建一些线程,同时包括一个监听线程,每个请求过来会被分配到一个线程来服务。线程比起进程会更轻量,因为线程是通过共享父进程的内存空间,因此,内存的占用会减少一些,在高并发的场景下会比prefork有更多可用的线程,表现会更优秀一些;另外,如果一个线程出现了问题也会导致同一进程下的线程出现问题,如果是多个线程出现问题,也只是影响Apache的一部分,而不是全部。由于用到多进程多线程,需要考虑到线程的安全了,在使用keep-alive长连接的时候,某个线程会一直被占用,即使中间没有请求,需要等待到超时才会被释放(该问题在prefork模式下也存在)。图片描述如何配置在Apache的配置文件httpd.conf的配置方式:<IfModule mpm_worker_module> StartServers 3 ServerLimit 16 MinSpareThreads 75 MaxSpareThreads 250 ThreadsPerChild 25 MaxRequestWorkers 400 MaxConnectionsPerChild 1000 </IfModule> 配置参数解释:StartServers 服务器启动时建立的子进程数量,在workers模式下默认是3.ServerLimit 系统配置的最大进程数量,默认不显示,自己添加上MinSpareThreads 空闲子进程的最小数量,默认75MaxSpareThreads 空闲子进程的最大数量,默认250ThreadsPerChild 每个子进程产生的线程数量,默认是64MaxRequestWorkers / MaxClients 限定服务器同一时间内客户端最大接入的请求数量.MaxConnectionsPerChild 每个子进程在其生命周期内允许最大的请求数量,如果请求总数已经达到这个数值,子进程将会结束,如果设置为0,子进程将永远不会结束。在Apache2.3.9之前称之为MaxRequestsPerChild。这里建议设置为非零,注意原因:1)能够防止(偶然的)内存泄漏无限进行,从而耗尽内存; 2)给进程一个有限寿命,从而有助于当服务器负载减轻的时候减少活动进程的数量(重生的机会)。Worker模式下所能同时处理的请求总数是由子进程总数乘以ThreadsPerChild值决定的,应该大于等于MaxRequestWorkers。如果负载很大,现有的子进程数不能满足时,控制进程会派生新的子进程。默认ServerLimit 最大的子进程总数是16,加大时也需要显式声明ServerLimit(最大值是20000)。需要注意的是,如果显式声明了ServerLimit,那么它乘以 MaxRequestWorkers必须是hreadsPerChild的整数倍,否则 Apache将会自动调节到一个相应值。3、Event MPM 这是Apache最新的工作模式,它和worker模式很像,不同的是在于它解决了keep-alive长连接的时候占用线程资源被浪费的问题,在event工作模式中,会有一些专门的线程用来管理这些keep-alive类型的线程,当有真实请求过来的时候,将请求传递给服务器的线程,执行完毕后,又允许它释放。这增强了在高并发场景下的请求处理。图片描述如何配置在Apache的配置文件httpd.conf的配置方式:<IfModule mpm_event_module> StartServers 3 ServerLimit 16 MinSpareThreads 75 MaxSpareThreads 250 ThreadsPerChild 25 MaxRequestWorkers 400 MaxConnectionsPerChild 1000 </IfModule> event 模式与 worker 模式完全一样,参考 worker 模式参数即可,这里不再重复。Apache httpd 能更好的为有特殊要求的站点定制。例如,要求更高伸缩性的站点可以选择使用线程的 MPM,即 worker 或 event; 需要可靠性或者与旧软件兼容的站点可以使用 prefork。常见问题查看apache的error日志,可以发现许多系统运行中的问题。server reached MaxRequestWorkers setting[mpm_prefork:error] [pid 1134] AH00161: server reached MaxRequestWorkers setting, consider raising the MaxRequestWorkers setting进程或者线程数目达到了MaxRequestWorkers,可以考虑增加这个值,当然先考虑增加硬件,如内存大小、CPU、SSD硬盘等。scoreboard is full[mpm_event:error] [pid 7555:tid 140058436118400] AH00485: scoreboard is full, not at MaxRequestWorkers这个问题好像是apache2自带的bug,我们无力解决。好在这个问题一般只会影响单个线程,所以暂时可以忍。StackOverflow: Scoreboard is full,not at MaxRequestWorkers 1、I had this same problem. I tried different Apache versions and MPMs. I seem to get this alot with MPM Worker. Also error does not reoccur using Apache 2.2.2,Are you using cPanel? IF so try /upcp –force and increase StartServers to a higher amount like 50 as that’s all I did to get this error away.2、Try EnableMMAP Off in 00_default_settings.confapache 主要版本有:Version 2.4 (Current)Version 2.2 (Historical)Version 2.0 (Historical)Version 1.3 (Historical)参考:https://httpd.apache.org/docs/ 关于 Apache 配置优化,请参见米扑博客:Apache 服务器负载低访问慢的原因分析和优化方案 ...

January 6, 2019 · 2 min · jiezi