前端er必会手写代码原理

694 阅读6分钟

JavaScript 基本数据类型

// 7个基本类型 其中es6新增symbol ES2020引入了大整数BigInt
boolean number string null undefined symbol BigInt

// 注意:对象 (object)也是 js 内置类型,非基本类型

instanceof 实现原理

用代码实现 如果 A 沿着原型链能找到 B.prototype 那么A instanceof B 为 true 遍历 A 的原型链 找到 B.prototype 返回 true

const instanceof = (A, B) => {
  let p = A;
  while (p) {
    if (p === B.prototype) {
      return true;
    }
    p = p.__proto__;
  }
  return false;
};

手写 call

Function.prototype.newCall = function(context) {
  context = Object(context) || window;
  context.fn = this;
  let result = "";
  const args = [...arguments].slice(1);
  result = context.fn(...args);
  delete context.fn;
  return result;
};

手写 apply

Function.prototype.newApply = function(context, args) {
  context = Object(context) || window;
  context.fn = this;
  let result;
  if (args) {
    result = context.fn(...args);
  } else {
    result = context.fn();
  }
  delete context.fn;
  return result;
};

手写 bind

bind 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数

Function.prototype.newBind = function(context) {
  if (typeof this !== "function") {
    throw new Error("不是一个函数");
  }
  const self = this;
  const args1 = [...arguments].slice(1);
  return function() {
    // 函数科里化
    const args2 = [...arguments];
    self.apply(context, args1.concat(args2));
  };
};

new 实现原理

function Animal(type) {
  this.type = type;
}
Animal.prototype.say = function() {
  console.log("say");
};

function mockNew(Constructor, ...args) {
  // let Constructor = arguments.shift(); // 取出构造函数
  // new 执行会创建一个新对象
  let obj = {};
  obj.__proto__ = Object.create(Constructor.prototype);
  Constructor.apply(obj, args);
  return obj;
}

let animal = mockNew(Animal, "dog");
console.log(animal.type); // dog

手写 Promise 简版

符合 A+规范

function myPromise(constructor) {
  let self = this;
  self.status = "pending";
  self.resolveVal = undefined;
  self.rejectedVal = undefined;
  function resolve(value) {
    if (self.status === "pending") {
      self.resolveVal = value;
      self.status = "resolved";
    }
  }
  function reject(reason) {
    if (self.status === "pending") {
      self.rejectedVal = reason;
      self.status = "rejected";
    }
  }
  try {
    constructor(resolve, reject);
  } catch (e) {
    reject(e);
  }
}

myPromise.prototype.then = function(onFullfilled, onRejected) {
  switch (this.status) {
    case "resolved":
      onFullfilled(this.resolveVal);
      break;
    case "rejected":
      onRejected(this.rejectedVal);
      break;
  }
};

手写 Promise (考虑异步)

const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';

function myPromise(fn) {
  const self = this; // 在function找到正确this指向
  self.status = PENDING;
  self.value = null; // 保存resolve/reject传入的值
  self.resolvedCallbacks = []; // 保存then中的回调,promise执行完后,state状态还可能是padding
  self.rejectedCallbacks = []; // 因此把回到函数保存起来,等待state状态改变时执行
  // 实现resolve
  funtion resolve(value) {
    if(value instanceof myPromise) {
      return value.then(resolve, reject)
    }
    setTimeout(()=> {
      if(self.status === PENDING) {
        self.status = RESOLVED;
        self.value = value;
        self.resolvedCallbacks.forEach(cb => cb(self.value)) // 执行回调
      }
    },0);
  }
  function reject(value) {
     setTimeout(()=> {
      if(self.status === PENDING) {
        self.status = REJECTED;
        self.value = value;
        self.rejectedCallbacks.forEach(cb => cb(self.value)) // 执行回调
      }
    },0);
  }
  // 执行函数fn
  try {
    fn(resolve,reject);
  } catch(e) {
    reject(e)
  }
}

myPromise.prototype.then = function(onFulfilled, onRejected) {
  const self = this;
  // 参数为可选参数 判断下
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
  onRejected = typeof onRejected === 'function' ? onRejected : v => {throw v};
  // 如果状态还是padding, 往回掉函数中push函数, 否则执行函数
  if(self.status === PENDING) {
    self.resolvedCallbacks.push(onFulfilled);
    self.rejectedCallbacks.push(onRejected);
  }
  if(self.status === RESOLVED) {
    onFulfilled(self.value);
  }
  if(self.status === REJECTED) {
    onRejected(self.value);
  }
}

