手写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]
实现
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));
};
}
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;
}