Open logan70 opened 4 years ago
在具体谈论this取值的各种情况前,我们先来看看一个函数从创建到执行的过程中对我们了解this有帮助的一些规范信息。
this
在函数创建阶段会标记该函数的this模式,有以下三种模式,由上往下进行判定,详见 ECMAScript#FunctionInitialize:
lexical
strict
global
无论函数通过何种方式调用,最终JS引擎都会通过函数对象内部的 [[Call]] ( thisArgument, argumentsList ) 方法来调用函数并执行。第一个参数为指定this的值,不传则为undefined,第二个参数为函数被调用时的参数列表,详见 ECMAScript#[[Call]] 。
[[Call]] ( thisArgument, argumentsList )
undefined
在函数调用的初始阶段,会进行this的绑定,具体表现为以下步骤,详见 ECMAScript#BindThis 。:
thisArgument
null
这一步中如果传入的thisArgument为基本类型值,会进行装箱操作
JavaScript函数中this取值主要区分以下几个情况:
call
apply
bind
函数普通调用时,未指定this的值,thisArgument为undefined,this的值分两种情况:
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"}
这个不难理解,即通过指定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解析改变
this的原理以及几种不同使用场景的取值
了解函数
在具体谈论this取值的各种情况前,我们先来看看一个函数从创建到执行的过程中对我们了解
this
有帮助的一些规范信息。函数的this模式
在函数创建阶段会标记该函数的this模式,有以下三种模式,由上往下进行判定,详见 ECMAScript#FunctionInitialize:
lexical
;strict
;global
。函数的执行
无论函数通过何种方式调用,最终JS引擎都会通过函数对象内部的
[[Call]] ( thisArgument, argumentsList )
方法来调用函数并执行。第一个参数为指定this的值,不传则为undefined
,第二个参数为函数被调用时的参数列表,详见 ECMAScript#[[Call]] 。函数的this绑定
在函数调用的初始阶段,会进行this的绑定,具体表现为以下步骤,详见 ECMAScript#BindThis 。:
lexical
,不进行绑定;strict
,则this绑定值严格等于传入的thisArgument
;global
,thisArgument
为null
或undefined
,则this绑定值为全局对象;global
,thisArgument
不为null
或undefined
,则this绑定值为thisArgument
。不同使用场景的this取值
JavaScript函数中this取值主要区分以下几个情况:
call
、apply
、bind
间接调用函数的普通调用
函数普通调用时,未指定this的值,
thisArgument
为undefined
,this的值分两种情况:undefined
。函数作为对象方法调用
函数作为对象方法调用时,会将该对象作为
thisArgument
,所以this为函数所在对象。函数作为构造函数调用
函数作为构造函数调用时,会将构造的对象作为
thisArgument
,所以this为构造的对象。函数通过
call
、apply
、bind
间接调用这个不难理解,即通过指定
thisArgument
的值来改变this的指向。箭头函数的调用
箭头函数this模式为
lexical
,执行时不进行this绑定,所以箭头函数中this的值取决于作用域链上最近的this值。需要注意的是,箭头函数没有this绑定,所以使用
call
、apply
、bind
无法改变箭头函数内this的指向。测试练习