Function.prototype.bind
执行会返回一个新的函数,并将this关键字设置为指定的值。并可以在执行该返回的函数之前传入参数。
语法
fun.bind(thisArg[, arg1[, arg2[, ...]]])
thisArg
:表示返回的函数中this的指向。[, arg1[, arg2[, ...]]]
:表明参数是可选的。
使用方法
绑定this
var obj = {
name: 'real',
getName: function() {
console.log(this.name)
}
}
var name = 'window';
obj.getName(); // "real"
var getName = obj.getName;
getName(); // "window";
var getName2 = obj.getName.bind(obj);
getName2(); // "real";
上述getName2
方法中this
指向了obj
。
偏函数(提前传入参数)
var log = function log(...args) {
console.log(args);
}
log(1,2,3); // [1, 2, 3]
var log2 = log.bind(null, 0);
log2(1,2,3); // [0, 1, 2, 3]
提前传入函数0。
结合setTimeout
setTimeout(fn, delay)
。其中fn
中的this
默认指向的是全局window/global
。
function Foo(name){ this.name = name; };
Foo.prototype.getName = function(){ console.log(this.name); }
Foo.prototype.declare = function() { setTimeout(this.getName, 100); }
var name = 'window';
var foo = new Foo('real');
foo.declare(); // 'window'
setTimeout(this.getName, 100);
中的this
指向的foo
对象,可以找到Foo.prototype.getName
。没能执行预期结果是因为Foo.prototype.getName
中的this
指向了window
。如果改成:
setTimeout(this.getName.bind(this), 100);
可以取得预期效果,打印real
。
结合new
这里大致说一下。你不知道的javascript 上卷一书中提到this
有四种绑定方式。
① 默认绑定到window
var name = 'real';
console.log(this.name);
②隐式绑定到所在对象
var obj = {
name: 'real',
getName: function() { return this.name; }
}
console.log(obj.getName()) // 'real';
③显示绑定Function.prototype.apply/call
及其变种Function.prototype.bind
④new
绑定。
function Foo(name) { this.name = name; }
var foo = new Foo('real');
console.log(foo.name); // 'real'
其中,优先级从①到④依次增高。
和Function.prototype.apply
/call
区别
区别在于Function.prototype.bind
不会执行被绑定的函数,并且返回一个新的函数。而Function.prototype.apply
/call
会执行被绑定的函数,并且被绑定的函数中的this
发生偏移,并且传入参数。
polyfill的理解
bind 函数在 ECMA-262 第五版(es5)才被加入;它可能无法在所有浏览器上运行。你可以部分地在脚本开头加入以下代码,就能使它运作,让不支持的浏览器也能使用 bind() 功能。
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
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 aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function() {},
fBound = function() {
return fToBind.apply(this instanceof fNOP
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
if (this.prototype) {
// Function.prototype doesn't have a prototype property
fNOP.prototype = this.prototype;
}
fBound.prototype = new fNOP();
return fBound;
};
}
以一个demo为例。
function foo(name) { this.name = name; }
var obj = { name: 'real'};
var bar = foo.bind(obj);
bar('123'); // 注1
console.log(obj.name); // 123
var baz = new bar('abc'); // 注2
console.log(baz.name); // 'abc'
接下来逐步分析。
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');
}
这个很好理解。一般我们使用bind
的方式是fn.bind(obj)
。这里的this
就是fn
函数。上述demo
中的foo
。
if (this.prototype) {
// Function.prototype doesn't have a prototype property
fNOP.prototype = this.prototype;
}
同理这里的this
也是指上述demo
中的foo
。所以foo.prototype
存在,设置fNOP.prototype = this.prototype;
。
接下来就是最关键的地方了:
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function() {},
fBound = function() {
return fToBind.apply(this instanceof fNOP
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
首先要明确的是bind
函数返回的就是fBound
函数,也就是外部的bar
函数。并且fToBind = this,
这里的this
指向的还是还是demo中的foo
函数。
最核心的这一行
this instanceof fNOP ? this : oThis, // 注3
的作用是区分fBound
函数的调用方式。
如果是通过上述demo中的bar('123');
这种方式调用,那么注3中的this就是window,this instanceof fNOP ? this : oThis
返回oThis,也就是demo中的obj对象。那么就相当于执行foo.apply(obj, 123),其结果就是改变了obj.name的值。
如果是通过var baz = new bar(‘abc’)这种方式调用。我们知道,使用new一个构造函数的过程中,会返回一个对象。并且构造函数中的this(在这里也就是foo中的this)会和该返回的对象baz绑定。所以this instanceof fNOP ? this : oThis
中的this就是该对象baz。由于fBound.prototype = new fNOP();
,所以fBound.prototype.__proto__指向FNOP.prototype。并且上面提到,bar函数就是fBound函数。所以baz是bar的一个实例,也就是fBound的一个实例。所以会有baz.__proto__指向fBound.prototype。并且fBound.prototype.__proto__指向FNOP.prototype,后者继续指向foo.prototype…所以baz的原型链上层是存在FNOP.prototype的。换言之,this instanceof fNOP ? this : oThis
就返回this,也就是baz这个新new出来的对象。所以bar.name的结果是abc。
总结
主要讲了bind函数的基本用法,this的多种绑定方式。并且polyfill中大量使用了闭包和原型链的技巧,都是js中的基础。想想看,我们的js基础真的扎实吗!