# 事件流

QQ截图20200128111606.png

# 事件产生

事件是用户或浏览器自身执行的某种动作,如click,load和mouseover都是事件的名字。
  • stopPropagation() 阻止事件传播

  • preventDefault() 阻止事件的默认行为

# 事件流

冒泡型事件流:从DOM树的叶子到根。【推荐】

addEventListener('type',fn,false) //默认为false冒泡阶段。

捕获型事件流:从DOM树的根到叶子。

addEventListener('type',fn,true)

# passive event listener

addEventListener第三个参数常规情况下true事件捕获()/false(默认事件冒泡)其实并不一定是一个布尔值。他也可以是一个对象,一组配置。

{
    // 表示`listener`会在该类型的事件捕获阶段传播到该`EventTarget`时触发
	capture: Boolean, 
    // 表示`listener`在添加之后最多只调用一次。如果是`true`,`listener`会在其被调用之后自动移除
	once: Boolean, 
    // 表示`listener`永远不会调用`preventDefault()`。如果`listener`仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告
	passive: Boolean,
}

因为浏览器无法预先知道一个监听器会不会调用 preventDefault(),它需要等监听器执行完后,再去执行默认行为,而监听器执行是要耗时的,这样就会导致页面卡顿。

假设:当你触摸滑动页面时,页面应该跟随手指一起滚动。而此时你绑定了一个 touchstart 事件,你的事件大概执行 200 毫秒。这时浏览器就犯迷糊了:如果你在事件绑定函数中调用了 preventDefault,那么页面就不应该滚动,如果你没有调用 preventDefault,页面就需要滚动。但是你到底调用了还是没有调用,浏览器不知道。只能先执行你的函数,等 200 毫秒后,绑定事件执行完了,浏览器才知道,“哦,原来你没有阻止默认行为,好的,我马上滚”。此时,页面开始滚。

提前给不需要preventDefault的事件预设 { passive: true },可以避免性能浪费。 { passive: true }来避免浏览器检测这个我们是否有在touch事件的handler里调用preventDefault。在这个时候,如果我们依然调用了preventDefault,就会在控制台打印一个警告。告诉我们这个preventDefault会被忽略。

从Chrome 56开始,如果我们给document绑定touchmove或者touchstart事件的监听器,这个passive是会被默认设置为true以提高性能,具体chromestatue 文档。但是我们大多数人并不知道这点,并且依旧调用了preventDefault。这并不会导致什么页面崩溃级的错误,但是这可能导致我们忽略了一个页面性能优化的点,特别是在移动端这种更加重视性能优化的场景下。

passive部分浏览器支持兼容写法如下

var supportsPassive = false;
try {
  var opts = Object.defineProperty({}, 'passive', {
    get: function() {
      supportsPassive = true;
    }
  });
  window.addEventListener("test", null, opts);
} catch (e) {}

// Use our detect's results. 
// passive applied if supported, capture will be false either way.
elem.addEventListener(
  'touchstart',
  fn,
  supportsPassive ? { passive: true } : false
); 

# 通用的事件绑定函数

    function bindEvent(elm,type,selector,fn){
        if(fn === null){
            fn = selector
            selector = null
        }
        elem.addEventListener(type,e=>{
            let target 
            //需要代理
            if(selector){
                //获取触发元素
                target = e.target 
                //matches 判断是否触发元素
                if(target.matches(selector)){
                    fn.call(target,e)
                }
            }else{
                //不需要代理
                fn)(e)
            }
        })
    }

# target VS currentTarget

target为目标点

currentTarget不固定会随着事件冒泡和捕获阶段变化。