1. 确定输入输出
第一个参数是this, 第二个及以后的参数是函数参数
返回值是一个新的函数
function bind(asThis, p1, p2){
return function(){}
}
export default bind
如果当前环境下,函数的原型上没有bind, 就绑定上去
if(!Function.prototype.bind) {
Function.prototype.bind = bind
}
2.返回一个绑定this的函数
实现思路:返回一个函数,在这个函数里,给调用者函数传递this, 绑定参数,然后返回调用者函数
测试用例:
const bind = require("../src")
Function.prototype.bind2 = bind // 绑定自己写的bind
const fn1 = function () {
return this
}
const newFn1 = fn1.bind2({'name': 'lisa'}) // newFn1 是一个返回this的新函数
console.assert(newFn1().name === 'lisa')
最简单的实现:
function bind(asThis) {
const fn = this
return function () {
return fn.call(asThis)
}
}
module.exports = bind
3.可以传this和其他参数
测试用例:
fn2 接受两个参数p1, p2, 并且返回[this, p1, p2]
const fn2 = function (p1, p2) {
return [this, p1, p2]
}
const newFn2 = fn2.bind({'name':'lisa'}, 123, 456)
console.assert(newFn2()[0].name === 'lisa')
console.assert(newFn2()[1] === 123)
console.assert(newFn2()[2] === 456)
解法很简单,只需要读取函数参数即可
function bind(asThis, ...args) {
const fn = this
return function (...args2) {
return fn.call(asThis, ...args, ...args2)
}
}
4.更加兼容的写法
一个不支持Bind浏览器,肯定也不会支持const和...扩展符
我们需要用var 代替 const
用slice 代替 ...
最后用apply 来代替 return fn.call(asThis, ...args, ...args2)。因为apply才能接受参数是数组
var slice = Array.prototype.splice
function bind(asThis) {
var fn = this
// 因为arguments 不是数组,没有slice方法,所以用slice.call(arguments)
var args = slice.call(arguments, 1) // 获取除了第0个参数this,之外的所有其他参数
if (typeof fn !== "function") {
throw new Error("bind必须调用在函数上")
}
return function () {
var args2 = slice.call(arguments, 0) // 获取所有参数
// 用apply传数组,合并两次的参数数组,用apply传
return fn.apply(asThis, args.concat(args2))
}
}
5.支持new
5.1 new的实现
首先来看看new的用法:
new fn1('x')之后得到了一个对象,{a:'x'}
new的实现有四步
new fn1('x') //等价于下面四句话
function createNew(fn, ...args) {
var temp = {} // 创建临时对象
temp.__proto__ = fn.prototype // 给临时对象绑定原型
fn.call(temp, ...args) // 把临时对象作为this,调用函数,并传递参数
return temp // 返回这个临时对象
}
5.2 原版Bind里关于new的用法
var fn1 = function(a) {
this.a = a
}
var fn2 = fn1.bind(undefined, 'y')
new fn2() // 得到 {a:'y '}
实现可以使用new的关键在于,new会重新改变this, 会重新覆盖掉之前通过bind绑定的this
所以关键点之一就是:判断是否使用了new, 如果使用了new ,就用new的新this, 如果没有用new, 就用之前绑定的this
怎么判断是否用了new?
resultFn.prototype.isPrototypeOf(this);- 或者
this instanceof resultFn
并且一般来说let o2 = new fn2() 之后,o2.__proto__=fn2.prototype 即o2是fn2构造出来的,
- 即
o2 instanceof fn2o2是fn2的一个实例 - 即
fn2.prototype.isPrototypeOf(o2)
但是我们希望 o2.__proto__=fn1.prototype 而不是fn2的原型,所以关键点之二就是重新绑定原型
5.3 最后实现
function _bind(asThis, ...args) {
// this 就是函数
var fn = this;
function resultFn(...args2) {
// resultFn.prototype.isPrototypeOf(this); 也可以用这句话判断是否用了new
// 判断是否用了new, 对this做不同处理
return fn.call(this instanceof resultFn ? this : asThis, ...args, ...args2);
}
// 重新绑定原型
resultFn.prototype = fn.prototype;
return resultFn;
}
5.4 更兼容的实现
var slice = Array.prototype.slice;
function bind(asThis) {
// this 就是函数
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;
}
5.5 测试用例
function test6(message) {
// new 的时候绑定了 p1, p2,并且 fn 有 prototype.sayHi
Function.prototype.bind2 = bind;
const fn = function(p1, p2) {
this.p1 = p1;
this.p2 = p2;
};
fn.prototype.sayHi = function() {};
const fn2 = fn.bind2(undefined, "x", "y");
const object = new fn2();
console.assert(object.p1 === "x", "x");
console.assert(object.p2 === "y", "y");
// console.assert(object.__proto__ === fn.prototype);
console.assert(fn.prototype.isPrototypeOf(object));
console.assert(typeof object.sayHi === "function");
}
本文为fjl的原创文章,著作权归本人和饥人谷所有,转载务必注明来源