手写代码

167 阅读5分钟

1.call 实现

1.call 改变 this 指向

2.传参要解析传过去

var foo = {
  value: 1
};
var value = 2;
function bar() {
  console.log(this.value);
}
// bar._call(foo);

在这里要 console.log要打印出来 1 来。就是说 foo 里面有个 bar 方法执行。

var foo = {
  value: 1,
  bar: bar()
};

怎么写呢

bar._call(foo, 'aa', 'bb');
Function.prototype._call = function () {
  // 首先要解析传入的值
  // 改变this 的指向 到call的函数也就是foo
  // foo上面新加一个属性就是bar这个方法
  ctx = arguments[0] || window;
  ctx.fn = this;
  ctx.fn();
  delete ctx.fn;
};
Function.prototype._call2 = function (ctx, ...args) {
  // 首先要解析传入的值
  // 改变this 的指向 到call的函数也就是foo
  // foo上面新加一个属性就是bar这个方法
  var fn = Symbol();
  ctx = ctx || window;
  ctx[fn] = this;
  let result = ctx[fn](...args);
  delete ctx[fn];
  return result;
};

2.apply 的实现

与 call 实现不同,apply 传入的参数是个数组

Function.prototype._apply = function (ctx, arr) {
  var ctx = ctx || window;
  var fn = Symbol();
  ctx[fn] = this;
  var result;
  if (!arr) {
    result = ctx[fn]();
  } else {
    result = ctx[fn](...arr);
  }
  delete ctx[fn];
  return result;
};

3.bind 的实现

多次 bind 指向的还是第一个绑定的 this

bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。(来自于 MDN )

1.返回一个函数

2.可以传入参数

Function.prototype._bind = function (context) {
  var self = this;
  var args = Array.prototype.slice.call(arguments, 1);
  return function () {
    var bindArgs = Array.prototype.slice.call(arguments);
    self.apply(context, args.concat(bindArgs));
  };
};

// 第三版
Function.prototype.bind2 = function (context) {
  var self = this;
  var args = Array.prototype.slice.call(arguments, 1);

  var fbound = function () {
    var bindArgs = Array.prototype.slice.call(arguments);
    // 当作为构造函数时,this 指向实例,self 指向绑定函数,因为下面一句 `fbound.prototype = this.prototype;`,已经修改了 fbound.prototype 为 绑定函数的 prototype,此时结果为 true,当结果为 true 的时候,this 指向实例。
    // 当作为普通函数时,this 指向 window,self 指向绑定函数,此时结果为 false,当结果为 false 的时候,this 指向绑定的 context。
    self.apply(this instanceof self ? this : context, args.concat(bindArgs));
  };
  // 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承函数的原型中的值
  fbound.prototype = Object.create(self.prototype);
  return fbound;
};
bind = function (context) {
  var self = this;
  var args = [].slice.call(arguments, 1);
  var found = function () {
    var bindArgs = [].slice.call(arguments);
    self.apply(context, args.concat(bindArgs));
  };
};
apply = function (ctx, ...args) {
  ctx = ctx || window;
  const fn = Symbol();
  // 把函数挂载到对象上面去
  ctx[fn] = this;
  // 执行函数
  let result = ctx[fn](...args);
  delete ctx[fn];
  return result;
};
一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。

4.new 的实现

  • 返回一个新的对象,
  • 把实例的原型指向构造函数的原型
  • this 指向实例本身
// 将构造函数以参数形式传入
function New(func) {
  // 声明一个中间对象,该对象为最终返回的实例
  var res = {};
  if (func.prototype !== null) {
    // 把实例的原型指向构造函数的原型
    res.__proto__ = func.prototype;
  }
  // ret 是构造函数执行的结果,也就是构造函数里面return出来的
  // 将构造函数的this指向为实例对象 res 的this
  const ret = func.apply(res, [].slice.call(arguments, 1));
  if ((typeof ret === 'object' || typeof ret === 'function') && ret !== null) {
    return ret;
  }
  return res;
}

function Person(name, age) {
  this.name = name;
  this.strength = 60;
  this.age = age;
  return {
    name: name,
    time: 80
  };
  return 'aaa';
}
var student = New(Person, 'tom');

如果构造函数返回的是一个对象或者函数,在实例中 student 中就只能访问返回的对象中的属性。

如果返回的是一个字符串,相当于没有返回值

5.instanceof 实现

function _instanceof(left, right) {
  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 = proto.prototype;
  }
}

6.Promise 实现

