day6 深入理解JS | 青训营笔记

120 阅读5分钟

基本概念

数据类型

基础类型

  • number
  • string
  • null
  • undefined
  • boolean
  • symbol
  • bigInt

引用类型

  • object
  • array
  • function
  • date
  • regExp

作用域

深入理解JavaScript作用域和作用域链

什么是作用域

作用域决定了代码区块中变量和其他资源的可访问性和可见性
作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。
js采用的是静态作用域,在函数定义时就决定了作用域。

分类

  • 全局作用域
  • 函数作用域
  • 块级作用域(可用letconst声明,变量不提升,变量禁止重复声明)

变量提升:指定义时提升到顶部,针对var和函数,但如果是函数赋值给变量,则无法提前调用。

JS如何执行

JavaScript的执行分为:解释和执行两个阶段,这两个阶段所做的事并不一样:

解释阶段

  • 词法分析
  • 语法分析
  • 作用域规则确定

执行阶段

  • 创建执行上下文
  • 执行函数代码
  • 垃圾回收

JavaScript解释阶段便会确定作用域规则,因此作用域在函数定义时就已经确定了,而不是在函数调用时确定,但是执行上下文是函数执行之前创建的。

作用域和执行上下文之间最大的区别是: 执行上下文在运行时确定,随时可能改变;作用域在定义时就确定,并且不会改变

执行上下文

执行上下文三个重要属性

  • 变量对象(Variable object,VO)
  • 作用域链
  • this

执行上下文栈

JavaScript深入之执行上下文栈

JavaScript 引擎创建了执行上下文栈(Execution context stack,ECS)来管理执行上下文。
初始化的时候首先就会向执行上下文栈压入一个全局执行上下文,只有当整个应用程序结束的时候,ECStack 才会被清空,所以程序结束之前, ECStack 最底部永远有个 全局上下文。
当执行一个函数的时候,就会创建一个执行上下文,并且压入执行上下文栈,当函数执行完毕的时候,就会将函数的执行上下文从栈中弹出。

变量对象

JavaScript深入之变量对象

变量对象是与执行上下文相关的数据作用域,存储了在上下文中定义的变量和函数声明。

  1. 全局上下文的变量对象初始化是全局对象
  2. 函数上下文的变量对象初始化只包括 Arguments 对象
  3. 在进入执行上下文时会给变量对象添加形参函数声明变量声明等初始的属性值
  4. 在代码执行阶段,会再次修改变量对象的属性值

作用域链

# JavaScript深入之作用域链

当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。

函数的作用域在函数定义的时候就决定了。

这是因为函数有一个内部属性 [[scope]],当函数创建的时候,就会保存所有父变量对象到其中,你可以理解 [[scope]] 就是所有父变量对象的层级链,但是注意:[[scope]] 并不代表完整的作用域链!

举个例子:

function foo() {
    function bar() {
        ...
    }
}
复制代码

函数创建时,各自的[[scope]]为:

foo.[[scope]] = [
  globalContext.VO
];

bar.[[scope]] = [
    fooContext.AO,
    globalContext.VO
];
复制代码

当函数激活时,进入函数上下文,创建 VO/AO 后,就会将活动对象添加到作用链的前端。

这时候执行上下文的作用域链,我们命名为 Scope:

Scope = [AO].concat([[Scope]]);
复制代码

至此,作用域链创建完毕。

this

  • 普通函数指向window
  • 对象调用时指向对象

new 一个对象时发生了什么?

  1. 创建了临时对象
  2. 将this指向临时对象
  3. 执行构造函数
  4. 返回临时对象

JS进阶

闭包

JavaScript 深入之闭包

在某个内部函数的执行上下文创建时,会将父级函数的活动对象加到内部函数的 [[scope]] 中,形成作用域链,所以即使父级函数的执行上下文销毁(即执行上下文栈弹出父级函数的执行上下文),但是因为其活动对象还是实际存储在内存中可被内部函数访问到的,从而实现了闭包。

垃圾回收

两种策略

  • 标记清除
  • 引用计数

标记清除

标记阶段把所有的活动对象做上标记,清除阶段把所有没有标记的活动对象销毁。

缺点: 存在内存碎片,分配速度慢。
改进: 标记整理。会将活着的对象向内存一端整理,清理掉内存的边界。

引用计数

跟踪记录每个变量被引用的次数,当变量的引用次数为0时进行回收。

缺点:计数器占内存,不知道被引用次数的上限;循环引用无法回收。

事件循环

先执行同步任务,再执行异步任务,同步任务结束后,会先执行微任务,再执行宏任务。