深入理解js | 青训营笔记

50 阅读5分钟

一、基础概念

1.数据类型

js数据类型分为基础数据类型和复杂数据类型,基础数据类型有string、undefined、null、number、symbol、bigInt、boolean,复杂数据类型有数组、函数、正则等等。

2.js语言特点

  1. 单线程
  2. 动态、弱语言类型
  3. 面向对象、函数式
  4. 解释类语言、jit
  5. 安全、性能差

...

3.作用域

  1. 全局作用域
  2. 函数作用域
  3. 块级作用域({}内变量生效)

4.变量提升

  1. var有变量提升
  2. let、const没有变量提升,提前访问会报错
  3. function函数可以先调用再定义
  4. 赋值给变量的函数无法提前调用

5.浏览器中的运行的顺序

其中GUI线程和js线程无法同时执行,GUI会渲染DOM,js会修改DOM,所以这两个线程只能互斥执行,当js计算量特别大或者陷入死循环的时候,页面会很卡就是这个原因。

image-20230508205448582.png

二、js如何执行

1.执行流程

AST不直接转化为机器码(汇编)代码行数会急剧增加从而导致内存溢出,所以需要通过字节码再逐行解释执行。字节码编译执行机器码就是jit,将执行频率较高的代码记录下来,下次直接转换成机器码输出结果,而不经过字节码中转。

image-20230508203615904.png

2.执行上下文

当js引擎解析到可执行代码片段(通常是函数调用)的时候,就会先做一些执行前的准备工作,这个准备工作就叫做“执行上下文”,也叫执行环境,内部包括变量环境、词法环境、this、outer、可执行代码。

词法环境:基于ECMAScript代码的词法嵌套结构来定义标识符和具体变量和函数的关联。一个语法环境由环境记录器和一个可能的引用外部词法环境的空值组成。

变量环境:变量环境和词法环境的一个不同就是前者被用来存储函数声明和变量(let、const)绑定,而后者只用来存储var变量绑定。

Outer: 指向外部变量环境的一个指针。

this: 指向当前变量环境的一个指针。

image-20230508204311761.png

js中分为全局执行上下文、函数执行上下文、Eval执行上下文(不常用)。

全局执行上下文:代码开始执行时就会创建,将它压到执行栈的栈底,每个生命周期只有一份

函数执行上下文:当执行一个函数时,这个函数内的代码会被编译,生成变量环境、词法环境等,当函数执行结束的时候该执行环境从栈顶弹出。

三、进阶知识点

1.闭包原理

function showName() {
    const dep = "边缘云";
    const name = "zhang";
    return function() {
        console.log(dep);
        return name
    }
}
​
const getName = showName();
console.log(getName());

showName函数里面形成了闭包,返回函数里面使用了变量dep和name,会直接指向堆内存中的这两个变量的内存地址,因此const getName = showName()虽然调用完了showName后这个函数的执行上下文会出栈,但是console.log(getName())仍旧能获取到变量的值。所以过度使用闭包也会导致内存溢出。

2.this指向

  1. 普通函数(function)指向window

    function show(){
        console.log(this); // window
        function showName() {
            console.log(this); // window
        }
        showName();
    }
    show();
    
  2. 对象调用指向对象

    const personObj = {
        name: "zhang",
        showName() {
            console.log(this.name);
        }
    }
    personObj.showName(); // zhang
    const getName = personObj.showName; 
    // 先赋值再调用,所以下一行结果为undefined,因为调用对象是window
    getName(); // undefined
    
  3. 创建临时对象时指向这个临时对象

    function showName() {
        this.name = "zhang";
    }
    const getName = new showName();
    // 1.创建临时对象
    // 2.将this指向临时对象
    // 3.执行构造函数
    // 4.返回临时对象
    

3.垃圾回收

堆内的垃圾回收分为新生代和老生代,新生代空间较小,仅存储占用空间较小的数据,老生代空间较大,会存储占用空间较大的数据,以及新生代数据中非常活跃的数据。

新生代垃圾回收流程为垃圾标记、对象复制和区域反转,具体来说就是将对象区域中数据标记为垃圾数据和活跃数据,然后将活跃数据移动到空闲区域,将对象区域清空,最后反转两个区域。

老生代垃圾回收流程为垃圾标记和垃圾清除,与新生代清除方式不同的是,数据量要大的多,并且在垃圾清除的过程中js是暂停执行的,所以不能一次性执行完标记和清除,可以简单理解为js执行一小段,垃圾标记和回收一小部分,交替执行。

image-20230509134915505.png

4.事件循环

image-20230509140122779.png

在每一次事件循环中,先执行同步任务,再执行异步任务,先将宏任务添加到宏任务队列中,将宏任务里面的微任务添加到微任务队列中。所有同步执行完之后执行异步,再将异步微任务从队列中调入主线程执行,微任务执行完毕后再将异步宏任务从队列中调入主线程执行。

异步任务有宏任务和微任务两种,宿主(浏览器)发起的任务我们可以称之为宏观任务;引擎(js)自己也可以发起任务,这个任务就叫做微观任务。

宏任务:

  1. script (可以理解为外层同步代码)
  2. setTimeout/setInterval
  3. UI rendering/UI事件
  4. postMessage,MessageChannel
  5. setImmediate,I/O(Node.js)

微任务:

  1. Promise
  2. async,await

例子:

console.log("同步任务1");
setTimeout(() => { // 宏任务
    console.log("setTimeout"); // 微任务
}, 0); // 这里0,1,2,3,4的效果是一样的,规定上说明了最小的设定时间为4msnew Promise((resolve) => {
    console.log("同步代码2");
    resolve();
}).then(() => {
    console.log("promise.then"); // 微任务
}).then(() => {
    console.log("promise.then2"); // 微任务
})
​
console.log("同步代码3");
​
// 结果
// 同步代码1
// 同步代码2
// 同步代码3
// promise.then
// promise.then2
// setTimeout// 同步任务执行完后,执行Promise两个then的微任务,然后去执行setTimeout的宏任务

四、总结

通过视频学习复习和巩固的了js基础的数据类型、语言特点、作用域和变量提升,并了解了js代码的执行流程、执行上下文、js垃圾回收机制等的一些概念,虽然可能没有特别深入,但是让我有了一些深入学习的方向。