# 数据类型√
# JS数据类型√
- 原始类型8种:number、string、boolean、null、undefined、
 - ES6添加的:symbol、bigInt
 - 引用类型:Object、Array、函数
 
栈存放指针、堆存放实体
# 判断数据类型的办法√
- typeof 判断基本数据类型,和函数,不能准确判断null
 - instanceof 判断对象类型,依据原型链判断
 - Object.prototype.toString.call() 返回调用者的具体类型,所有基础类型都能判断,对象数组也可以
 
# 0.1+0.2为什么不等于0.3
进制转换:IEEE754,最大存储53为有效数字,大于的会被截掉导致精度丢失
解决办法:转为大数或整数运算
# new操作符具体做了什么 √
function myNew(context) {
  // 创建一个新的对象
  const obj = new Object();
  // 设置空对象的__proto__为构造函数的prototype
  obj.__proto__ = context.prototype;
  // 构造函数的this指向这个对象,执行构造函数的代码
  const res = context.apply(obj, [...arguments].slice(1));
  // 判断函数的返回值类型,如果是引用类型,就返回这个引用类型的对象
  return typeof res === "object" ? res : obj;
}
# dom.onclick和dom.addEventListener区别?√
dom.onclick:元素被点击是发生
- 可以在内联标签上添加事件,只能有一个,后来的会覆盖前者
 - 潜在安全问题XSS跨站脚本攻击
 - 内联不能分离文档结构和逻辑
 - 只能处理冒泡阶段
 
dom.addEventListener:用于向元素添加事件,三个参数分别是事件、回调函数、是否在捕获和冒泡阶段执行
- 可以添加多个事件,按注册顺序执行
 - 分离文档结构和逻辑
 - 可以处理冒泡和捕获阶段,true表示在捕获阶段处理,false表示在冒泡阶段处理
 
# 作用域和闭包√
# 作用域
作用域:代码的当前上下文,控制着变量和函数的可见性和生命周期。最大的作用是隔离变量,不同作用域下同名变量不会冲突。
作用域链:当前作用域没有查到值,会向上级作用域查询,直到全局作用域。
作用域分类:
全局作用域:代码在程序的任何地方都能被访问,例如window对象。生命周期和页面生命周期等同。
模块作用域:AMD、CommonJS、ES6等等。模块有自己独立的作用域
函数作用域:闭包在该作用域产生。
块级作用域:JS的变量提升存在变量覆盖、变量污染的情况,ES6引入了块级作用域let、const
# 执行上下文(重点)
执行一段代码时的准备工作,成为执行上下文,用执行上下文栈管理执行上下文
每个执行上下文,有三个重要属性:
- 变量对象VO,是执行上下文相关的数据作用域,存储了上下文中定义的变量和函数声明。
 - 作用域链[[scope]],活动对象AO保存在作用域链的最前端
 - this
 
函数执行时上下文的初始化:(注意:父函数执行时,子函数才会被创建)
- 复制函数[[scope]]属性创建作用域链
 - 用arguments创建活动对象AO
 - 初始化AO,加入形参、函数声明、变量声明
 - 将AO压入函数作用域链的顶端
 - 子函数被创建,保存作用域链到函数的内部属性[[scope]]
 
# 全局上下文
全局上下文的变量对象就是全局对象
# 函数上下文
在函数上下文中,用活动对象AO表示变量对象。当进入一个执行上下文中,这个上下文的变量对象才会被激活,所以叫活动对象。通过函数的arguments属性初始化。
进入执行上下文时,会先处理函数声明,在处理变量声明,变量对象包括:
- 函数的所有形参
 - 函数声明
 - 变量声明
 
