手写bind

手写bind

bind语法

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

function fn(...args) {
  console.log(this)  
  console.log(args)  
}

const newFn = fn.bind({name: 'sss'}, 1, 2)
newFn(3, 5)
//{name: 'sss'}
//[1, 2, 3, 5] 
image.png

实现

1.确定 api

fn.bind(newThis)
fn.bind(newThis, param1, param2)
fn.bind(newThis)()
fn.bind(newThis, param1, param2)()
fn.bind(newThis)(param1)
fn.bind(newThis, param1, param2)(p3, p4)

2.测试用例和功能实现

2.1 fn.bind(newThis) 和fn.bind(newThis)()
const fn1 = function () {
  return this;
};
const newFn1 = fn1.bind2({ name: 'sss' });
console.assert(newFn1().name === 'sss');
function bind(asThis) {
  const fn = this;
  return function () {
    return fn.call(asThis);
  };
}
2.2 fn.bind(asThis, param1, param2)和fn.bind(newThis, param1, param2)()
const fn2 = function (p1, p2) {
  return [this, p1, p2];
};
const newFn2 = fn2.bind2({ name: 'sss' }, 123, 456);
console.assert(newFn2()[0].name === 'sss');
console.assert(newFn2()[1] === 123);
console.assert(newFn2()[2] === 456);
function bind(asThis, ...args1) {
  const fn = this;
  return function () {
    return fn.call(asThis, ...args1);
  };
}
2.3 fn.bind(newThis)(param1)和fn.bind(newThis, param1, param2)(p3, p4)
const newFn2 = fn2.bind2({ name: 'sss' }, 123, 456);
console.assert(newFn2()[0].name === 'sss');
console.assert(newFn2()[1] === 123);
console.assert(newFn2()[2] === 456);

const anotherFn2 = fn2.bind2({ name: 'sss2' }, 123);
console.assert(anotherFn2(245)[0].name === 'sss2', 'this');
console.assert(anotherFn2(245)[1] === 123, 'p1');
console.assert(anotherFn2(245)[2] === 245, 'p2');
function bind(asThis, ...args1) {
  const fn = this;
  return function (...args2) {
    return fn.call(asThis, ...args1, ...args2);
  };
}

通过ES6的语法,我们很容易的实现了bind的功能,但是有个问题,bind是早于ES5的语法,所以我们不能直接的用ES6实现。

3 ES5实现bind

var slice = Array.prototype.slice;
function bind(asThis) {
  var args = slice.call(arguments, 1);
  var fn = this;
  if (typeof fn !== 'function') {
    throw new Error('bind 必须使用在函数上');
  }
  return function () {
    var args2 = slice.call(arguments, 0);
    return fn.apply(asThis, args.concat(args2));
  };
}
image.png

4. 支持new操作

4.1 new
const fn = function(a) {
    this.a = a
}
new fn('xx')
// fn {a: "xx"}

使用new操作符号,相当于以下的操作

let temp = {};
temp.__proto__ = fn.prototype;
fn.call(temp,'xxx')
return this
4.2测试用例和代码
const fn3 = function (p1, p2) {
  this.p1 = p1;
  this.p2 = p2;
};
const newFn3 = fn3.bind2(undefined, 'x', 'y');
const newObj = new newFn3();
console.assert(newObj.p1 === 'x');
console.assert(newObj.p2 === 'y');

此时很明显,之前的bind是无法通过该用例的,因为bind中并没有返回原来的this,newObj还是{}

4.2.1 返回this
function bind(asThis, ...args1) {
  const fn = this;
  return function resultFn (...args2) {
    return fn.call(this.__proto__ === resultFn.prototype?this:  asThis, ...args1, ...args2);
  };
}
4.2.2 原型链上的属性
const fn3 = function (p1, p2) {
  this.p1 = p1;
  this.p2 = p2;
};
fn3.prototype.sayHi = function () {};
const newFn3 = fn3.bind2(undefined, 'x', 'y');
const newObj = new newFn3();
console.assert(newObj.p1 === 'x');
console.assert(newObj.p2 === 'y');
console.assert(typeof newObj.sayHi === 'function');

测试用例此时不能正常通过

console.assert(newObj.__proto__ === fn3.prototype);
console.assert(fn3.prototype.isPrototypeOf(newObj)

此时newObj的proto应该等于fn3.prototype

function bind(asThis, ...args1) {
  const fn = this;
  function resultFn(...args2) {
    return fn.call(
      this.__proto__ === resultFn.prototype ? this : asThis,
      ...args1,
      ...args2
    );
  }
  resultFn.prototype = fn.prototype;
  return resultFn;
}
4.2.3 bind的对象不再是undefined
const fn3 = function (p1, p2) {
  this.p1 = p1;
  this.p2 = p2;
};
fn3.prototype.sayHi = function () {};
const object1 = new fn3('a', 'b');
const newFn3 = fn3.bind2(object1, 'x', 'y');
const newObj = newFn3();
console.assert(newObj === undefined, 'object 唯恐');
console.assert(object1.p1 === 'x');
console.assert(object1.p2 === 'y');

此时,我们的bind2也能顺利通过

4.2.4 proto并不是es官方的推荐做法

我们使用instanceof来代替proto

function bind(asThis, ...args1) {
  const fn = this;
  function resultFn(...args2) {
    return fn.call(
      this instanceof resultFn ? this : asThis,
      ...args1,
      ...args2
    );
  }
  resultFn.prototype = fn.prototype;
  return resultFn;
}
4.2.5 修改ES5的代码
var slice = Array.prototype.slice;
function bind(asThis) {
  var args = slice.call(arguments, 1);
  var fn = this;
  if (typeof fn !== 'function') {
    throw new Error('bind 必须使用在函数上');
  }
  function resultFn() {
    var args2 = slice.call(arguments, 0);
    return fn.apply(
      resultFn.prototype.isPrototypeOf(this) ? this : asThis,
      args.concat(args2)
    );
  }
  resultFn.prototype = fn.prototype;
  return resultFn;
}

你可能感兴趣的:(手写bind)