如果对call,apply,bind的应用和区别还不了解,可以去看我之前的文章了解下。 让你弄懂 call、apply、bind的应用和区别
如果出现错误,请在评论中指出,我也好自己纠正自己的错误
author: thomaszhou
bind实现
一般我们会直接使用bind函数,但是这次我们通过原生js来尝试实现这个函数
bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数
由此我们可以首先得出 bind 函数的三个特点:
(1)返回一个函数
:我们可以使用 call 或者 apply 实现(2)可以传入参数
:我们用 arguments 进行处理
var foo = { value: 1 };
function bar(name, age) {
console.log(this.value);
console.log(name);
console.log(age);
}
var bindFoo = bar.bind(foo, 'daisy'); // (1)bindFoo是一个函数
bindFoo('18'); // (2)此处可以再次传入参数
// 1
// daisy
// 18
复制代码
先实现前两个特点:实现代码(version 1.0):
Function.prototype.bind2 = function (context) {
var self = this;
// 获取bind2函数从第二个参数到最后一个参数
var args = Array.prototype.slice.call(arguments, 1);
return function () {
// 这个时候的arguments是指bind返回的函数bindFoo调用时传入的参数
var bindArgs = Array.prototype.slice.call(arguments);
self.apply(context, args.concat(bindArgs));
}
}
复制代码
(3)一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。
: 通过修改返回的函数的原型来实现
var value = 2;
var foo = {
value: 1
};
function bar(name, age) {
this.habit = 'shopping';
console.log(this.value);
console.log(name);
console.log(age);
}
bar.prototype.friend = 'kevin';
var bindFoo = bar.bind(foo, 'daisy');
var obj = new bindFoo('18'); // this 已经指向了 obj
// undefined
// daisy
// 18
console.log(obj.habit);
console.log(obj.friend);
// shopping
// kevin
复制代码
注意:尽管在全局和 foo 中都声明了 value 值,最后依然返回了 undefind,说明绑定的 this 失效了
先实现第三个特点:实现代码(version 2.0):
Function.prototype.bind2 = function (context) {
let self = this;
// self --> ƒ bar(){}
let args = Array.prototype.slice.call(arguments, 1);
let fbound = function () {
let bindArgs = Array.prototype.slice.call(arguments);
// (1) 当作为构造函数时,this --> 实例(fbound创建的的实例),self --> 绑定函数bar,结果为true,那么self指向实例
// (2) 当作为普通函数时,this -->window,self -->绑定函数,此时结果为false,那么 self指向绑定的 context。
self.apply(this instanceof self ? this : context, args.concat(bindArgs));
};
// 为了要让 this instanceof self为true,就要使创建的实例继承绑定函数bar,
// 唯一办法就是让创建实例的函数fbound的prototype指向bar函数的prototype
fbound.prototype = this.prototype;
return fbound;
};
复制代码
优化:实现代码(version 3.0):
version 2.0 直接将 fbound.prototype = this.prototype
,我们直接修改fbound.prototype 的时候,也会直接修改函数bar的 prototype。这个时候,我们可以通过一个空函数来进行中转,然后利用组合继承的方式来实现
Function.prototype.bind2 = function (context) {
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fNOP = function () {}; // // 定义一个中间函数,用于作为继承的中间值
var fbound = function () {
var bindArgs = Array.prototype.slice.call(arguments);
self.apply(this instanceof self ? this : context, args.concat(bindArgs));
}
// 先让 fNOP 的原型方法指向 this 即函数bar的原型方法,继承 this 的属性
fNOP.prototype = this.prototype;
// 再将 fbound 即要返回的新函数的原型方法指向 fNOP 的实例化对象
// 这样,既能让 fBound 继承 this 的属性,在修改其原型链时,又不会影响到 this 的原型链
fbound.prototype = new fNOP();
return fbound;
}
复制代码
在上面的代码中,我们引入了一个新的函数 fun,用于继承原函数的原型,并通过 new 操作符实例化出它的实例对象,供 fBound 的原型继承,至此,我们既让新函数继承了原函数的所有属性与方法,又保证了不会因为其对原型链的操作影响到原函数
-------------优化三点-------------------------
(兼容性)当 Function 的原型链上没有 bind 函数时,才加上此函数
Function.prototype.bind = Function.prototype.bind || function () {
……
};
复制代码
只有函数才能调用 bind 函数,其他的对象不行。即判断 this 是否为函数。
if (typeof this !== 'function') {
throw new TypeError("NOT_A_FUNCTION -- this is not callable");
}
复制代码
对于参数传递的代码,可以用ES6的拓展运算符来替换
最终版本!!!
Function.prototype.bind = Function.prototype.bind || function (context, ...formerArgs) {
let self = this;
if (typeof this !== 'function') {
throw new TypeError("NOT_A_FUNCTION -- this is not callable");
}
let fNOP = function () {};
let fbound = function (...laterArgs) {
self.apply(this instanceof self ? this : context, formerArgs.concat(laterArgs));
// es6 : formerArgs.concat(laterArgs)
};
fNOP.prototype = this.prototype;
fbound.prototype = new fNOP();
return fbound;
};
复制代码