# 1. 手写深拷贝方法

const deepclone = (obj, hash = new WeakMap()) => {
  if (obj instanceof RegExp) return new RegExp(obj);
  if (obj instanceof Date) return new Date(obj);
  if (typeof obj !== 'object' || !obj) return obj;
  if (hash.has(obj)) return hash.get(obj);
  const newObj = Array.isArray(obj) ? [] : {};
  hash.set(obj, newObj);
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      newObj[key] = deepclone(obj[key], hash);
    }
  }
  return newObj;
};

const obj = {name: {age: 10}};
obj.name = obj;
const newobj = deepclone(obj);
console.log(obj, newobj);

# 2. 手写防抖与节流

防抖

  • 短时间大量触发同一事件,只会执行一次函数,实现原理为设置一个定时器,约定在 xx 毫秒后再触发事件处理,每次触发事件都会重新设置计时器,直到 xx 毫秒内无第二次操作。
  • 应用场景 => 搜索框/滚动条/按钮点击的监听事件处理
/**
 * func => 执行的函数体
 * wait => 等待的时间
 * immediate => 是否立即执行
 */
const debounce = (func, wait, immediate) => {
  //timeout => 控制间隔保存计时器的变量
  //result => 执行函数如果有返回值 最后给它 return 出去
  let timeout, result;
  //防抖函数主体
  const debounced = function () {
    //debounce(doSomeing,300);
    const context = this; //当前执行的方法的 this ,这里是 上面绑定事件的 dom
    let args = arguments; //当前执行方法里doSomeing 的 arguments
    if (timeout) clearTimeout(timeout); //如果还在运动中 清除上一次的计时器

    if (immediate) {
      //立即执行
      let callNow = !timeout; //默认立即执行 默认值 true
      // timeout = setTimeout 时 = true; 在等待时间后 timeout 等于null ;用户控制 callNow 变量
      timeout = setTimeout(() => {
        timeout = null;
      }, wait);

      //如果 callNow = true 执行
      if (callNow) result = func.apply(context, args); //改变执行函数内部this指向 ,并传入arguments
    } else {
      //非立即执行 事件结束后 等待时间 后执行
      timeout = setTimeout(function () {
        result = func.apply(context, args); //改变执行函数内部this指向 ,并传入arguments
      }, wait);
    }
    return result;
  };
  //防抖函数的取消方法
  debounced.cancel = function () {
    clearTimeout(timeout);
    timeout = null;
  };
  return debounced;
};

节流

  • 防抖是延迟执行,节流是间隔执行,函数节流即每隔一段时间就执行一次,实现原理为设置一个定时器,约定 xx 毫秒后执行事件,如果时间到了,那么执行函数并重置定时器。
  • 应用场景 => DOM 元素拖拽,射击游戏,计算鼠标距离,scroll 滚动事件
/**
 * func => 绑定的方法
 * wait => 间隔的时间
 * {leading,trailing} 3中情况 不能同时为false
 * leading => true,trailing => false, 第一次会立即执行 最后一次不会调用
 * leading => false,trailing => true, 第一次不会立即执行 最后一次会调用
 * leading => true,trailing => true, 第一次会立即执行 最后一次会调用
 */
const throttle = (func, wait, options) => {
  let context,
    args,
    timeout,
    oldTime = 0;
  if (!options) options = {};
  const later = function () {
    oldTime = new Date().valueOf();
    timeout = null;
    func.apply(context, args);
  };
  return function () {
    conntext = this;
    args = arguments;
    const nowTime = new Date().valueOf();
    //第一次不会立即执行
    if (options.leading === false && !oldTime) {
      oldTime = newTime;
    }
    //判断第一次进来 会不会 立即执行
    if (nowTime - oldTime > wait) {
      if (timeout) {
        clearTimeout(timeout);
        timeout = null;
      }
      func.apply(context, args); //间隔时间到了就可以执行
      oldTime = nowTime;
    }
    //判断最后一次 会不会 执行
    else if (!timeout && options.trailing !== false) {
      timeout = setTimeout(later, wait);
    }
  };
};

# 3. 闭包的理解

  • 使用闭包主要是为了设计私有的方法和变量。闭包的优点是可以避免全局变量的污染,缺点是闭包会常驻内存,会增大内存使用量,使用不当容易造成内存泄露。在 js 中,函数即闭包,只有函数才会产生作用域的概念。
  • 闭包有三个特性:
    1. 函数嵌套函数。
    2. 函数内部可以引用外部的参数和变量。
    3. 参数和变量不会被垃圾回收机制回收。
  • 使用场景
    1. setTimeout:原生的 setTimeout 传递的第一个函数不能带参数,通过闭包可以实现传参效果。
    2. 回调:定义行为,然后把它关联到某个用户事件上(点击或者按键)。代码通常会作为一个回调(事件触发时调用的函数)绑定到事件。
    3. 函数防抖:在事件被触发 n 秒后再执行回调,如果在这 n 秒内又被触发,则重新计时。
      实现的关键在于 setTimeOut 这个函数,由于还需要一个变量来保存计时,考虑维护全局纯净,可以借助闭包来实现。
    4. 封装私有变量

# 4. new 操作符

  1. 创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型。
  2. 属性和方法被加入到 this 引用的对象中。
  3. 新创建的对象由 this 所引用,并且最后隐式的返回 this。

# 5. 数据类型的判断(typeof, instanceof, constructor, Object.prototype.toString.call())

  1. typeof
    • typeof对于原始类型来说,除了null都可以显示正确的类型
    • typeof对于引用类型来说,除了函数都会显示object
  2. instanceof
    • instanceof可以正确的判断对象的类型,因为内部机制是通过判断对象的原型链中是否能找到类型的prototype
    • instanceof可以精准判断引用数据类型,基本数据类型因为不是实例而不能被instanceof精准判断
  3. constructor
    • constructor使用方式为(xx).constructor === Number/Boolean/...
    • 如果手动改变了创建对象的原型,constructor判断将不准确
  4. Object.prototype.toString.call()
    • 最精准能判断各种类型的变量,返回的是诸如[object, 数据类型(Number, String, Undefined, Null, Array...)],所以用在判断类型等效于typeof返回值的写法是Object.prototype.toString.call(变量).slice(8, -1) === 数据类型

注意

所有 js 数据类型都拥有 valueOf 和 toString 这两个方法,null 除外

  • valueOf()方法:返回指定对象的原始值。
  • toString()方法:返回对象的字符串表示。 在数值运算中,优先调用了 valueOf,字符串运算中,优先调用 toString