Skip to content

getType

js
function getType(data) {
  return Object.prototype.toString.call(data).slice(8, -1); // [object Type]
}
console.log(getType("")); // String
console.log(getType([])); // Array

instanceof

A instanceof B: 判断对象 A的原型链上是否存在构造函数 B的原型对象

js
function myInstanceof(L, R) {
  if (typeof L !== "object") {
    return false;
  }
  while (true) {
    if (L === null) {
      return false;
    }
    if (L.__proto__ === R.prototype) {
      return true;
    }
    L = L.__proto__;
  }
}

myInstanceof("", String);
myInstanceof([], Object);

call

fn.call(target, args1, args2...)

主要思路:

  • myCall 方法需绑定到 Function.prototype 原型上
  • 将 fn 作为 target 的临时属性去执行,执行后删除该属性,返回执行结果
js
Function.prototype.myCall(context = window, ...args) {
  if (this === Function.prototype) {
    return undefined // 避免 Function.prototype.myCall() 直接调用
  }
  let fn = Symbol()
  context[fn] = this
  let result = context[fn](...args)
  delete context[fn]
  return result
}

apply

fn.apply(target, [args1, args2...]) 主要思路:

  • 同 call 区别在于传参
js
Function.prototype.myApply(context = window, args) {
  if (this === Function.prototype) {
    return undefined // 避免 Function.prototype.myCall() 直接调用
  }
  let fn = Symbol()
  context[fn] = this
  let result = Array.isArray(args) ? context[fn](...args) : context[fn]()
  delete context[fn]
  return result
}

bind

let newFn = fn.bind(obj, arg1, arg2...)

主要思路:

  • 判断函数是否正常调用
  • 柯理化的实现,内外参数的合并
  • 兼容构造函数,当 bind 返回的函数被当作构造函数搭配 new 关键字使用, 手动绑带的 this 会被忽略
js
Function.prototype.myBind(context = window) {
  const fn = this
  if (typeof fn !== 'function'){
    throw new TypeError('Function.prototype.bind called error')
  }
  const outArgs = Array.prototype.slice.call(arguments, 1)
  function F(){}
  F.prototype = this.prototype
  const bound = function(){
    const innerArgs = Array.prototype.slice.call(arguments)
    const args = outArgs.concat(innerArgs)
    return fn.apply(this instanceof F ? this : context, args)
  }
  bound.prototype = new F()
  return bound
}

new

主要思路

    1. 创建空对象作为返回的实例
    1. 绑定空对象原型
    1. 执行构造函数,指定 this 为空对象,保存返回结果
    1. 判断返回结果类型是否为对象
js
function newFun(...args) {
  const constructor = args.shift();
  let obj = Object.create(constructor.prototype);
  const result = constructor.apply(obj, args);
  return result && typeof result === "object" ? result : obj;
}

数组相关

  • map
js
Array.prototype.myMap = function (executerFn) {
  const arr = this;
  if (!Array.isArray(arr)) {
    throw new TypeError("the caller must be array");
  }
  let result = [];
  for (let i = 0; i < arr.length; ++i) {
    result.push(executerFn(arr[i], i, arr));
  }
  return result;
};
  • reduce

  • flat

js
Array.prototype.myFlat = function (deep = 1) {
  let arr = this;
  if (!Array.isArray(arr)) {
    throw new TypeError("the caller must be Array");
  }
  if (deep < 1) return arr;
  return arr.reduce((result, cur) => {
    return result.concat(Array.isArray(cur) ? cur.myFlat(deep - 1) : cur);
  }, []);
};
let a = [1, 2, [3, [4, [5, 6]]]];
let b = a.myFlat(2); // [ 1, 2, 3, 4, [ 5, 6 ] ]
  • 去重

filter + indexOf

js
function unique(arr) {
  return arr.filter((item, index) => arr.indexOf(item) === index);
}

set

js
function unique(arr) {
  return [...new Set(arr)];
}

debounce 防抖

debounce(fn, time): 触发事件 n 秒之内不再触发新事件,才会去执行 fn

应用场景:

  • 搜索框,输入后等待 1000ms 后才去搜索
  • 窗口变化,1000ms 后再去调整样式

主要思路:

  • 返回一个闭包,
  • 添加一个标识,判断 fn 是否需要立即执行
js
let timer;
function debounce(fn, time, flag = false) {
  let timer;
  return function (...args) {
    if (timer) {
      clearTimeout(timer);
    }
    if (flag && !timer) {
      fn.apply(this, args); // 立即执行一次
    }
    timer = setTimeout(() => {
      timer = null
      !flag && fn.apply(this, args);
    }, time);
  };
}

throttle 节流

throttle(fn, time): 无论事件的执行频次,fn 总是在单位时间内执行

可以想象成:无论早高峰还是晚高峰,公交车都是固定班次出发的

主要思路:

  • 返回一个闭包
js
function throttle(fn, time) {
  let timer = null;
  let pre = 0;
  return function (...args) {
    if (!timer) {
      timer = setTimeout(() => {
        timer = null;
        fn.apply(this, args);
      }, time);
    }
  };
}

deepClone

常见的深拷贝

js
JSON.parse(JSON.stringify(obj));

缺陷:JSON 的限制

  • 会自动忽略值为 undefined, Symbol, function 的属性
  • Date 日期会自动转化成字符串
  • RegExp 正则自动转成空对象 {}
  • NaN, Infinity 会被当初 null 处理

递归版

