深入浅出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;
相关推荐
天下无贼!1 小时前
2024年最新版Vue3学习笔记
前端·vue.js·笔记·学习·vue
Jiaberrr1 小时前
JS实现树形结构数据中特定节点及其子节点显示属性设置的技巧(可用于树形节点过滤筛选)
前端·javascript·tree·树形·过滤筛选
赵啸林1 小时前
npm发布插件超级简单版
前端·npm·node.js
我码玄黄2 小时前
THREE.js:网页上的3D世界构建者
开发语言·javascript·3d
罔闻_spider2 小时前
爬虫----webpack
前端·爬虫·webpack
吱吱鼠叔2 小时前
MATLAB数据文件读写:1.格式化读写文件
前端·数据库·matlab
爱喝水的小鼠2 小时前
Vue3(一) Vite创建Vue3工程,选项式API与组合式API;setup的使用;Vue中的响应式ref,reactive
前端·javascript·vue.js
小晗同学2 小时前
Vue 实现高级穿梭框 Transfer 封装
javascript·vue.js·elementui
盏灯2 小时前
前端开发,场景题:讲一下如何实现 ✍电子签名、🎨你画我猜?
前端
WeiShuai2 小时前
vue-cli3使用DllPlugin优化webpack打包性能
前端·javascript