专栏首页小码农学习笔记一文看懂 JavaScript 的 this 指向
原创

一文看懂 JavaScript 的 this 指向

this 指向

this 的定义

红宝书(第3版):this 对象是在运行时基于函数的执行环境绑定的:在全局函数中,this 等于 window,而当函数被作为某个对象的方法调用时,this 等于那个对象。(P182)

小黄书(上):this 是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this 既不指向函数自身也不指向函数的词法作用域(P80)

MDN:在绝大多数情况下,函数的调用方式决定了 this 的值。(原链接

概括一下:

this 是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。

当一个函数被调用时,会创建一个执行上下文。这个执行上下文会包含函数在哪里被调用(调用栈)、函数的调用方式、传入的参数等信息。this 就是这个执行上下文的一个属性,会在函数执行的过程中用到。

this 的作用

this 的诞生主要是因为在对象内部的方法中使用对象内部的属性是一个非常普遍的需求

举个例子来说明:

var mySite = { 
  name: "www.fedbook.cn",
  printName: function () {
    console.log(name);
  }    
};

let name = "前端修炼小册";
mySite.printName();

::: details 输出结果

"前端修炼小册"

:::

::: details 代码分析

由于 mySite 不是一个函数,因此 mySite 当中的 printName 其实是一个全局声明的函数,mySite 当中声明的 name 只是对象的一个属性,也和 printName 没有联系。因此,printName 会通过词法作用域链去它声明的环境(也就是全局环境)中查找 name

:::

不过按照常理来说,调用 mySite.printName 方法时,方法内部的变量 name 应该使用 mySite 对象中的,因为它们是一个整体,事实上大多数面向对象语言都是这样设计的。

基于这个需求,JavaScript 就搞出来一套 this 机制。在 JavaScript 中可以使用 this 实现在 printName 函数中访问到 mySite 对象的 name 属性了。

调整后的代码,如下所示:

var mySite = { 
  name: "www.fedbook.cn",
  printName: function () {
    console.log(this.name); // 修改了这一行
  }    
}

let name = "前端修炼小册";
mySite.printName();

::: details 输出结果

"www.fedbook.cn"

:::

接下来进一步学习 this,不过在这之前需要强调一句,作用域链和 this 是两套不同的系统,它们之间基本没太多联系。明确了这点,可以避免你在学习 this 的过程中,和作用域产生一些不必要的关联。

如何寻找函数的调用位置

既然 this 的指向完全取决于函数在哪里被调用,我们就要学会寻找函数的调用位置,从而判断函数在执行过程中会如何绑定 this

通过浏览器调试工具查找

最简单的方法是,通过浏览器的调试工具查看调用栈:给目标函数的第一行代码设置断点,或者直接在目标函数的第一行代码之前插入一条 debugger 语句。运行代码时,调试器会在那个位置暂停,同时会展示当前位置的函数调用列表,这就是你的调用栈,然后找到栈中第二个元素,这就是真正的调用位置。

举个例子,见下方代码,我们要寻找 foo 函数的调用位置。

function baz() { 
  console.log("baz");
  bar();
}
function bar() {
  console.log("bar");
  foo();
}
function foo() {
  debugger;
  console.log("foo");
}

baz();

使用浏览器的调试工具来查找 foo 函数的调用位置,如下图所示:

通过分析代码查找

也可以通过阅读代码进行分析,方法是把调用栈想象成一个函数调用链。只不过这种方法非常麻烦并且容易出错,下面的代码演示了这种分析过程。

function baz() {
  // 当前的调用栈是:baz
  // 因此,当前调用位置是全局作用域
  console.log("baz");
  bar(); // <-- bar 的调用位置
}

function bar() {
  // 当前的调用栈是:baz -> bar
  // 因此,当前调用位置在 baz 中
  console.log("bar");
  foo(); // <-- foo 的调用位置
}

function foo() {
  // 当前的调用栈是:baz -> bar -> foo
  // 因此,当前调用位置在 bar 中
  debugger;
  console.log("foo");
}

baz(); // <-- baz 的调用位置

在调用位置查找 this 绑定对象

找到函数的调用位置后,按照下面的步骤,就可以判断出 this 的绑定对象。

  • Step1 函数是否在 new 中调用(new 绑定)?如果是的话 this 绑定的是新创建的对象。
var bar = new foo();
  • Step2 函数是否通过 callapply(显式绑定)或者硬绑定调用?如果是的话 this 绑定的是指定的对象。
var bar = foo.call(obj2);
  • Step3 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话 this 绑定的是那个上下文对象。
var bar = obj1.foo();
  • Step4 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到 undefined,否则绑定到全局对象。
var bar = foo();

this 的缺陷以及应对方案

this 在使用过程中存在着非常多的坑,下面举两个例子。

嵌套函数中的 this 不会从外层函数中继承

先看下面一段代码,试分析两次 this 打印出来是什么?

var mySite = {
  name : "www.fedbook.cn", 
  showThis: function(){
    console.log(this);
    function printName(){ console.log(this) };
    printName();
  }
};
mySite.showThis()

::: details 输出结果

▸ {name: "www.fedbook.cn", showThis: ƒ}
▸ Window {parent: Window, opener: null, top: Window, length: 0, frames: Window, …}

:::

执行这段代码后,会发现函数 printName 中的 this 指向的是全局 window 对象,而函数 showThis 中的 this 指向的是 mySite 对象。这就是 JavaScript 中非常容易让人迷惑的地方之一,在实际项目开发中也是很多问题的源头。

要解决这个问题,可以有两种思路:

  • 第一种:把 this 保存为一个 self 变量,再利用变量的作用域机制传递给嵌套函数。
  • 第二种:继续使用 this,但是要把嵌套函数改为箭头函数,因为箭头函数没有自己的执行上下文,所以箭头函数的 this 就是它外层函数的 this

普通函数中的 this 默认指向全局对象 window

在默认情况下调用一个函数,其执行上下文中的 this 是默认指向全局对象 window 的。

不过在实际工作中,有时候我们并不希望函数执行上下文中的 this 默认指向全局对象,因为这样会打破数据的边界,造成一些误操作。如果要让函数执行上下文中的 this 指向某个对象,最好的方式是通过 call 方法来显示调用。

这个问题也可以通过设置 JavaScript 的「严格模式」来解决。在严格模式下,默认执行一个函数,其函数的执行上下文中的 this 值是 undefined,这就解决上面的问题了。


文章持续更新,本文 GitHub 前端修炼小册 已经收录,欢迎 Star。如对文章内容有不同见解,欢迎留言交流。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 一文看懂Javascript的this关键字

    记住一条:当function被作为method调用时,this指向调用对象。另外,JavaScript并不是OO的,而是object based的一种语言。

    JavaEdge
  • 一文看懂Javascript的this关键字

    记住一条:当function被作为method调用时,this指向调用对象。另外,JavaScript并不是OO的,而是object based的一种语言。

    JavaEdge
  • JavaScript this的指向

    在 JavaScript 中 this 取什么值,是在函数真正被调用执行的时候确定的,函数定义的时候确定不了,因为 this 的取值是执行上下文环境的一部分,每...

    Nian糕
  • Javascript中的this指向

    多层嵌套的对象,内部方法的this指向离被调用函数最近的对象(window也是对象,其内部对象调用方法的this指向内部对象, 而非window);

    超级大帅比
  • (鸡汤文)这一次我终于搞懂了 JavaScript 定时器的 this 指向!

    忽然有一种感觉,每次学习一个知识点就像是谈一场恋爱:从初次邂逅,到彼此了解,一切都那么的符合恋爱的过程!

    隐逸王
  • javascript-this的指向的问题

    javasript函数中this的指向一直都是许多编程入门新手的一个问题,老师把这个this的指向弄错误。下面我们可以来看看关于this指向的几种情况。

    踏浪
  • JavaScript中的this指向问题

    this一般指向的是调用它的对象,比如调用它的上下文是window对象,那就是指向window对象,如果调用它的上下文是某对象就是指向某对象……

    全栈开发Dream
  • 秒懂——惹人烦恼的this指向

      This是什么?什么是this指针?this指针指向哪里?何时使用this? useornotuse,thatisa question.  其中至关重要的 ...

    流眸
  • 第149天:javascript中this的指向详解

    js中的this指向十分重要,了解js中this指向是每一个学习js的人必学的知识点,今天没事,正好总结了js中this的常见用法,喜欢的可以看看:

    半指温柔乐
  • 深入理解JavaScript的this指向问题

    this是Javascript语言的一个关键字。 它代表函数运行时,自动生成的一个内部对象,只能在函数内部使用。比如:

    小周sri的码农
  • 一文看懂 JavaScript 执行上下文

    一段 JavaScript 代码在执行之前需要被 JavaScript 引擎编译,编译完成之后,才会进入执行阶段。

    文渊同学
  • 挖坑无止境,来看看这个《this的指向》

    无事乱翻书,偶然发现这个东西: var length = 10; function fn() { console.log(this.length); }...

    web前端教室
  • 在javascript中对于this指向的再次理解

    总所周知,function () {}函数体内的this对象指向的是调用该函数的对象,那么我们看一下这个例子

    Theone67
  • 《JavaScript函数式编程》的读后总结二:this指向

    解释:等同于window.a(),而this指向的是在函数执行时最终调用它的那个对象,在本例中就是window调用的,而window对象中又没有userName...

    前端_AWhile
  • 每个JavaScript工程师都应懂的33个概念

    这个项目是为了帮助开发者掌握 JavaScript 概念而创立的。它不是必备,但在未来学习(JavaScript)中,可以作为一篇指南。

    Fundebug
  • 每个JavaScript工程师都应懂的33个概念

    这个项目是为了帮助开发者掌握 JavaScript 概念而创立的。它不是必备,但在未来学习(JavaScript)中,可以作为一篇指南。

    Fundebug
  • 一文看懂 JavaScript 闭包 - 闭包是什么

    在 JavaScript 中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行...

    文渊同学
  • javascript基础修炼(2)——What's this(上)

    this是javascript关键字之一,是javascript能够实现面向对象编程的核心概念。用得好能让代码优雅高端,风骚飘逸,用不好也绝对是坑人坑己利器。我...

    大史不说话
  • JS面向对象二:this/原型链/new原理

    也可以看看这篇文章周大侠啊 进击的 JavaScript(六) 之 this先了解一下`this的四种绑定规则和箭头函数的this绑定

    代码之风

扫码关注云+社区

领取腾讯云代金券