深入浅出JavaScript执行机制(下)

前言

本文是【JavaScript专栏】系列文章。

前面两篇文章 深入浅出JavaScript执行机制(上)深入浅出JavaScript执行机制(中) 已经介绍了 【变量提升、JS 代码执行流、调用栈、块级作用域、作用域链、词法作用域、闭包】 等相关工作原理。本文将从 JavaScript 执行上下文的视角讲一下 this

引言

先看下一段代码,它的执行结果是什么:

js 复制代码
var bar = {
    myName: "juejin.cn",
    printName: function () {
        console.log(myName)
    }    
}
let myName = "juejin"
bar.printName()

根据前两篇文章的介绍,相信大家已经知道结果了,在 printName 函数里面使用的变量 myName 是属于全局作用域下面的,所以最终打印出来的值都是"juejin"。这是因为作用域链是由词法作用域决定的,而词法作用域是由代码结构来确定的。

但是按照常理来说,调用 bar.printName() 时,访问到的 myName 变量应该是 bar 对象中的,因为这是一个整体,大多数面向对象语言都是这样设计的。面向对象语言中:在对象内部的方法中使用对象内部的属性是一个非常普遍的需求 ,但是 JavaScript 的作用域机制并不支持这一点,基于这个需求,JavaScript 又设计出来另外一套 this 机制。

为了实现在 printName 函数中访问到 bar 对象的 myName 属性, printName 函数可以这样改写:

js 复制代码
 printName: function () {
   console.log(this.myName)
 }  

注意一点:作用域链this 是两套不同的系统,它们之间基本没太多联系。

JavaScript 中的 this 是什么

前面说了,执行上下文中包含 变量环境、词法环境、外部环境(outer),其实还有个 this。

图中可以看出,this 是和执行上下文绑定的,也就是说每个执行上下文中都有一个 this。

执行上下文分为:全局执行上下文、函数执行上下文、eval 执行上下文。所以对应的 this 有:全局执行上下文中的 this、函数执行上下文中的 this、eval 中的 this。

全局执行上下文中的 this

在控制台中输入console.log(this)来打印出来全局执行上下文中的 this,最终输出的是 window 对象.

所以:全局执行上下文中的 this 是指向 window 对象的。这也是 this 和作用域链的唯一交点,作用域链的最底端包含了 window 对象,全局执行上下文中的 this 也是指向 window 对象。

函数执行上下文中的 this

js 复制代码
function foo(){
  console.log(this)
}
foo()

foo 函数内部打印出来 this 值,也是 window 对象,这说明在默认情况下调用一个函数,其执行上下文中的 this 也是指向 window 对象的。但是可以通过以下的方式去设置函数执行上下文中的 this 值。

1、通过函数的 call 方法设置 this

通过函数的 call 方法来设置函数执行上下文的 this 指向。 如下:

js 复制代码
let bar = {
  myName : "zhihu",
  test1 : 1
}
function foo(){
  this.myName = "juejin"
}
foo.call(bar)
console.log(bar)   // 'juejin'
console.log(myName) // Uncaught ReferenceError: myName is not defined

从输出结果中看出:foo 函数内部的 this 已经指向了 bar 对象。bar.myName = 'juejin',同时在全局执行上下文中打印 myName,提示该变量未定义。

当然还可以使用 apply、bind 方法可以设置函数执行上下文的 this。

2、通过对象调用方式设置 this

js 复制代码
var myObj = {
  name : "juejin", 
  showThis: function(){
    console.log(this)
  }
}
myObj.showThis()

通过 myObj 对象来调用 showThis 方法,最终输出的 this 值是指向 myObj。

所以:使用对象来调用其内部的一个方法,该方法的 this 是指向对象本身的。

再来看下这段代码:

js 复制代码
var myObj = {
  name : "极客时间",
  showThis: function(){
    this.name = "极客邦"
    console.log(this)
  }
}
var foo = myObj.showThis
foo()

发现 this 又指向全局对象 window。

所以:在全局环境中调用一个函数,函数内部的 this 指向的是全局变量 window。