手写 promise.all

将多个 Promise 实例包装成一个 Promise 实例,接收包含 Promise 对象或普通值的数组(或其它可迭代对象)作为参数,成功时返回值为 Promise 实例数组对应的结果数组,失败时返回第一个 rejected 的 Promise 实例的结果。

const p1 = new Promise((resolve) => {
  setTimeout(resolve, 200, 1);
});
const p2 = new Promise((resolve) => {
  resolve(2);
});
const p3 = 3;
console.log(Promise.all([p1, p2, p3])); // [1, 2, 3]

Promise.prototype.all = function(promiseArr) {
  let resArr = [];
  let count = 0;
  let len = promiseArr.length;
  // 返回一个新的promise实例
  return new Promise(function(resolve, reject) {
    for (let promise of promiseArr) {
      Promise.resolve(promise).then(
        function(res) {
          resArr[count] = res;
          count++;
          if (count === len) {
            return resolve(resArr);
          }
        },
        function(err) {
          return reject(err);
        }
      );
    }
  });
};

手写 promise.race

与 Promise.all 一样,Promise.race 也接收包含 Promise 对象或普通值的数组(或其它可迭代对象)作为参数,返回一个 Promise 实例对象。与 Promise.all 不同的是,一旦有一个 Promise 实例对象 resolve ,立即把这个 resolve 的值作为 Promise.race resolve 的值。一旦有一个对象 reject, Promise.race 也会立即 reject。

Promise.prototype.race = function(promiseArr) {
  return new Promise(function(resolve, reject) {
    for (let promise of promiseArr) {
      if (typeof promise === "object" && typeof promise.then === "function") {
        promise.then(resolve, reject);
      } else {
        // 不是promise实例对象直接返回
        resolve(promise);
      }
    }
  });
};

手写防抖

非立即执行

作用是在短时间内多次触发同一个函数,只执行最后一次,或者只在开始时执行。 1.非立即执行:,如果你在一个事件触发的 n 秒内又触发了这个事件,那我就以新的事件的时间为准,n 秒后才执行

function debounce(fn, delay) {
  let timeout;
  return function() {
    let context = this;
    let args = arguments;
    clearTimeout(timeout);
    timeout = setTimeout(() => {
      fn.apply(context, args);
    }, delay);
  };
}
立即执行

我不希望非要等到事件停止触发后才执行,我希望立刻执行函数,然后等到停止触发 n 秒后,才可以重新触发执行, 我们也可以为 debounce 函数加一个参数,可以选择是否立即执行函数

function debounce(fn, delay, immediate) {
  let timeout = null;
  return function() {
    let context = this;
    let args = arguments;
    if (timeout) {
      clearTimeout(timeout);
    }
    if (immediate) {
      const callNow = !timeout;
      timeout = setTimeout(() => {
        timeout = null;
      }, delay);

      if (callNow) {
        fn.apply(context, args);
      }
    } else {
      timeout = setTimeout(() => {
        fn.apply(context, args);
      }, delay);
    }
  };
}

手写节流

持续触发事件,每隔一段时间,只执行一次事件。

function throttle(func, delay) {
  var prev = Date.now();
  return function() {
    var context = this;
    var args = arguments;
    var now = Date.now();
    if (now - prev >= delay) {
      func.apply(context, args);
      prev = Date.now();
    }
  };
}

// 定时器实现
var throttle = function(func, delay) {
  let timer = null;
  return function() {
    const context = this;
    const args = arguments;
    if (!timer) {
      timer = setTimeout(function() {
        func.apply(context, args);
        timer = null;
      }, delay);
    }
  };
};

手写浅拷贝

function copy(obj) {
  if (typeof obj !== "object" || obj === null) {
    return obj;
  }
  let obj1 = {};
  if (obj.constructor === Array) {
    obj1 = [];
  }
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      obj1[key] = obj[key];
    }
  }
  return obj1;
}

手写深拷贝

该实现有可能引起爆栈,可考虑使用 weakMap 代替,应付面试足矣。

