JavaScript
🌸 您好,欢迎您的阅读,等君久矣,愿与君畅谈.
🔭 § 始于颜值 § 陷于才华 § 忠于人品 §
📫 希望我们可以进一步交流,共同学习,共同探索未知的技术世界 稀土掘金 OR GitHub.
# 1.JavaScript 介绍
# 1.JavaScript 应用
- web 端开发
- 移动端开发
- 小程序端开发
- 桌面应用开发
- 后端开发
# 2.JavaScript 运行环境
# 1. 浏览器工作原理
-
浏览器内核
- Gecko:早期被 Netscape 和 Mozilla、Firefox 浏览器使用,代码完全公开,因此,其可开发程度很高,全世界的程序员都可以为其编写代码,增加功能
- Trident:微软开发的一种排版引擎,被 IE4~IE11 浏览器使用,但是 Edge 浏览器已经转向 Blink
- Webkit:苹果基于 KHTML 开发、开源的,用于 Safari 浏览器,Google Chrome 之前也在使用
- Blink:Google 谷歌基于 webkit 内核开发的,目前应用于 Google Chrome、Edge、Opera 等
- Presto:Opera 浏览器曾经使用过,特点是渲染速度的优化达到了极致,然而代价是牺牲了网页的兼容性
- 内核也称排班引擎、浏览器引擎、页面渲染引擎、样板引擎
-
内核工作原理
- 浏览器渲染过程,需要将 JavaScript 代码转换为机器语言后交给 CPU 执行

