# new 实现

function myNew() {
  // 获取构造函数
  const constr = Array.prototype.shift.call(arguments);
  if (typeof constr !== 'function') {
    throw new TypeError('Constructor Type Error');
  }
  // 创建对象 obj,并把 obj 的原型指向构造函数的原型对象上
  const obj = Object.create(constr.prototype);
  // 执行构造函数中的代码(为这个新对象添加属性)
  const result = constr.apply(obj, arguments);
  // 如果构造函数有返回值,则返回;否则,就会默认返回新对象。
  return result instanceof Object ? result : obj;
}

# instanceof 实现

function myInstanceof(left, right) {
  // 基本数据类型都返回false
  if (typeof left !== 'object' || left === null) return false;
  // 获取当前对象原型
  let proto = Object.getPrototypeOf(left);
  while (true) {
    if (proto === null) return false;
    if (proto === right.prototype) return true;
    // 未找到再获取上层原型
    proto = Object.getPrototypeOf(proto);
  }
}

# debounce 防抖

所谓防抖,就是指触发事件后 n 秒后才执行函数,如果在 n 秒内又触发了事件,则会重新计算函数执行时间

<!-- 非立即执行版本 -->
function debounce(fn, wait) {
  if (typeof fn !== 'function') {
    throw new TypeError('Expected a function');
  }
  wait = Number(wait) || 0;
  let timeout;

  return function() {
    // 获取 this 和 参数
    const cxt = this;
    const args = [...arguments];
    // 定时器存在,就清除前面一个定时器,并开启一个新的定时器
    if (timeout) clearTimeout(timeout);
    timeout = setTimeout(() => {
      fn.apply(cxt, args);
    }, wait);
  }
}

<!-- 立即执行版本 -->
// 触发事件后函数会立即执行
// n 秒内触发事件不会执行功能函数下一次调用
// n 秒后再次触发才会再次执行功能函数
function debounce(func, wait) {
  let timeout;
  return function () {
    const cxt = this;
    const args = [...arguments];
    if (timeout) clearTimeout(timeout);
    // 无定时器立刻执行标识
    const immediate = !timeout;
    timeout = setTimeout(() => {
      timeout = null;
    }, wait);
    if (immediate) func.apply(cxt, args);
  };
}
<!-- 合并版本 -->
function debounce(func, wait, immediate) {
  let timeout;
  return function () {
    const cxt = this;
    const args = [...arguments];
    if (timeout) clearTimeout(timeout);
    if (immediate) {
      const nowFlag = !timeout;
      timeout = setTimeout(() => {
        timeout = null;
      }, wait)
      if (nowFlag) func.apply(cxt, args)
    } else {
      timeout = setTimeout(() => {
        func.apply(cxt, args)
      }, wait);
    }
  }
}

# throttle 节流

所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。节流会稀释函数的执行频率

<!-- 时间戳 -->
function throttle(func, wait) {
  // 上一次时间
  let previous = 0;
  return function() {
    let now = Date.now();
    let context = this;
    let args = arguments;
    // 当前时间 - 上一次时间 大于了触发事件,则执行
    if (now - previous > wait) {
      func.apply(context, args);
      // 执行后将时间赋值给变量
      previous = now;
    }
  }
}


<!-- 定时器 -->
function throttle(func, wait) {
  let timeout;
  return function() {
    let context = this;
    let args = arguments;
    if (!timeout) {
      timeout = setTimeout(() => {
        timeout = null;
        func.apply(context, args)
      }, wait)
    }
  }
}

# 数组扁平化

let arr = [[1, 4, 5], 12, 24, [2, 6, [19, [35, 90]]], 10, [13, 18], [[22, 29]]]

<!-- 方法一 Array.prototype.flat -->

const result = arr.flat(Infinity);

// flat() 方法会移除数组中的空项
let arr1 = [1, 2, , 4, 5];
arr1.flat(); // [1, 2, 4, 5]

<!-- 方法二 利用正则,替换花括号 -->

const res2 = JSON.stringify(arr).replace(/\[|\]/g, '').split(',');

<!-- 方法三 改良版正则,上面得到的数据类型会变成字符串 -->

const res3 = JSON.parse('[' + JSON.stringify(arr).replace(/\[|\]/g, '') + ']');

<!-- 方法四 利用 toString & split -->

function flatten(arr) {
  // toString 将数组变为字符串
  // split 将字符串分割为数组
  return arr.toString().split(',').map(function(item) {
    return Number(item);
  });
}

<!-- 方法五 利用 join & split -->

function flatten(arr) {
  return arr.join(',').split(',').map(function(item) {
    return Number(item);
  });
}