在代码执行阶段,会顺序执行代码,根据代码,修改变量对象的值
function foo() {
    console.log(a);
    a = 1;
}
foo(); // Uncaught ReferenceError: a is not defined
function bar() {
    a = 1;  //a会被绑定在全局作用域
    console.log(a);
}
bar(); // 1
console.log(foo); // 函数,因为会优先处理函数声明
function foo(){
    console.log("foo");
}
var foo = 1;
# 函数创建(复制)
函数创建时,就会把所有的父变量对象保存在内部属性[[scope]]中,此时不包含自身的变量对象
# 函数激活(添加)
Scope = [AO].concat([[Scope]]); 
函数激活时,进入函数上下文,创建VO/AO之后,就会将活动对象添加到作用域链的前端
# 作用域链
查找变量时,会先从当前上下文的变量对象中查找,没有找到,会从父级执行上下文查找,直到查找全局上下文。
# this
Reference,是一种规范中的类型,用于描述语言底层行为逻辑
var foo = 1;
// 对应的Reference是:
var fooReference = {
    base: EnvironmentRecord,// 属性所在的对象
    name: 'foo',//属性的名称
    strict: false
};
获取Reference组成部分的方法:
- GetBase:返回reference的base value
 - IsPropertyReference:如果base value是一个对象,就返回true
 - GetValue:返回具体的值
 
函数调用:
- 计算MemberExpreesion,结果赋给ref
 - 判断ref是不是Reference类型,如果是:
- 如果IsPropertyReference(ref) 是 true, 那么 this 的值为 GetBase(ref)
 - 或者base value 是Environment Record,那么那么this的值为 ImplicitThisValue(ref)
 
 - 否则,this为undefined
 
MemberExpreesion是什么?简单理解就是表达式中()左边的部分
var value = 1;
var foo = {
  value: 2,
  bar: function () {
    return this.value;
  }
}
//示例1 foo.bar this->foo
console.log(foo.bar());
//示例2 (foo.bar)和foo.bar一样 this->foo
console.log((foo.bar)());
//示例3   GetValue this->undefined,会被隐式转换为全局对象
console.log((foo.bar = foo.bar)());
//示例4   GetValue this->undefined
console.log((false || foo.bar)());
//示例5   GetValue this->undefined
console.log((foo.bar, foo.bar)());
# 闭包
# 理解闭包
表象:函数A包含了函数B,函数B使用了函数A的变量,那么函数B就被称为闭包,闭包就是能够 读取函数A内部变量的函数。
实质:在某个内部函数的执行上下文创建时,会将父级函数的活动对象加到内部函数的[[scope]]中,形成作用域链,所以即使父级函数的执行上下文销毁,但是活动对象还是存储在内存中,可以被内部函数访问到,所以实现了闭包。
闭包的特征:
- 函数中存在函数
 - 内部函数可以访问外部函数的额作用域
 - 外部函数的参数和变量不会被GC,始终留在内存中
 - 使用闭包会消耗内存
 
# 应用场景
- for循环闭包变量i
 
// demo1 输出 3 3 3  var定义的是全局变量
for(var i = 0; i < 3; i++) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
} 
// demo2 输出 0 1 2  let具有块级作用域
for(let i = 0; i < 3; i++) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
}
// demo3 输出 0 1 2  闭包
for(let i = 0; i < 3; i++) {
    (function(i){
        setTimeout(function() {
        console.log(i);
        }, 1000);
    })(i)
}
- 模拟私有方法
 
/* 模拟私有方法 */
// 模拟对象的get与set方法
var Counter = (function() {
var privateCounter = 0;
function changeBy(val) {
    privateCounter += val;
}
return {
    increment: function() {
    changeBy(1);
    },
    decrement: function() {
    changeBy(-1);
    },
    value: function() {
    return privateCounter;
    }
}
})();
- setTimeout中使用
 
// setTimeout(fn, number): fn 是不能带参数的。使用闭包绑定一个上下文可以在闭包中获取这个上下文的数据。
function func(param){ return function(){ alert(param) }}
const f1 = func(1);
setTimeout(f1,1000);
- 实现继承
 
