为什么要用this
使用this可以让我们灵活地在不同的上下文中重复的使用函数,不需要针对每个对象编写不同版本的函数。
因此,掌握this的指向,对我们来说额外重要。
什么是上下文?
当一个函数被调用时,会创建一个活动记录,包括函数在哪被调用,调用方式,传入参数等,this则是其中的一个属性,在函数执行的过程中会用到。
对this的误解
- this指向函数本身?no!
- this指向函数作用域?no!
this指向是在进行时绑定的,与函数声明的位置没有任何关系,取决于函数的调用方式,在哪里被调用。
函数的调用位置
首先我们想理解一下调用位置和调用栈,这里有一段函数。
function baz() {
// 此处的调用栈为 baz
// 调用位置:全局作用域
console.log('baz');
bar();
}
function bar() {
// 此处的调用栈为 baz -> bar
// 调用位置:baz
console.log('bar');
foo();
}
function foo() {
// 此处的调用栈为 baz -> bar -> foo
// 调用位置:bar
debugger
console.log('foo')
}
baz()
我们在浏览器中执行一下它:
this的绑定规则
默认绑定
this指向全局对象,当函数内使用严格模式时,this会绑定到undefined。
有个细节:决定this绑定对象的并不是调用位置是否处于严格模式,而是该函数的函数体是否是处于严格模式。见下面的代码:
function foo(){
"use strict";
console.log(this.a);
}
function foo1(){
console.log(this.a);
}
var a = 2;
foo(); // TypeError:this is undefined
(function(){
"use strict";
foo1(); // 2
})();
隐式绑定
判断调用位置是否有上下文对象,或者是否被某个对象拥有或包含该函数引用,符合条件的话,this会隐式绑定为该对象。
-
函数中的this只会指向它上一层的
function foo(){ console.log(this.a); } var obj2 = { a:3, foo:foo }; var obj1 = { a:2; obj2:obj2 }; obj1.obj2.foo() // 3 而不是2 隐式
-
隐式丢失
看下面的例子,fn虽然是obj.foo的一个引用,但实际上引用的是foo函数本身,因此可以fn()是一个不带修饰的函数调用,应用了默认绑定。
注意:无论是在Object中直接定义函数,还是想定义函数再添加为引用属性,这个函数严格来说都不属于这个对象。
function foo(){ console.log(this.a); } var obj = { a:3, foo:foo }; var a = 2; var fn = obj.foo; fn(); // 2 而不是3
显式绑定
直接指定this的绑定对象
-
call(thisArg,arg1,arg2,...) 与 apply(...)
-
参数
- this的绑定对象
- 参数列表
-
使用一个指定的
this
值和单独给出的一个或多个参数来调用一个函数
-
如果传入一个原始值,会被转化为其对象形式,以下同理。
-
bind(thisArg,arg1,arg2,...)——硬绑定
-
参数
- this的绑定对象
- 参数列表
-
创建一个新的函数,在
bind()
被调用时,这个新函数的this
被指定为bind()
的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
-
-
API调用的上下文
某些API提供了可选参数,其作用和bind(...)一样,确保你的回调函数使用指定的this。
function foo(el){ console.log(el,this.id) } var obj = { id:'ok' } [1,2,3].forEach(foo,obj); //1ok 2ok 3ok
new绑定
创建过程:
- 创建一个全新的对象
- 执行[[Prototype]]连接
- 将该对象绑定到函数调用的this
- 如果函数中没有返回其他对象,自动返回创建的对象。
function foo(a){
this.a = a
}
var bar = new foo(2);
console.log(bar.a); // 2
绑定规则优先级
- 函数是否在new中调用 ?this绑定的是新创建的对象 :继续往下
- 函数是否通过call、apply或者bind调用 ?this绑定指定对象:继续往下
- 函数是否在某个上下文对象中调用 ?this绑定该上下文对象 :继续往下
- 默认绑定,是否是严格模式 ?undefined :全局对象。
绑定的其他情况
-
在call、apply等中,如果将要绑定的this传入
null
作为占位符时,在非严格模式下,会应用默认绑定规则绑定到全局对象,将导致不可预计的后果,如修改全局对象。-
创建一个空的非委托的对象,将this指向它。
Object.create(null) 不会创建Object.prototype这个委托,比{}更空。
-
-
间接引用(见隐式绑定中的隐式丢失)
-
使用软绑定
if (!Function.prototype.softBind) { Function.prototype.softBind = function (obj,...args1) { //传入的obj是我们想要设置的默认的this绑定值 var fn = this; var bound = function (...args2) { return fn.apply( // 如果this指向全局对象,说明默认绑定,则绑定为obj (!this || this === (window || global)) ? obj : this, //新的参数列表 [...args1,...args2] ); }; bound.prototype = Object.create(fn.prototype); return bound; }; } //来自《你不知道的JavaScript》上册
箭头函数的this
箭头函数的this是根据外层(函数或者全局)作用域来决定的。常用于回调函数中。
function foo(){
return ()=>{
console.log(this.a)
}
}
var obj = {
a:2;
}
var obj2 = {
a:3;
}
var bar = foo.bind(obj);
bar.call(obj2); // 2 而不是3,箭头函数的绑定无法修改。