- 本文为青训营-前端-08-深入理解JS的课程笔记,字数2000+
- 阅读需要前置知识:JS的开发经验
01 JS的基本概念
-
1995 Eich 开发
- Java的数据结构与内存管理+C的语法+Scheme的函数+Self的原型
-
Mocha--LiveScript--Javascript--1997 ECMAScript(网景为了与微软的语言抗争将JS交给了ECMA规范)--2009Node.js -- 2010 npm -- 2015 6th ECMAScript
-
浏览器模型:
-
Browser 进程
-
GPU 进程
-
渲染进程
- GUI 线程(绘制页面)
- JS 线程(执行JS)
- 事件触发线程
- 定时器触发线程
- 网络线程……
-
插件进程、网络进程……
-
-
JS
- 单线程(GUI线程与JS线程是互斥的)
- 动态、弱类型(运行时确定数据类型的语言,变量在使用之前无需申明类型,对应的是静态语言/强类型语言)
- 面向对象、函数式(JS 不是纯粹的面向对象,也不是纯粹的函数式编程,是两个的结合)
- 解释类语言、JIT(JS是解释一行执行一行吗?)
- 安全、性能差(JS的安全性来自于浏览器作为执行环境)
- ……
-
JS 的数据类型
-
对象
- 数组、函数、……
-
基础类型
- Null Undefined Boolean Number BigInt String Symbol
-
重要结论
- 在JS中复杂数据类型使用别名,浅拷贝,修改会改变原来的值;基础数据类型会拷贝值、深拷贝,修改不影响原来的值。
- 在JS中,复杂数据类型的原始值不会被改变,简单数据类型的原始值可以被改变(?)
-
//complex type assignment is shallow copy ,in fact ,is a location/pointer
//simple type
const str="this_is_a_str";
let newstr=str;//notice:newstr cannot be const
newstr="123";
console.log(str,newstr);
//complex type
const a={
name:"alice"
}
const b=a;
b.name="bob";
console.log(a.name,b.name);
//output:
this_is_a_str 123
bob bob
//original value
//simple type
const str2="strstrstr";
str2.slice(0,2);
//complex type
const arr=[1,2];
arr.push(3)
console.log(str2,arr)
//output:
strstrstr [ 1, 2, 3 ]
-
JS 作用域——变量的可访问性、可见性(JS 是静态作用域,通过静态作用域能够预测代码在执行过程中如何查找标识符)
- 全局作用域
- 函数作用域
- 块级作用域 ES6之后加入
-
JS 变量提升【说明了JS并不是纯解释型语言,还存在一个编译过程】
- var 有变量提升
- let 、const 没有变量提升,提前访问会报错
- function函数可以先调用后定义
- 赋值给变量的函数无法提前调用
//var
console.log(a);
var a=123;
//output:
undefined
//reason
//上面的代码相当于
var a;
console.log(a);
a=123;
//let const
console.log(b,c);
let b=456;
const c=789;
//output:
报错,没有输出
//function
func();
function func(){
console.log("func");
}
//output:
func
//reason
//上面的代码相当于//与词法有关
function func(){
console.log("func");
}
func();
//function and var
func2var();
var func2var=func2;
function func2(){
console.log("func2");
}
//output:
报错,无法执行,没有输出
//reason
//上面的代码相当于
var func2var;
func2var();
func2var=func2;
function func2(){
console.log("func2");
}
02 JS的执行过程
-
-
为什么不直接转换为汇编?
- 比如一个加法语句,机器码需要好多汇编代码,而字节码相对来说更加节省空间,节省内存
-
最后的 编译执行+优化代码+机器码 是JIT
- V8会将多次执行的代码作为“热代码”
- 将热代码转换为机器码,下次直接执行提高速度。
-
一个token与AST的示例(下图左侧为转换出的token,右侧是一个抽象语法树)
-
-
JS 执行上下文
-
当JS引擎解析到可执行代码片段(通常是函数调用),会先做一些执行前准备工作,这个准备工作,叫做“执行上下文(execution context EC) ” ,也叫做执行环境
-
执行上下文一般分为三种
- 全局执行上下文
- 函数执行上下文
- Eval 执行上下文
-
- 全局执行上下文:代码开始执行时就会创建、将他压到执行栈的栈底,每个生命周期内只有一份
- 函数执行上下文:当执行一个函数时,这个函数内的diamagnetic会被编译,生成变量环境、词法环境等等,当函数执行结束时该执行环境从栈顶弹出。
-
-
一个调用栈的例子
-
//代码示例 var scop="global"; func(); console.log(company); function func(){ var funcVar="func"; console.log("这是函数",funcVar); } var company="ByteDance";
- 注意上图中,func是在词法环境中,不在变量环境中。
-
-
创建上下文时:
- 绑定This(与执行上下文)
- 创建词法环境
- 创建变量环境
-
补充解释
- 词法环境:基于 ECMAscript代码的词法嵌套结构来定义标识符与具体变量和函数的关联。一个词法环境由环境记录器和一个可能的引用外部词法环境的空值组成
- 变量环境:变量环境与词法环境的一个不同,前者被用来存储函数声明和变量(let和const)绑定。后者只用来存储var变量的绑定
- Outer:指向外部变量环境的一个指针。
-
JS 执行栈如何退栈/弹栈
- ESP 指针下移(V8引擎中维护了一个ESP指针,和汇编实际上没有任何区别)
03 JS 进阶
闭包
this
- 普通函数中的this指向global(包含嵌套)
function showName(){
console.log(this)
}
showName()
在浏览器中
在VScode中
- 在对象调用时this指向对象,如果是先赋值再调用则看调用的地方
const obj={//注意定义了一个对象的方法
name:'douyin',//注意使用逗号,不要使用分号
show(){
console.log(this.name);
}
}
const obj2={
name:'bytedance',
show(){
console.log(this.name);
}
}
obj.show();
obj.show.apply(obj2);//使用 call 或者 bind 也可以在obj中调用obj2的show
//output:
douyin
bytedance
- 构造函数
function ShowName(){
this.name='bytedance';
}
const getName=new ShowName();
上述代码的解读
1. 创建临时对象
2. 将this指针指向临时对象
3. 执行构造函数
4. 返回临时对象
垃圾回收
- 栈的垃圾回收
使用ESP指针,前面讲过
- 堆的垃圾回收
「硬核JS」你真的了解垃圾回收机制吗 - 掘金 (juejin.cn) 建议看这个。
MDN 内存管理-垃圾回收 MDN文档
事件循环
- 详解JavaScript中的Event Loop(事件循环)机制 - 知乎 (zhihu.com) 解释的很清楚了
- 并发模型与事件循环 - JavaScript | MDN (mozilla.org)
- 【JS】深入理解事件循环,这一篇就够了!(必看) - 知乎 (zhihu.com)
课程总结
- JS 单线程,但是 Render 进程里有多个线程
- JS 线程和GUI线程互斥,执行大的计算任务会导致页面卡顿
- 基础数据类型存在栈上,复杂数据类型存在堆上
- const 、let没有变量提升,提前使用会出现报错
- JS也有编译过程,执行之前会生成执行上下文
- 一个执行上下文包括变量环境、词法环境、this
- 变量环境里面有一个指向外部函数执行上下文的指针,形成了作用域链
- 全局执行上下文只有一份
- this 和执行上下文绑定