// 以下两种方式都可以实现继承,但是闭包方式每次构造器都会被调用且重新赋值一次所以,所以实现继承原型优于闭包
// 闭包
function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
  this.getName = function() {
    return this.name;
  };
  this.getMessage = function() {
    return this.message;
  };
}
# 预解析和变量提升 √
预解析:JS引擎将JS所有var声明和function都提升到作用域的最前面
变量提升:将所有的变量声明提升到作用域的最前面
# call、apply、bind √
call和apply是使用后马上执行,bind是返回一个新的函数
call与apply的区别在于传参,call需要把参数列举出了
call实现:
Function.prototype.myCall = function (context) {
  // 判断调用对象
  if (typeof this !== "function") {
    throw new Error("Type error");
  }
  // 首先获取参数
  let args = [...arguments].slice(1);
  let result = null;
  // 判断 context 是否传入,如果没有传就设置为 window
  context = context || window;
  // 将被调用的方法设置为 context 的属性
  // this 即为我们要调用的方法
  context.fn = this;
  // 执行要被调用的方法
  result = context.fn(...args);
  // 删除手动增加的属性方法
  delete context.fn;
  // 将执行结果返回
  return result;
};
apply的实现:
Function.prototype.myApply = function (context) {
  if (typeof this !== "function") {
    throw new Error("Type error");
  }
  let result = null;
  context = context || window;
  // 与上面代码相比,我们使用 Symbol 来保证属性唯一
  // 也就是保证不会重写用户自己原来定义在 context 中的同名属性
  const fnSymbol = Symbol();
  context[fnSymbol] = this;
  // 执行要被调用的方法
  if (arguments[1]) {
    result = context[fnSymbol](...arguments[1]);
  } else {
    result = context[fnSymbol]();
  }
  delete context[fnSymbol];
  return result;
};
bind实现:
Function.prototype.myBind = function (context) {
  // 判断调用对象是否为函数
  if (typeof this !== "function") {
    throw new Error("Type error");
  }
  // 获取参数
  const args = [...arguments].slice(1),
  const fn = this;
  return function Fn() {
    return fn.apply(
      this instanceof Fn ? this : context,
      // 当前的这个 arguments 是指 Fn 的参数
      args.concat(...arguments)
    );
  };
};
# ...args剩余参数和arguments对象的区别
- ...args只包含没有形参的实参,而arguments包含了传给函数的所有实参
 - ...args是真正的Array实例,arguments不是真正的数组
 - arguments还有附加属性callee
 
# 遍历方法
# 遍历数组
- for循环
 - forEach() 不能执行break和return
 - map()
 - for...of 可迭代的对象都可遍历
 
# 遍历对象
- for...in 遍历一个对象自由的、继承的、可枚举的、非Symbol的属性名
 - Object.keys() 返回一个可枚举属性名组成的数组
 - Object.values() 可枚举属性值
 - Object.getOwnPropertyNames() 返回指定对象的所有自身属性名
 
# 遍历字符串
- for...of
 - for循环
 
# JS的NAN属性
NaN属性代表非数字值的特殊值,不可配置,不可写
NaN!==NaN
isNaN()判断是否为NaN
# null和undefined
undefined == null //true
undefined === null //false
let a
typeof a //undefined
let b= null
typeof b //object
null表示空对象指针,JS的最初版本使用32位系统,为了性能考虑使用低位存储变量类型信息,000开头代表对象,而null表示为全零
# ===和==
==对比时,若类型不相同,会先转换为相同类型,然后再比较值
- 一个是null、一个是undefined、相等
 - 一个是字符串、一个是数值、字符串转换为数值
 - true/false转换为1/0
 - 一个是对象、一个是数值和字符串
- 先尝试valueOf()
 - toString()
 
 
===不会转换类型
# 函数的length√
- 形参的个数
 
function fn1 () {}
function fn2 (name) {}
function fn3 (name, age) {}
console.log(fn1.length) // 0 
console.log(fn2.length) // 1 
console.log(fn3.length) // 2
- 第一个具有默认值之前的参数个数
 
function fn1 (name) {} 
function fn2 (name = '林三心') {} 
function fn3 (name, age = 22) {} 
function fn4 (name, aaa, age = 22, gender) {} 
function fn5(name = '林三心', age, gender, aaa) {} 
console.log(fn1.length) // 1 
console.log(fn2.length) // 0 
console.log(fn3.length) // 1 
console.log(fn4.length) // 2
console.log(fn5.length) // 0
# DOM事件流√
事件三要素:事件源、绑定事件、添加事件处理程序
事件流:
- 捕获阶段:DOM最顶层window开始,逐级向下寻找目标元素传播的过程,遇见绑定的捕获事件会向下传递
 - 目标阶段:到达目标阶段出发绑定事件
 - 冒泡阶段:事件开始由具体元素接受,逐级向上传播到DOM顶层window的过程
 
