1. 类型检测的方式有哪些, 使用场景是?
- 使用 typeof 检测基础类型
 - 使用 instanceof 检测引用类型
 - 使用 Object.prototype.toString.call(value)可以处理大部分情况
 
2. 如果类没有用new关键字初始化一般会造成什么后果, 怎么避免?
在构造函数中的属性会污染全局变量window, 可以在构造函数中通过instanceof判断下当前实例的构造函数是不是自己, 如果不是则使用new方法返回.
function Person(name, age, job) {
  if (this instanceof Person) {
    this.name = name;
    this.age = age;
    this.job = job;
  } else {
    return new Person(name, age, job);
  }
}3. 补充一个完整类继承及实例化的示例?
// 1. 父类定义
function SuperType(name) {
  if (this instanceof SuperType) {
    this.name = name;
    this.colors = ["red", "blue", "green"];
  } else {
    return new SuperType(name);
  }
}
SuperType.prototype.sayName = function() {
  console.log(this.name);
};
// 2. 子类定义
function SubType(name, age) {
  if (this instanceof SubType) {
    // 2.1 继承了 SuperType 构造函数中定义的属性
    SuperType.call(this, name);
    // 2.2 子类自己的属性
    this.age = age;
  } else {
    return new SubType(name, age);
  }
}
// 父类原型链复制
SubType.prototype = Object.create(SuperType.prototype);
// 因为使用“.prototype =...”后,constructor会改变为“=...”的那个
// constructor,所以要重新指定.constructor 为自身。
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
  console.log(this.age);
};补充 Object.create 的 polyfill:
(function () {
    if(typeof Object.prototype.create === 'undefined') {
        Object.prototype.create = function (proto) {
            function F () {}
            F.prototype = proto
            return new F()
        }
    }
})()
}4. 讲一个惰性函数的实例场景, 优缺点?
- 优点: 避免不必要分支执行
 - 缺点: 运行第一次牺牲点性能
 - 适用于初次运行就能确定分支方向的场景, 比如能力判断/平台判断等
 
两种写法:
function doSomething() {
  if (xx1) {
    doSomething = function() {};
  } else {
    doSomething = function() {};
  }
  return doSomething();
}var doSomething = (function() {
  if (xx1) {
    return function() {};
  } else {
    return function() {};
  }
})();5. bind()函数使用场景及优缺点?
支持原生bind()函数的浏览器有: IE9+/Firefox4+/Chrome 等, 其他浏览器需要 Polyfill:
(function() {
  if (typeof Function.prototype.bind === "undefined") {
    Function.prototype.bind = function(obj) {
      const _self = this;
      return function() {
        return _self.apply(obj, arguments);
      };
    };
  }
})();bind()函数固定了执行环境, 对于异步环境保持this指向有很大帮助- 类似于: 
setTimeout/setInterval/事件绑定等 - 与普通函数相比会使用更多开销和内存, 使用时注意
 
6. 如何理解函数柯里化?
函数柯里化:用于创建一个已经设置好一个或多个参数的函数。简单的说,对函数的一次分装,简化操作的方式。这个过程需要创建一个闭包,指定某个入参后返回新函数。
这个过程可能会用到:call、apply、Array.prototype.slice.call(arguments, n)、bind等函数。
7. 在编写库时, 如何防止核心对象被意外修改?
根据对象防篡改等级, 可以分为以下几种:
第一级: 不可扩展对象Object.preventExtensions(obj)
- 不可再添加属性和方法
 - 已有属性和方法不受影响
 - 通过
Object.isExtensible(obj)判断是否可拓展 
var person = { name: "Nicholas" };
Object.preventExtensions(person);
person.age = 29;
alert(person.age); //undefined第二级: 密封对象Object.seal(obj)
- 继承上一级
 - 已有成员不可删除, 但属性值可修改
 - 通过
Object.isSealed(obj)判断是否被密封 
var person = { name: "Nicholas" };
Object.seal(person);
person.age = 29;
alert(person.age); //undefined
delete person.name;
alert(person.name); //"Nicholas"第三级: 冻结对象Object.freeze(obj)
- 继承上两级
 - 以后成员不可修改
 - 如果要修改需要重新定义
