深入浅出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;
相关推荐
「、皓子~18 分钟前
后台管理系统的诞生 - 利用AI 1天完成整个后台管理系统的微服务后端+前端
前端·人工智能·微服务·小程序·go·ai编程·ai写作
就改了20 分钟前
Ajax——在OA系统提升性能的局部刷新
前端·javascript·ajax
凌冰_22 分钟前
Ajax 入门
前端·javascript·ajax
京东零售技术37 分钟前
京东小程序JS API仓颉改造实践
前端
奋飛1 小时前
TypeScript系列:第六篇 - 编写高质量的TS类型
javascript·typescript·ts·declare·.d.ts
老A技术联盟1 小时前
从小白入门,基于Cursor开发一个前端小程序之Cursor 编程实践与案例分析
前端·小程序
风铃喵游1 小时前
构建引擎: 打造小程序编译器
前端·小程序·架构
sunbyte1 小时前
50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | ThemeClock(主题时钟)
前端·javascript·css·vue.js·前端框架·tailwindcss
小飞悟1 小时前
🎯 什么是模块化?CommonJS 和 ES6 Modules 到底有什么区别?小白也能看懂
前端·javascript·设计
浏览器API调用工程师_Taylor1 小时前
AOP魔法:一招实现登录弹窗的全局拦截与动态处理
前端·javascript·vue.js