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();//2setTimeout(bar,100);//2bar.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:
-
函数是否在new中调用(new绑定)?如果是的话,this绑定的是新创建的对象
var bar=new foo();
-
函数是否通过call、apply(显式绑定)或者硬绑定调用?如果是的话,this绑定的是指定的对象
var bar=foo.call(obj2);
-
函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定到的是那个上下文对象
var var=obj1.foo();
-
如果都不是的话,使用默认绑定。如果在于按个模式下,就绑定到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();//objobj2.foo=foo.softBind(obj);obj2.foo();//obj2fooOBJ.call(obj3);//obj3setTimeout(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绑定。