有了事件流,就可以控制事件在事件流的那一个环节执行
# 中文的长度
js是unicode编码,所有的字符都是一个
前后端不一致的情况,可以用正则替换后再获取长度
# 简述cookie/session记录登录状态机制原理
第一次访问服务器,服务器会开辟一块空间来存放用户信息。
每一个登录之后的用户信息,都会以key-value格式记录在session中
同事服务器会把sessionId存在cookie中返回给客户端
客户端把sessionId保存在本地cookie中对应的网站记录下
下次访问携带这个sessionId
# 简述cookies、session、sessionStorage、localStorage区别
cookie
- 把数据存在用户的浏览器中
 - 刷新页面不会丢失数据
 - 只能存储字符串
 
session
- 位于web服务器,主要负责访问者与网站的交互,关闭网站时会话就已经结束
 
sessionStorage
- 生命周期为关闭浏览器
 - 同一个窗口数据可共享
 - 2.5m
 
localStorage
- 生命周期为永久
 - 多个窗口共享
 - 5m
 
cookie与sessionStorage、localStorage区别:
- cookie始终在同源的http请求中携带,另外两个不会自动发送到服务器
 - cookie不能超过4k,另外两个可到5M以上
 - 数据有效期不同:sessionStorage仅在浏览器窗口关闭之前有效,localStorage始终有效,窗口或浏览器关闭也一直保存;cookie在过期时间之前有效
 - 作用域不同:sessionStorage在同一个的浏览器窗口共享,localStorage和cookie在所有同源窗口共享
 
# for-in与for-of区别 √
- for-in:用于遍历对象可枚举属性,包括对象的原型链中的属性,遍历数组的索引
 - for-of:用于遍历可迭代对象,不会遍历原型,遍历数组的值,对象部署了Symbol.iterator属性就可以遍历
 
# for...of与Iterator
遍历器Iterator提供一种接口,为不同的数据结构提供统一的访问机制,即for...of循环
手写next方法
function makeIterator(array){
  var nextIndex = 0
  return {
    next: function(){
      return nextIndex < array.length ?
        {value:array[nextIndex++],done:false}:
        {done:true}
    }
  }
}
var iter = makeIterator([1,2,3])
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
一个数据结构只要具有Symbol.iterator属性,就认为是可以遍历的。Symbol.iterator属性本身是一个函数,执行这个函数,返回一个遍历器。
对象没有默认部署Iterator接口
调用了Iterator接口的场合:
- for...of
 - Array.from()
 - Map(),Set()
 - Promise.all()
 - Promise.race()
 
# 常见的事件
- 点击事件:onclick、ondblclick
 - 焦点事件:onblur失去焦点、onfocus获得焦点
 - 加载事件:onload
 - 鼠标事件:onmosedown、onmouseup
 - 键盘事件:onkeydown
 - 选择和改变:onchange
 - 表单事件:onsubmit
 
# JS严格模式
"use strict" 减少优于隐式转换、全局变量污染带来的安全问题
- 变量必须声明再使用
 - 预编译时this为undefined,
 - 不支持arguments、caller、callee、with
 - 函数内部不能通过arguments修改参数值
 - 不允许对变量或函数使用delete
 - 不允许变量重名
 - 不允许使用8进制
 - 不可对对象的只读属性赋值
 - 全局this不再指向window对象
 
# ~~字符√
~是按位取反,~~会保持原值,并转化为int类型
正值相当于Math.floor(x)
# Map和Object区别
- 内存占用:同样的内存,map比object多存储50%的键值对
 - 插入性能:有大量插入操作时map性能更好
 - 查找速度:大数据量时差异较小,某些情况Object更好
 - 删除性能:选择map
 
# 位运算符√
位与&、位或|、位异或^、位非~
左移<<、右移>>、无符号右移>>>、无符号左移<<<
# delete删除性能
- 对象不存在也返回true,不能通过结果来判断
 - 对象的属性存在并且不能删除时返回false
 - 全局作用域或者var、function声明的无法用delete删除
 
# 浏览器缓存各个文件的具体缓存配置
Content-Length加Last-modified的哈希
# val、let、const
var:
- 声明提升
 - 同一个作用域可以多次声明,最开始的会被覆盖
 - 没有块级作用域
 - 会绑定在顶层window对象上,其他两个不会
 
