bind的原理
var foo = function () { }
var bar = foo
console.log(foo === bar); //true
/*---------------------------------------------*/
var foo = function () { }
var bar = function (callback1, callback2) {
console.log(callback1 === callback2); //true
}
bar(foo, foo)
/*---------------------------------------------*/
var foo = function () { }
var bar = function () { }
console.log(foo === bar); //false //两个函数,不在同一个内存地址中,所以返回false
我们都知道对象有属性和方法,函数也是对象,也有属性和方法,其中bind就是函数的方法。
对象是引用类型,引用的是内存中的一个地址,上面的callback1和callback2这两个指针就指向了一个地址,所以为true。
var foo = function () { }
var fooBind = foo.bind()
console.log(foo === fooBind); //false
//---------------------------------------------/
var foo = function () { };
var fooBind = foo.bind();
var bar = function (callback1, callback2) {
console.log(callback1 === callback2); //false
}
bar(foo, fooBind)
上面的foo.bind()返回的一个新的函数,其实是把foo拷贝了一份,它们两个已经没有任何联系了。即foo和fooBind已经不在同一个内存地址中了。所以返回了false.
//两个函数都用bind,返回的也不是同一个函数,是两个完全独立的函数
var foo = function () { }
var bindFoo1 = foo.bind();
var bindFoo2 = foo.bind();
console.log(bindFoo1 === bindFoo2); //false
虽然上面的bindFoo1和bindFoo2都用foo.bind()返回了一个新函数,但生成的两个新函数没有任何关系,是两个完全独立的函数。
//让bindFoo1和bindFoo2都指向obj
var obj = { key: 'value' };
var foo = function () {
return this;
}
var bindFoo1 = foo.bind(obj);
var bindFoo2 = foo.bind(obj);
console.log(bindFoo1()); //{key: "value"}
console.log(bindFoo2()); //{key: "value"}
console.log(bindFoo1() === bindFoo2()); //true 他们都指向了同一个obj
console.log(bindFoo1 === bindFoo2); //false 虽然他们都指向obj,但是bindFoo1和bindFoo2仍然是两个独立的函数,指向不同的内存地址,与函数中的this无关
问题:有人可能会这么想,用bind最重要就是改变this指向,如果我让fooBind1和fooBind2都指向同一个obj,那这两个函数就会指向同一个内存地址吗??
由上面的注释可知,虽然fooBind1和fooBind2的this都指向了obj,但是两个函数却指向不同的内存地址,是两个不同的函数,与this的指向无关。
//bind的用法
var obj = { key: 'value' };
var foo = function () {
console.log(this); //{key: "value"} 即this指向了obj
}.bind(obj)
foo()
//下面是另一个bind的用法
var obj = { key: 'value' };
var foo = function () {
console.log(this); //{key: "value"} 即this指向了obj
}
foo.bind(obj)() //foo.bind()返回了一个新函数,后面再调用这个新函数
让foo中的this指向obj,foo.bind(obj)返回一个新函数,再调用这个新函数自然就打印了obj对象
var obj = {
name: 'Mike',
method: function () {
setTimeout(function () {
console.log(this); //obj 敲黑板:function(){console.log(this)}.bind(this)返回值是一个函数
console.log('name:', this.name); //mike
}.bind(this), 1000)
}
}
obj.method()
//下面是另一种实现
var obj = {
name: 'lily',
foo: function () {
var argFun = function () {
console.log("this:", this); //{name: "lily", foo: ƒ} 即obj对象本身
console.log('name:', this.name); //lily
}
var argBind = argFun.bind(this);
setTimeout(argBind, 1000)
}
}
obj.foo()
模拟实现
思路分析: bind函数的两个特点: 1、返回一个新函数。 2、可以传入参数
接下来我们就来实现bind
指定this的指向
//通过call或apply先实现this的指向
Function.prototype.bind2 = function (context) {
var self = this;
return function () {
self.apply(context)
}
}
传参的模拟实现
接下来看第2点,bind可以传入参数。这个有点困难,我在bind的时候是否可以传入参数呢?下面看一个例子:
var foo = {
value: 1
}
function bar(name, age) {
console.log(this.value);
console.log(name);
console.log(age);
}
// bar.call(foo, 'lily', 29) //1,lily,29
var bindFoo = bar.bind(foo, 'Mike');
bindFoo('28') //1,Mike,28
由上面的例子可以看出,bind还真可以传入参数name和age。而且还可以在bind的时候传一个参数name,在调用返回的新函数时再传入另一个参数age。 这可咋办?哈哈,不用着急,我们用arguments处理:
//bind2 传参的模拟实现
Function.prototype.bind2 = function (context) {
var self = this;
//获取bind2从第2个参数到最后一个参数
var args = Array.prototype.slice.call(arguments, 1);
return function () {
//这个时候的arguments指bind2返回的函数中传入的参数
var bindArgs = Array.prototype.slice.call(arguments);
self.apply(context, args.concat(bindArgs));
}
}
bind用于构造函数及构造函数效果的模拟实现
//如果bind用于构造函数,那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 = 'Mike';
var bindFoo = bar.bind(foo, 'lily')
var obj = new bindFoo('18') //undefined,lily,18
//尽管在全局作用域和foo上都已定义了value,但是这里还是打印undefined,说明了绑定的this指向foo已经失效了。因为这个时候this已经指向了obj
console.log(obj.habit); //shopping
console.log(obj.friend); //Mike
以上例子可以看出,绑定函数的this失效了。
但是我们可以修改返回函数的prototype来实现:
//第三版
Function.prototype.bind2 = function (context) {
var self = this;
//获取bind2从第2个参数到最后一个参数
var args = Array.prototype.slice.call(arguments, 1);
var fbound = function () {
//这个时候的arguments指bind2返回的函数中传入的参数
var bindArgs = Array.prototype.slice.call(arguments);
//当作为构造函数时,this指向实例,self指向绑定函数,因为下面一句'fbound.prototype = this.prototype'已经修改了fbound.prototype为绑定函数的prototype,此时结果为true,当结果为true的时候,this指向实例。
//当作为普通函数时,this指向window,self指向绑定函数,此时结果为false,当结果为false时,this指向绑定的context.
self.apply(this instanceof self ? this : context, context, args.concat(bindArgs));
}
//修改返回函数的prototype为绑定函数的prototype
fbound.prototype = this.prototype
return fbound;
}
优化构造函数效果的实现
在上面的写法中,直接fbound.prototype = this.prototype,修改fbound的prototype的同时也把函数的prototype也给修改了,我们可以通过一个空函数fNOP来进行中转。
//第四版
Function.prototype.bind2 = function (context) {
var self = this;
//获取bind2从第2个参数到最后一个参数
var args = Array.prototype.slice.call(arguments, 1);
//声明一个空函数
var fNOP = function () { }
var fbound = function () {
//这个时候的arguments指bind2返回的函数中传入的参数
var bindArgs = Array.prototype.slice.call(arguments);
//当作为构造函数时,this指向实例,self指向绑定函数,因为下面一句'fbound.prototype = this.prototype'已经修改了fbound.prototype为绑定函数的prototype,此时结果为true,当结果为true的时候,this指向实例。
//当作为普通函数时,this指向window,self指向绑定函数,此时结果为false,当结果为false时,this指向绑定的context.
self.apply(this instanceof self ? this : context, context, args.concat(bindArgs));
}
//修改返回函数的prototype为绑定函数的prototype,但是这样直接修改fbound的prototype,连带函数的prototype也修改了,可以通过一个空函数来进行中转
// fbound.prototype = this.prototype
fNOP.prototype = this.prototype
fbound.prototype = new fNOP()
return fbound;
}
2个小问题
现在还有2个问题: 1、调用的不是函数怎么办?那一定是要报错的。
if (typeof this !== 'function') {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}
2、如果要在线上使用怎么办?当然是要加上兼容性判断
Function.prototype.bind = Function.prototype.bind || function () {
……
};
最终代码
//最终代码
Function.prototype.bind2 = function (context) {
if (typeof this !== 'function') {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}
var self = this;
//获取bind2从第2个参数到最后一个参数
var args = Array.prototype.slice.call(arguments, 1);
//声明一个空函数
var fNOP = function () { }
var fbound = function () {
//这个时候的arguments指bind2返回的函数中传入的参数
var bindArgs = Array.prototype.slice.call(arguments);
//当作为构造函数时,this指向实例,self指向绑定函数,因为下面一句'fbound.prototype = this.prototype'已经修改了fbound.prototype为绑定函数的prototype,此时结果为true,当结果为true的时候,this指向实例。
//当作为普通函数时,this指向window,self指向绑定函数,此时结果为false,当结果为false时,this指向绑定的context.
self.apply(this instanceof self ? this : context, context, args.concat(bindArgs));
}
//修改返回函数的prototype为绑定函数的prototype,但是这样直接修改fbound的prototype,连带函数的prototype也修改了,可以通过一个空函数来进行中转
// fbound.prototype = this.prototype
fNOP.prototype = this.prototype
fbound.prototype = new fNOP()
return fbound;
}
写这篇也是参考网上其它大牛的博客,纯粹是为了自己理解。下面是原文链接。