深入理解JS | 青训营笔记

58 阅读6分钟

JS的一些特点:

• 单线程执行
• 动态、弱类型语言:运行时才确定数据类型,变量在使用之前无需申明类型
• 面向对象、函数式
• 解释类语言、JIT
• 安全、性能差

JS的数据类型:

• 引用数据类型(复杂数据类型):ArrayFunctionObject、正则表达式、日期
• 基本数据类型:7种。 stringundefinednumbernullsymbol、bigInt、boolean

复杂数据类型和基本数据类型的区别:

• 赋值区别
    对于复杂数据类型来说,将a赋值给b后,更改ba也被更改了
    因为把a赋值给b,其实是把a对应的内存地址赋值给了b
    而对于基本数据类型来说,将a赋值给b后,更改ba并没有被更改
    因为把a赋值给b,所赋予的就是值


• 值存储方式区别
    对于复杂数据类型来说,其值是可以被改变的。
    而对于基本数据类型来说,其值是不可以被改变的。
   (其含义为2、"Hello"(字符串字面值)不可变,但为什么可以修改变量的值呢?其实就将变量指向了栈中的新值)

扩展:

程序运行的时候,需要内存空间存放数据。一般来说,系统会划分出两种不同的内存空间:一种叫做堆(heap),另一种叫做栈(stack)

堆(heap)是没有结构的,数据可以任意存放,它是用于存放复杂数据类型(引用类型)的,例如数组对象、object对象等。

栈(stack)是有结构的,每个区块按照一定次序存放(后进先出),栈中主要存放的是基本类型的变量的值以及指向堆中的数组或者对象的地址,存在栈中的数据大小与生存期必须是确定的。除此之外,还可以明确知道每个区块的大小,因此,stack的寻址速度要快于heap

另外,栈有一个很重要的特殊性,就是存在栈中的数据可以共享

假设我们同时定义 int a = 3; int b = 3;,编译器先处理 int a = 3:

首先它会在栈中创建一个变量为 a 的引用,然后查找有没有字面值为 3 的地址,没找到,就开辟一个存放 3 这个字面值的地址,然后将 a 指向 3 的地址

接着处理 int b = 3;,在创建完 b 的引用变量后,由于在栈中已经有 3 这个字面值,便将 b 直接指向 3 的地址。这样,就出现了 a 与 b 同时均指向 3 的情况

特别注意的是,这种字面值的引用与类对象的引用不同。假定两个类对象的引用同时指向一个对象,如果一个对象引用变量修改了这个对象的内部状态,那么另一个对象引用变量也即刻反映出这个变化

相反,通过字面值的引用来修改其值,不会导致另一个指向此字面值的引用的值也跟着改变的情况。如上例,我们定义完 a 与 b 的值后,再令 a=4;,那么,b 不会等于 4,还是等于 3

在编译器内部,遇到 a=4; 时,它就会重新搜索栈中是否有 4 的字面值,如果没有,重新开辟地址存放 4 的值;如果已经有了,则直接将 a 指向这个地址。因此 a 值的改变不会影响到 b 的值

变量提升:

var 有变量提升(不会报错,打印结果为undefine)

• letconst 没有变量提升,提前访问会报错(如下图,报错:cannot access 'company' before initialization)

• function函数可以先访问再定义

• 赋值给变量的函数无法提前调用(报错:'func' is not a function)
    因为func存在变量提升 var func = undefined 

JS是怎么执行的:

源代码通过词法分析和语法分析后,生成AST树(抽象语法树(Abstract Syntax Tree),是源代码语法结构的一种抽象表示),同时创建执行上下文。

执行上下文:

当JS引擎解析到可执行代码片段(通常是函数调用)的时候,就会做一些执行前的准备工作,这个准备工作,就叫做执行上下文(execution context,简称EC),也叫执行环境。

一个执行上下文包括变量环境、词法环境、this

执行上下文的分类:

• 全局执行上下文:代码开始执行时就会创建,将他压到执行栈的栈底,每个生命周期内只有一份。
• 函数执行上下文:当执行一个函数时,这个函数内的代码会被编译,生成变量环境、词法环境等,当函数执行结束的时候该执行环境从栈顶弹出。
• Eval执行上下文

调用栈:

存放各种执行上下文的栈

一些概念:

• 词法环境:
    基于ECMAScript代码的词法嵌套结构来定义标识符和具体变量和函数的关联。一个词法环境由环境记录器和一个可能的引用外部词法环境的空值组成。
    
• 变量环境:
    变量环境和词法环境的一个不同就是前者被用来存储函数声明和变量(letconst)绑定,而后者只用来存储var变量绑定
    
• Outer:
    指向外部变量环境的一个指针

JS的进阶知识点:

闭包:

本质为没有被回收的对象

this:

记住最核心的一句话:哪个对象调用函数,函数里面的 this 指向哪个对象

this指向的几种情况:

• 方法调用模式下,this 总是指向调用它所在方法的对象,this 的指向与所在方法的调用位置有关,而与方法的声明位置无关(箭头函数特殊);
• 函数调用下,this 指向 window 。调用方法没有明确对象的时候,this 指向 window,如 setTimeout、匿名函数等;
• 构造函数调用模式下,this 指向被构造的对象;
• apply,call,bind 调用模式下,this 指向第一个参数;
• 箭头函数,在声明的时候绑定this,而非取决于调用位置;
• 严格模式下,如果 this 没有被执行环境(execution context)定义,那 this是 为undefined;

垃圾回收:

V8常用的GC算法:

• 分代回收 (一定会用)
• 空间复制
• 标记清除
• 标记整理
• 标记增量 (提高效率用)

分代回收:

• 新生代 —— 就是指存活时间较短的对象,例如:一个局部作用域中,只要函数执行完毕之后变量就会回收。
• 老生代 —— 就是指存活时间较长的对象,例如:全局对象,闭包变量数据。

新生代对象回收: 主要使用的算法:赋值算法 + 标记整理算法

事件循环:

JS的任务队列可分为宏任务队列和微任务队列。 是微任务队列先执行。