[[set]]函数 - 通过
Object.isFrozen(obj)判断是否被冻结 
函数设计的意义
对 JavaScript 库的作者而言,冻结对象是很有用的。因为 JavaScript 库最怕有人意外(或有意)地修 改了库中的核心对象。冻结(或密封)主要的库对象能够防止这些问题的发生。
8. JavaScript 的定时器相关点
- JavaScript 运行在单线程环境中, 某个时刻只有一个代码在执行.
 - 异步代码会被推入独立的异步队列(Tasks)
 - 在 JavaScript 中代码不会立刻执行的,但一旦进程空闲则尽快执行
 - 浏览器在这个过程中只负责排序调度, 指派某段代码在某个时间点运行的优先级.
 - 定时器队列是一个特殊的队列, 当定时的时间到了, 就将代码插入异步队列
 - 定时器中设定的时间表示何时将定时器的代码添加到异步队列, 而不是何时执行代码
 - 队列中的所有代码都要等到 JavaScript 进程空闲后才能执行, 而不管他们是如何添加到队列中
 - 即使执行完一个异步代码(Tasks), 也会有一个很短的时间间隔, 用于处理页面上的其他事情(UI 更新), 防止页面锁定
 
9. 每个 Tasks 异步都是紧挨着立即执行吗?
不是, 中间会有段时间间隔以便 UI 等工作,当 JavaScript 主进程空闲时, 才将 Tasks 中的异步代码插入 stack 中。
10. 当setInterval中的函数处理时间超过设置的间隔会发生什么问题, 怎么避免或修复?
注意点:
当在 Tasks 队列中没有该定时器的任何其他代码实例时,才将定时器代码添加到队列中。这确保定时器代码被加入到 Tasks 队列中的最短时间间隔为指定的间隔。这里是加入到 Tasks 队列,不是立即执行。
因此:
- 某些定时器回调会被跳过
 - 多个定时器代码执行之间的间隔会比预期小
 
可以使用两个setTimeout解决:
setTimeout(function() {
  //处理中
  setTimeout(arguments.callee, interval);
}, interval);11. 如果一个 JS 函数执行时间接近 1s, 如何优化执行不会让 UI 卡顿?
- 不必要的同步部分放入 setTimeout 中
 - 使用问题 10 中的方式处理,间隔时间设为 0
 
12. 函数防抖是如何控制调用执行的次数的?
clearTimeout和setTimeout的组合使用
13. debounce 和 throttle 两个函数的区别?
debounce:
防抖函数,多次触发只执行最后一次,保证 ideal 时间。比如 input 联想、window 的 resize 事件。
function debounce(fn, delay, context) {
  fn.timer && clearTimeout(fn.timer);
  fn.timer = setTimeout(function() {
    fn.call(context);
    clearTimeout(fn.timer);
    fn.timer = null;
  }, delay);
  return {
    clear: function() {
      clearTimeout(fn.timer);
      fn.timer = null;
    }
  };
}需要注意的地方:
- 计时器 timer 放在要处理的函数上, 因为是引用关系, 下次进入能携带 timer 信息
 - 每次执行完毕记得清理 timer 信息, 防止下次调用产生问题
 - clear 主要作用是清理定时器
 
throttle:
节流函数,根据时间节奏触发,保证间隔。比如点击事件保证间隔,scroll 时控制在 16.6ms 内执行一次等。
function throttle(fn, delay, context) {
  if (fn.timer) return;
  fn.timer = setTimeout(function() {
    clearTimeout(fn.timer);
    fn.timer = null;
  }, delay);
  fn.call(context);
  return {
    clear: function() {
      clearTimeout(fn.timer);
      fn.timer = null;
    }
  };
}需要注意的地方:
- 计时器 timer 放在要处理的函数上, 因为是引用关系, 下次进入能携带 timer 信息
 - 每次执行完毕记得清理 timer 信息, 防止下次调用产生问题
 - 先设置定时, 再去 call 函数
 
14. 实现一个自定义事件库需要用到哪个设计模式?
观察者模式
15. 设计自定义事件库必须具备的功能有哪些?
- 定义一个 hash 对象,key 为事件名,value 为回调函数数组
 - 事件包括的方法有:on、off、once、emit
 - 参考这个库的设计: events
 
16. 简述拖放实现的方式是?
监听 mousedown、mouseup、mousemove 事件然后进行相应处理。
- mousedown:记录拖动元素
 - mousemove:对拖动元素修改 style 的 top、left 属性
 - mouseup:清理操作
 
- 本文作者:烈风裘
 - 本文题目:第二十二章 高级技巧
 - 本文链接:https://xiangst0816.github.io/blog/di-er-shi-er-zhang/
 - 版权声明:本博客所有文章除特别声明外,均采用CC BY-NC-SA 3.0 许可协议。转载请注明出处!