this是什么

this是运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用的各种条件。this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。

当一个函数被调用时,会创建一个活动记录(也称执行上下文)。

this既不指向函数本身,也不指向函数的词法作用域。

this的绑定规则

默认绑定

function foo(){
    console.log(this.a);
}

var a=2;
foo();//2

本例中函数调用时应用了this的默认绑定,因为foo()是直接使用不带任何修饰的函数引用进行调用的,因此this指向全局对象。

*:如果使用严格模式,则不能将全局对象用于默认绑定,因此this会绑定到undefined。
**:对于默认绑定来说,决定this绑定对象的并不是调用位置是否处于严格模式,而是函数体是否处于严格模式。

隐式绑定

function foo(){
    console.log(this.a);
}

var obj={
    a:2,
    foo:foo
};

obj.foo();//2

当foo()被调用时,它的前面确确实实加上了对obj的引用。当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。因此调用foo()时this被绑定到obj,因此this.a和obj.a是一样的。

对象属性引用链中只有上一层或者说最后一层在调用位置中起作用。

隐式丢失
function foo(){
    console.log(this.a);
}

var obj={
    a:2,
    foo:foo
};

var bar=obj.foo;

var a=3;

bar();//3

虽然bar是obj.foo的一个引用,实际上,它引用的是foo函数本身,因此此时的bar()其实是一个不带任何修饰的函数调用,因此应用了默认绑定。

同样,传人回调函数时也会发生隐式丢失,不管传入的是自己声明的函数还是传入语言内置的函数。

显式绑定

function foo(){
    consol.log(this.a);
}

var obj={
    a:2
};

foo.call(obj);//2

通过foo.call(),我们在调用foo()时强制把它的this绑定到obj上。

*:从this绑定的角度来看,call()和apply()是一样的。

硬绑定
function foo(){
    console.log(this.a);
}

var obj={
    a:2
};

var bar=function(){
    foo.call(obj);
};

bar();//2

setTimeout(bar,100);//2

bar.call(window);//2

本例中我们创建了bar(),并在内部手动调用了foo.call(obj),强制把foo的this绑定到了obj。无论之后怎样调用bar,它都会手动在obj上调用foo。这是一种显示的强制绑定,称为硬绑定。

由于硬绑定是一种非常常用的模式,ES5内置了bind()方法。其会返回一个新函数,把你指定的参数设置为this的上下文并调用原始函数。

API调用的上下文
function foo(el){
    console.log(el,this.id);
}

var obj={
    id="awesome"
};

//调用 foo()的时候把this绑定到obj
[1,2,3].forEach(foo,obj);
//1 awesome 2 awesome 3 awesome

new绑定

关于new,有一个重要的点:实际上并不存在所谓的“构造函数”,只有对于函数的“构造调用”。
使用new来调用函数,或者说发生构造函数调用时,会执行下列操作:
(1):创建一个全新的对象
(2):这个新对象会被执行[[Prototype]]连接
(3):这个新对象会绑定到函数调用的this
(4):如果函数没有返回其他对象,那么new表达式中的函数会自动返回这个新对象
function foo(a){
    this.a=a;
}

var bar=new foo(2);
console.log(bar.a);//2

使用new来调用foo()时,我们会构造一个新对象并把它绑定到foo()调用的this上。

优先级

判断this:

  1. 函数是否在new中调用(new绑定)?如果是的话,this绑定的是新创建的对象

    var bar=new foo();
  2. 函数是否通过call、apply(显式绑定)或者硬绑定调用?如果是的话,this绑定的是指定的对象

    var bar=foo.call(obj2);
  3. 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定到的是那个上下文对象

    var var=obj1.foo();
  4. 如果都不是的话,使用默认绑定。如果在于按个模式下,就绑定到undefined,否则绑定到全局对象

    var bar =foo();

绑定例外

被忽略的this

如果你把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
更安全的this
一种更安全的做法是把this绑定到一个特殊的对象,且不会对程序造成任何副作用。可以使用一个DMZ对象,比如'xx'=Object.create(null)以保护全局变量。

间接引用

间接引用最容易在赋值时发生:

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(),这里应用的是默认绑定

软绑定

function foo(){
    console.log("name"+this.name);
}
var obj={ name:"obj"};
var obj1={ name:"obj1"};
var obj2={ name:"obj2"}; 

var fooOBJ=foo.softBind(obj);
fooOBJ();//obj

obj2.foo=foo.softBind(obj);
obj2.foo();//obj2

fooOBJ.call(obj3);//obj3

setTimeout(obj.foo,10);//obj

this词法

function fo(){
    return (a)=>{
        console.log(this.a);
    };
}

var obj1={a:1};
var obj2={a:2};

var bar=foo.call(obj1);

bar.call(obj2);//1

foo()内部创建的箭头函数会捕获到调用foo()的this,由于foo()的this绑定到obj1,bar的this也会绑定到obj1,箭头函数的绑定无法被修改

具体来说,箭头函数会继承外层函数调用的this绑定。


Teemo
38 声望1 粉丝