默认绑定
全局环境中,this默认绑定到全局对象(严格模式:undefined)
在控制台中分别执行以下代码,注意,全局变量相当于window对象的属性
注意:对于默认绑定,决定this绑定对象的并不是调用位置是否处于严格模式,而是函数体是否处于严格模式。
<1> console.log(this==window) // true
<2> var a={
foo:"123",
bar:"456",
that:this
}
a.that // window
<3> this // window
函数独立调用或者被嵌套函数独立调用时,this默认绑定到window
虽然test()函数被嵌套在obj.foo()函数中,但test()函数是独立调用,而不是方法调用。所以this默认绑定到window
var a = 0;
var obj = {
a : 2,
foo:function(){
function test(){
console.log(this.a);
}
test();
}
}
obj.foo();//0
立即执行函数实际上是函数声明后直接调用执行
var a = 0;
function foo(){
(function test(){ console.log(this.a); })()
};
var obj = { a : 2, foo:foo }
obj.foo();//0
闭包中被返回到外部的函数也是独立调用
var a = 0;
function foo(){
var a = 1
function test(){
console.log(this.a);
}
return test;
};
var obj = {
a : 2,
foo:foo
}
obj.foo()();//0
隐式绑定
函数做为对象方法调用时(即方法调用),this隐式绑定到该直接对象
function foo(){
console.log(this.a);
};
var obj1 = {
a:1,
foo:foo,
obj2:{
a:2,
foo:foo
}
}
//foo()函数的直接对象是obj1,this隐式绑定到obj1
obj1.foo();//1
//foo()函数的直接对象是obj2,this隐式绑定到obj2
obj1.obj2.foo();//2
隐式丢失
隐式丢失是指被隐式绑定的函数丢失绑定对象,从而默认绑定到window,有以下几种情况会造成隐式丢失
<1>函数别名
var a = 0;
function foo(){
console.log(this.a);
};
var obj = {
a : 2,
foo:foo
}
//把obj.foo赋予别名bar,造成了隐式丢失
var bar = obj.foo;
bar();//0
<2>参数传递
var a = 0;
function foo(){
console.log(this.a);
};
function bar(fn){
fn();
}
var obj = {
a : 2,
foo:foo
}
//把obj.foo当作参数传递给bar函数时,有隐式的函数赋值fn=obj.foo
bar(obj.foo);//0
<3>作为内置函数的回调
var a = 0;
function foo(){
console.log(this.a);
};
var obj = {
a : 2,
foo:foo
}
//如果没有隐式丢失,this应该指向obj
setTimeout(obj.foo,100);//0
//结果为0,证明函数作为作为内置函数的回调时发生了隐式丢失
<4>其他特殊情况
function foo() {
console.log( this.a );
}
var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 };
o.foo(); // 3
//将o.foo函数赋值给p.foo函数,然后立即执行。相当于仅仅是foo()函数的立即执行
(p.foo = o.foo)(); // 2
tips:
p.foo = o.foo;
p.foo(); //4
引用与调用的区别
调用:用原函数名调用函数,如foo()
引用:通过别名调用函数,比如var a=foo;a()
显式绑定
通过call()、apply()、bind()方法把对象绑定到this上,叫做显式绑定。对于被调用的函数来说,叫做间接调用
var a = 0;
function foo(){ console.log(this.a); }
var obj = { a:2};
foo();//0
foo.call(obj);//2
显式绑定不能解决隐式丢失问题
function foo() {
setTimeout(function (){
console.log('id:',this.id);
}, 100);
}
var id = 21;
foo.call({ id: 42 });//21
tip: 由于this
在箭头函数中已经按照词法作用域绑定了,所以,用call()
或者apply()
调用箭头函数时,无法对this
进行绑定,即传入的第一个参数被忽略.
new绑定
构造函数通常不使用return关键字,它们通常初始化新对象,当构造函数的函数体执行完毕时,它会显式返回。在这种情况下,构造函数调用表达式的计算结果就是这个新对象的值
function fn(){ this.a = 2; }
var test = new fn();
console.log(test);//{a:2}
如果构造函数使用return语句但没有指定返回值,或者返回一个原始值,那么这时将忽略返回值,同时使用这个新对象作为调用结果
function fn(){ this.a = 2; return; }
var test = new fn();
console.log(test);//{a:2}
如果构造函数显式地使用return语句返回一个对象,那么调用表达式的值就是这个对象
优先级
默认绑定<隐式绑定<显式绑定<new绑定
判断this通用方法
- 函数是否存在new中调用(new绑定)?是的话this绑定的是新创建的对象。
- 函数是否有call\apply\bind等显式绑定,有的话this绑定的是指定的对象
- 函数是否存在某个上下文对象中调用(隐式绑定)有的话this绑定那个上下文对象。
- 若以上都没有,则是默认绑定。严格模式-undefined,非严格模式-全局对象
例外
被忽略的this
如果把null,undefined作为this的绑定对象传入call,apply,bind,这些值在调用时会被忽略,实际会应用默认绑定规则。
function foo() {
console.log(this.a)
}
var a = 2;
foo.call(null); // 2
需要注意这种情况的副作用,会把this绑定到全局对象。
如果你希望忽略this,可以使用Object.create(null)创建一个空对象。
function foo(a,b) {
console.log(a,b)
}
var m = Object.create(null);
foo.apply(m,[2,3]); // 2,3
var bar = foo.bind(m,4)
bar(3) // 4,3
间接引用
你可能会无意间创建一个函数的间接引用,这时候调用这个函数会应用默认绑定规则。
//比如赋值时
function foo(){
console.log(this.a)
}
var a = 2;
var o = { a: 3, foo: foo }
var p = {a: 4}
o.foo(); //3
(p.foo = o.foo)(); //2
// 解析:赋值表达式p.foo = o.foo的返回值是目标函数的引用,
//因此调用位置是foo()而不是p.foo() || o.foo(),根据之前说的,
//此处会使用默认绑定。
软绑定
硬绑定:显式的强制绑定,会大大降低函数灵活性,硬绑定之后无法使用隐式绑定活着显式绑定去修改this
我们创建了函数bar(),并在其内部手动调用foo.call(obj),强制把foo的this绑定到obj,无论之后如何调用函数bar,它总会在obj上调用foo。
function foo(){
console.log(this.a)
}
var obj = {a:2}
var bar=function(){
foo.call(obj)
}
bar() // 2
setTimeout(bar,10000) //2
//硬绑定的bar不可能再修改它的this
bar.call(window) // 2
典型应用场景是场景是创建一个包裹函数,负责接受参数并返回值
function foo(sth){
console.log(this.a, sth)
return this.a + sth
}
var obj = {a:2}
var bar=function(){
return foo.apply(obj,arguments)
}
var b = bar(3) // 2 3
console.log(b) //5
另一种方法是创建一个可以重复使用的辅助函数:
function foo(sth){
console.log(this.a, sth)
return this.a + sth
}
//辅助绑定函数
function bind(fn,obj){
return function(){
return fn.apply(obj, arguments)
}
}
var obj = {a:2}
var bar = bind(foo, obj)
var b = bar(3) // 2 3
console.log(b) //5
软绑定:给默认绑定指定一个全局对象和undefined以外的值,就可以实现和硬绑定相同的效果,同时保留隐式绑定和显示绑定修改this的能力
// 首先检查调用时的this,如果绑定到全局对象或者undefined,那就把指定的默认对象obj绑定到this,否则不修改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;
}
}
//example
function foo(){
console.log(this.name)
}
var obj = {name: 'obj'},obj2 = {name: 'obj2'},obj3 = {name: 'obj3'};
var fooOBJ = foo.softBind(obj)
fooPBJ() // obj
obj2.foo = foo.softBind(obj)
obj2.foo() // obj2
fooOBJ.call(obj3) //obj3
setTimeout(obj2.foo, 10000) //obj ---应用了软绑定
箭头函数(ES6 新增)
箭头函数并不使用this的四种标准规则,而是根据外层(函数或者全局)作用域来决定this。
function foo(){
return (a) => {
// this继承自foo()
console.log(this.a)
}
}
var obj1 = {a:2}
var obj2 = {a:3}
var bar = foo.call(obj1)
bar.call(oj2) //2!
解析:箭头函数的绑定无法被修改(new也不行)
总结
判断一个运行中函数的this绑定,需要找到这个函数的直接调用位置,找到之后顺序应用四条规则判断this的绑定对象。箭头函数根据当前的词法作用域决定this,简答来说,箭头函数继承外层函数的this绑定。