Skip to content

JavaScript 底层

JavaScript is a

  • high-level
  • garbage-collected
  • interpreted or just-in-time compiled 解释或即时编译
  • multi-padadigm 多范式:过程式编程 + 函数式编程 + 面向对象式编程
  • prototype-based; object oriented
  • first-class functions
  • dynamic
  • single-threaded
  • non-blocking event loop

programming language.

编译与解释

编译

源码通过编译变为机器码,编译后的文件可以在计算机上被执行

解释

解释器一行一行地去将代码编译为机器码并直接运行。

即时编译

曾经的 JavaScript 是一种纯解释性语言,但是解释型语言存在的问题是通常太慢了。

现在的 JavaScript 混合了解释与编译,被称为即时编译。也就是全部编译,然后立刻执行。但不会产生可执行文件。

  1. 解析代码 -> AST抽象语法树,和 DOM 树没有关系
  2. 编译代码 -> AST -> 机器语言01
  3. 执行:发生在调用栈

运行时

运行时就像是一个盒子,装着运行 JavaScript 程序所需的所有内容。

  • JavaScript 引擎:包含调用栈与堆
  • Web API
  • 事件循环

JavaScript 引擎

JavaScript 引擎就是运行 JavaScript 代码的环境。常见的有谷歌浏览器的 v8 引擎,nodejs 环境。

无论哪个引擎一定会包含的两个部份为 调用栈(call stack)堆(heap)

调用栈是我们代码执行的地方,也就是存储执行上下文的地方。调用栈能让我们知道当前代码执行到哪里了。

堆是对象存储的地方,是一个非结构化的存储空间,存储了我们应用程序需要的所有对象。

执行上下文

Execution context:Environment in which a piece of JavaScript is executed. Stores all the necessary information for some code to be executed.

执行上下文(Execution context)

执行 JavaScript 的环境。存储执行某些代码所需的所有信息。

JavaScript 代码总是在执行上下文中被执行。

最上层的代码的执行上下文是全局执行环境,它们不位于任何函数内部。

执行上下文中有什么

  1. 环境变量 variable environment:
    1. let, const, var 声明
    2. 函数 Functions
    3. arguments object 参数对象
  2. Scope chain 作用域链
  3. this

要注意的是,箭头函数没有自己的 arguments objectthis 关键字,箭头函数的这两个属于其最近的父级。

作用域

三种作用域

  • 全局作用域:函数或块外面
  • 函数作用域:函数内部
  • 块作用域:iffor 内部等,只对 let const 生效

作用域链

作用域有权访问其外部的所有变量。

这就是可以在任意位置访问全局作用域中的变量的原因。

作用域链与调用栈

调用栈中按被调用顺序存放函数的执行上下文。

提升 hoisting

变量提升(Hoisting)是 JavaScript 中的一个行为,指的是在代码执行之前,函数声明和变量声明会被提升到其所在作用域的顶部。这意味着你可以在声明之前使用这些变量或函数,但它们的值在提升后未被赋值时为 undefined

函数表达式和箭头函数是否被提升取决于它们存储到什么类型(let, const, var )的变量中,毕竟,它们都是变量。

var 声明的变量的作用域是函数作用域,其余都是块级作用域。

变量提升的顺序

  1. 函数声明提升:函数声明会被优先提升到最顶层。这意味着你可以在函数定义之前调用它。
  2. 变量声明提升:变量声明会被提升到作用域的顶部,但它们不会被初始化为 undefined 直到实际的赋值语句执行时。如果变量是使用 var 声明的,提升时不会改变顺序。如果使用 letconst 声明的变量,它们会被提升但不会被初始化,导致在声明前访问这些变量会引发 ReferenceError

this 关键字

this keyword is a special variable that is created for every execution context(every function). Takes the value of the "owner" of the function in which the this keyword is used.

TIP

this 关键字是为每个执行上下文创建的特殊变量。它的值是使用了 this 关键字的函数的所有者的值。

this 的值不是固定的,它取决于函数是如何被调用的,并且它的值只有在函数真正被调用的时候才被分配(assigned)。

因此需要明确函数的调用:

  1. 函数作为方法被调用: this 指向调用该方法的对象。
  2. 简单(常规)函数的调用:在严格模式下 this = undefined
  3. 箭头函数:箭头函数没有自己的 this 关键字,它的this 关键字属于其父函数(词法的this)。
  4. 事件监听器:this 指向绑定的事件处理函数所绑定的 DOM 元素。
  1. new, call, apply, bind

this 不会指向函数本身,也不会指向他的变量环境,而是当它被调用时,调用它的那个对象;因此,再次理解,this 的值只有在函数真正被调用的时候才被分配。

常规函数与箭头函数

首先来看一个例子:

js
const jonas = {
    firstName: "jonas",
    year: 1991,
    calAge: function () {
        console.log(2037 - this.year);
    },
    greet: () => console.log(`Hey, ${this.firstName}`),
}

jonas.greet();

在控制台上打印的结果为: Hey, undefined 。这是由于箭头函数的 this 关键字指向其父亲,在这个函数中就是 window 的 this 关键字,还要注意的是,上面代码中大括号包围的并不是一个代码块,只是一个对象的字面量。

永远不要用箭头函数作为方法。

js
'use strict'
const jonas = {
    firstName: 'jonas',
    year: 1991,
    calAge: function(){
        console.log(2037 - this.year);
        const isMillenial = function(){
            console.log(this.year >= 1981 && this.year <= 1996);
        }
        isMillenial();// 作为一个普通函数而不是方法被调用,因此 `this = undefined`(严格模式下)
    }
}

jonas.calAge();

在严格模式下运行上述代码的时候会报错,常规函数的调用 this = undefined

js
'use strict'
const jonas = {
    firstName: 'jonas',
    year: 1991,
    calAge: function(){
        console.log(2037 - this.year);

        const self = this; // 保存 this 关键字
        const isMillenial = function(){
            console.log(self.year >= 1981 && self.year <= 1996);
        }
        isMillenial();// 作为一个普通函数而不是方法被调用,因此 `this = undefined`(严格模式下)
    }
}

jonas.calAge();

通过 const self = this 来保存 this 关键字,可以保留执行上下文。这是 ES6 之前常用的方法,现在可以通过箭头函数来解决这个问题。我们都知道箭头函数的 this 关键字继承它的父作用域,因此可以利用这一层关系,如下:

js
const jonas = {
    firstName: 'jonas',
    year: 1991,
    calAge: function () {
        console.log(2037 - this.year);

        const isMillenial = () => { // 箭头函数
            console.log(this.year >= 1981 && this.year <= 1996);
        }
        isMillenial();// 作为一个普通函数而不是方法被调用,因此 `this = undefined`(严格模式下)
    }
}

jonas.calAge();

isMillenial 中的 this 关键字继承其父作用域即 calAge 中的 this 关键字,在这里也就是 jonas

上面是理解 this 关键字的非常有效的方法,可以多多回顾。

arguments 关键字

arguments 只在常规函数中存在,即不包含箭头函数的其他函数。