- 浏览器渲染过程,需要将 JavaScript 代码转换为机器语言后交给 CPU 执行
-
Webkit浏览器内核,由 WebCore 与 JavaScriptCore 两部分组成- WebCore:负责 HTML、CSS 解析、布局、渲染等相关工作
- JavaScriptCore:解析、执行 JavaScript 代码
# 2.V8 引擎工作原理
-
JavaScript 引擎(V8 引擎是 Google 开发)
- SpiderMonkey:第一款 JavaScript 引擎,由 BrendanEich 开发(也就是 JavaScript 作者)
- Chakra:微软开发,用于 IT 浏览器
- JavaScriptCore:WebKit 中的 JavaScript 引擎,Apple 公司开发
- V8 引擎:Google 开发的强大 JavaScript 引擎,也帮助 Chrome 从众多浏览器中脱颖而出
-
V8 引擎工作原理
-
V8 定义:C 编写的 Google 开源高性能 JavaScript 和 webAssembly 引擎,它用于 Chrome 和 Node.js,在很多环境及平台下可以运行,也可以单独运行,也可以嵌入 C 应用程序中运行
-
Parse:转换器,Parse 模块会将 JavaScript 代码转换成 AST (抽象语法树), 这是因为解释器并不直接认识 JavaScript 代码,如果函数没有被调用,那么是不会被转换成 AST (词法分析:
Scanner转换生成多个 tokens:[{type:‘keyword’,value:‘const’}] 等、语法分析、)注意:js 代码转换为 AST 抽象语法树网站 (https://astexplorer.net/)
-
lgnition:解释器,会将 AST 转换成 ByteCode(字节码), 因为编写代码运行在具体什么样环境并不清楚,所以需要将该代码转换为字节码,字节码可以运行在各个环境,同时会收集 TurboFan 优化所需要的信息 (比如函数参数的类型信息,有了类型才能进行真实的运算), 如果函数只调用一次,Ignition 会解释执行字节码 ByteCode
-
TurboFan:编译器,可以将字节码编译为 CPU 可以直接执行的机器码;如果一个函数被多次调用,那么就会被标记为
热点函数,那么就会经过 TurboFan 转换成优化的机器码,提高代码的执行性能;但是机器码实际上也会被还原为 ByteCode,这是因为如果后续执行函数的过程中,类型发生了变化(比如 sum 函数原来执行的是 number 类型,后来执行变成了 string 类型),之前优化的机器码并不能正确的处理运算,就会逆向的经过Deoptimization优化转换成字节码,继续进行执行 (从这一点也可以看出 typescript 代码运行效率会好一些,因为在编写时就已经确定参数的类型,减少了逆向转换为字节码再一次进行运行编译为汇编语言然后机器代码,越过这个过程就会快一些) -
Orinoco:垃圾回收,将程序中不需要的内存回收???
-
执行原理过程

-
官方执行原理过程

-
JavaScript 源码解析过程
- Blink 将源码交给 V8 引擎,Stream 获取到源码并且进行编码转换
- Scanner 会进行词法分析(lexical analysis),词法分析会将代码转换成各种 tokens
- 接下来 tokens 会被转换成 AST 树,经过 Parser 和 PreParser
- Parser 就是直接将 tokens 转成 AST 树架构
- PreParser 称之为预解析
- 因为并不是所有的 JavaScript 代码,在一开始时就会被执行。那么对所有的 JavaScript 代码进行解析,必然会影响网页的运行效率
- 所以 V8 引擎就实现了 Lazy Parsing(延迟解析)的方案,它的作用是将不必要的函数进行预解析,也就是只解析暂时需要的内容,而对函数的全量解析是在函数被调用时才会进行
- 比如我们在一个函数 outer 内部定义了另外一个函数 inner,那么 inner 函数就会进行预解析,生成 AST 树后,会被 Ignition 转成字节码(bytecode), 之后的过程就是代码的执行过程(后续会详细分析)
-
JavaScript 源码执行具体操作
- 初始化全局对象
- js 引擎会在执行代码之前,会在堆内存中创建一个全局对象:Global Object(GO)
- 该对象所有的作用域(scope)都可以访问,里面会包含 Date、Array、String、Number、setTimeout、setInterval 等等,其中有一个 window 属性指向自己
- 执行上下文栈 (调用栈)
- js 引擎内部有一个执行上下文栈 (Execution Context Stack, 简称 ECS), 它是用于执行代码的调用栈
- 该栈执行的是全局代码块,全局的代码块为了执行会构建一个 Global Execution Context (GEC: 全局执行上下文),GEC 会被放入到 ECS 中执行
- GEC 被放入到 ECS 中里面包含两部分内容
- 在代码执行前,在 parser 转成 AST 的过程中,会将全局定义的变量、函数等加入到 GlobalObject 中,但是并不会赋值,这个过程也称之为变量的作用域提升(hoisting)
- 在代码执行中,对变量赋值,或者执行其他的函数
- 在执行的过程中执行到一个函数时,就会根据函数体创建一个函数执行上下文(Functional Execution Context,简称 FEC),并且压入到 EC Stack 中
- FEC 中包含三部分内容
- 在解析函数成为 AST 树结构时,会创建一个 Activation Object (AO):AO 中包含形参、arguments、函数定义和指向函数对象、定义的变量(VO:AO)
- 作用域链:由 VO(在函数中就是 AO 对象)和父级 VO 组成,查找时会一层层查找
- this 绑定的值:根据不同的情况绑定 this
- 变量环境和记录 (
作用域链考点)- 概念
- VO:全局变量对象,创建执行上下文时与之关联的会有一个变量对象,该上下文中的所有变量和函数全都保存在这个对象中
- GO:全局对象,进入到一个执行上下文时,此执行上下文中的变量和函数都可以被访问 (全局)
- AO:私有变量对象 (函数即将被执行,还没有还执行),进入到一个执行上下文时,此执行上下文中的变量和函数都可以被访问 (局部)
- GEC (global excution context) 全局执行上下文:执行全局代码,FEC (function excution context) 函数执行上下文:执行函数代码
- 函数的作用域与函数定义位置有关,与调用位置无关
- 早期 ECMA 版本规范:每一个执行上下文被关联到一个变量对象 (variable,VO), 在源代码中的变量和函数声明会被作为属性添加到 VO 中,对于函数来说,参数也会被添加到 VO 中
- 在最新的 ECMA 的版本规范中,已经修改不称作 VO、GO、AO 等,而是称为变量环境,每添加的变量、函数叫作环境记录。每一个执行上下文会关联到一个
变量环境(VariableEnvironment 简称 VE) 中,在执行代码中变量和函数的声明会作为环境记录(Environment Record) 添加到变量环境中,对于函数来说,参数也会被作为环境记录添加到变量环境中
- 概念
- 初始化全局对象
-
# 3.JavaScript 内存原理
- 内存管理
- 内存的管理有如下的生命周期
- 分配所申请的内存空间
- 使用所分配的内存空间
- 不使用时进行释放
- 管理内存的方式
- 手动管理内存:C、C++
- 自动管理内存:Java、JavaScript、python、Swift、Dart
- 内存分配
- JS 对于基本数据类型内存的分配会在执行时,直接在栈空间进行分配
- 对于复杂数据类型内存的分配会在堆内存中开辟一块空间,并且将这块空间的指针返回值变量引用
- JS 的垃圾回收,Garbage Collection,简称 GC
- 不再使用的对象,称之为垃圾,需要被回收释放更多的内存空间
- Java 的运行环境 JVM,JavaScript 的运行环境 js 引擎都会有内存垃圾回收器
- GC 算法
- 引用计数:内存中存在一个引用计数,retain count:默认为 0,缺点可能会产生循环引用
- 标记清除:设置一个根对象 (root object), 垃圾回收器会定期从这个根开始,找所有从开始有引用的对象,对于没有引用的对象,就认为是不可用对象
- 内存的管理有如下的生命周期
- 常见 GC 内存算法
- JavaScript 垃圾回收
- 引用计数算法
- 标记清除算法
- 标记整理算法
- 分代回收算法
- V8 引擎内存管理
- V8 的分代算法
- V8 的内存分配
- 新生代对象回收
- 旧生代对象回收
- Performmance 调试
- JavaScript 内存泄漏
- 严格模式
- 概念
- 严格模式是一种具有限制性的 JavaScript 模式,从而使代码隐式的脱离了
懒散(sloppy)模式 - 支持严格模式的浏览器在检测到代码中有严格模式时,会以更加严格的方式对代码进行检测和执行
- 严格模式对正常的 JavaScript 语义进行了一些限制
- 严格模式通过抛出错误来消除一些原有的静默(silent)错误
- 严格模式让 JS 引擎在执行代码时可以进行更多的优化(不需要对一些特殊的语法进行处理)
- 严格模式禁用了在 ECMAScript 未来版本中可能会定义的一些语法
- 严格模式是一种具有限制性的 JavaScript 模式,从而使代码隐式的脱离了
- 开启严格模式
- 支持在 js 文件中开启严格模式
- 支持对某一个函数开启严格模式
- 严格模式通过在文件或者函数开头使用 use strict 来开启
- 严格模式限制
- 禁止意外创建全局变量
- 不允许函数有相同的参数名称
- 出现静默错误
- 不允许使用原先的八进制格式
- with 语句不允许使用
- eval 函数不会向上引用变量 (不会再向上层作用域添加变量)
- 严格模式下试图删除不可删除的属性
- 严格模式下,this 绑定不会默认转成对象
- 概念
# 4.JavaScript 事件循环
- 浏览器的进程模式
- 进程
- 线程
- JavaScript 线程
- 浏览器的事件循环
- 宏任务 macrotask
- 微任务 microtask
- 常见面试题
- Node 的事件循环
- libuv
- 阻塞 IO
- 非阻塞 IO
- 宏任务 macrotask
- 微任务 microtask
- 常见面试题
# 3.JavaScript 作用域和函数
# 1. 认识作用域
-
认识作用域
- JavaScript 编译、执行
- 深入理解作用域
- 作用域的嵌套
-
词法作用域
-
认识词法分析
-
eval 函数
- eval 是一个特殊的函数,可以将传入的字符串当作 JavaScript 代码来运行
- 不建议在开发中使用
- eval 代码可读性较差
- eval 是一个字符串,可能在执行过程中被篡改,造成恶意攻击
- eval 的执行必须经过 JS 解释器,不能被 JS 引擎优化
1
2var jsString = 'var message = "Hello World~~";console.log(message);';
eval(jsString); -
with 关键字
with的使用,但不推荐使用,在严格模式下会报错
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// "use strict"
var message = "Hello world!";
var object = {
name: "tests",
age: 12,
message: "您好"
};
// with语句:可以形成自己的作用域
function foo() {
function bar() {
with (object) {
console.log(message);
}
}
bar();
}
foo();
-
-
作用域提升
- 编译器中变量声明和提升
- 函数和变量的提升
-
块级作用域
- with 作用域
with可以自己形成作用域,先查找传进来的对象,之后再去上层查找
try...catch...作用域- let 变量声明
- const 变量声明
- with 作用域
-
作用域面试题
# 2. 执行上下文
- 执行上下文
- Global EC
- Function EC
- Eval EC
- 变量对象 VO
- VO: 全局变量对象
- AO: 私有变量对象
- GO: 全局对象
# 3. 深入函数执行
-
call/apply执行函数 -
立即执行函数
-
Scopechain -
深入闭包-
补充
- 高阶函数:一个函数接收另一个函数作为参数或者一个函数会作为另一个函数的返回值返回
filter: 过滤,接收一个返回布尔类型的函数为参数,并且该函数具有三个参数分别为数组中的元素、数组中的元素的下标、数组本身map: 映射,forEach: 迭代遍历,无返回值find: 查找的为一个对象findIndex: 查找的为对象索引值reduce: 累加计算- 方法:对象里面的函数(特殊位置的函数),函数:独立的 function 称为函数
-
闭包:Closure-
维基百科:闭包又称词法闭包、函数闭包,是在支持头等函数的编程语言中,实现词法绑定的一种技术,闭包在实现上是一个结构体,它存储了一个函数和一个关联的环境,闭包和普通函数的区别在于,当捕捉闭包的时候,它的自由变量会在捕捉时被确定,这样即使脱离了捕捉时的上下文,它也能照常运行
-
MDN:一个函数和对其周围状态 (词法环境) 的引用捆绑在一起 (说函数被引用包围),这样的组合就是闭包,闭包让你可以在一个内层函数中访问到其外层函数的作用域,在 JavaScript 中,每当创建一个函数,闭包就会在函数创建时被创建出来
-
个人理解:一个普通的函数,如果它可以访问到外层作用域的自由变量,那么这个函数就是一个闭包。从广义角度:(可以访问到) JavaScript 中的函数都是闭包;从狭义角度 (有访问到):JavaScript 中的一个函数,若访问了外层作用域的自由变量,那么它就是一个闭包
-
闭包引起的内存泄漏
- 使用完毕后本该销毁的内存而没有销毁继续存在称为内存泄露
- 内存释放将其设置为
null即可
-
闭包中引用的自由变量
- AO 对象不会被销毁时,该对象里面被闭包引用的自由变量不会被销毁,没有引用的自由变量会被 js 引擎销毁
1
2
3
4
5
6
7
8
9
10
11
12
13function foo() {
var name = "GXX";
var age = 19;
function bar() {
debugger
console.log(name);
// 按照ECAM规范整个foo函数的AO对象不会删除,所以该对象中的age属性也应该存在,但是浏览器的js引擎即V8引擎会进行优化将其删除掉
console.log(age);//bar函数中不访问变量age
}
return bar;
}
var fn = foo();
fn();
-
-
# 4. 函数的 this 绑定
-
全局作用域下
-
浏览器环境:全局作用域下
this绑定 window -
node 环境:
空对象{}- 执行步骤:首先将要执行的 js 文件作为模块,然后进行加载编译,此时将 js 代码所有文件放入一个函数中,执行该函数,但是该函数调用
call(), 在调用时向call()中传入一个空对象绑定为this即执行函数.call({})
- 执行步骤:首先将要执行的 js 文件作为模块,然后进行加载编译,此时将 js 代码所有文件放入一个函数中,执行该函数,但是该函数调用
-
-
函数作用域下
this指向-
函数在被调用时,JavaScript 会默认给 this 绑定一个值,this 指向与函数所处位置 (函数定义位置) 无关,与函数被调用的方式及调用位置有关,this 是在运行时被绑定
-
默认绑定
-
独立函数调用
this指向的是全局对象window1
2
3
4
5
6
7
8
9
10function foo() {
console.log(this);
}
var obj1 = {
name: "obj1",
foo: foo
}
// 将obj1的foo赋值给bar
var bar = obj1.foo;
bar();
-
-
隐式绑定
-
通过某个对象进行调用,即调用位置中是通过某个对象发起的函数调用 (谁发起函数调用 this 就绑定谁)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function foo() {
console.log(this);
}
var test = {
name: "test",
foo: foo
}
var obj1 = {
name: "obj1",
test: test
}
var obj2 = {
name: "obj2",
obj1: obj1
}
obj2.obj1.test.foo();//test对象 -
object.fn():object 对象会被 js 引擎绑定到 fn 函数中的 this 上 -
隐式绑定的前提条件
- 必须在调用的对象内部有一个对函数的引用(比如一个属性)
- 如果没有这样的引用,在进行调用时,会报找不到该函数的错误
- 正是通过这个引用,间接的将 this 绑定到了这个对象上
-
-
显示绑定
-
call()- 第一个参数为绑定对象,第二个参数为参数列表 (剩余参数形式)
-
apply()- 第一个参数为绑定对象,第二个参数为参数列表 (数组形式)
1
2
3
4
5function sum(num1,num2,num3) {
console.log(num1+num2+num3,this);
}
sum.call("call",20,30,40);//剩余参数形式
sum.apply("apply",[20,30,40]);//数组形式 -
bind()- 第一个参数为绑定对象,第二个参数为参数列表 (剩余参数形式),
bind返回为一个新的函数
1
2
3
4
5
6function foo() {
console.log(this);
}
// 默认绑定和显示绑定bind冲突:优先级(显示绑定)
var newFoo = foo.bind("aaaa");
newFoo(); - 第一个参数为绑定对象,第二个参数为参数列表 (剩余参数形式),
-
-
new 绑定
- 使用 new 关键字来调用函数执行如下的操作
- 创建一个全新的对象,并将该对象赋值给 this,函数最后返回该对象
- 这个新对象会被执行 prototype 连接
- 这个新对象会绑定到函数调用的 this 上(this 的绑定在这个步骤完成)
- 如果函数没有返回其他对象,表达式会返回这个新对象
- 使用 new 关键字来调用函数执行如下的操作
-
内置函数绑定
-
setTimeout1
2
3
4// 因为setTimeout内部实现函数调用时为独立函数调用,所以打印this时指向window
setTimeout(function (){
console.log(this); //window
}, 2000); -
数组.forEach/map/filter/find等高阶函数1
2
3
4
5
6
7
8
9var names = ["aaa","bbb","ccc","ddd"];
// 函数直接独立调用指向window
names.forEach(function (item) {
console.log(item,this);
})
// forEach函数接收两个参数,第一个参数为一个函数,第二个参数为this绑定对象
names.forEach(function (item) {
console.log(item,this);
},"forEach") -
div的点击1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 2、监听box盒子的点击,内部函数调用实现为:隐式this绑定调用进行打印即boxDiv.onclick()形式调用
const boxDiv = document.querySelector(".box");
// 1.方式一:该方式添加监听事件会进行覆盖,也就是后面一个监听事件会覆盖前面一个,只生效一个最后一个事件
boxDiv.onclick = function () {
console.log(this);
}
// 2.方式二:不会进行事件覆盖,会将函数收集到一个数组里面,执行时进行遍历
// 内部函数调用实现为:call()方式,即fn.call(boxDiv)
boxDiv.addEventListener("click",function () {
console.log(this);
})
boxDiv.addEventListener("click",function () {
console.log(this);
})
boxDiv.addEventListener("click",function () {
console.log(this);
})
-
-
-
规则的优先级
-
new 绑定 > 显示绑定 (bind/call/apply) > 隐式绑定 (object.foo ()) > 默认绑定 (独立函数调用)
-
默认规则 (独立调用) 的优先级最低
-
显示绑定优先级高于隐式绑定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20var object = {
name:"object",
foo:function () {
console.log(this);
}
}
object.foo();
object.foo.call("call");//call()绑定
object.foo.apply("apply");//apply()绑定
var bar = object.foo.bind("bind");
bar();
//bind()绑定优先级比较
function foo() {
console.log(this);
}
var object = {
name:"object",
foo:foo.bind("bind")
}
object.foo(); -
new 绑定优先级高于隐式绑定
1
2
3
4
5
6
7
8var object = {
name:"GXX",
foo:function () {
console.log(this);
}
}
// new的优先级高于隐式绑定
var fn = new object.foo(); -
new 绑定优先级高于 bind
1
2
3
4
5
6
7// 结论:new关键字不能和apply/call一起来使用
// new VS bind
function foo() {
console.log(this);
}
var bar = foo.bind("bind");
var object = new bar(); -
特殊情况 —— 忽略显示绑定
1
2
3
4
5
6
7
8
9
10
11function foo() {
console.log(this);
}
foo.call("call");
foo.apply("apply");
// apply/call/bind:当传入null/undefined时,自动将this绑定成全局对象
foo.call(null);
foo.call(undefined);
var fn = foo.bind(null);
// var fn = foo.bind(undefined);
fn(); -
特殊情况 —— 间接函数引用
1
2
3
4
5
6
7
8
9
10
11
12
13var object1 = {
name:"object1",
foo:function () {
console.log(this);
}
};
var object2 = {
name:"object2"
};
object2.bar = object1.foo;
object2.bar();//打印object2对象
// 立即执行函数(匿名函数),当作独立函数调用,打印全局对象window
(object2.bar = object1.foo)(); -
特殊情况 ——ES6 箭头函数
- 根据外层作用域来决定
this指向
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21var object ={
data:[],
getData:function () {
/* // 发送网络请求,将结果放到上面data属性中
// 在箭头函数之前的解决方案
// 使用闭包
var _this = this;
setTimeout(() => {
var result = ["111","222","333","444"];
_this.data = result;
}, 2000); */
// 箭头函数出现后
setTimeout(() => {
var result = ["111","222","333","444"];
this.data = result;
}, 2000);
}
}
// 隐式绑定,将getData方法中的this指向object对象,则_this就为object对象,即可拿到result数据
object.getData(); - 根据外层作用域来决定
-
this指向面试题-
对象花括号不是作用域
1
2
3
4
5
6var object = {
name:"test",
foo:function(){
//上层作用域为全局
}
} -
箭头函数不看绑定,向上层作用域查找
-
一定要看:执行时看是谁发起函数调用
-
-
箭头函数
- 介绍及简写语法练习
-
-
call&apply&bind-
call实现1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30// 给所有的函数添加一个hycall方法
Function.prototype.hycall = function (thisArg,...args) {//...args没有传入参数是一个空的数组
// 第一步:获取哪一个函数调用hycall
var fn = this;
// 第二步:对thisArg转成对象类型(防止传入的是非对象类型),使用thisArg对象时,当thisArg为字符串、数字、布尔等类型时需要进行转换为对应的对象
thisArg = (thisArg !== null && thisArg !== undefined && thisArg == 0) ? Object(thisArg) : window;
// 第三步:调用需要被执行的函数
// 1、使用call实现
// fn.call(thisArg);
// 2、使用thisArg对象实现
thisArg.fn = fn;
var result = thisArg.fn(...args);//展开时至少是一个空的数组
// delete thisArg.fn;
// 第四步:将最终的结果返回出去
return result;
}
function foo(num1,num2) {
console.log("foo函数被执行",this,num1,num2);
}
function sum(num1,num2) {
console.log("sum函数被执行",this,num1,num2);
return num1 + num2;
}
foo.hycall({});
foo.hycall({},1,2);
foo.hycall(null);
foo.hycall(undefined,6,6);
sum.hycall("hycall",9,9);
var result = sum(20,30);
console.log(result); -
apply实现1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40Function.prototype.hyapply = function (thisArg,argArray) {//argArray没有传入参数是undefined
// 第一步:获取那一个函数调用hyapply方法
var fn = this;
// 第二步:保证传入都是对象类型
thisArg = (thisArg !== null && thisArg !== undefined && thisArg == 0) ? Object(thisArg) : window;
// 第三步:调用执行函数
thisArg.fn = fn;
// 判断是否传入参数
// 方式一:!argArray为true没有参数,为false有参数
var result = !argArray ? thisArg.fn() : thisArg.fn(...argArray);
// 方式二:使用
/* // 方式三:使用或运算符
// argArray = argArray ? argArray:[]
argArray = argArray || [];
var result = thisArg.fn(...argArray); */
delete thisArg.fn;
return result;
}
function sum(num1,num2) {
console.log("sum函数被调用了",this,num1,num2);
return num1 + num2;
}
// 特殊情况一个参数也需要放入数组中
function foo(num) {
console.log("foo函数被调用了",this,num);
return num;
}
function bar() {
console.log("bar函数被执行了",this);
}
sum.hyapply("test",[20,30]);
sum.hyapply(null,[11,11]);
var res1 = sum.hyapply("hyapply",[12,12]);
console.log(res1);
var res2 = foo.hyapply("hyapply",[12]);
console.log(res2);
var res3 = bar.hyapply("hyapply");
// 边界判断(edge case)
bar.hyapply(0); -
bind实现1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42Function.prototype.hybind = function (thisArg, ...argArray) {
// 第一步:获取到真实需要调用的函数
var fn = this;
// 第二步:绑定this
thisArg = (thisArg !== null && thisArg !== undefined) ? Object(thisArg) : window;
function proxyFn(...args) {
// 第三步:将函数放到thisArg中进行调用
thisArg.fn = fn;
// 特殊处理:对两个传入的参数进行合并
var finalArgs = [...argArray, ...args]
var result = thisArg.fn(...finalArgs);
delete thisArg.fn;
// 第四步:返回结果
return result;
}
return proxyFn;
}
function foo() {
console.log("foo函数被调用", this);
return 20;
}
function sum(num1, num2, num3, num4) {
console.log(num1, num2, num3, num4);
}
/* // 系统的bind调用
var bar = foo.bind("bind");
bar();
// 传入参数的方式
// 方式一
var newSum = sum.bind("bind",11,22,33,44);
newSum();
// 方式二
var newSum = sum.bind("bind");
newSum(11,22,33,44);
// 方式三
var newSum = sum.bind("bind",11,22);
newSum(33,44); */
/* var bar = foo.hybind("hybind");
var res = bar();
console.log(res); */
var newSum = sum.hybind("hybind", 10, 20);
var res = newSum(30, 40);
-
-
arguments-
类数组对象 (array-like),形式像是一个数组,本质上是一个对象,即为对象类型,不能使用数组的方法
-
arguments参数转换数组1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38function foo(num1,num2) {
console.log(arguments);
// arguments转换为数组类型方式
/* // 1、使用for循环遍历
var newArray1 = [];
for (let i = 0; i < arguments.length; i++) {
newArray1.push(arguments[i] * 10);
}
console.log(newArray1); */
/* // 2、将arguments转换为array
var newArray2 = Array.prototype.slice.call(arguments);
console.log(newArray2);
var newArray3 = [].slice.call(arguments);
console.log(newArray3); */
// 3、使用ES6的语法
var newArray4 = Array.from(arguments);
console.log(newArray4);
var newArray5 = [...arguments];
console.log(newArray5);
}
foo(10,20,30,40,50,60);
/* // Array中slice方法实现
Array.prototype.hyslice = function (start,end) {
var array = this;
start = start || 0;
end = end || array.length;
var newArray = [];
for (let i = start; i < end; i++) {
newArray.push(array[i]);
}
return newArray;
}
// var newArrayFinal = Array.prototype.hyslice.call(["aaa","bbb","ccc"],1,3);
var newArrayFinal = Array.prototype.hyslice.call(["aaa","bbb","ccc"]);
console.log(newArrayFinal); */
/* // slice方法的使用
var names = ["111","222","333","444"];
console.log(names.slice(1,3)); */ -
箭头函数中没有arguments- 浏览器的全局作用域中没有 arguments
- node 环境下存在全局的 arguments
- 箭头函数可以使用剩余参数
(...args)方式获取参数
-
-
编程方式
- 函数式编程范式
- 面向对象式编程范式
# 5. 函数的柯里化
-
纯函数
-
纯函数的条件
- 确定的输入,一定会产生确定的输出
- 函数在执行过程中,不能产生副作用
-
副作用理解
- 表示在执行一个函数时,除了返回函数值之外,还对调用函数产生了附加的影响,比如修改了全局变量,修改参数或者改变外部的存储
1
2
3
4
5
6
7
8
9
10
11
12
13
14// test是一个纯函数
function test(info) {
return {
...info,//展开运算符,对原对象进行浅拷贝,并没有修改原对象
age:10
}
}
var obj1 = {
name:"111",
age:12
}
var obj2 = test(obj1);
console.log(obj2);
console.log(obj1); -
纯函数的优势
- 安心的编写和安心的使用
- 只是单纯实现自己的业务逻辑即可,不需要关心传入的内容是如何获得的或者依赖其他的外部变量是否已经发生了修改
- 你在用的时候,你确定你的输入内容不会被任意篡改,并且自己确定的输入,一定会有确定的输出
-
纯函数的案例学习
- 展开运算符,对原对象进行浅拷贝,并没有修改原对象
-
-
柯里化是什么
- 柯里化 (英语:Currying) 又译为
卡瑞化或加里化 - 只传递给函数一部分参数来调用它,让它返回一个函数去处理剩余的参数
- 柯里化 (英语:Currying) 又译为
-
实现柯里化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24//一般函数
function add(x,y,z) {
return (x+y+z);
}
// 使用柯里化技术
function bax(x) {
return function (y) {
return function (z) {
return (x+y+z);
}
}
}
// 一般函数实现
var result1 = add(10,20,30);
console.log(result1);
// 柯里化实现
var result2 = bax(11)(22)(33);
console.log(result2);
// 简化柯里化操作
var foo = (x) => (y) => (z) => {
return (x+y+z);
}
var result3 = foo(22)(33)(44);
console.log(result3); -
柯里化函数的实现 (自动)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33function add(x,y,z) {
return (x+y+z);
}
// 柯里化函数的实现hyCurrying
function hyCurrying(fn) {
function curried1(...args1) {
// 判断接收参数args和函数本身需要接收的参数数量是否一致
// 当传入的参数大于等于函数需要的参数时,就执行函数
if (args1.length >= fn.length) {
//方式一
// return fn(...args1);
/* // 方式二
return fn.call(this,...args1); */
// 方式三
// 此处this理解:若curryAdd.apply("test")即curried1中的this,把this绑定在fn函数中
return fn.apply(this,args1);
} else {
// 没有达到函数所需参数数量时,需要返回一个新的函数,继续来接收参数
function curried2(...args2) {
// 接收到参数后,需要递归调用curried1来检查函数的参数个数是否达到所需参数的个数
return curried1.apply(this,[...args1,...args2])
// return curried1.apply(this,args.concat(args2))
}
return curried2;
}
}
return curried1;
}
var curryAdd = hyCurrying(add);
var result1 = curryAdd(10,20,30);
var result2 = curryAdd(10)(20)(30);
console.log(result1);
console.log(result2); -
柯里化的应用
-
函数的职责单一1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// 一般函数实现
function add(x, y, z) {
x: x + 2;
y: y * 2;
z: z * z;
return (x + y + z)
}
console.log(add(10,20,30));
// 柯里化实现
var bar = (x) => {
x+=2;
return (y) => {
y*=2;
return (z) => {
z*=z;
return (x+y+z);
}
}
}
var result = bar(10)(20)(30);
console.log(result); -
逻辑复用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19/* // 23 + x = ?(x = 任意实数)
// 一般函数实现
function log(date, type, message) {
console.log(`[${date.getHours()}:${date.getMinutes()}][${type}]:[${message}]`);
}
log(new Date(), "DEBUG", "查询数据错误");
log(new Date(), "DEBUG", "轮播图的错误");
log(new Date(), "DEBUG", "查询表单错误"); */
// 柯里化实现
var log = date => type => message => {
console.log(`[${date.getHours()}:${date.getMinutes()}][${type}]:[${message}]`);
}
// 柯里化--复用时间逻辑
var nowLog = log(new Date());
nowLog("6666")("undefined");
// 柯里化--复用时间逻辑、错误信息逻辑
var newAndDebug = log(new Date())("同一类型错误信息");
newAndDebug("undefined");
newAndDebug("null");
-
-
组合函数-
将多个函数组合起来,自动依次调用
-
实现组合函数 (多个函数,多个参数)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25function hyCompose(...fns) {
// var length = fns.length;
for (var i = 0; i < fns.length; i++) {
if (typeof fns[i] !== 'function') {
throw TypeError("Expected arguments are functions");
}
}
function compose(...args) {
var index = 0;
var result = fns.length ? fns[index].apply(this,args):args;
while (++index < fns.length) {
result = fns[index].call(this,result);
}
return result;
}
return compose;
}
function double(num1) {
return num1*2;
}
function square(num2) {
return num2**2;
}
var newFn = hyCompose(double,square);
console.log(newFn(10));
-
# 4.JavaScript 面向对象
# 1. 深入理解对象
-
补充
- JavaScript 支持多种编程范式,包括函数式编程、面向对象编程
- JavaScript 中的对象被设计成一组属性的无序集合,类似哈希表
- key 是一个标识符名称,value 可以是任意类型,也可以是其他对象或者函数类型
- 如果值是一个函数,那么我们可以称之为是对象的方法
-
对象的语法
- 创建对象的两种方式
- 使用
new关键字及Object类进行创建 - 通过字面量进行创建
- 使用
- 对象字面量
- 对象的类型
- 函数对象
- 创建对象的两种方式
-
对象的内容
-
属性和方法定义
- 对象方法补充
preventExtensions: 禁止对象扩展新属性seal: 密封对象,不允许配置和删除属性,禁止对象配置 / 删除里面的属性即设置configurable:falsefreeze: 冻结对象,不允许修改现有的属性,使属性不可以修改即设置writable:false
- 对象方法补充
-
对象属性描述符
-
想要对一个属性进行比较精准的操作控制,那么我们就可以使用属性描述符
-
通过属性描述符可以精准的添加或修改对象的属性
-
属性描述符需要使用 **
Object.defineProperty()** 来对属性进行添加或者修改 -
Object.defineProperty(obj,prop,descriptor)obj: 需要定义属性的对象prop: 将要定义或修改的属性名称或Symboldescriptor: 将要定义或修改的属性描述符- 该方式添加的属性默认是不可枚举的
-
Object.defineProperties(): 在一个对象上定义多个新的属性或修改现有属性,返回该对象 -
属性描述符
-
数据属性描述符
configurable: 属性是否可以通过 delete 删除属性,是否可以修改它的特性,或者是否可以将它修改为存取属性描述符- 当我们直接在一个对象上定义某个属性时,这个属性的 [Configurable] 为 true
- 当我们通过属性描述符定义一个属性时,这个属性的 [Configurable] 默认为 false
enumerable: 属性是否可以通过 for-in 或者 Object.keys () 返回该属性- 当我们直接在一个对象上定义某个属性时,这个属性的 [[Enumerable]] 为 true
- 当我们通过属性描述符定义一个属性时,这个属性的 [[Enumerable]] 默认为 false
value: 属性的 value 值,读取属性时会返回该值,修改属性时,会对其进行修改- 默认情况下这个值是 undefined
writable: 表示是否可以修改属性的值- 当我们直接在一个对象上定义某个属性时,这个属性的 [[Writable]] 为 true
- 当我们通过属性描述符定义一个属性时,这个属性的 [[Writable]] 默认为 false
-
存取 (访问器) 属性描述符
configurable: 属性是否可以通过 delete 删除属性,是否可以修改它的特性,或者是否可以将它修改为存取属性描述符- 当我们直接在一个对象上定义某个属性时,这个属性的 [Configurable] 为 true
- 当我们通过属性描述符定义一个属性时,这个属性的 [Configurable] 默认为 false
enumerable: 属性是否可以通过 for-in 或者 Object.keys () 返回该属性- 当我们直接在一个对象上定义某个属性时,这个属性的 [[Enumerable]] 为 true
- 当我们通过属性描述符定义一个属性时,这个属性的 [[Enumerable]] 默认为 false
get: 获取属性时会执行的函数,默认值为undefinedset: 设置属性时会执行的函数,默认值为 undefined
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31var object = {
name:"德尔蓝卡",
age:12,
_address:"北京市"
}
// 存取属性描述符
// 1、隐藏某一个私有属性不希望直接被外界使用和赋值
// 2、截获某一个属性它访问和设置值的过程时,会使用存储属性描述符
Object.defineProperty(object,"address",{
configurable:true,
enumerable:true,
get:function () {
foo();
return this._address;
},
set:function (value) {
bar();
this._address = value
}
})
console.log(object.address);
console.log(object);
object.address = "上海市";
console.log(object);
function foo() {
console.log("获取一次属性");
}
function bar() {
console.log("设置一次属性值");
}
configurable enumerable value writable get set 数据属性描述符 可以 可以 可以 可以 不可以 不可以 存取属性描述符 可以 可以 不可以 不可以 可以 可以 -
-
-
获取对象的属性描述符
getOwnPropertyDescriptor: 获取某一个特定属性的属性描述符getOwnPropertyDescriptors: 获取所有属性的属性描述符
-
访问器属性使用
-
对象属性判断
-
-
对象的拷贝
- 对象的引用赋值
- 对象的浅拷贝
- 对象的深拷贝
-
ES6 对象增强
Object.is()- 简写属性名
- 可计算属性名称
- 简写方法名
- 对象解构
- 嵌套解构
- 部分解构
- 参数上下文匹配
# 2. 面向对象编程
-
理解面向对象
- 什么是面向对象编程
- 面向对象编程的特性
- 封装
- 继承
- 多态
- 类和对象的关系
-
创建对象方式
- 工厂模式创建
- 该模式可以减少代码的重复量,缺点是无法获取对象的真实类型,获取对象类型都是
object
- 该模式可以减少代码的重复量,缺点是无法获取对象的真实类型,获取对象类型都是
- 构造函数创建
- 构造函数也称之为构造器 (constructor), 通常是我们在创建对象时会调用的函数
- 一个普通的函数被使用 new 操作符来调用了,那么这个函数就称之为是一个构造函数
new- 在函数内存中创建一个新的对象(空对象)
- 这个对象内部的 [[prototype]] 属性会被赋值为该构造函数的 prototype 属性
- 构造函数内部的 this,会指向创建出来的新对象
- 执行函数的内部代码(函数体代码)
- 如果构造函数没有返回非空对象,则返回创建出来的新对象
- 缺点:构造函数的缺点,需要为每一个对象的函数去创建一个函数对象实例,重复的创建对象
- 原型创建模式
- 认识原型
- 对象的原型(隐式原型)
- JavaScript 当中每个对象都有一个特殊的内置属性 [[prototype]],这个特殊的对象可以指向另外一个对象
- 原型对象的作用
- 当我们通过引用对象的属性 key 来获取一个 value 时,它会触发 [[Get]] 的操作
- 这个操作会首先检查该属性是否有对应的属性,如果有的话就使用它
- 如果对象中没有该属性,那么会访问对象 [[prototype]] 内置属性指向的对象上的属性
- 获取原型的方式
- 通过对象的__proto__属性可以获取到(但是这个是早期浏览器自己添加的,存在一定的兼容性问题)
- 通过 Object.getPrototypeOf 方法可以获取到
- 函数的原型(显示原型)
- 所有的函数都有一个 prototype 的属性
prototype属性- 指向对象的显式原型
constructor属性- 原型对象的属性:返回构造函数
__proto__属性- 指向对象的隐式原型
- 工厂模式创建
-
面向对象继承
-
认识原型链
- 深入原型对象
Object是顶层对象,Object的原型对象上面没有原型对象而是nullobject是所有类的父类
- 简洁的原型语法
- 修改原型的属性
- 需要在原型上添加过多的属性,通常我们会重新整个原型对象
- 每创建一个函数,就会同时创建它的 prototype 对象,这个对象也会自动获取 constructor 属性
- 这里给 prototype 重新赋值了一个对象,那么这个新对象的 constructor 属性,会指向 Object 构造函数,而不是 Person 构造函数了
- 默认情况下原生的 constructor 属性是不可枚举
- 如果希望解决这个问题,就可以使用我们前面介绍的 Object.defineProperty () 函数了
- 深入理解原型链
- 可以使用原型链继承
- 原型链最顶层的原型对象就是
Object的原型对象
- 原型和实例的关系
- 实例的属性
__proto__指向原型对象
- 实例的属性
- 深入原型对象
-
继承的实现
-
原型链实现继承
- 实现方式:
子类.prototype = new 父类(); - 原型链实现继承的弊端
- 通过直接打印对象是看不到继承的属性
- 这个属性会被多个对象共享,如果这个属性是一个引用类型,,其中一个共享对象进行修改时,那么就会造成问题,若是基本数据类型则不会出现该问题
- 不能给 Person 传递参数,因为这个对象是一次性创建的
- 实现方式:
-
借用构造函数实现继承 (经典继承)
-
constructor stealing: 称之为借用构造函数、经典继承、伪造对象 -
因为函数可以在任意的时刻被调用,因此通过 apply () 和 call () 方法也可以在新创建的对象上执行构造函数
-
实现方式
1
2
3
4
5
6// 子类:私有属性和方法定义
function Student(name,age,friends,son) {
// this指的是通过new绑定创建的stu对象
Person.call(this,name,age,friends);
this.sno = son;
} -
借用构造函数继承存在的弊端
- 父类 Person 函数至少被调用了两次
- stu 原型对象上多出一些属性,而这些属性出现重复,因为子类本身就已经存在,但是这些属性没有存在的必要
-
-
组合借用继承方式(和借用继承没有不同)
- 实现方式
- 存在弊端
- 一般都会调用两次父类构造函数,一次在创建子类原型的时候,一次在子类构造函数内部 (也就是每次创建子类实例的时候)
- 所有的子类实例事实上会拥有两份父类的属性,一份在当前的实例自己里面 (也就是 person 本身的),另一份在子类对应的原型对象中 (也就是 person.__proto__里面), 无需担心访问出现问题,默认一定是访问实例本身的属性
-
寄生式继承函数
- 实现方式
- 存在弊端
-
集成式组合继承
- 释放方式
-
-
-
ES6 类的使用
- class 类的定义
- 声明式、表达式两种
- 构造方法
- 属性定义
- 方法定义
- 类的实例化过程
- 类的构建过程解析
- 类的类型、function 类型
- 属性分类解析
- 实例属性和方法
- 原型属性和访问器
- static 类方法和属性
- class 类的继承
- extends 关键字
- super 函数的使用
- 构造函数
- 普通函数
- Babel 的处理
- Babel 工具对 class 的处理
- 阅读 Babel 转换后的代码
- Babel 对继承的转换处理
- Babel 继承的源码阅读
- _inherits
- _possibleConstructorReturn
- _classCallCheck
- Babel 工具对 class 的处理
- class 类的定义
-
面向对象面试题
# 5.ES6~12 新特性
# 1.ES6 常见新特性
- 基础补充
- 对象增强写法
- 数组 / 对象解构
- var 与 let/const 区别
- 作用域提升
- 不允许重复声明变量
- window 对象的区别,let/const 不会添加到 window 对象上,保存的位置与之前也会有一定的区别
# 2.ES7 常见新特性
# 3.ES8 常见新特性
# 4.ES9 常见新特性
# 5.ES10 常见新特性
# 6.ES11 常见新特性
# 7.ES12 常见新特性
# 6.Proxy-Reflect
# 1.Proxy
- 在 ES6 中,新增了一个 Proxy 类,这个类从名字就可以看出来,是用于帮助我们创建一个代理
- 如果我们希望监听一个对象的相关操作,那么我们可以先创建一个代理对象(Proxy 对象)
- 之后对该对象的所有操作,都通过代理对象来完成,代理对象可以监听我们想要对原对象进行哪些操作
# 2.Reflect
- Reflect 也是 ES6 新增的一个 API,它是一个对象,字面的意思是反射
- 它主要提供了很多操作 JavaScript 对象的方法,有点像 Object 中操作对象的方法
# 7. 异步处理
# 1. 迭代器和生成器
-
迭代器
- 本身为一个对象,使用户在容器对象上遍访的对象,使用该接口无需关心对象的内部实现细节,迭代器是帮助我们对某个数据结构进行遍历的对象
- 在 JavaScript 中,迭代器也是一个具体的对象,这个对象需要符合迭代器协议 (iterator protocol), 迭代器协议定义了产生一系列值 (无论是有限还是无限个) 的标准方式,那么在 js 中这个标准就是一个特定的 next 方法
- next 方法有如下的要求
- 一个无参数函数或者含有一个参数的函数,返回一个应当拥有以下两个属性的对象
- done(boolean)
- 如果迭代器可以产生序列中的下一个值,则为 false,这等价于没有指定 done 这个属性
- 如果迭代器已将序列迭代完毕,则为 true。这种情况下,value 是可选的,如果它依然存在,即为迭代结束之后的默认返回值
- value
- 迭代器返回的任何 JavaScript 值,done 为 true 时可省略
- done(boolean)
- 一个无参数函数或者含有一个参数的函数,返回一个应当拥有以下两个属性的对象
-
可迭代对象
-
当一个对象实现了 **
iterable protocol** 协议时,它就是一个可迭代对象 -
这个对象要求是必须实现
@@iterator方法,即 **[Symbol.iterator]属性对应着一个函数** 在代码中我们使用Symbol.iterator访问该属性1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32//可迭代对象iteratorObj
const iteratorObj = {
array: [111, 222, 333],
[Symbol.iterator]: function () {
let index = 0;
return {
// 返回一个迭代器
next:() => {
if (index < this.array.length) {
return { done: false, value: this.array[index++] }
} else {
return { done: true, value: undefined }
}
}
}
}
}
console.log(iteratorObj[Symbol.iterator]);
// 调用该方法会生成一个可迭代对象
const iterator = iteratorObj[Symbol.iterator]();
console.log(iterator);
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
// 返回一个新的可迭代对象
const iterator2 = iteratorObj[Symbol.iterator]();
console.log(iterator2);
console.log(iterator2.next());
console.log(iterator2.next());
console.log(iterator2.next());
console.log(iterator2.next()); -
原生迭代器对象,已经实现可迭代协议有:
String、Array、Map、Set、arguments对象、NodeList集合 -
可迭代对象的应用
- JavaScript 语法:
for...of、展开语法(spread syntax)、yield*、解构赋值 - 创建一些对象时:
new Map、new WeakMap、new Set、new WeakSet - 一些方法调用:
Promise.all(iterator)、Promise.race(iterator)、Array.from(iterator)
- JavaScript 语法:
-
-
生成器
- 生成器是 ES6 中新增的一种函数控制、使用的方案,它可以让我们更加灵活的控制函数什么时候继续执行、暂停执行等
- 生成器函数也是一个函数,但是和普通的函数有一些区别
- 生成器函数需要在 function 的后面加一个符号
* - 生成器函数可以通过 **
yield关键字** 来控制函数的执行流程 - 生成器函数的返回值是一个 Generator(生成器)
- 生成器事实上是一种特殊的迭代器,MDN:Instead,they return a special type of iterator,called aGenerator
- 生成器函数需要在 function 的后面加一个符号
# 2.Promise 的使用
-
Promise是一个类,可以翻译为承诺、许诺、期约 -
then是一个 Promise 对象上的一个方法:实际上是 Promise 原型上的Promise.prototype.then() -
浏览器的循环事件
-
进程与线程理解
- 进程:计算已经运行的程序,是操作系统管理程序的一种方式,启动一个应用程序,就会默认启动一个进程 (也可能是多个进程)
- 线程:操作系统能够运行运算调度的最小单位,通常情况下它被包含在进程中,启动每一个进程中,都会启动至少一个线程用来执行程序中的代码,这个线程被称为主线程
-
浏览器中的 JavaScript 线程
- JavaScript 是单线程,拥有自己的容器进程:浏览器或 node
- 浏览器是多进程的,当我们打开一个 tab 页面时就会开启一个新的进程,这是为了防止一个页面卡死而造成所有页面无法响应,整个浏览器需要强制退出
- JavaScript 同一时刻只能做一件事情,如果该事情非常耗时,那么当前的线程就会被阻塞,然而浏览器每一个进程是多线程的,那么其他的线程可以来完成这个耗时的操作,如:网络请求、定时器等,我们只需要在特定的时刻执行回调即可
-
事件循环图解

-
微任务和宏任务
- 微任务队列 (microtask queue):一般有 queueMicrotask ()、Promise 的 then 回调、MutationObserver 的 API: 监听 DOM 的调用
- 宏任务队列 (macrotask queue):一般有定时器、ajax、DOM 事件点击、UI Rendering 渲染
- 规范:在执行任何的宏任务之前,都需要先保证微任务队列已经被清空
-
事件循环对于两个队列的优先级
main script代码优先执行- 在执行任何一个宏任务之前 (不是队列,而是一个宏任务),都会先查看微任务队列中是否有任务需要执行,也就是说宏任务执行之前,必须保证微任务对队列是空的,若果不为空,那么就优先执行微任务队列中的任务 (回调函数)
-
-
node 的事件循环
- 浏览器中
EventLoop即事件循环是根据 HTML5 定义的规范来实现的,不同的浏览器可能会有不同的实现,而 node 中是由libuv库现实的 - 微任务和宏任务
- 宏任务 (macrotask):setTimeout、setInterval、IO 事件、setImmediate、close 事件
- 微任务 (microtask):Promise 的 then 回调、process.nextTick、queueMicrotask
- node 队列执行顺序
next tick microtask queueother microtask queuetimer queuepoll queuecheck queueclose queue
- 浏览器中
# 3.async 和 await
async关键字用于声明一个异步函数async是asynchronous单词的缩写,意思为异步、非同步sync是synchronous单词的缩写,意思为同步、同时
# 8. 模块化与包管理工具
- 模块化开发
- 认识模块化开发
- 事实上模块化开发最终的目的是将程序划分成一个个小的结构,这个结构中编写属于自己的逻辑代码,有自己的作用域,不会影响到其他的结构,这个结构可以将自己希望暴露的变量、函数、对象等导出给其结构使用,也可以通过某种方式,导入另外结构中的变量、函数、对象
commonJS规范CommonJS和Node关系- CommonJS 是一个规范,最初是在浏览器以外的地方使用,当时被命名为 ServerJS,后来为了体现它的广泛性,修改为 CommonJS,平时我们也会简称为 CJS
- Node 是 CommonJS 在服务器端一个具有代表性的实现,Browserify 是 CommonJS 在浏览器中的一种实现,webpack 打包工具具备对 CommonJS 的支持和转换,Node 中对 CommonJS 进行了支持和实现,让我们在开发 node 的过程中可以方便的进行模块化开发,在 Node 中每一个 js 文件都是一个单独的模块,这个模块中包括 CommonJS 规范的核心变量:exports、module.exports、require,我们可以使用这些变量来方便的进行模块化开发,前面我们提到过模块化的核心是导出和导入,Node 中对其进行了实现:exports 和 module.exports 可以负责对模块中的内容进行导出,require 函数可以帮助我们导入其他模块(自定义模块、系统模块、第三方库模块)中的内容
AMD和CMD规范ESModule规范化ES Module和CommonJS
- 认识模块化开发
- npm 包管理工具