本文共 1100 字,读完只需 4 分钟
概述
前一篇文章我们尝试模拟实现了 call 和 apply 方法,其实 bind 函数也可以用来改变 this 的指向。bind 和 call和 apply 两者的区别在于,bind 会返回一个被改变了 this 指向的函数。
本文介绍如何模拟实现 bind 函数:
首先观察 bind 函数有什么特点:
var person = {
name: 'jayChou'
}
function say(age, sex) {
console.log(this.name, age, sex);
}
var foo = say.bind(person, '男', 39);
foo(); // jayChou 男 39
- 返回一个函数
- 函数参数以逗号的形式传入
- 改变了 this 的指向
一、call 简单实现
既然 bind 内部也要用改变 this 指向,我们可以用现成的 call 函数来实现这一功能。
Function.prototype.newBind = function(context) {
if(typeof this !== 'function') {
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
var self = this;
return function () {
return self.call(context)
}
}
说明一下:
if 判断是为了校验,只能让函数来调用此方法,并抛出错误。
第二个 return
是为了让返回的函数有返回值的功能。
测试一下:
var person = {
name: 'jayChou'
};
var say = function() {
console.log(this.name);
}
var foo = say.newBind(person);
foo(); // jayChou
成功啦。
二、传递参数
刚才的函数是没有传递参数,当然不行,所以我们想办法把函数的参数也传递进去。
bind 函数有个特点,就是在绑定的时候可以传参,返回的函数还可以继续传参。
var person = {
name: 'jayChou'
};
var say = function(p1, p2) {
console.log(this.name, p1, p2);
}
var foo = say.bind(person, 18);
foo(20); // jayChou 18 20
还可以写成:
say.bind(person, 18)(20); // jayChou 18 20
好,进入正题,考虑传参的事。在前面的文章,我们讲过 arguments 类数组对象的一些特性,不能直接调用数组的方法,但是可以用原型方法间接来调用,我们采用 apply 的方式来传递参数。
Function.prototype.newBind = function(context) {
if(typeof this !== 'function') {
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
var self = this;
var args = Array.prototype.slice.call(arguments, 1); // 间接调用数组方法,获取第一次传的参数
return function () {
var innerArgs = Array.prototype.slice.call(arguments);
return self.apply(context, args.concat(innerArgs))
}
}
var person = {
name: 'jayChou'
};
var say = function(p1, p2) {
console.log(this.name, p1, p2);
}
var foo = say.newBind(person, 18); // 第一次传参
foo(20); // 第二次传参
最后输出:
jayChou 18 20
结果正确,以上就完成了 bind 函数基本功能的实现。
但是!
三、作为构造函数时?
bind 函数其实还有一个非常重要的特点:
bind返回的函数如果作为构造函数,搭配new关键字出现的话,我们的绑定this就需要“被忽略”。
意思就是指:当使用 nuw 关键字把 bind 返回的函数作为构造函数,之前改变了指向的 this 就失效了。返回的函数的 this 就关联到了构造函数的实例对象上。
针对这个特点,需要再做一些修改:
Function.prototype.newBind = function(context) {
if(typeof this !== 'function') {
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
var self = this;
var args = Array.prototype.slice.call(arguments, 1); // 间接调用数组方法,获取第一次传的参数
let tempFn = function {}; // 利用一个空函数作为中转
tempFn.prototype = this.prototype; // 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值
var resultFn = function () {
var innerArgs = Array.prototype.slice.call(arguments);
if (this instanceof tempFn) { // 如果 返回函数被当做构造函数后,生成的对象是 tempFn 的实例,此时应该将 this 的指向指向创建的实例。
return fn.apply(this, args.concat(innerArgs));
} else {
return self.apply(context, args.concat(innerArgs))
}
}
resultFn = new tempFn();
return resultFn;
}
总结
本文尝试模拟实现了 bind 函数,bind 函数与 call,apply 函数的区别在于,bind 函数返回一个指定了 this 的函数,函数并未执行。其次,当返回的函数作为构造函数时,之前绑定的 this 会失效。