1. 利用call实现一个最基础的bind
var getName = function(){
console.log(this.name)
}
//call
getName.call({name:"Jan"}) //直接输出"Jan"
//bind
var f = getName.bind({name:"Jan"});
f(); //执行f才输出"Jan"
bind和call的区别 : bind返回的是函数,函数调用后才运行。而call直接运行。所以得到如下bind实现
//bind是给所有函数来调用的
Function.prototype.bind = function(obj){
var fn = this;
return function(){
//返回call的调用形式
return fn.call(obj)
}
}
2. 传递多个参数
bind和call一样,都可以传递多个额外参数, 这里我们可以用ES6的...args
,也可以用ES5的arguments
(通过Array.prototype.slice.call(arguments, 0)
将arguments
转换成普通数组)
//bind是给所有函数来调用的
Function.prototype.bind = function(obj, ...args){
var fn = this;
return function(){
//返回call的调用形式
return fn.call(obj, ...args)
}
}
但是bind除了支持
var getName = function(age, city){
console.log(this.name + "," + age + "," + city)
}
var f = getName.bind({name:"Jan"}, 11, "BeiJing");
f(); //Jan,11,BeiJing
还支持在f()
中传递多个参数(预设)
var f = getName.bind({name:"Jan"}, 11);
f("Beijing"); //Jan,11,BeiJing
所以我们要把bind函数改写成如下:
Function.prototype.bind = function(obj, ...args){
var fn = this;
return function(...others){
//返回call的调用形式
fn.call(obj, ...args, ...others)
}
}
3. 作为构造函数使用
可以用new
操作符创建一个由bind实现的绑定函数,如:
function Person(name) {
this.name = name
}
Person.prototype.getName = function(){
console.log(this.name)
}
var PersonBind = Person.bind({}, "Jan")
var p = new PersonBind();
p.getName() //Jan
console.log(p instanceof PersonBind) //true
console.log(p instanceof Person) //true
这是我们要返回的不能是一个简单的函数,而应该是一个继承了Person
属性和方法的对象。
首先我们先使用原型链的方式继承,原型链的写法为B.prototype = new A()
(B继承A),所以可以得到如下的bind写法
Function.prototype.bind = function(obj, ...args){
var fn = this;
var res = function(...others){
//返回call的调用形式
fn.call(obj, ...args, ...others)
}
res.prototype = new fn(...args); //通过原型链继承
return res
}
乍一看没什么问题,而且结果输出也都是正确的,但是当我们输出var p = new PersonBind();
中的p
,发现在使用了我们的bind后,浏览器输出是这样的
而原始的bind输出是这样的
原始的bind输出
bind要求我们只是继承方法,而传递的参数我们通过借用构造函数来继承,即通过组合继承来实现(原型链继承和组合继承的区别请参考js对象——继承(2))
使用组合继承实现如下
Function.prototype.bind = function(obj, ...args){
var fn = this;
var res = function(...others){
fn.call(this, ...args, ...others); //借用构造函数继承属性
}
res.prototype = fn.prototype; //原型链的方式继承方法
return res
}
但是我们突然发现,虽然实现了用new创建,但传统的bind使用方式失效了
4. 使用组合继承来改造
构造函数(new
创建)和普通函数的调用有什么区别呢?使用构造函数时,内部的this指向的是该对象,而调用普通函数是,内部的this指向的是window,所以我们可以通过这一点来返回不同的内容
Function.prototype.bind = function(obj, ...args){
var fn = this;
var res = function(...others){
if(this == window){
return fn.call(obj, ...args, ...others);
}else{
return fn.call(this, ...args, ...others); //借用构造函数继承属性
}
}
res.prototype =new f(); //原型链的方式继承方法
return res
}
存疑:
return fn.call(this, ...args, ...others)
和obj并没有联系
5. 官方实现
链接:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
if (!Function.prototype.bind) (function(){
var ArrayPrototypeSlice = Array.prototype.slice;
Function.prototype.bind = function(otherThis) {
if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
var baseArgs= ArrayPrototypeSlice.call(arguments, 1),
baseArgsLength = baseArgs.length,
fToBind = this,
fNOP = function() {},
fBound = function() {
baseArgs.length = baseArgsLength; // reset to default base arguments
baseArgs.push.apply(baseArgs, arguments);
return fToBind.apply(
fNOP.prototype.isPrototypeOf(this) ? this : otherThis, baseArgs
);
};
if (this.prototype) {
// Function.prototype doesn't have a prototype property
fNOP.prototype = this.prototype;
}
fBound.prototype = new fNOP();
return fBound;
};
})();
其实变成ES6写法就可以简化成如下:
Function.prototype.myBind = function(obj, ...args){
var fn = this;
//仿制一个Person的构造函数
var F = function(){};
if(fn.prototype){
//将Person的原型全部拷贝到F
F.prototype = fn.prototype;
}
var res = function(){
//this: BindPerson{}对象 / window
//F: function Person
var callObj = F.prototype.isPrototypeOf(this) ? this : obj;
return fn.call(callObj, ...args)
}
res.prototype = new F();
return res
}
我们用一个例子来说明上面代码的干了什么
function Person(name){
this.name = name
}
Person.prototype.getName = function(){
return this.name
}
var BindPerson = Person.bind({}, "xxx");
var bp = new BindPerson();
console.log(bp.getName())
(1)复制Person构造函数,名为F
(2)创建一个全新构造函数res,通过原型链的方法继承F的方法
(3)返回构造函数res,BindPerson = res
(4)当通过bp = new BindPerson();
来创建对象时,此时res里面的this指向bp对象,而bp对象是res的实例,res又是继承了Person,所以F.prototype.isPrototypeOf(this) = true
。再通过构造函数继承Person的属性fn.call(this)
(5)当通过普通方式调用时BindPerson ()
,此时的this指向window,F.prototype.isPrototypeOf(this) = false
。就只需要直接调用fn.call(obj)
6. 总结
对比官方提供的标准答案我们发现,其实唯一的区别在于:判断是不是通过new创建的,简单的用this == window
并不严谨,如果不是通过浏览器调用,而是通过node运行,这时的this就不等于window了
网友评论