主要思路:

  • 1、普通数据类型, null 等直接返回
  • 2、Date, RegExp 等直接返回新实例
  • 3、使用 WeakMap 解决对象自身嵌套引用的问题

    ps: 当使用 WeakMap 时,hashobj 之间就是弱引用关系,不影响垃圾回收机制的执行

  • 4、性能优化,当数据量大时 for...in...循环效率低,可替换成 for, while循环等
js
function deepClone(obj, hash = new WeakMap()) {
  // 基本类型,null 直接返回
  if (typeof obj !== "object" || obj === null) return obj;
  // 日期,正则
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  // 解决嵌套循环
  if (hash.has(obj)) return hash.get(obj);
  let target = new obj.constructor();
  hash.set(obj, target);
  // for...in... 效率低
  // for(let key in obj){
  //   if(obj.hasOwnProperty(key)){
  //     target[key] = deepClone(obj[key])
  //   }
  // }
  const isArray = Array.isArray(obj);
  const keys = isArray ? obj : Object.keys(obj);
  for (let i = 0; i < keys.length; ++i) {
    const key = isArray ? i : keys[i];
    target[key] = deepClone(obj[key]);
  }

  return target;
}

let x = {
  a: 1,
  b: undefined,
  c: new Date("1996/01/01"),
  d: NaN,
  e: function () {
    console.log("hello");
  },
  f: Symbol("123"),
  g: /(0-9)/,
  h: { name: "1", value: "2" },
  i: [1, 2, 3, 4],
};

let y = deepClone(x);
console.log(y);

Object.is

Object.is(value1, value2): 判断是否是同一个值

主要思路:

  • 需要区分与 === 运算符在处理 NaN+0,-0 等值区别:
js
Object.is(NaN, NaN); // true
Object.is(+0, -0); // false

代码实现:

js
function isSame(a, b) {
  if (a === b) {
    // 需要区分 +0,-0
    // a !== 0, 直接返回 true
    // a === 0, 就需要判断  +0,-0, 使用 1/+0 === Infinity 和 1/-0 === -Infinity来进行判断
    return a !== 0 || 1 / a === 1 / b;
  }
  // 当 a,b 都是 NaN 的时候返回 true
  return a !== a && b !== b;
}
isSame(NaN, NaN); // true
isSame(+0, -0); // false

浅拷贝 Object.assign

TIP

Object.assign(target, ...sources): 从一个或多个源对象sources上复制可枚举属性的值到目标对象target

主要思路

  • 1、判断原生 Object 是否支持该函数, 没有就使用 Object.defineProperty 将该函数绑定到 Object
  • 2、校验参数,目标对象 target 不能为 null, undefined
  • 3、当目标对象 target 为基本类型,需要转换成对象
  • 4、使用 for...in... 遍历源对象上的可枚举属性到目标对象上
js
if (typeof Object.myAssign !== "function") {
  // 1,默认 Object 上的属性是不可枚举的,直接往 Object 上添加属性是可枚举的,所以需要通过
  // Object.defineProperty 进行设置
  ("use strict");
  Object.defineProperty(Object, "myAssign", {
    writeable: true,
    configurable: true,
    value: function (target) {
      if (target == null) {
        // 2
        throw TypeError("Cannot convert undefined or null to object");
      }
      let to = Object(target); // 3
      for (let i = 1; i < arguments.length; ++i) {
        const source = arguments[i];
        if (source != null) {
          for (let key in source) {
            // 没直接调用 source.hasOwnProperty ,是因为 source 对象可能是通过
            // Object.create() 创造的,原型没关联到 Object.prototype 对象上,不存在该方法
            if (Object.prototype.hasOwnProperty.call(source, key)) {
              to[key] = source[key];
            }
          }
        }
      }
      return to;
    },
  });
}

Object.create

EventEmit 发布订阅

主要思路

  • EventEmit 作为一个类,其实例上存在 on,off,emit 等方法
js
class EventEmit{
  constructor(){
    this.cache = {}
  },
  on(name, fn){
    if(!this.cache[name]){
      this.cache[name] = []
    }
    this.cache[name].push(fn)
  },
  off(name, fn){
    const tasks = this.cache[name]
    if(tasks){
      const idx = tasks.findIndex(it => it === fn)
      if (idx >= 0) {
        tasks.splice(idx, 1)
      }
    }
  },
  emit(name, once = false, ...args){
     if (this.cache[name]) {
      // 创建副本,如果回调函数内继续注册相同事件,会造成死循环
      let tasks = this.cache[name].slice()
      for (let fn of tasks) {
        fn(...args)
      }
      if (once) {
        delete this.cache[name]
      }
    }
  },
}

解析 URL

JSONP

主要思路

  • 封装 jsonp 函数,传入 url,data,callback 等参数
  • 拼接 url,
  • 动态创建 script 标签并插入页面中
  • 挂载回调函数
js
(function (window, document) {
  const jsonp = (url, data, callback) => {
    // 1. 拼接url,处理 data
    let dataString = url.indexOf("?") === -1 ? "?" : "";
    for (let key in data) {
      dataString += `${key}=${data[key]}&`;
    }
    // 2.创建自定义 cb
    const cbFuncName = `json_cb_${Math.random().toString().replace(".", "")}`;
    dataString += `callback=${cbFuncName}`;

    // 3.创建 script 标签
    const scriptEl = document.createElement("script");
    scriptEl.src = url + dataString;

    // 4.挂载回调函数
    window[cbFuncName] = function (data) {
      callback(data);
      document.body.removeChild(scriptEl);
    };
    document.body.appendChild(scriptEl);
  };
  window.$jsonp = jsonp;
})(window, document);

Ajax

Promise