思考:javascript
function foo() {
var a = 2;
this.bar();
}
function bar() {
console.log( this.a );
}
foo(); // ReferenceError: a is not defined ?
复制代码
this 其实是在函数被调用时发生的绑定,它指向什么彻底取决于函数在哪里被调用。java
当一个函数被调用时,会建立一个活动记录(有时候也称为执行上下文)。这个记录会包 含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this 就是记录的 其中一个属性,会在函数执行的过程当中用到。数组
这里要说的
call,apply,bind
都是来改变this
的指向的浏览器
call,apply能够屡次改变绑定对象;只是apply接受数组格式参数;缓存
bind()方法会建立一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以建立它时传入 bind()方法的第一个参数做为 this,传入 bind() 方法的第二个以及之后的参数加上绑定函数运行时自己的参数按照顺序做为原函数的参数来调用原函数。bash
bind 是返回对应函数,便于稍后调用;apply 、call 则是当即调用 。app
bind 这些改变上下文的 API 了,对于这些函数来讲,this 取决于第一个参数,若是第一个参数为空,那么就是 window。函数
优先级: 构造绑定>显示绑定>隐式绑定>默认的 this 绑定 post
![]()
bind只能被绑定一次;以第一次为准;优化
function foo() {
console.log("name: " + this.name);
}
var obj = { name: "obj" }, obj2 = { name: "obj2" }, obj3 = { name: "obj3" };
foo.bind(obj).call(obj2) // name: obj
foo.bind(obj).bind(obj2)() // name: obj
复制代码
bind 内部就是 包了一个apply;等到调用的时候再执行这个包含apply的函; 实际上,ES5 中内置的 Function.prototype.bind(..) 更加复杂。下面是 MDN 提供的一种bind(..) 实现:
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
//一个函数去调用,也就是说bind,call,apply的this是个函数;
//而后再去改变这个函数里面的this;
if (typeof this !== "function") {
// 与 ECMAScript 5 最接近的
// 内部 IsCallable 函数
throw new TypeError(
"Function.prototype.bind - what is trying " +
"to be bound is not callable"
);
}
//这里将初始化的参数缓存起来;
var aArgs = Array.prototype.slice.call( arguments, 1 ),
// ftoBind 指向要bind的函数;
fToBind = this,
// 返回一个新函数
fNOP = function(){},
fBound = function(){
//fToBind.apply 改变绑定this;
// 执行的时候判断,当前this等于fNOP而且传入oThis,就设置成当前this,否则就改变成初始化传入的oThis;
return fToBind.apply(
(this instanceof fNOP && oThis ? this : oThis ),
aArgs.concat(Array.prototype.slice.call( arguments ) )
);
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}
复制代码
解释
(this instanceof fNOP && oThis ? this : oThis )
这段代码请看 javascript 深刻解剖bind内部机制
硬绑定这种方式能够把 this 强制绑定到指定的对象(除了使用 new 时),防止函数调用应用默认绑定规则。问题在于,硬绑定会大大下降函数的灵活性,使用硬绑定以后就没法使用隐式绑定或者显式绑定来修改 this。
if (!Function.prototype.softBind) {
Function.prototype.softBind = function(obj) {
var fn = this; // 捕获全部 curried 参数
var curried = [].slice.call( arguments, 1 );
var bound = function() {
return fn.apply((!this || this === (window || global)) ?
obj : this,curried.concat.apply( curried, arguments ) );
};
bound.prototype = Object.create( fn.prototype );
return bound;
};
}
复制代码
它会对指定的函 数进行封装,首先检查调用时的 this,若是 this 绑定到全局对象或者 undefined,那就把 指定的默认对象 obj 绑定到 this,不然不会修改 this。此外,这段代码还支持可选的柯里化;
function foo() {
console.log("name: " + this.name);
}
var obj = { name: "obj" }, obj2 = { name: "obj2" }, obj3 = { name: "obj3" };
var fooOBJ = foo.softBind( obj );
fooOBJ(); // name: obj
obj2.foo = foo.softBind(obj);
obj2.foo(); // name: obj2 <---- 看!!!
fooOBJ.call( obj3 ); // name: obj3 <---- 看!
setTimeout( obj2.foo, 10 );// name: obj <---- 应用了软绑定
复制代码
若是你把 null 或者 undefined 做为 this 的绑定对象传入 call、apply 或者 bind,这些值 在调用时会被忽略,实际应用的是 默认绑定规则:
function foo(a,b) {
console.log( "a:" + a + ", b:" + b );
}
// 把数组“展开”成参数
foo.apply( null, [2, 3] ); // a:2, b:3
// 使用 bind(..) 进行柯里化
var bar = foo.bind( null, 2 );
bar( 3 ); // a:2, b:3
复制代码
用 null 来忽略 this 绑定可能会有反作用。若是某个函数确实使用了 this(好比第三方库中的一个函数),那默认绑定规则会把 this 绑定到全局对象(在浏览 器中这个对象是 window),这将致使不可预计的后果(好比修改全局对象)。
优化:用
Object.create(null)
替代null
或者undefined