function deepClone(obj) {
  // 如果是值类型或 null,则直接return
  if (typeof obj !== "object" || obj === null) {
    return obj;
  }
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);

  // 定义结果对象
  let copy = {};
  // 如果对象是数组,则定义结果数组
  if (obj.constructor === Array) {
    copy = [];
  }
  // 遍历对象的key
  for (let key in obj) {
    // 如果key是对象的自有属性
    if (obj.hasOwnProperty(key)) {
      // 递归调用深拷贝方法
      copy[key] = deepClone(obj[key]);
    }
  }
  return copy;
}

// weakMap
function deepClone(obj, hash = new WeakMap()) {
  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.get(obj)) return hash.get(obj);

  let cloneObj = new obj.constructor();
  hash.set(obj, cloneObj);
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      cloneObj[key] = deepClone(obj[key], hash);
    }
  }
  return cloneObj;
}

手写 ajax

(1)创建 XMLHttpRequest 对象,也就是创建一个异步调用对象. (2)创建一个新的 HTTP 请求,并指定该 HTTP 请求的方法、URL 及验证信息. (3)设置响应 HTTP 请求状态变化的函数. (4)发送 HTTP 请求. (5)获取异步调用返回的数据. (6)使用 JavaScript 和 DOM 实现局部刷新.

// 步骤
var XHR = new XMLHttpRequest();
XHR.open("get", "./a.php");
XHR.send(null);
XHR.onreadystatechange = function() {
  if (XHR.readyState === 4) {
    if (XHR.status === 200) {
      alert(XHR.responseText);
    }
  }
};

// 封装
function ajax(url, method, body, success, fail) {
  let request = new XMLHttpRequest();
  request.open(method, url);
  request.onreadystatechange = () => {
    if (request.readyState === 4) {
      if (request.status >= 200 && request.status < 300) {
        success.call(undefined, request.responseText);
      } else if (request.status >= 400) {
        fail.call(undefined, request);
      }
    }
  };
  request.send(body);
}

手写 Promise 版 ajax

window.ajax = function({ url, method }) {
  return new Promise(function(resolve, reject) {
    let request = new XMLHttpRequest();
    request.open(method, url);
    request.onreadystatechange = () => {
      if (request.readyState === 4) {
        if (request.status >= 200 && request.status < 300) {
          resolve.call(undefined, request.responseText);
        } else if (request.status >= 400) {
          reject.call(undefined, request);
        }
      }
    };
    request.send();
  });
};

es5 实现继承

// 使用寄生组合继承
function Person(name, like) {
  this.name = name;
  this.like = like;
  this.money = 10000;
}
Person.prototype.say = function() {
  console.log(this.name);
};

function Person1(name, like) {
  Person.call(this, name, like);
}
Person1.prototype = Object.create(Person.prototype);

let p = new Person1("明", "篮球");
p.say(); // 明

es6 实现继承

class Father {
  constructor(name, like) {
    this.name = name;
    this.like = like;
  }
  say() {
    console.log(this.name);
  }
}

class Children extends Father {
  constructor(name, like) {
    super(name, like);
  }
}
let p = new Children("C罗", "足球");
p.say(); // C罗

TypeScript 中内置方法实现原理

// Partial实现原理
type Partial<T> = { [P in keyof T]?: T[P] };

// Required实现原理
type Required<T> = { [P in keyof T]-?: T[P] };

// Pick实现原理
type Pick<T, K extends keyof T> = { [P in K]: T[P] };

// Exclude实现原理
type Exclude<T, U> = T extends U ? never : T;

// Omit实现原理
type Omit = Pick<T, Exclude<keyof T, K>>

实现一个函数组合 compose

const a1 = (x) => x + 1;
const d2 = (x) => x / 2;
const m3 = (x) => x * 3;
d2(m3(a1(a1(0)))); // => 3

// 而这样的写法可读性明显太差了,我们可以构建一个compose函数,它接受任意多个函数作为参数(这些函数都只接受一个参数),然后compose返回的也是一个函数,达到以下的效果:
const calcNum = compose(
  d2,
  m3,
  a1,
  a1
);
calcNum(0); // => d2(m3(a1(a(0))))

function compose() {
  let args = argumnets;
  let start = args.length - 1;
  return function() {
    let i = start;
    let result = args[i].apply(this, arguments);
    while (i-- && i >= 0) {
      result = args[i].call(this, result);
    }
    return result;
  };
}