JavaScript关键字this的使用方法详解
作者:IanLew
在绝大多数情况下,函数的调用方式决定了
this
的值(运行时绑定)。this
不能在执行期间被赋值,并且在每次函数被调用时this
的值也可能会不同。ES5 引入了bind
方法来设置函数的this
值,而不用考虑函数如何被调用的。ES2015 引入了箭头函数,箭头函数不提供自身的this
绑定(this
的值将保持为闭合词法上下文的值)。
涵义
当前执行上下文(global、function 或 eval)的一个属性,在非严格模式下,总是指向一个对象,在严格模式下可以是任意值。
this
可以用在构造函数之中,表示实例对象。除此之外,this
还可以用在别的场合。但不管是什么场合,this
都有一个共同点:它总是返回一个对象。简单说,this
就是属性或方法“当前”所在的对象。
const p = { name: 'jay', describe() { console.log(`${this.name} is a good man`) } } p.describe() // jay is a good man
上面代码中,this.name
表示 name
属性所在的那个对象。由于 this.name
是在 describe
方法中调用,而 describe
方法所在的当前对象是 p
,因此 this
指向 p
,this.name
就是 p.name
。
由于对象的属性可以赋给另一个对象,所以属性所在的当前对象是可变的,即 this
的指向是可变的。
const n = { name: 'jj' } n.describe = p.describe n.describe() // jj is a good man
上面代码中,p.describe
属性被赋给 n
,于是 n.describe
就表示 describe
方法所在的当前对象是 n
,所以this.name
就指向 n.name
。
关于 this
的指向改变的理解可以参考之前编译执行的相关文章,这里不做赘述。稍稍重构这个例子,this
的动态指向就能看得更清楚。
function describe () { console.log(`${this.name} is a good man`) } const p = { name: 'jay', describe } const n = { name: 'jj', describe } p.describe() // jay is a good man n.describe() // jj is a good man
只要函数被赋给另一个变量,this
的指向就会变。
const p = { name: 'jay', describe: function () { console.log(`${this.name} is a good man`) } } const name = 'jj' const f = p.describe f() // jj is a good man
上面代码中,p.describe
被赋值给变量 f
,内部的 this
就会指向 f
运行时所在的对象(本例是顶层对象)。
实质
JavaScript 语言之中,一切皆对象,运行环境也是对象,所以函数都是在某个对象之中运行,this
就是函数运行时所在的对象(环境)。JavaScript 支持运行环境动态切换,也就是说,this
的指向是动态的,没有办法事先确定到底指向哪个对象。
const o = { n: 1 }
JavaScript 引擎会先在内存里面,生成一个对象 { n: 1 }
,然后把这个对象的内存地址赋值给变量 o
。也就是说,变量 o
是一个地址引用(reference)。后面如果要读取 o.n
,引擎先从 o
拿到内存地址,然后再从该地址读出原始的对象,返回它的n
属性。原始的对象以字典结构保存,每一个属性名都对应一个属性描述对象。
{ n: { [[value]]: 1 [[writable]]: true [[enumerable]]: true [[configurable]]: true } }
属性的值可能是一个函数。
const o = { n: function () {} }
这时,引擎会将函数单独保存在内存中,然后再将函数的地址赋值给n
属性的 value
属性。
{ n: { [[value]]: 函数的地址 ... } }
由于函数是一个单独的值,所以它可以在不同的环境(上下文)执行。
const f = function () {} const o = { f } f() // 在全局对象执行 obj.f() // 在 obj 对象里面执行
由于函数可以在不同的运行环境执行,所以需要有一种机制,能够在函数体内部获得当前的运行环境(context)。所以,this
就出现了,它的设计目的就是在函数体内部,指代函数当前的运行环境。
使用场合
this
主要有以下几个使用场合。
全局上下文
无论是否在严格模式下,在全局执行环境中(在任何函数体外部)this
都指向全局对象。在浏览器环境下,它指的就是顶层对象 window
。可以使用 globalThis
获取全局对象,无论你的代码是否在当前上下文运行。
console.log(this === window) // true
函数上下文
在函数内部,this
的值取决于函数被调用的方式。
function f () { return this } // 在浏览器中,全局对象是 window console.log(f() === window) // true //在 Node 中 console.log(f() === globalThis) // true
然而,在严格模式下,如果进入执行环境时没有设置 this
的值,this
会保持为 undefined
function f () { "use strict" return this } console.log(f() === undefined) // true
箭头函数
在箭头函数中,this
与封闭词法环境的 this
保持一致。在全局代码中,它将被设置为全局对象。
const f = () => this console.log(f() === this) // true
构造函数
当一个函数用作构造函数时(使用new关键字),它的 this
被绑定到正在构造的新对象。
function C() { this.a = 37 } const o = new C() console.log(o.a) // 37
DOM 事件处理函数
当函数被用作事件处理函数时,它的 this
指向触发事件的元素(一些浏览器在使用非 addEventListener
的函数动态地添加监听函数时不遵守这个约定)。
function bluify (e) { console.log(this === e.currentTarget) // true } const element = document.getElementById('do') element.addEventListener('click', bluify, false)
内联事件处理函数
当代码被内联 on-event
处理函数调用时,它的 this
指向监听器所在的 DOM 元素。
<button onclick="alert(this.tagName.toLowerCase());">Show this</button>
类上下文
this
在 class
中的表现与在函数中类似,因为类本质上也是函数,但也有一些区别和注意事项。在类的构造函数中,this
是一个常规对象。类中所有非静态的方法都会被添加到 this
的原型中。
class P { constructor () { const proto = Object.getPrototypeOf(this) console.log(Object.getOwnPropertyNames(proto)) } first () {} second () {} static third () {} } new P() // [ 'constructor', 'first', 'second' ]
不像基类的构造函数,派生类的构造函数没有初始的 this
绑定。派生类不能在调用 super()
之前返回,除非其构造函数返回的是一个对象,或者根本没有构造函数。
class A extends P { } class B extends P { constructor() { return { a: 5 } } } class C extends P { constructor() { super() } } class D extends P { constructor() { } } new A() // ['constructor'] new B() // new C() // ['constructor'] new D() // ReferenceError: Must call super constructor in derived class before
对象的方法
当函数作为对象里的方法被调用时,this
被设置为调用该函数的对象。
const o = { prop: 37, f: function () { return this.prop } } console.log(o.f()) // 37
请注意,这样的行为完全不会受函数定义方式或位置的影响。如果对象的方法里面包含 this
,this
的指向就是方法运行时所在的对象。该方法赋值给另一个对象,就会改变this
的指向。
const o = { prop: 37 } function independent() { return this.prop } o.f = independent console.log(o.f()) // 37
this
的绑定只受最接近的成员引用的影响,如果 this
所在的方法不在对象的第一层,这时 this
只是指向当前一层的对象,而不会继承更上面的层。
a = { p: 1, b: { p: 2, m: function() { console.log(this, this.p) } } } a.b.m() // { p: 2, m: [Function: m] }, 2
但是,下面这几种用法,都会改变 this
的指向。
var o ={ n: function () { console.log(this) } }; o.n() // 情况一 (o.n = o.n)() // window // => (o.n = function () { console.log(this) })() // => (function () { console.log(this) })() // 情况二 (false || o.n)() // window // => (false || function () { console.log(this) })() // 情况三 (1, o.n)() // window // => (1, function () { console.log(this) })()
原型链
对于在对象原型链上某处定义的方法,同样的概念也适用。如果该方法存在于一个对象的原型链上,那么 this
指向的是调用这个方法的对象,就像该方法就在这个对象上一样。
const o = { f: function () { return this.a + this.b } } const p = Object.create(o) p.a = 1 p.b = 4 console.log(p.f()) // 5
getter 与 setter
再次,相同的概念也适用于当函数在一个 getter
或者 setter
中被调用。用作 getter
或 setter
的函数都会把 this
绑定到设置或获取属性的对象。
function sum() { return this.a + this.b + this.c } const o = { a: 1, b: 2, c: 3, get average() { return (this.a + this.b + this.c) / 3 } } Object.defineProperty(o, 'sum', { get: sum, enumerable: true, configurable: true }) console.log(o.average, o.sum) // 2, 6
使用注意项
this
的动态切换,为 JavaScript 创造了巨大的灵活性,但也使得编程变得困难和模糊。所以在使用的时候,需要特别注意以下几点。
注意多层this
由于 this
的指向是不确定的,所以切勿在函数中包含多层的 this
。
var o = { f1: function () { console.log(this) // {f1: ƒ} var f2 = function () { console.log(this) // Window }() } } o.f1()
f2
在编译的时候提升到全局,所以是 window
。如果要使用 o
作为 this
,可以在 f1
记录 this
然后使用。使用一个变量固定 this
的值,然后内层函数调用这个变量,是非常常见的做法,请务必掌握。
JavaScript 提供了严格模式,也可以硬性避免这种问题。严格模式下,如果函数内部的 this
指向顶层对象,就会报错。
const counter = { count: 0 } counter.inc = function () { 'use strict' this.count++ } const f = counter.inc f() // TypeError: Cannot read properties of undefined (reading 'count')
注意数组处理方法中的this
数组的 map
和 foreach
方法,允许提供一个函数作为参数。这个函数内部不应该使用 this
。
const o = { v: 'hello', p: [ 'a1', 'a2' ], f: function f() { this.p.forEach(function (item) { console.log(this, `${this.v} ${item}`) }) } } o.f() // window, undefined a1 // window, undefined a2
forEach
方法会调用数组中每一项的 toString
方法,所以 this
指向顶层对象 window
。解决办法同样可以记录 f
的 this
,然后使用。另一种方法是将 this
当作 foreach
方法的第二个参数,固定它的运行环境。
注意回调函数中的this
回调函数中的 this
往往会改变指向,最好避免使用。
var o = new Object() o.f = function () { console.log(this) // $('#button') } // jQuery 写法 $('#button').on('click', o.f)
点击按钮以后,此时 this
不再指向 o
对象,而是指向按钮的 DOM
对象,因为f方法是在按钮对象的环境中被调用的。为了解决这个问题,可以采用 call
、apply
、bind
方法对 this
进行绑定,也就是使得 this
固定指向某个对象,减少不确定性。
以上就是JavaScript关键字this的使用方法详解的详细内容,更多关于JavaScript关键字this的资料请关注脚本之家其它相关文章!