深入浅出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;
相关推荐
子兮曰16 小时前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰16 小时前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万16 小时前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝16 小时前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋17 小时前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁18 小时前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
李少兄18 小时前
JavaScript 隐式全局变量解析
javascript
汉堡大王952719 小时前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大19 小时前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师20 小时前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端