深入理解JS | 青训营笔记

40 阅读9分钟

JS 基本概念

  • 诞生:1995年,Brendan Eich开发
    1. 借鉴C语言的基本语法
    2. 借鉴Java语言的数据类型和内存管理
    3. 借鉴Scheme语言,将函数提升到"第一等公民”(first class) 的地位4.借鉴Self语言,使用基于原型 (prototype)的继承机制

image.png

  • 注意进程和线程的区别
  • GUI线程:绘制页面内容
  • JS线程:执行JS
  • JS是单线程的:GUI线程和JS线程是互斥的
    • 因为JS可以改变页面内容,故无法同时执行
    • GUI线程在渲染时,JS线程是停顿的;JS线程在计算时,GUI线程是停顿的

JS的特点

image.png

  • JS是动态语言

    • 动态语言(弱类型语言)
      • 运行时才确定数据类型的语言,变量在使用之前无需申明类型
      • eg. const company = 'Bytedance'
    • 静态语言(强类型语言)
      • 编译时变量的数据类型就需要确定的语言
      • eg. String company = 'Bytedance'
  • 纯函数: 在任何时间,用同样的参数,函数运行的结果都是一样的

数据类型

image.png

  • 7种 基本数据类型:不可变

    1. Undefined:只有一个值:undefined;当一个变量声明了,但尚未赋值时,其默认值为 undefined;表示变量的值尚未确定
    2. Null:只有一个值:null;空值或不存在的引用;变量没有被赋值,或表示一个空对象
    3. String:JavaScript 中,字符串是不可变的
    4. Number:整数和浮点数;在 JavaScript 中,所有数字都是双精度浮点数(64位)
    5. BigInt
    6. Boolean:true(真)和 false(假)
    7. Symbol:Symbol 类型的值是唯一且不可变的,主要用于创建对象的唯一属性名,以防止属性名冲突
  • Object 引用数据类型:可变

    • Object 类型用于表示复杂的数据结构,如对象、数组和函数。对象是键值对的集合,键是字符串,值可以是任何数据类型。数组是值的有序集合,而函数是可调用的代码块。
    • Object
    • Array
    • Date
    • Function
  • Examples

image.png

  • 结果:{name: '111'} {name: '111'}
    • 复杂类型存的是address,因此把a的地址传给了b,修改b也是修改a

image.png

  • 结果:11 22
    • 简单类型存的是value,只把a的值传给了b,修改b不会修改a

image.png

  • 结果:[1,2,3] strstrstr

  • JS中复杂类型可以被改变的基础类型不可被改变的

  • slice(start, end)

    • 提取字符串的某个部分 (start包含 ~ end不包含),并以新的字符串返回被提取的部分
    • 如果是负数,表示从尾部截取多少个字符串,slice(-2) 表示提取原数组中的倒数第二个元素到最后一个元素(包含)

作用域

  • JS是静态作用域 (Static Scoping)

image.png

  • 图三的第二个company未被定义 -> 报错 ReferenceError

变量提升 Hoisting

  • var有变量提升

  • let, const没有变量提升,提前访问会报错

  • function函数可以先调用再定义

    • 赋值给变量的函数无法提前调用

    • 下图代码会报错 TypeError

      image.png
  • 变量可以在声明之前进行初始化和使用

    • 只会提升声明,不会提升其初始化如果没有初始化,就不能使用它们 (undefined)

image.png

image.png

  • 实际上变量和函数声明在代码里的位置是不会动的,而是在编译阶段被放入内存中。(JS不是纯解释类语言,有编译过程:JS代码 -> 编译阶段 -> 执行阶段)

详细阅读:

变量提升

JS 的执行

image.png

  • JIT:重复出现的代码,存储其机器码,下次直接用

执行上下文 / 执行环境

image.png

  • 注意:词法环境 和 词法作用域 不一样,需要区分

三种执行上下文

  • 全局执行上下文
    • 默认/基础的上下文,任何不在函数内部的代码都在全局上下文中
    • 代码开始执行时就会创建,永远在执行栈的栈底
    • 它会创建一个全局的 window对象(浏览器的情况下),并且设置 this 的值等于这个全局对象。
    • 一个程序中只会有一个全局执行上下文。
  • 函数执行上下文
    • 每当一个函数被调用时, 都会为该函数创建一个新的执行上下文
    • 这个函数内的代码会被编译,生成变量环境、词法环境等,当函数执行结束的时候该执行环境从栈顶弹出
    • 每个函数都有它自己的执行上下文,是在函数被调用时创建的
    • 函数上下文可以有任意多个
  • Eval 函数执行上下文
    • 不经常使用

执行上下文的生命周期

  • 三个阶段:

    • 创建阶段
    • 执行阶段:执行变量赋值、代码执行
    • 回收阶段:执行上下文出栈,等待虚拟机回收执行上下文
  • 创建阶段 :会发生三件事

    1. This 绑定this 值的决定
    2. 创建词法环境组件 (LexicalEnvironment component)
      • 词法环境: 是一种规范类型,基于 ECMAScript 代码的词法嵌套结构来定义标识符具体变量和函数的关联。一个词法环境由环境记录器和一个可能的引用外部词法环境的空值组成。简单来说,词法环境是一种持有标识符—变量的映射的结构。
    3. 创建变量环境组件 (VariableEnvironment component)
      • 变量环境: 它同样是一个词法环境,其环境记录器持有变量声明语句在执行上下文中创建的绑定关系。变量环境和词法环境的一个不同就是前者被用来存储函数声明和变量(let 和 const)绑定而后者只用来存储var 变量绑定
  • Outer: 指向外部变量环境的一个指针

