前言
如果不了解bind的使用的请前往 Function.prototype.bind,这里不在赘述。
想要看bind
原理的童鞋,需要对以下知识有了解:
思路
bind
方法不会立即执行函数,而且需要保存一个入参,所以这里利用到了 闭包- 改变函数的作用域,因此要利用
apply
或者call
方法来实现 - 获取函数的所有入参
arguments
,以及使用slice
转化出一个数组
先利用上面的思路实现一个大致雏形的实现代码:
Function.prototype.mybind = function () {
if (typeof this !== 'function') throw 'Bind must be called on a function'
let _this = this, //这里的this是原函数
context = arguments[0],//获取要this指向的对象
slice = Array.prototype.slice,
args = slice.call(arguments, 1);//获取bind函数除this指向对象外的所有参数
//返回函数
return function () {
args = args.concat(slice.call(arguments))//合并bind的入参和执行时的入参
return _this.apply(context, args)
}
}
此时我们可以测试一下:
function test(a, b) {
console.log('this.name', this.name)
console.log('a', a)
console.log('b', b)
return str
}
const henry = {
name: 'henry',
}
// test.bind(henry, 1)(2, 3)
test.mybind(henry, 1)(2, 3)
好了,到此我们实现了bind
的基础用法,但是还没完,假如我们用bind
返回的函数当构造函数new
一个对象会怎样呢?
原型链
官方文档 上有一段话:
A bound function may also be constructed using the new operator: doing so acts as though the target function had instead been constructed. The provided this value is ignored, while prepended arguments are provided to the emulated function.
意思就是说绑定函数也可以使用new
操作符创建实例,这样做的行为就像是构造了目标函数一样,提供的this
将被忽略,而前置参数还会参与使用。
- 我们要让实例的构造函数是目标函数
- 检测绑定函数的
this
是否是目标函数的实例,是的话则忽略绑定时提供的this
...
context = this instanceof _this ? this : context
...
fn.prototype = _this.prototype
完整的代码是:
Function.prototype.mybind = function () {
if (typeof this !== 'function') throw 'Bind must be called on a function'
let _this = this,
context = arguments[0],//获取要this指向的对象
slice = Array.prototype.slice,
args = slice.call(arguments, 1);//获取bind函数除this指向对象外的所有参数
//返回函数
const fn = function () {
args = args.concat(slice.call(arguments))//合并bind的入参和执行时的入参
context = this instanceof _this ? this : context //如果this指向的对象的构造函数是原函数,则说明this是new过程中产生的对象,此时忽略绑定时提供的context
return _this.apply(context, args)
}
fn.prototype = _this.prototype
return fn
}
测试一下:
function test() {}
const testBind = test.mybind()
const tb = new testBind()
console.log('tb', tb.constructor) //tb ƒ test() {}
好像到这来都没问题了,那我们来看看下面代码执行的结果
function test() { }
const testBind = test.mybind()
const tb = new testBind()
testBind.prototype.say = function () {
console.log('say testBind')
}
test.prototype.say = function () {
console.log('say test')
}
tb.say() //say test
这不符合bind
使用new的结果,结果应该是say testBind
才对,这是因为绑定函数和构造函数的原型对象是同一个,所以我们这里应该用Object.create
优化一下,完整代码如下:
Function.prototype.mybind = function () {
if (typeof this !== 'function') throw 'Bind must be called on a function'
let _this = this,
context = arguments[0],//获取要this指向的对象
slice = Array.prototype.slice,
args = slice.call(arguments, 1);//获取bind函数除this指向对象外的所有参数
//返回函数
const fn = function () {
args = args.concat(slice.call(arguments))//合并bind的入参和执行时的入参
context = this instanceof _this ? this : context //如果this指向的对象的构造函数是原函数,则说明this是new过程中产生的对象,此时忽略绑定时提供的context
return _this.apply(context, args)
}
fn.prototype = Object.create(_this.prototype)
return fn
}