全局环境的this指向全局对象,在浏览器中就是咱们熟知的window对象git
说到this
的种种状况,就离不开函数的调用,通常咱们调用函数,无外乎如下四种方式:github
foo()
。obj.foo()
。new foo()
。call
、apply
、bind
等方法。除箭头函数外的其余函数被调用时,会在其词法环境上绑定this
的值,咱们能够经过一些方法来指定this
的值。浏览器
call
、apply
、bind
等方法来显式指定this
的值。function foo() {
console.log(this.a)
}
foo.call({a: 1}) // 输出: 1
foo.apply({a: 2}) // 输出: 2
// bind方法返回一个函数,须要手动进行调用
foo.bind({a: 3})() // 输出: 3
复制代码
this
的值将被隐式指定为这个对象。let obj = {
a: 4,
foo: function() {
console.log(this.a)
}
}
obj.foo() // 输出: 4
复制代码
new
操做符做为构造函数调用时,this
的值将被隐式指定新构造出来的对象。上面讲了几种比较容易记忆和理解this
的状况,咱们来根据ECMAScript规范来简单分析一下,这里只说重点,一些规范内具体的实现就不讲了,反而容易混淆。app
其实当咱们调用函数时,内部是调用函数的一个内置[[Call]](thisArgument, argumentsList)
方法,此方法接收两个参数,第一个参数提供this
的绑定值,第二个参数就是函数的参数列表。函数
ECMAScript规范: 严格模式时,函数内的
this
绑定严格指向传入的thisArgument
。非严格模式时,若传入的thisArgument
不为undefined
或null
时,函数内的this
绑定指向传入的thisArgument
;为undefined
或null
时,函数内的this
绑定指向全局的this
。post
因此第一点中讲的三种状况都是显式或隐式的传入了thisArgument
来做为this
的绑定值。咱们来用伪代码模拟一下:ui
function foo() {
console.log(this.a)
}
/* -------显式指定this------- */
foo.call({a: 1})
foo.apply({a: 1})
foo.bind({a: 1})()
// 内部均执行
foo[[Call]]({a: 1})
/* -------函数构造调用------- */
new foo()
// 内部执行
let obj = {}
obj.__proto__ = foo.prototype
foo[[Call]](obj)
// 最后将这个obj返回,关于构造函数的详细内容可翻阅我以前关于原型和原型链的文章
/* -------做为对象方法调用------- */
let obj = {
a: 4,
foo: function() {
console.log(this.a)
}
}
obj.foo()
// 内部执行
foo[[Call]]({
a: 1,
foo: Function foo
})
复制代码
那么当函数普通调用时,thisArgument
的值并无传入,即为undefined
,根据上面的ECMAScript规范,若非严格模式,函数内this
指向全局this
,在浏览器内就是window。this
伪代码模拟:spa
window.a = 10
function foo() {
console.log(this.a)
}
foo() // 输出: 10
foo.call(undefined) // 输出: 10
// 内部均执行
foo[[Call]](undefined) // 非严格模式,this指向全局对象
foo.call(null) // 输出: 10
// 内部执行
foo[[Call]](null) // 非严格模式,this指向全局对象
复制代码
根据上面的ECMAScript规范,严格模式下,函数内的this
绑定严格指向传入的thisArgument
。因此有如下表现。prototype
function foo() {
'use strict'
console.log(this)
}
foo() // 输出:undefined
foo.call(null) // 输出:null
复制代码
须要注意的是,这里所说的严格模式是函数被建立时是否为严格模式,并不是函数被调用时是否为严格模式:
window.a = 10
function foo() {
console.log(this.a)
}
function bar() {
'use strict'
foo()
}
bar() // 输出:10
复制代码
ES6新增的箭头函数在被调用时不会绑定this
,因此它须要去词法环境链上寻找this
。
function foo() {
return () => {
console.log(this)
}
}
const arrowFn1 = foo()
arrowFn1() // 输出:window
// 箭头函数没有this绑定,往外层词法环境寻找
// 在foo的词法环境上找到this绑定,指向全局对象window
// 在foo的词法环境上找到,并不是是在全局找到的
const arrowFn2 = foo.call({a: 1})
arrowFn2() // 输出 {a: 1}
复制代码
切记,箭头函数中不会绑定this
,因为JS采用词法做用域,因此箭头函数中的this
只取决于其定义时的环境。
window.a = 10
const foo = () => {
console.log(this.a)
}
foo.call({a: 20}) // 输出: 10
let obj = {
a: 20,
foo: foo
}
obj.foo() // 输出: 10
function bar() {
foo()
}
bar.call({a: 20}) // 输出: 10
复制代码
当函数做为回调函数时会产生一些怪异的现象:
window.a = 10
let obj = {
a: 20,
foo: function() {
console.log(this.a)
}
}
setTimeout(obj.foo, 0) // 输出: 10
复制代码
我以为这么解释比较好理解:obj.foo
做为回调函数,咱们其实在传递函数的具体值,而并不是函数名,也就是说回调函数会记录传入的函数的函数体,达到触发条件后进行执行,伪代码以下:
setTimeout(obj.foo, 0)
//等同于,先将传入回调函数记录下来
let callback = obj.foo
// 达到触发条件后执行回调
callback()
// 因此foo函数并不是做为对象方法调用,而是做为函数普通调用
复制代码
要想避免这种状况,有三种方法,第一种方法是使用bind
返回的指定好this
绑定的函数做为回调函数传入:
setTimeout(obj.foo.bind({a: 20}), 0) // 输出: 20
复制代码
第二种方法是储存咱们想要的this值,就是常见的,具体命名视我的习惯而定。
let _this = this
let self = this
let me = this
复制代码
第三种方法就是使用箭头函数
window.a = 10
function foo() {
return () => {
console.log(this.a)
}
}
const arrowFn = foo.call({a: 20})
arrowFn() // 输出:20
setTimeout(arrowFn, 0) // 输出:20
复制代码
this
绑定,this
的值取决于其建立时所在词法环境链中最近的this
绑定this
指向全局对象this
为undefined
this
指向该对象new
调用,this
指向构造出的新对象call
、apply
、bind
等间接调用,this
指向传入的第一个参数
这里注意两点:
bind
返回一个函数,须要手动调用,call
、apply
会自动调用- 传入的第一个参数若为
undefined
或null
,this
指向全局对象
call
、apply
、bind
等间接调用,this
严格指向传入的第一个参数有时候文字的表述是苍白无力的,真正理解以后会发现:this
不过如此。
例子来自南波的JavaScript之例题中完全理解this
// 例1
var name = 'window'
var person1 = {
name: 'person1',
show1: function () {
console.log(this.name)
},
show2: () => console.log(this.name),
show3: function () {
return function () {
console.log(this.name)
}
},
show4: function () {
return () => console.log(this.name)
}
}
var person2 = { name: 'person2' }
person1.show1() // ?
person1.show1.call(person2) // ?
person1.show2() // ?
person1.show2.call(person2) // ?
person1.show3()() // ?
person1.show3().call(person2) // ?
person1.show3.call(person2)() // ?
person1.show4()() // ?
person1.show4().call(person2) // ?
person1.show4.call(person2)() // ?
复制代码
选中下方查看答案:
person1 // 函数做为对象方法调用,this指向对象
person2 // 使用call间接调用函数,this指向传入的person2
window // 箭头函数无this绑定,在全局环境找到this,指向window
window // 间接调用改变this指向对箭头函数无效
window // person1.show3()返回普通函数,至关于普通函数调用,this指向window
person2 // 使用call间接调用函数,this指向传入的person2
window // person1.show3.call(person2)仍然返回普通函数
person1 // person1.show4调用对象方法,this指向person1,返回箭头函数,this在person1.show4调用时的词法环境中找到,指向person1
person1 // 间接调用改变this指向对箭头函数无效
person2 // 改变了person1.show4调用时this的指向,因此返回的箭头函数的内this解析改变
欢迎前往阅读系列文章,若是喜欢或者有所启发,欢迎 star,对做者也是一种鼓励。
菜鸟一枚,若是有疑问或者发现错误,能够在相应的 issues 进行提问或勘误,与你们共同进步。