class MyPromise {
  constructor(executor) {
    this.thencallback = undefined;
    this.rejectcallback = undefined;
    this._value = undefined;
    this._status = 'pending';
    executor(this._resolve.bind(this), this._reject.bind(this));
  }
  _resolve(value) {
    setTimeout(() => {
      this.thencallback(value);
    });
  }
  _reject(value) {
    setTimeout(() => {
      this.rejectcallback(value);
    });
  }
  then(then_cb, onRejected) {
    this.thencallback = then_cb;
    this.rejectcallback = onRejected;
  }
  catch(onrejected) {
    this.then(null, onrejected);
  }
}

Promise.resolve = function (value) {
  if (value instanceof Promise) return value;
  // 如果是thenable
  if (value && value.then && isFunction(value.then)) return new Promise(value.then);
  return new Promise(resolve => resolve(value));
};
Promise.reject = function (value) {
  return new Promise((null, reject) => reject(value));
};
Promise.all = function (promises) {
  let i = 0;
  let returnList = [];
  return new Promise((resolve, reject) => {
    for (let i = 0; i < promises.length; i++) {
      // 如果有一个报错那么所有的.all也进入reject
      promises[i].then(data => {
        procssData(num, data);
      }, reject);
    }
    function procssData(num, data) {
      i++;
      // 数组的顺序要对应
      returnList[num] = data;
      // 如果标记的数字等于长度的话就返回
      if (i === promises.length) {
        resolve(returnList);
      }
    }
  });
};
Promise.race = function (promises) {
  return new Promise((resolve, reject) => {
    for (let i = 0; i < promises.length; i++) {
      promises[i].then(resolve, reject);
    }
  });
};

7.防抖、节流

防抖

事件在触发一段时间后才执行,如果在这时间段内又被触发,则重新计时

function debounce(fn, time = 300) {
  let timer = null;
  return function (...args) {
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, time);
  };
}
debounce(() => {
  console.log(11);
}, 1000);

节流

事件再一段时间内只会被出发一次,就像公交车 10 分钟一趟,适用于点击时间

function throttle(fn, time = 1000) {
  let last = 0;
  return function (...args) {
    let now = Date.now();
    if (now - last < time) return;
    last = now;
    fn.apply(this, args);
  };
}

合并优化

有时候长时间的防抖会导致一次都没有执行, 一段时间必须执行一次

function cover(fn, delay) {
  let timer = null;
  let last = 0;
  return function (...args) {
    let now = +new Date();
    if (now - last < delay) {
      if (timer) clearTimeout(timer);
      timer = setTimeout(() => {
        last = now;
        fn.apply(this, args);
      }, delay);
    } else {
      last = now;
      fn.apply(this, args);
// 每次触发都会清空上一个定时器,开始执行新的定时器

```js
function deounce(func, wait) {
  let timer;
  return function () {
    let arg = arguments;
    if (timer) clearTimeout(timer);
    timer = setTimeout(func.apply(this, arg), wait);
  };
}

8.深、浅拷贝

浅拷贝

function shallowCopy(obj) {
  if (typeof obj !== 'object') return obj;
  let newObj = obj.constructor === Array ? [] : {};
  for (key in obj) {
    if (obj.hasOwnProperty(key)) {
      newObj[key] = obj[key];
    }
  }
  return newObj;
}
// 深拷贝

9. for in \ for of

for of 循环用来获取一对键值对中的值,而 for in 获取的是 键名

一个数据结构只要部署了 Symbol.iterator 属性, 就被视为具有 iterator 接口, 就可以使用 for of 循环。

  • 数组 Array、 Map、 Set、 String、 arguments 对象、 Nodelist 对象,这些都可以用 for of 循环值

10.数组方法实现

map

arr.map((item, index) => {});
Array.prototype._map = function (fn, context) {
  var temp = [];
  if (typeof fn == 'function') {
    for (let i = 0; i < this.length; i++) {
      temp.push(fn.call(context, this[i], i, this));
    }
  } else {
    console.log('TypeError' + fn + 'is not a function');
  }
};

reduce

promise

// 简化版本

class Promise {
  constructor(executor) {
    this.thenCb = null;
    this.rejectCb = null;
    executor(this._resolve.bind(this), this._reject.bind(this));
  }
  _resolve(value) {
    setTimeout(() => {
      this.thenCb(value);
    }, 0);
  }
  _reject(value) {
    setTimeout(() => {
      this.rejectCb(value);
    }, 0);
  }
  then(onResolved, onRejected) {
    this.thenCb = onResolved;
    this.rejectCb = onRejected;
  }
  catch(onRejected) {
    this.then(null, onRejected);
  }
}