你不知道的JavaScript - this
有关 JavaScript 流传最广、最持久的不实论点是,关键字
this
指向它所在的函数。这简直错的离谱。
本文是一篇读书笔记,对应《你所不知道的JavaScript(上卷)》—— “this 和对象原型”中的 this
部分。
什么是 this
this
是一个特别的关键字,被自动定义在所有函数的作用域中。它提供了一种优雅的方式来隐式“传递”一个对象引用,因此可以将 API 设计的更加简洁并且易于复用。
对于刚接触 JS 的朋友来说,经常会理解为 this
指向函数自身,其实这并不是准确的。
this
是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this
的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。
当一个函数被调用时,会创建一个活动记录(执行上下文)。这个纪录会包含函数在哪里被调用、函数的调用方式、传入的参数等信息。this
就是这个纪录的一个属性,会在函数执行的过程中用到。
this 的绑定规则
分析 this
的绑定时,要时刻记住 this
是在调用时被绑定的,其绑定规则完全取决于函数的调用位置。而调用位置实际就是在当前正在执行的函数的前一个调用中。
下面将介绍 this
的四种绑定规则:
1. 默认绑定
当 this
所属的函数作为独立函数调用时,采用默认绑定规则:在非严格模式下,this
指向全局变量(window
、global
);在严格模式下,this
会绑定到 undefined
。
1 | var a = 2; |
2. 隐式绑定
当函数引用有上下文对象时,将采用隐式绑定规则:this
将绑定到这个上下文对象。
1 | var a = 1; |
注意,这存在一个隐式丢失的问题:被隐式绑定的 this
会丢失绑定对象,也就是说它会应用默认绑定,从而把 this
绑定到全局对象或者 undefined
。
1 | function foo() { |
类似上述代码这种情况,通过参数传递、赋值等方式,被赋值的变量引用的是原函数本身(上述为 foo
),调用时不再带有任何的上下文修饰,因此会应用回默认绑定。
3. 显示绑定
通过使用函数原型的 call(...)
、apply(...)
方法,可以直接指定 this
的绑定对象,因此我们称之为“显示绑定”。
1 | function foo() { |
扩展 —— 硬绑定
硬绑定是一种显式的强制绑定,其典型的应用场景就是创建一个包裹函数,负责接收参数并返回值。
1 | function bind(fn, obj) { |
4. new 绑定
在传统的面向对象函数中,“构造函数” 是类中的一些特殊方法,使用 new
初始化类时会调用类中的构造函数。
但是,在 JS 中的并不是这样定义的,JS 中,构造函数只是一些使用 new
操作符时被调用的函数,它们不会属于某个类,也不会实例化一个类。
可以这么说,当一个函数通过 new
操作符调用时,可以将它称之为 “构造函数”;不通过 new
调用时,它就是一个普通函数。在 JS 中,并不存在所谓的 ”构造函数“,只有对与函数的 ”构造调用“。
当使用 new
来调用函数时,会自动执行以下操作:
- 创建(构造)一个全新的对象
- 这个新对象会被执行 [[Prototype]] 连接
- 这个新对象会绑定到函数调用的
this
- 如果函数没有返回其他对象,那么
new
表达式中的函数调用会自动返回这个新对象。
简而言之,通过 new
调用的函数,会将 this
绑定到新创建的对象。
一个问题,在构造函数 prototype
里定义的函数中的 this
指向谁呢?
答案依然是通过 new
创建出来的对象本身。其实,不仅仅是构造函数的 prototype
,即使是在整个原型链中,this
代表的也都是指向 new
创建的对象。
判断 this
了解了 this
的四种绑定规则后,我们总结一下 this
的判断顺序(优先级):
- 函数是否通过
new
调用,如果是的话this
绑定的是新创建的对象。(new 绑定) - 函数是否通过
call
、apply
或者硬绑定调用,如果是的话,this
绑定的是指定的对象。(显示绑定) - 函数是否在某个上下文对象中调用,是的话,
this
绑定的是那个上下文对象。(隐式绑定) - 如果都不是的话,采用默认绑定规则。
特殊情况
- 将
null
、undefined
作为this
的绑定对象传入call
、apply
或者bind
时,这些值在调用时将被忽视,采用默认绑定规则。(这种经常会出现在不需要关心this
绑定对象的情况,null
、undefined
只是作为call
、apply
的第一个参数占用符出现。比如在函数的柯里化中就经常出现) - 函数的间接引用。如上文所述的隐式丢失问题,将函数通过赋值等方式赋给另一个变量,从而该变量获得了函数的间接引用。
箭头函数
ES6 提供了一种特殊的函数类型:箭头函数。在箭头函数内,上述介绍的四种 this
绑定规则均无法适用。在箭头函数中,他仅根据外层(函数或者全局)作用域来决定 this
。
1 | function foo() { |
箭头函数内的 this
指向可以理解为继承箭头函数所处的作用域中的 this
的指向。如上例中注释的 var self = this
,这是我们之前经常采用的一种写法,而箭头函数中的 this
实际就是替代这里的 self
。
还需要注意的一点是,箭头函数的 this
无法通过其他方式进行修改(apply
、call
、new
等都不行)。