<!-- 方法六 reduce -->

function flatten(arr) {
  return arr.reduce((result, item)=> {
    return result.concat(Array.isArray(item) ? flatten(item) : item);
  }, []);
}

<!-- 方法七 递归 -->

function flatten(arr) {
    let res = [];
    arr.map(item => {
        if(Array.isArray(item)) {
            res = res.concat(flatten(item));
        } else {
            res.push(item);
        }
    });
    return res;
}

# 数组去重

let arr = [1, 2, '1', '2', 'a', 'b', true, true, false, '3', '2', 8, 'b']

// 方法一 利用 Set ,因为 Set 中的值总是唯一的
const res1 = [...new Set(arr)];

const res2 = Array.form(new Set(arr));


// 方法二 利用 indexOf | includes
function unique(arr) {
  const res = [];
  for (let i = 0; i < arr.length; i++) {
    // 一下二选一
    if (res.indexOf(arr[i]) === -1) {
      res.push(arr[i]);
    }
    if (!res.includes(arr[i])) {
      res.push(arr[i]);
    }
  }
  return res;
}

// 方法三 利用 filter + indexOf
function unique(arr) {
  return arr.filter((item, index) => {
    // 在数组中的索引==当前索引值
    return arr.indexOf(item) === index;
  });
}

// 方法四 利用 Map
function unique(arr) {
  let map = new Map();
  let array = [];
  for (let i = 0; i < arr.length; i++) {
    // 没有则添加
    if (!map.has(arr[i])) {
      map.set(arr[i], true)
      array.push(arr[i]);
    }
  }
  return array;
}

# 深拷贝

<!-- 简易版 -->
function DeepCopy(target) {
  // 基础类型直接返回
  if (!target || (target && typeof target !== 'object' && typeof target !== 'function')) {
    return target;
  }
  let data;

  // 判断是否是 object 对象
  function isObject(obj) {
    let type = typeof obj;
    return obj != null && type == 'object';
  }

  if (Array.isArray(target)) {
    data = [];
    for (let item of target) {
      // 数组每项也可能是引用类型,对其做深拷贝
      data.push(DeepCopy(item));
    }
  } else if (isObject(target)) {
    data = {}
    for (let key in target) {
      data[key] = DeepCopy(target[key]);
    }
  } else {
    data = target;
  }
  return data;
}
<!-- 优化版 -->
function DeepCopy(target) {
  // 用 WeakMap 记录对象,防止循环引用(WeakMap的键名只能是对象,除 null 意外)
  const map = new WeakMap();

  // 判断是否为 object 类型
  function isObject(value) {
    const type = typeof value;
    return value != null && (type == 'object' || type == 'function');
  }

  function clone(data) {
    // 基础类型直接返回
    if (!isObject(data)) {
      return data;
    }

    // 日期或正则返回一个新对象
    if ([Date, RegExp].includes(data.constructor)) {
      return new data.constructor(data);
    }

    // 函数类型
    if (typeof data === 'function') {
      return new Function('return ' + data.toString())();
    }

    // 判断对象是否存在
    const exist = map.get(data);
    if (exist) return exist;

    // 处理 Map 对象
    if (data instanceof Map) {
      const result = new Map();
      map.set(data, result);
      data.forEach((value, key) => {
        // 如果值是 object 也需要深拷贝
        result.set(key, isObject(value) ? clone(value) : value);
      });
      return result;
    }

    // 处理 Set 对象
    if (data instanceof Set) {
      const result = new Set();
      map.set(data, result);
      data.forEach((value, key) => {
        // 如果值是 object 也需要深拷贝
        result.add(key, isObject(value) ? clone(value) : value);
      });
      return result;
    }

    // 收集键名,返回这个对象自身的属性(考虑了以Symbol作为key以及不可枚举的属性)
    const keys = Reflect.ownKeys(data);
    // 利用 Object 的 getOwnPropertyDescriptors 方法可以获取对象的所有属性以及对应的属性描述
    const objAllDesc = Object.getOwnPropertyDescriptors(data);
    // 利用 Object 的 create 方法创建新对象,并继承传入原对象的原型链,则可以得到的 result 是 data 的浅拷贝
    const result = Object.create(Object.getPrototypeOf(data), objAllDesc);

    // 新对象加入到 map 中
    map.set(data, result);

    // Object.create() 是浅拷贝,所以要执行深拷贝
    keys.forEach(key => {
      const value = data[key];
      result[key] = isObject(value) ? clone(value) : value;
    });
    return result;
  }

  return clone(target);
}