const:
- 不可重新赋值
 - 引用类型例外
 - 必须同时初始化和赋值
 - 栈保存引用、堆保存值
 
let:
- 不会声明提升
 - 可更改
 - 不可重复声明
 
# 将值设置不可修改
Object.freeze:
- 不能添加删除属性
 - 不能修改已有属性
 - 对象原型也不能修改
 
Object.seal:
- 不能添加删除
 - 可以修改已有属性
 
Object. preventExtensions:
- 不能添加新属性
 
# 箭头函数和普通函数√
箭头函数:
- 没有prototype属性,不能作为构造函数使用
 - 不能绑定arguments,会继承上一级作用域的arguments
 - this指向定义时父级作用域的this
 
主要解决问题:
- 简化函数写法,多用于回调函数
 - 解决this指向问题,函数不再是一等公民
 
不能通过call、apply、bind绑定
# symbol类型√
定义一个唯一的对象属性名,不可改变。当使用一个别人提供的对象时,想要添加一些属性,可能会重复,那么就可以使用Symbol
- 作为属性名,解决全局变量名冲突的问题
 - 不能用new构建 var s1 = Symbol("dish")
 - 作为属性key,只能通过括号获取,不能用点获取
 - 不能被for-in迭代
 
Symbol.description可以读取描述信息
symbol.for()返回的可以是相等的
Symbol.for("bar") === Symbol.for("bar")
// true
Symbol("bar") === Symbol("bar")
// false
# 异步√
 调用栈call stack,也就是执行上下文栈
 异步任务的执行,首先会进入调用栈,解释器会将其响应的回调任务放在任务队列中,紧接着调用栈会删除这个任务。当主线程清空后,解释器会读取任务队列,并依次将已完成的异步任务加入调用栈执行。
# event loop、宏任务和微任务
宏任务(浏览器规定的):整体代码块,setTimeout、setInterval、MessageChannel、IO操作、事件队列、setImmediate(Node环境)
微任务(ES6语法规定的):Promise.[then|catch|finally]、MutationObserver(浏览器环境)、process.nextTick(Node环境)
区别:
- 宏任务在DOM渲染后触发
 - 微任务在DOM渲染前触发
 
事件循环的具体流程:
- 从宏任务队列中,按照入队顺序,找到第一个执行的宏任务,放入调用栈,开始执行
 - 执行完该宏任务下所有的同步任务后,该宏任务被推出宏任务队列,然后微任务开始按照入队顺序,依次执行,直到微任务队列清空,微任务中产生微任务会立即添加在队尾
 - 微任务队列清空后,一个事件循环结束
 - 接着从宏任务队列,找到下一个宏任务,开始第二轮循环。
 
# async/await
async将一个同步函数变为异步函数,返回值变为promise
await可以放在任何异步的、基于promise的函数之前,执行过程中会暂停在该代码上,直到promise完成。
async函数的函数体可以被看做是0个或者多个await表达式分开的,从函数的第一行开始到第一个await都是同步运行的。因此,如果async函数有一个await表达式,那么async函数就会异步执行
# promise对象
三个状态:
- Pending :初始状态,表示异步操作还在进行中
 - Fulfilled:表示异步操作成功,此时会执行.then()方法的回调函数
 - Rejected:异步操作失败,执行.catch()方法的回调函数
 
创造一个Promise实例
const promise = new Promise(function(resolve, reject) {
  // ... some code
  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});
