js运行机制

JS 执行是单线程的,它是基于事件循环的。事件循环大致分为以下几个步骤:
(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个”任务队列”(task queue)。只要异步任务有了运行结果,就在”任务队列”之中放置一个事件。
(3)一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。

事件捕获、冒泡

当一个事件发生在具有父元素的元素上时,现代浏览器会运行2个不同的阶段 - 捕获阶段和冒泡阶段,这2个阶段就是获取这个元素所在的DOM树分支上的所有祖先节点然后进行捕获和冒泡。
捕获阶段:

form.submit = function(e) {
  if( name ==='' ){
    e.preventDefault();
    alert("用户名不能为空")
  }
}

e.stopPropagation():停止冒泡,它只会让当前事件处理程序运行,但事件不会在冒泡链上进一步扩大,因此将不会有更多事件处理器被运行(不会向上冒泡)。

// 此时只会执行method1、method2,因为div是冒泡链的上一层
var btn = document.getElementById("btn_id");
var btn_parent_div = document.getElementById("btn_parent_div_id");
btn_parent_div.addEventListener("click", div_method1);

btn.addEventListener("click", method1);
btn.addEventListener("click", method2);

function method1(e){
  e.stopPropagation(); // 停止冒泡
  console.loog("method1");
}

function method2(e){
  console.loog("method2");
}

事件绑定addEventListener、attachEvent

addEventListener(type, listener[, options]):
注意:那些不支持参数options的浏览器,会把第三个参数默认为useCapture,即设置useCapture为true
options表示{
capture:false(capture值为true时:表示listener会在该类型的事件捕获阶段传播到该EventTarget时触发),

passive:false(passive值为true时:表示listener永远不会调用preventDefault(),如果listener仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告,在后续的chrome版本中已经升级成为错误了)(PS:由于浏览器必须在事件执行结束之后才能知道有没有使用e.preventDefault(),这样就导致了浏览器不能及时响应滚动,从而产生卡顿。所以为了让页面滚动流畅,从chrome56开始,在window、document和body上面的touchstart、touchmove事件处理函数,默认passive:true,从而达到忽略preventDefault,这样浏览器在第一事件就可以进行滚动了。但是此时如果在事件里面调用preventDefault,浏览器就会报错:Unable to preventDefault inside passive event listener due to target being treated as passive,比如fastclick库里就存在这种情况,而且fastclick已经停更好几年了,所以最好自己处理一下),

once:false(once值为true时表示listener在添加之后最多调用一次。如果是true,listener会在其被调用之后自动移除)
};
addEventListener优势:

// 执行顺序:method1 -> method1 -> method2 btn.addEventListener(“click”, method1, { capture:false }); btn.addEventListener(“click”, method1, { capture:true }); // 因为capture参数不一致,所以都会执行,但是passive、once参数不一致也只会执行一次,会将这个method1抛弃 btn.addEventListener(“click”, method2);

- addEventListener的listener中的this是指向当前元素的,btn.onclick=method:this指向的是btn,相比之下:<a onclick='method'>、attachEvent的this指向的是window;
- addEventListener在IE8下无法使用,所以在做PC端的时候需要处理一下,removeEventListener:移除事件的绑定

**attachEvent(type, listener),IE特有**
attachEvent绑定的事件都是在冒泡阶段时触发。detachEvent:移除事件的绑定

```javascript
var btn = document.getElementById("btn_id");

// 执行顺序:method3,前面2个方法会被覆盖掉
btn.onclick = method1;
btn.onclick = method2;
btn.onclick = method3;

// 执行顺序:method3 -> method2 -> method1
btn.attachEvent("onclick", method1);
btn.attachEvent("onclick", method2);
btn.attachEvent("onclick", method3);

// 执行顺序:method1 -> method2 -> method3
btn.addEventListener("click", method1);
btn.addEventListener("click", method2);
btn.addEventListener("click", method3);

// 此时如果将btn元素,置于div中
// 执行顺序:div_method2 -> method1 -> method2 -> div_method1
var btn_parent_div = document.getElementById("btn_parent_div_id");
btn_parent_div.addEventListener("click", div_method1);

// 表明在div_method2事件在捕获阶段就会被触发,默认是在冒泡时才会触发事件
btn_parent_div.addEventListener("click", div_method2, { capture: true }); 
btn.addEventListener("click", method1);
btn.addEventListener("click", method2);

oninput,onchange,onpropertychange区别

1、onchange 事件与 onpropertychange 事件的区别:

2、oninput 事件与 onpropertychange 事件的区别:

3、oninput 与 onpropertychange 失效的情况:

节流(Throttle)和防抖(Debounce)

目的:防止scroll、resize、keyup、mousemove等事件被频繁触发,导致页面出现抖动或卡顿的情况。
函数节流throttle:throttle会强制函数以固定的速率(时间或滚动举例等)执行。因此这个方法比较适合于动画相关的场景。
实现思路:在每次触发input事件时,以固定的时间间隔去执行方法,先判断是否时间间隔满足条件,如果不满足就赋值到定时器中,以防止如果是最后一次,能正常执行。

function throttle(fn, threshhold) {
 var timeout
 var start = new Date;
 var threshhold = threshhold || 160
 return function () {

 var context = this, args = arguments, curr = new Date() - 0
 
 clearTimeout(timeout)// 清除回调事件
 if(curr - start >= threshhold){ 
     console.log("now", curr, curr - start)// 注意这里相减的结果,都差不多是160左右
     fn.apply(context, args) // 只执行一部分方法,这些方法是在某个时间段内执行一次
     start = curr
 }else{
 //让方法在脱离事件后也能执行一次
     timeout = setTimeout(function(){
        fn.apply(context, args) 
     }, threshhold);
    }
  }
}
var mousemove = throttle(function(e) {
 console.log(e.pageX, e.pageY)
});

// 绑定监听
document.querySelector("#panel").addEventListener('mousemove', mousemove);

函数防抖debounce:指定时间内js中多个调用的方法,只执行最后一次调用的。适用场景:全文搜索、用户校验等;
实现思路:在每次触发input事件时,防抖时不需要进行时间间隔的判断,只需要clear掉上一次的timeout,然后设置一个新的定时器,这样确保在规定的间隔时间内只会执行最后一次。

function debounce(func, delay) {
    var timeout;
    return function(e) {
        clearTimeout(timeout);
        var context = this, args = arguments
        timeout = setTimeout(function(){
          func.apply(context, args);
        },delay)
    };
};

var validate = debounce(function(e) {
    console.log("change", e.target.value, new Date-0)
}, 300);

// 绑定监听
document.querySelector("input").addEventListener('input', validate);