初次接触 this
是在 c#
中,再后来的 JavaScript ,两者在 this
处理上非常相似。但是 JavaScript 是一种基于原型的编程语言,没有类的概念。意味着 this
将指向调用函数的对象,通常称为上下文。当然 this
不止于此,在函数内部的引用可以绑定到不同的对象,这得取决于函数是从哪里被调用。this
问题和变量函数提升是前端面试常见的问题,关于变量提升可以参阅《加深Javascript变量函数声明提升理解》
绑定
在 JavaScript 中,词法环境(Lexical Environments)是一种规范类型,用于根据ECMAScript代码的词法嵌套结构定义标识符与特定变量和函数的关联。一个词法环境由一个环境记录(Environment Record)和一个可能为空的外部词法环境的引用组成。通常,词法环境与ECMAScript代码的特定句法结构有关。例如函数申明、块语句、try
语句中的catch
等代码每次运算后会产生新的词法环境。
执行上下文(Execution Contexts)是一种规范设备,通过ECMAScript编译器来跟踪代码的运行时评估。在任何时候,每个代理(agent)最多只有一个正在执行代码的执行上下文。
每个执行上下文包含一个环境记录(Environment Record),当 JavaScript 引擎执行代码时,变量和函数名会被添加到环境记录(Environment Record)中,这就是绑定的基本概念,有助于将标识符(变量、函数名)与执行上下文的 this
关键字关联起来。
函数调用
这个很简单,this
是指调用的来源,在函数中的 this
指的是全局对象。
function callAuthor() { console.log(this.author); } callAuthor(); // undefined var author = "DevPoint"; callAuthor(); // DevPoint
在严格模式下,this.author
的值为 undefined
。
let & const
如果在全局级使用 let
或 const
声明变量,则它们不会存储在全局对象中。相反,在不可访问的声明性环境记录中,因此前面的示例在使用 const
时输出不一样的结果。
function callAuthor() { console.log(this.author); } const author = "DevPoint"; callAuthor(); // undefined window.author = "DevPoint"; callAuthor(); // DevPoint
隐式绑定
当函数引用有上下文对象时,隐式绑定规则会把函数中的this绑定到这个上下文对象。对象属性引用链中只有上一层或者说最后一层在调用中起作用。
const thisArticle = { title: "JavaScript", printTitle: function () { console.log(this.title); }, }; thisArticle.printTitle(); // JavaScript
如果对象只包含对 printTitle
调用函数的引用,可以获得相同的结果:
function printTitle() { console.log(this.title); } const thisArticle = { title: "JavaScript", printTitle: printTitle, }; thisArticle.printTitle(); // JavaScript
显式绑定
通过call
、apply
或bind
方法把对象绑定到this上,叫做显式绑定。
function article() { console.log(this.title); } const thisArticle = { title: "JavaScript", }; article.call(thisArticle); // JavaScript
call
和 apply
都执行相同的操作,两者的第一个论点应该是这指向什么。如果需要将额外的参数传递给被调用的函数,则会出现差异。
- 使用
call
时参数作以普通逗号分隔的参数列表传递 - 使用
apply
时参数以数组方式传递 - 使用
bind
将创建一个新函数并将其永久绑定到this
下面创建一个将 this
永久绑定到 thisArticle
的新函数,并将 article
重新分配给该新的永久绑定函数:
function article() { console.log(this.title); } const thisArticle = { title: "JavaScript", }; const newArticle = article.bind(thisArticle); newArticle(); // JavaScript
new绑定
new
关键字构造一个新对象, this
指向它。当使用 new
关键字将函数作为构造函数调用时, this
指向创建的新对象。
function Article(title) { this.title = title; } const thisArticle = new Article("JavaScript"); console.log(thisArticle.title); // JavaScript
箭头函数
使用箭头函数,this
保持与其父作用域相同的值。例如,箭头函数中的 this
值与外面 Article
函数中的 this
值保持一致:
function Article(title) { this.title = title; this.toUpper = () => { return this.title.toUpperCase(); }; } const thisArticle = new Article("JavaScript"); console.log(thisArticle.title); // JavaScript console.log(thisArticle.toUpper()); // JAVASCRIPT
结论
JavaScript 中的 this
是一个容器带来 Bug 的关键字,为了减少类似问题就需要加深对其 this
的理解。从上面的介绍来看,最佳的实践是尽量使用箭头函数。