Promise构造函数接受一个函数作为参数,该函数的两个参数分别为resolve和reject,它们是两个函数,由JS提供,不用自己部署。
resolve函数的作用是,将Promise对象的状态从pending变为resolved,在异步函数操作成功时调用,并将异步操作的结果作为参数传递出去;reject函数的作用是,将Promise对象的状态从pending转为rejected,在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise实例生成后,可以用then方法分别指定resolved状态和rejected状态的回调函数
promise.then(function(value) {
  // success
}, function(error) {
  // failure
});
如果调用resolve函数和reject函数时带有参数,那么它们的参数会被传递给回调函数
# Promise.then()
作用是为Promise实例添加状态改变后的回调函数。返回一个新的Promise
# Promise.catch()
相当于.then(null,rejection)或者.then(undefined,rejection)的别名。
这三种写法是等价的:
const promise = new Promise(function(resolve, reject) {
  throw new Error('test');
});
promise.catch(function(error) {
  console.log(error);
});
// Error: test
// 写法一
const promise = new Promise(function(resolve, reject) {
  try {
    throw new Error('test');
  } catch(e) {
    reject(e);
  }
});
promise.catch(function(error) {
  console.log(error);
});
// 写法二
const promise = new Promise(function(resolve, reject) {
  reject(new Error('test'));
});
promise.catch(function(error) {
  console.log(error);
});
Promise对象的错误具有冒泡性质,会一直向后传递,直到被捕获为止。所以只需要在链式的尾部添加一个catch即可,通常不要在then里面处理reject
# Promise.finally() ES2018
不管promise对象最后状态如何,都会执行的操作,因为都用的resolve
Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );
};
# Promise.all()
用于将多个Promise实例,包装为一个新的Promise实例
const p = Promise.all([p1, p2, p3]);
p的状态由p1、p2、p3决定:
- 只有p1、p2、p3的状态都变为fulfilled,p->fulfilled,返回数组
 - 只有有一个被rejected,p->rejected,返回第一个被reject的实例的返回值
 - 如果作为参数的p。定义了自己的catch方法,一旦被rejected,并不会触发Promise.all()d的catch方法
 
# Promise.reslove()
将现有的对象转为Promise对象
参数的情况:
- 参数是一个Promise实例,返回这个实例
 - 参数是thenable对象,转为Promise对象后,立即执行它的then方法
 - 参数不是thenable对象或者根本不是对象,返回一个新的对象,状态为resolved
 - 不带任何参数,返回一个resolved状态的Promise对象
 
# Promise.reject()
返回一个新的 Promise 实例,该实例的状态为rejected
# arguments对象√
- callee用于匿名函数递归
 - 本身不是数组,但有length属性,可以索引元素
 - 检测参数个数
 - 模拟函数重载
 
# ArrayBuffer
操作二进制数据的一个接口
- ArrayBuffer代表原始二进制数据
- TypedArray 读写简单的二进制数据
 - DataView读写复杂二进制数据
 
 
# 浏览器工作原理和V8引擎
# 浏览器内核
实质上是浏览器的排版引擎、页面渲染引擎、样板引擎
- webkit :苹果基于KHTML开发的,用于safari
 - Blink:webkit一个分支,用于Google、edge等
 
# 浏览器渲染过程√
# 生成渲染树
- 解析HTML文件,构建DOM树
 - CSS解析,构建CSSOM树
 - 合并形成一个渲染树 Render tree
 - Layout回流,确定节点在屏幕中的位置和大小
 - Painting:根据渲染树以及回流得到的几何信息,得到节点的绝对像素
 - Display:像素发送给GPU,展示在页面上
 
# 回流
构造渲染树时,需要将可见的DOM及其样式结合起来,还需要计算在视口内的确切位置和大小。计算的阶段就是回流。
# 重绘
将渲染树的每个节点转换为屏幕的实际像素
# v8引擎
- JS代码、词法语法分析、抽象语法树、解释为字节码、运行结果
 - 上下文栈、预解析、变量提升、作用域、暂时性死区
 - 基本类型存放在栈中分配
 - 复杂类型存放在堆中,引用的指针在栈中
 
# 垃圾回收机制√
两种策略:
- 标记清除:标记阶段为所有活动对象做标记,清除阶段把没有标记的销毁。
- 缺点:内存碎片化、分配速度慢
 - 采用标记整理算法
 
 - 引用计数:如果没有引用指向该对象,对象将被垃圾回收机制回收
- 缺点:需要一个计数器,所占内存空间大,解决不了循环引用的问题
 
 
v8的垃圾回收机制基于标记清除
# 常见手写
# 防抖
事件被触发时,设置一个定时器。如果在定时器内事件再次触发,那么久清除之前的定时器,重新设置一个新的定时器。只有当定时器时间到达之后,才会真正的触发事件。可以保证,事件被频繁触发的时候,只有最后一次会被执行。
# 节流
对于高频率触发的事件,限制触发的时间间隔,以减少对系统资源的消耗