执行上下文栈/调用栈/执行栈(Execution Context Stack)

  • LIFO(后进先出)的栈,存储代码运行时创建的所有执行上下文
  • 当JS引擎第一次遇到脚本时,它会创建一个全局的执行上下文并且压入当前调用栈。每当引擎遇到一个函数调用,它会为该函数创建一个新的函数执行上下文并压入栈的顶部。引擎会执行执行上下文位于栈顶的函数。当该函数执行结束时,执行上下文从栈中弹出。

程序执行全过程

来源: 彻底搞懂作用域、执行上下文、词法环境

  • 程序启动,全局执行上下文被创建,压入调用栈

    1. 创建全局上下文的 词法环境

      1. 创建 对象环境记录器 ,它用来定义出现在 全局上下文 中的变量和函数的关系(负责处理 letconst 定义的变量)
      2. 创建 外部环境引用 (outer),值为 null
    2. 创建全局上下文的 变量环境

      1. 创建 对象环境记录器,它持有 变量声明语句 在执行上下文中创建的绑定关系(负责处理 var 定义的变量,初始值为 undefined 造成声明提升)
      2. 创建 外部环境引用,值为 null
    3. 确定 this 值为全局对象(以浏览器为例,就是 window

  • 函数被调用,函数执行上下文被创建,压入调用栈

    1. 创建函数上下文的 词法环境

      1. 创建 声明式环境记录器 ,存储变量、函数和参数,它包含了一个传递给函数的 arguments 对象(此对象存储索引和参数的映射)和传递给函数的参数的 length。(负责处理 letconst 定义的变量)
      2. 创建 外部环境引用,值为全局对象父级词法环境(作用域)
    2. 创建函数上下文的 变量环境

      1. 创建 声明式环境记录器 ,存储变量、函数和参数,它包含了一个传递给函数的 arguments 对象(此对象存储索引和参数的映射)和传递给函数的参数的 length。(负责处理 var 定义的变量,初始值为 undefined 造成声明提升)
      2. 创建 外部环境引用,值为全局对象父级词法环境(作用域)
    3. 确定 this

  • 进入函数执行上下文的执行阶段:

    1. 在上下文中运行/解释函数代码,并在代码逐行执行时分配变量值。

image.png

  • 更正:上图中func应该放在词法环境中

image.png

  • 基础数据类型:直接存在变量环境里
  • 复杂数据类型:在变量环境里存一个address,指向堆空间的内存

image.png

image.png

来源/推荐阅读

彻底搞懂作用域、执行上下文、词法环境

JS进阶

闭包 Closure

  • 定义:能够访问其他函数内部变量的函数,被称为闭包

    • 简单来说,闭包就是函数内部定义的函数,被返回了出去并在外部调用
  • 闭包是一种特殊的对象。它由两部分构成:函数,以及创建该函数的环境

    • 环境由闭包创建时在作用域中的任何局部变量组成。
  • 当一个函数被创建并传递或从另一个函数返回时,它会携带一个闭包,包含函数声明时作用域内的所有变量。

function createCounter() {
    let counter = 0
    const myFunction = function() {
      counter = counter + 1
      return counter
    }
    return myFunction
  }
  const increment = createCounter()
 const c1 = increment()
 const c2 = increment()
 const c3 = increment()
 console.log('example increment', c1, c2, c3)
  • 返回:1,2,3
    • 尝试 console.log(counter),得到undefined;为什么increment函数记住了counter的值?

    • 闭包!无论何时声明新函数并将其赋值给变量,都要存储函数定义和闭包。闭包包含在函数创建时作用域中的所有变量,它类似于背包。

image.png

  • 应用

    JS不支持私有方法,可以通过闭包来模拟。

    私有方法有利于限制对代码的访问,而且可以避免非核心的方法干扰代码的公共接口,减少全局污染

  • 问题

    有可能导致 内存泄露

来源/参考:

面试官:说说作用域和闭包吧

我从来不理解JavaScript闭包,直到有人这样向我解释它

什么是闭包?

this

  • 普通函数(包括嵌套函数):this指向windows
  • 对象调用的函数
    • 指向对象
    • 改变调用对象:apply()
    image.png
  • 构造函数
    • new 创建临时对象
    • 将this指向临时对象
    • 执行构造函数
    • 返回临时对象 image.png

垃圾回收

  • 栈的垃圾回收:ESP指针弹到下一个,之前的内存被覆盖

  • 堆的垃圾回收:

    • 新生代 & 老生代 机制不同 image.png

事件循环

image.png

image.png

print:
同步代码1 
同步代码2
同步代码3
promise.then
setTimeout

总结

image.png