Blog
Blog copied to clipboard
作用域与闭包 - this的原理以及几种不同使用场景的取值
this的原理以及几种不同使用场景的取值
了解函数
在具体谈论this取值的各种情况前,我们先来看看一个函数从创建到执行的过程中对我们了解this
有帮助的一些规范信息。
函数的this模式
在函数创建阶段会标记该函数的this模式,有以下三种模式,由上往下进行判定,详见 ECMAScript#FunctionInitialize:
-
lexical :箭头函数的this模式标记为
lexical
; -
strict :严格模式下函数的this模式标记为
strict
; -
global :其他情况的函数的this模式标记为
global
。
函数的执行
无论函数通过何种方式调用,最终JS引擎都会通过函数对象内部的 [[Call]] ( thisArgument, argumentsList )
方法来调用函数并执行。第一个参数为指定this的值,不传则为undefined
,第二个参数为函数被调用时的参数列表,详见 ECMAScript#[[Call]] 。
函数的this绑定
在函数调用的初始阶段,会进行this的绑定,具体表现为以下步骤,详见 ECMAScript#BindThis 。:
- 如果函数的this模式为
lexical
,不进行绑定; - 如果函数的this模式为
strict
,则this绑定值严格等于传入的thisArgument
; - 如果函数的this模式为
global
,thisArgument
为null
或undefined
,则this绑定值为全局对象; - 如果函数的this模式为
global
,thisArgument
不为null
或undefined
,则this绑定值为thisArgument
。这一步中如果传入的
thisArgument
为基本类型值,会进行装箱操作
不同使用场景的this取值
JavaScript函数中this取值主要区分以下几个情况:
- 函数的普通调用
- 函数作为对象方法调用
- 函数作为构造函数调用
- 函数通过
call
、apply
、bind
间接调用 - 箭头函数的调用
函数的普通调用
函数普通调用时,未指定this的值,thisArgument
为undefined
,this的值分两种情况:
- 非严格模式:this为全局对象;
-
严格模式:this为
undefined
。
function looseFn() {
console.log(this)
}
function strictFn() {
'use strict'
console.log(this)
}
looseFn() // <- window
strictFn() // <- undefined
函数作为对象方法调用
函数作为对象方法调用时,会将该对象作为thisArgument
,所以this为函数所在对象。
ECMA定义规范 -> Abstract operation Call on Objects
var myName = 'global'
const obj = {
myName: 'obj',
getMyName() {
console.log(this.myName)
}
}
obj.getMyName() // <- 'obj'
函数作为构造函数调用
函数作为构造函数调用时,会将构造的对象作为thisArgument
,所以this为构造的对象。
ECMA定义规范 -> Abstract operation Construct
function Person(name) {
this.name = name
console.log(this)
}
const person = new Person('Logan')
// <- Person {name: "Logan"}
函数通过call
、apply
、bind
间接调用
这个不难理解,即通过指定thisArgument
的值来改变this的指向。
var name = 'global'
function logName() {
console.log(this.name)
}
logName() // <- 'global'
logName.call({ name: 'call' }) // <- 'call'
logName.apply({ name: 'apply' }) // <- 'apply'
// 注意bind返回一个函数,而不是直接调用
logName.bind({ name: 'bind' })() // <- 'bind'
箭头函数的调用
箭头函数this模式为lexical
,执行时不进行this绑定,所以箭头函数中this的值取决于作用域链上最近的this值。
需要注意的是,箭头函数没有this绑定,所以使用call
、apply
、bind
无法改变箭头函数内this的指向。
function genArrowFn() {
return () => {
console.log(this)
}
}
const arrowFn1 = genArrowFn()
arrowFn1() // <- window
const arrowFn2 = genArrowFn.call({ a: 1 })
arrowFn2() // <- { a: 1 }
// `call`、`apply`、`bind`无法改变箭头函数内this的指向,仍然在作用域链上寻找
arrowFn1.call({ a: 2 }) // <- window
arrowFn2.apply({ a: 2 }) // <- { a: 1 }
arrowFn2.bind({ a: 2 })() // <- { a: 1 }
测试练习
var name = 'window'
const 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)
}
}
const person2 = { name: 'person2' }
person1.show1() // person1 函数作为对象方法调用,this指向对象
person1.show1.call(person2) // person2 使用call间接调用函数,this指向传入的person2
person1.show2() // window 箭头函数无this绑定,在全局环境找到this,指向window
person1.show2.call(person2) // window 间接调用改变this指向对箭头函数无效
person1.show3()() // window person1.show3()返回普通函数,相当于普通函数调用,this指向window
person1.show3().call(person2) // person2 使用call间接调用函数,this指向传入的person2
person1.show3.call(person2)() // window person1.show3.call(person2)仍然返回普通函数
person1.show4()() // person1 person1.show4调用对象方法,this指向person1,返回箭头函数,this在person1.show4调用时的词法环境中找到,指向person1
person1.show4().call(person2) // person1 间接调用改变this指向对箭头函数无效
person1.show4.call(person2)() // person2 改变了person1.show4调用时this的指向,所以返回的箭头函数的内this解析改变