JavaScript 开发人员应该理解的 this

JavaScript 开发人员应该理解的 this

初次接触 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

如果在全局级使用 letconst 声明变量,则它们不会存储在全局对象中。相反,在不可访问的声明性环境记录中,因此前面的示例在使用 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

显式绑定

通过callapplybind 方法把对象绑定到this上,叫做显式绑定。

function article() {
    console.log(this.title);
}

const thisArticle = {
    title: "JavaScript",
};

article.call(thisArticle); // JavaScript

callapply 都执行相同的操作,两者的第一个论点应该是这指向什么。如果需要将额外的参数传递给被调用的函数,则会出现差异。

  • 使用 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 的理解。从上面的介绍来看,最佳的实践是尽量使用箭头函数。