通过构造函数设置 this

js 复制代码
function CreateObj(){
  this.name = "juejin"
}
var myObj = new CreateObj()

使用 new 关键字创建了对象 myObj,此时的构造函数 CreateObj 中的 this 到底指向了谁?

new CreateObj(),主要做了以下工作:

  • 创建一个新对象 obj
  • 将构建函数中的this绑定到新建的对象obj上;
  • 返回新对象 obj
js 复制代码
  var obj = {}
  CreateObj.call(obj)
  return obj

所以,此时的构造函数 CreateObj 中的 this 指向新对象本身。

this 的设计缺陷

分析一下以下代码:

js 复制代码
var myObj = {
  name : "juejin", 
  showThis: function(){
    console.log(this)
    function bar(){console.log(this)}
    bar()
  }
}
myObj.showThis()

可能会很自然地觉得,bar 中的 this 应该和其外层 showThis 函数中的 this 是一致的,都是指向 myObj 对象的,这很符合人的直觉.

但实际情况是:函数 bar 中的 this 指向的是全局 window 对象,而函数 showThis 中的 this 指向的是 myObj 对象。

所以这是 this 的一点设计缺陷:嵌套函数中的 this 不会从外层函数中继承。

可以通过两种方式去解决这个问题:

1、声明一个变量 self 用来保存 this

js 复制代码
var myObj = {
  name : "zhihu", 
  showThis: function(){
    console.log(this)
    const self = this
    function bar(){
      self.name = "juejin"
    }
    bar()
  }
}
myObj.showThis()
console.log(myObj.name)
console.log(window.name)

最终 myObj 中的 name 属性值变成了"juejin"。其实,这个方法的的本质是把 this 体系转换为了作用域的体系

2、使用箭头函数;

js 复制代码
var myObj = {
  name : "zhihu", 
  showThis: function(){
    console.log(this)
    const bar = ()=> {
      this.name = "juejin"
      console.log(this)
    }
    bar()
  }
}
myObj.showThis()
console.log(myObj.name)
console.log(window.name)

使用 ES6 中的箭头函数也能得到我们想要的结果,也就是箭头函数 bar 里面的 this 是指向 myObj 对象的。这是因为 ES6 中的箭头函数并不会创建其自身的执行上下文,所以箭头函数中的 this 取决于它的外部函数。

总结

  • 全局执行上下文中的 this,指向 window 对象;
  • 函数执行上下文中的 this:
    • 当函数被正常调用时,在严格模式下,this 值是 undefined,非严格模式下 this 指向的是全局对象 window;
    • 当函数作为对象的方法调用时,函数中的 this 就是该对象, 如: obj.foo() ;
    • 嵌套函数中的 this 不会继承外层函数的 this 值;
  • 设置函数中的 this:
    • 通过调用函数的 call、apply、bind 方法可以改变 this 的指向;
    • 通过对象调用方法来设置;
    • 通过构造函数来设置;
  • 箭头函数没有自己的执行上下文,所以箭头函数的 this 就是它外层函数的 this;
相关推荐
Darling02zjh27 分钟前
GUI图形化演示
前端
Channing Lewis30 分钟前
如何判断一个网站后端是用什么语言写的
前端·数据库·python
互联网搬砖老肖40 分钟前
Web 架构之状态码全解
前端·架构
showmethetime1 小时前
matlab提取脑电数据的五种频域特征指标数值
前端·人工智能·matlab
码农捻旧1 小时前
解决Mongoose “Cannot overwrite model once compiled“ 错误的完整指南
javascript·数据库·mongodb·node.js·express
淡笑沐白1 小时前
探索Turn.js:打造惊艳的3D翻页效果
javascript·html5·turn.js
sunxunyong2 小时前
yarn任务筛选spark任务,判断内存/CPU使用超过限制任务
javascript·ajax·spark
Ynov2 小时前
详细解释api
javascript·visual studio code
左钦杨2 小时前
IOS CSS3 right transformX 动画卡顿 回弹
前端·ios·css3
NaclarbCSDN2 小时前
Java集合框架
java·开发语言·前端