this指向
this的绑定规则
this的指向
- 在函数调用的时候,JavaScript会 默认给this绑定一个值
- this的 绑定和定义的位置(即编写的位置) 没有关系
- this的 绑定和调用方式以及调用的位置有关系
- this 是在运行时才被绑定的
默认绑定
- 独立的调用函数this指向window,但在严格模式下独立调用的函数中的this指向的是Undefined(“use strict”)
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 42 43 44 45 46 47 48 49 50 51 52 53
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script>
function foo() { console.log(this) }
foo()
function test1() { console.log(this) test2() }
function test2() { console.log(this) test3() }
function test3() { console.log(this) } test1()
function project(func) { func() }
var obj = { name: "jojo", bar: function() { console.log(this) } }
project(obj.bar)
</script>
</body> </html>
|
隐式绑定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script>
function foo() { console.log(this) }
var obj = { bar: foo }
obj.bar()
</script> </body> </html>
|
new绑定
- 执行的操作
- 创建一个全新的对象
- 这个新对象会被执行prototype连接
- 这个新对象会绑定到函数调用的this上
- 如果函数没有返回其他对象,表达式会返回这个新对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script>
function foo() { console.log(this); this.name = "why" }
new foo()
</script>
</body> </html>
|
显式绑定
call:func.call(thisArg, arg1, arg2, ...)
apply:func.apply(thisArg, [arg1, arg2, ...]
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
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script>
var obj = { name: "why" }
function foo(name, age) { console.log(this) console.log(name, age) }
foo.call(obj, "jojo", 20) foo.apply(obj, ["小羊", 19])
</script> </body> </html>
|
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
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> function foo(name, age) { console.log(this); console.log(name); console.log(age); } var obj = { name: 'Tom' }
var bar = foo.bind(obj, "jojo", 20) bar()
</script> </body> </html>
|
绑定优先级
显式 > 隐式
new > 隐式
new > bind
this面试题
一
![[Pasted image 20240727141059.png]]
二
![[Pasted image 20240727191000.png]]
三
![[Pasted image 20240727211810.png]]
![[Pasted image 20240727211824.png]]
四
![[Pasted image 20240727220053.png]]
手写call、apply、bind函数
day36
箭头函数
- 箭头函数 不会绑定this、arguments属性, 没有显示原型,不能和new一起使用
nums.forEach((item, index, arr) => { })
- 优化:
- 如果只有一个参数,( )可以省略![[Pasted image 20240725150801.png]]
- 如果执行体只有一行代码,可以省略大括号,并且会返回这行代码的返回值![[Pasted image 20240725150853.png]]
- 如果执行体只返回一个对象,需要给对象加上( )![[Pasted image 20240725150934.png]]
深入浏览器的渲染原理
网页被解析的过程
![[Pasted image 20240801121708.png]]
浏览器的内核
- 常见的浏览器内核![[Pasted image 20240801123558.png]]
- 浏览器内核也称作浏览器排版引擎
渲染页面的详细流程
![[Pasted image 20240801123756.png]]
解析HTML生成 DOM Tree ,遇到css文件时,解析CSS生成 Style Rules,这两者的解析过程不产生冲突。DOM Tree 和 Style Rules生成 Render Tree(渲染树,渲染树中没有节点的位置),再通过 Layout进行布局,然后进行绘画和展示
HTML解析过程
- 解析HTML是所有步骤的开始(服务器给浏览器默认返回 .html 文件)
- 解析HTML构建 DOM Tree![[Pasted image 20240801124525.png]]
生成CSS规则
- 解析过程中,遇到CSS的 link元素,浏览器会下载对应的CSS文件(下载CSS文件不会影响DOM解析)
- 下载完CSS文件,会对CSS文件进行解析,得到对应的 Style Rules(规则树, 也可以称之为CSSOM,CSS对象模型)![[Pasted image 20240801124907.png]]
构建Render Tree
- DOM Tree 和 CSSOM Tree 结合构建 Render Tree![[Pasted image 20240801125031.png]]
- link元素不会阻碍DOM Tree 的构建过程,但会阻碍 Render Tree 的构建过程,因为在构建Render Tree时,需要对应的CSSOM Tree
- Render Tree 和 DOM Tree 并不是一一对应的关系, display为none的元素,不会出现在Render Tree中
布局(Layout)和绘制(paint)
- 在Render Tree上运行 布局 来计算每个节点的几何体
- Render Tree会显示节点及其他样式,但不显示 每个节点的尺寸、位置等信息
- 布局可以确定呈现树中 所有节点的宽度、高度和位置信息
- 将每个节点绘制到屏幕上
- 绘制时,浏览器将布局的每个frame转为屏幕上实际的像素点
- 包括将元素的可见部分进行绘制,如 文本、颜色、边框、阴影、替换元素(img等)
回流和重绘
- 回流
- 在第一次确定节点的大小和位置,称之为布局
- 之后再进行修改重新计算称为回流
- 引起回流的情况![[Pasted image 20240801131023.png]]
- 重绘
- 引起重绘的情况
- 修改背景色、文字颜色、边框颜色、样式等
![[Pasted image 20240801131232.png]]
特殊解析 - composite合成
- 每个合成层都是 单独渲染的
- 默认情况下,标准流中的内容都是被绘制在同一个图层(Layer)中
- 有一些属性绘创建一个新的合成层,利用GPU来加速绘制
- **3D transforms
- **video、canvas、iframe
- **opacity动画转换时
- **position: fixed(固定定位)
- will-change(目前还是一个实验性的属性,提前告诉浏览器元素可能发生的变化)
- **animation 或 transition 设置了 opacity(透明度)、transform
- 分层是以内存管理为代价提高性能,不能作为性能优化策略过度使用
script元素和页面解析的关系
- 浏览器在解析HTML过程中,遇到了 script元素是不能继续构建DOM树的,会停止构建,先下载JavaScript代码,并且执行JavaScript的脚本,等到JavaScript脚本执行结束后,再继续解析HTML,构建DOM树
![[Pasted image 20240801142428.png]]
defer属性
- defer属性告诉浏览器 不要等待脚本下载, 继续解析HTML, 构建DOM Tree, 如果脚本提前下载好了,它会 等待DOM Tree构建完成,在DOMContentLoaded事件完成前先执行defer中的代码
- 多个带defer的脚本会保持正确的顺序执行
- defer可以提高网页的性能,推荐放在head元素中
- 注意: defer只适用于外部脚本,对于script默认内容会被忽略
async属性
- async让一个脚本完全独立
- async脚本不会阻碍浏览器的解析(与defer类似)
- async脚本不能保证顺序,它独立下载、独立运行,不会等待其他脚本
- async不能保证在DOMContentLoaded之前或者之后执行
- defer常用于需要在文档解析后操作DOM的JavaScript代码,并且对多个script文件有顺序要求
- async通常用于独立的脚本,对其他脚本,甚至DOM没有依赖的
深入JavaScript的运行原理
V8引擎的执行原理
![[Pasted image 20240801154302.png]]
V8引擎的架构
![[Pasted image 20240801154530.png]]
JS执行上下文
执行上下文
- JS引擎内部有一个执行上下文栈(Execution Context Stack,简称ECS),用于执行代码的调用栈
- 全局的代码块为了执行会构建一个Global Execution Context(GEC),GEC会被放入ECS中执行
认识VO对象
- 每一个执行上下文会关联一个 VO(Variable Object,变量对象),变量和函数声明会被添加到这个VO对象中
- 当全局代码被执行的时候,VO就是GO对象了
函数执行上下文
![[Pasted image 20240801172129.png]]
JavaScript的内存管理和闭包
JavaScript内存管理
- JavaScript会在 定义数据时为我们分配内存
- JavaScript对于 原始数据类型内存的分配 会在执行时,直接在栈空间进行分配
- 对于 复杂数据类型内存的分配 会在堆内存中开辟一块空间,并且将这块空间的指针返回值变量引用
垃圾回收(GC)算法
引用计数
- 当 一个对象有一个引用指向它 时,这个对象的的引用就 +1
- 当 一个对象的引用为0 时,这个对象就可以被销毁掉
- 弊端:会产生循环引用![[Pasted image 20240802152232.png]]
标记清除
- 核心思路: 可达性
- 实现思路: 设置一个 根对象,垃圾回收器会定期从这个根对象开始,找到所有从根开始有引用到的对象,对于没有引用到的对象,认为是不可用的对象
- 这个算法解决了上一个算法产生的循环引用的问题![[Pasted image 20240802154857.png]]
算法优化
V8引擎为了进行更好的优化,在算法实现细节上会结合一些其他算法
- 标记整理
- 回收期间会将保存的储存对象 搬运汇集到连续的内存空间,从而 整合空闲空间,避免内存碎片化
- 分代收集:对象被分为 新的 和 旧的
- 很多对象完成工作并很快死去,它们会被 很快被清理
- 那些长期存活的对象会变 老旧,而且 被检查的频次也会减少
- **增量收集
- 将垃圾收集工作分成几个部分来做,然后将这几部分逐一处理,这样把一个大的延迟分成许多微小的延迟
- **闲时收集
- 垃圾收集器 只会在CPU空闲时尝试运行,减少可能对代码执行的影响
闭包
闭包的定义
- 一个函数和周围的环境的引用捆绑在一起,这样的组合就是闭包
- 闭包可以在一个内层函数中访问到其外层函数的作用域
- 广义理解:JavaScript中的函数都是闭包
- 狭义理解:JavaScript中的一个函数,如果访问了外层作用域的变量,那么它就是一个闭包
JavaScript函数的增强知识
函数对象的属性和argumens
属性name和length
- 属性name:一个函数的名词我们可以通过name来访问![[Pasted image 20240802203605.png]]
- 属性length:返回函数输入参数的个数(rest参数不参与参数的个数)![[Pasted image 20240802203941.png]]
认识arguments
- arguments是一个类数组对象(不是一个数组类型,而是一个对象类型)
- 它拥有数组的一些特性,如length、可以用Index索引来访问
- 但没有数组的一些方法,如filter、map
arguments转Array
- 方法一:遍历arguments,添加到一个新数组中![[Pasted image 20240802205908.png]]
- 方法二:ES6中的两个方法
- 1.Array.from![[Pasted image 20240802210001.png]]
- 2.
[...arguments]
![[Pasted image 20240802210026.png]]
- 方法三:调用slice函数的call方法![[Pasted image 20240802210124.png]]
函数的剩余(rest)参数
- ES6中引用了rest parameter,可以将不定数量的参数放入到一个数组中
- 最后一个参数是 … 为前缀,那么剩余的参数会作为一个数组放到该参数中![[Pasted image 20240803163950.png]]
- 剩余参数和arguments的区别
- 剩余参数只包含 没有对应形参的实参,arguments对象包含了 传给函数的所有实参
- arguments对象不是一个数组,只是类数组对象,而rest参数是一个真正的数组,可以进行数组的所有操作
- 剩余参数必须放到最后一个位置,否则会报错
纯函数
理解纯函数
- 有确定的输入,一定会产生确定的输出
- 函数在执行过程中,不能产生副作用
柯里化函数
![[Pasted image 20240803194335.png]]
自动柯里化(了解)
![[Pasted image 20240803201340.png]]
组合函数
![[Pasted image 20240803203237.png]]
严格模式
![[Pasted image 20240803210307.png]]
对象增强
Object.defineProperty
- Object.defineProperty()方法会直接在对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象:
Object.defineProperty(obj, prop, descriptor)
- 可接受三个参数
- obj:要定义属性的对象
- prop:要定义或修改属性的名称或Symbol
- descriptor:要定义或修改的属性描述符
- 返回值被传递给函数的对象
数据属性描述符
![[Pasted image 20240805170950.png]]
存取属性描述符
![[Pasted image 20240805172134.png]]
同时定义多个属性
- Object.defineProperties()方法直接在一个对象上定义多个新的属性或修改现有属性,并且返回该对象![[Pasted image 20240805173134.png]]
ES5中的继承
对象和函数的原型
认识对象原型
![[Pasted image 20240805192039.png]]
函数对象的原型
![[Pasted image 20240805192551.png]]
将方法放在原型上
- *减少内存占用:
- 当方法定义在对象的实例上时,每个对象都会有一个独立的方法副本,这会浪费内存。
- 将方法放在原型上,所有对象实例共享同一个方法,大大减少了内存的使用。
- 提高代码复用性:
- 将方法放在原型上,可以让所有对象实例都能访问和使用这些方法。
- 这提高了代码的复用性,避免了在每个对象实例上都定义相同的方法,提高了开发效率。
- *动态添加/修改方法:
- 通过修改原型,可以动态地为所有对象实例添加或修改方法。
- 这使得代码更加灵活和可扩展。
- 保持对象实例的轻量级:
- 将方法放在原型上,可以保持对象实例本身更加简单和轻量级。
- 对象实例中只保存自己的属性,方法都存储在原型上,这样可以提高性能。
- *继承和多态:
- 通过原型链机制,可以实现继承和多态。
- 子类可以重写或扩展从父类继承的方法,实现代码复用和多态特性。
注意: 讲方法放在原型上的方法也叫做实例方法,在没有实例对象的情况下,不能调用此函数
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
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script>
function Students(name, age, grade) { this.name = name; this.age = age; this.grade = grade; } Students.prototype.running = function() { console.log(this.name + " is running"); }
var std1 = new Students("jojo", 20, 100)
std1.running()
</script> </body> </html>
|
constructor属性
![[Pasted image 20240806141712.png]]
**理解
![[Pasted image 20240806145948.png]]
![[Pasted image 20240806145900.png]]
- 重写原型对象![[Pasted image 20240806151052.png]]![[Pasted image 20240806151113.png]]![[Pasted image 20240806151123.png]]![[Pasted image 20240806151136.png]]
通过原型链实现继承
创建父类对象,并且作为子类的原型对象![[Pasted image 20240806160312.png]]![[Pasted image 20240806160344.png]]![[Pasted image 20240806160612.png]]
借用构造函数实现继承
![[Pasted image 20240806161649.png]]![[Pasted image 20240806161712.png]]![[Pasted image 20240806161725.png]]
- 组合原型链和借用构造函数实现继承的问题![[Pasted image 20240806162633.png]]
寄生组合实现继承
![[Pasted image 20240806165456.png]]
最终实现方案:
- 将继承函数封装成工具放在JS文件中![[Pasted image 20240806170337.png]]
- 代码![[Pasted image 20240806170358.png]]
对象的方法补充
![[Pasted image 20240806173132.png]]
ES6实现继承
原型继承关系图
![[Pasted image 20240806211030.png]]
class方式定义类
- 使用class来定义一个类:
- 类声明和类表达式![[Pasted image 20240807133150.png]]
- 在创建对象的时候想给类传递一些参数:
- 每个类有一个固定的构造函数方法constructor
- 每个类只能有一个构造函数
- 注意:类中定义的多个内容不需要使用 “,” 进行分割
实例方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class Person { constructor(name, age) { this.name = name this.age = age }
running() { console.log(`${this.name} is running.`) }
eating() { console.log(`${this.name} is eating.`) } }
var p1 = new Person("jojo", 20) p1.running() p1.eating()
|
类的静态方法
- 静态方法通常用于定义直接使用类来执行的方法,不需要有类的实例,使用 static关键字来定义
- 类方法里面的this指向类本身
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <script>
class Person { constructor(name, age) { this.name = name this.age = age }
static running() { console.log(`${this.name} is running.`); } }
Person.running()
</script>
|
super关键字
- 执行 super.method(…) 来调用一个父类方法
- 执行 super(…) 来调用一个父类 constructor (只能在自己的constructor中调用)
- 注意: 在子类的构造函数中使用this或者返回默认对象之前,必须先通过super调用父类的构造函数
- super的使用位置有三个:子类的构造方法、实例方法、静态方法
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 42 43 44 45 46 47 48 49 50 51 52
| <script>
class Person {
constructor(name, age) { this.name = name this.age = age } running() { console.log('running') } eating() { console.log('eating') }
static sleep() { console.log('sleeping') } }
class Student extends Person { constructor(name, age, grade) { super(name, age) this.grade = grade }
running() { console.log('running in class') super.running() }
studying() { console.log('studying') }
static sleep() { console.log('sleeping in class') super.sleep() } }
var stu = new Student("jojo", 18) stu.running() stu.eating() stu.studying() Student.sleep() </script>
|
继承内置类
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
| <script>
class xyArray extends Array { get lastItem() { return this[this.length - 1] }
get firstItem() { return this[0] } }
var arr = new xyArray(1, 2, 3, 4, 5) console.log(arr) console.log(arr.length) console.log(arr.lastItem) console.log(arr.firstItem)
Array.prototype.lastItem = function() { return this[this.length - 1] }
Array.prototype.firstItem = function() { return this[0] }
var arr2 = [1, 2, 3, 4, 5] console.log(arr2) console.log(arr2.length) console.log(arr2.lastItem()) console.log(arr2.firstItem())
</script>
|
类的混入mixins
- JavaScript的类只支持单继承,当我们需要多继承时,可以使用混入![[Pasted image 20240807153120.png]]
ES6对象的增强
字面量的增强
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| var name = "小明" var age = 18
var obj = { name, age }
function foo() { var message = "Hello World" var info = "This is a message from " return { message, info } }
var result = foo() console.log(result.message) console.log(result.info)
|
1 2 3 4 5 6 7 8 9 10
| var obj = { name: "小明", age: 18, sayHello() { console.log("Hello, " + this.name) } }
obj.sayHello()
|
1 2 3 4 5 6 7 8 9
| var key = "address" var value = "北京市海淀区" var obj = { [key]: value }
console.log(obj.address)
|
解构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| var arr = [1, 2, 3, undefined, 5]
var [a, b, c] = arr console.log(a, b, c)
var [a, , c] = arr console.log(a, c)
var [a, b, ...arr2] = arr console.log(a, b, arr2)
var [a , b, c, d = 4, e] = arr console.log(a, b, c, d, e)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| var obj = {name: 'zhangsan', age: 20, city: 'beijing'}
var {name, age, city} = obj console.log(name, age, city)
var {age, name, city} = obj console.log(name, age, city)
var {name, age, ...obj2} = obj console.log(name, age, obj2)
var {name, age, city, job = 'teacher'} = obj console.log(name, age, city, job)
var {name: myName, age: myAge, city: myCity} = obj console.log(myName, myAge, myCity)
|
ES6新特性
let、const
基本使用
- let关键字:与var没有太大的区别,都是用于声明一个变量
- const关键字
- 用const关键字声明的变量一旦被赋值,就不能被修改
- 如果赋值的是引用类型,那么可以通过引用找到对应的对象,修改对象的内容![[Pasted image 20240810011845.png]]
- let、const都不允许重复声明变量
- let、const不会给window上添加任何属性
let、const的块级作用域
- let、const、function、class声明的标识符具备块级作用域的限制![[Pasted image 20240810022228.png]]
- 但是函数拥有块级作用域,但是在外面依然可以访问
应用场景
- 获取多个按钮监听点击
![[Pasted image 20240810023404.png]]
模版字符串
- ES6开始使用模版字符串来嵌入JS的变量或者表达式来进行拼接
- 使用反引号来编写字符串,称为模版字符串
- 再通过 ${expression} 来动态嵌入内容![[Pasted image 20240810025103.png]]
标签模版字符串
- 使用标签模版字符串,在调用时插入其他变量
- 模版字符串会被拆分
- 第一个元素是数组,是被模版字符串拆分的字符串组合
- 后面的元素是一个个模版字符串传入的内容![[Pasted image 20240810030121.png]]
展开运算符
展开语法
![[Pasted image 20240810032922.png]]
![[Pasted image 20240810033004.png]]
Symbol
![[Pasted image 20240810034748.png]]![[Pasted image 20240810034808.png]]
![[Pasted image 20240810035320.png]]![[Pasted image 20240810035344.png]]
Set-Map
Set
- 基本使用
- Set中的元素不能重复
- 这个功能可以给数组去重![[Pasted image 20240810143447.png]]
- 常见属性和方法
- 属性
- 方法
- add(value):添加某个元素,返回Set对象本身
- delete(value):从Set中删除和value值相等的元素,返回Boolean类型
- has(value):判断Set中是否存在某个元素,返回Boolean类型
- clear():清空Set中的所有的元素,没有返回值
- forEach(callback, [,thisArg]):通过forEach遍历Set
WeakSet
![[Pasted image 20240810145329.png]]
![[Pasted image 20240810145349.png]]
Map
![[Pasted image 20240810150140.png]]
![[Pasted image 20240810150225.png]]
WeakMap
![[Pasted image 20240810152228.png]]
![[Pasted image 20240810152332.png]]
Promise
Promise 的状态: Promise 有三种状态:Pending(进行中)、Fulfilled(已成功)和 Rejected(已失败)。这些状态可以帮助我们更好地跟踪异步操作的进度。
Promise 的链式调用: 通过 .then()
和 .catch()
方法,我们可以将多个异步操作串联起来,形成一个 Promise 链。这样可以使代码更加清晰和可读。
错误处理: 在 Promise 链中,只需在最后添加一个 .catch()
方法即可捕获任何一个步骤中出现的错误,大大简化了错误处理的逻辑。
async/await: 为了进一步简化 Promise 的使用,ES2017 引入了 async/await
语法糖。使用 async
函数可以让异步代码看起来更像同步代码,大大提高了可读性。
- 创建 Promise:
- 使用
new Promise()
创建一个新的 Promise 对象。
- 传递一个函数作为参数,这个函数被称为 Promise 执行器(Executor)。
- 在 Promise 执行器函数内部,我们执行异步操作。
- 如果异步操作成功,调用
resolve(result)
函数,将结果传递出去。
- 如果异步操作失败,调用
reject(error)
函数,将错误信息传递出去。
- 使用 Promise:
- 通过
.then()
方法处理 Promise 成功的情况。
- 通过
.catch()
方法处理 Promise 失败的情况。
- 通过
.finally()
方法处理无论成功还是失败都要执行的代码。
- Promise 链式调用:
- 每次调用
.then()
或 .catch()
方法都会返回一个新的 Promise 对象。
- 可以将多个 Promise 操作串联起来,形成 Promise 链。
- 下一个
.then()
方法会等待上一个 Promise 完成后再执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| new Promise((resolve, reject) => { if () { resolve(result); } else { reject(error); } }) .then(result => { }) .catch(error => { }) .finally(() => { });
|
- 传递普通值
1 2 3 4 5 6 7 8 9
| new Promise((resolve, reject) => { resolve('success'); }) .then(result => { console.log(result); }) .catch(error => { console.error(error); });
|
- 如果
resolve()
函数传递一个普通值,这个值会作为 Promise 的 resolve 结果被传递到后续的 .then()
方法中。
- 传递 Promise 对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| new Promise((resolve, reject) => { resolve( new Promise((innerResolve, innerReject) => { setTimeout(() => { innerResolve('inner promise result'); }, 2000); }) ); }) .then(result => { console.log(result); }) .catch(error => { console.error(error); });
|
- 如果
resolve()
函数传递的是另一个 Promise 对象,那么外层 Promise 的状态会跟随内层 Promise 的状态进行变化。
- 外层 Promise 会”等待”内层 Promise 完成,然后采用内层 Promise 的状态和结果。
- 传递 thenable 对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const myThenable = { then(resolve, reject) { resolve('thenable result'); } };
new Promise((resolve, reject) => { resolve(myThenable); }) .then(result => { console.log(result); }) .catch(error => { console.error(error); });
|
- 如果
resolve()
函数传递的是一个具有 .then()
方法的对象(称为 thenable 对象),那么 Promise 会”等待”这个对象执行完 .then()
方法,并采用其返回的结果。
- 传递 throw 或 reject()
1 2 3 4 5 6 7 8 9
| new Promise((resolve, reject) => { reject(new Error('Something went wrong')); }) .then(result => { console.log(result); }) .catch(error => { console.error(error); });
|
- 如果在 Promise 执行器函数内部调用
throw
或 reject()
函数,那么 Promise 的状态会变为 rejected
。
- 后续的
.catch()
方法会捕获到这个错误,并进行处理。
ES8~ES13
ES8中对象的相关属性
- Object.values():
- 该方法返回一个给定对象自身的所有可枚举属性值的数组。
- 它的行为与
Object.keys()
类似,但返回的是属性值,而不是属性名。
- Object.entries():
- 该方法返回一个给定对象自身可枚举字符串键属性
[key, value]
的数组。
- 这个方法提供了一种迭代一个对象的所有属性的便捷方式。
- Object.getOwnPropertyDescriptors():
- 该方法返回指定对象所有自身属性的描述符。
- 这个方法在
Object.create()
时很有用,用于实现属性的复制和继承。
- Trailing commas:
- Trailing commas 允许在对象字面量、数组字面量、函数参数列表和函数调用中使用尾随逗号。
- 这可以使代码更容易维护和扩展,因为添加新属性或参数不会影响前面的代码。
- Async functions:
- Async functions 是 ES8 中引入的一个重要特性,用于简化异步编程。
- Async 函数返回一个 Promise 对象,可以使用
await
关键字来等待 Promise 完成。
ES8-padStart和padEnd
padStart(targetLength, [padString])
方法将当前字符串填充到指定的长度。填充从字符串的开始(左侧)应用的。
1 2 3 4 5
| console.log('42'.padStart(5, '0'));
console.log('foo'.padStart(10));
|
padEnd(targetLength, [padString])
方法将当前字符串填充到指定的长度。填充从字符串的末尾(右侧)应用的。
1 2 3 4 5
| console.log('hello'.padEnd(5, '.'));
console.log('foo'.padEnd(10));
|
这些方法在处理金额、日期、编号等数据格式时非常有用,可以帮助我们快速地格式化字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| const amount = 123.45; console.log(amount.toFixed(2).padStart(10, '0'));
const date = '2023-5-1'; console.log(date.padStart(10, '0'));
function hideIdCard(idCard) { return idCard.slice(-4).padStart(18, '*'); }
console.log(hideIdCard('123456789012345678'));
function hideBankCard(bankCard) { const length = bankCard.length; const front = bankCard.slice(0, 4); const back = bankCard.slice(-4); return front + '*'.repeat(length - 8) + back; }
console.log(hideBankCard('1234567890123456')); console.log(hideBankCard('123456789012345678'));
|
ES9-Object spread operators
- Spread 操作符 (
...
)
Spread 操作符可以在对象字面量中使用,用于展开一个现有的对象,将其属性复制到新的对象中。
1 2 3 4
| const originalObj = { a: 1, b: 2 }; const newObj = { ...originalObj, c: 3 };
console.log(newObj);
|
在上面的例子中,我们使用 Spread 操作符 ...
将 originalObj
的属性复制到 newObj
中,并添加了一个新的属性 c
。
2. 合并多个对象
Spread 操作符可以方便地合并多个对象:
1 2 3 4 5
| const obj1 = { a: 1, b: 2 }; const obj2 = { b: 3, c: 4 }; const mergedObj = { ...obj1, ...obj2 };
console.log(mergedObj);
|
在这个例子中,我们使用多个 Spread 操作符将 obj1
和 obj2
的属性合并到 mergedObj
中。当有重复的属性时,后面的对象的属性会覆盖前面对象的属性。
3. 与解构赋值结合使用
Spread 操作符也可以与对象解构赋值结合使用:
1 2 3 4 5
| const originalObj = { a: 1, b: 2, c: 3 }; const { a, ...rest } = originalObj;
console.log(a); console.log(rest);
|
在这个例子中,我们使用对象解构赋值提取 a
属性,并使用 Spread 操作符将剩余的属性赋值给 rest
对象。
4. 浅拷贝和深拷贝
需要注意的是,Spread 操作符只能进行浅拷贝,如果对象中嵌套了其他对象或数组,则需要使用其他方法实现深拷贝。
1 2 3 4
| const originalObj = { a: 1, b: { c: 2 } }; const newObj = { ...originalObj };
console.log(newObj.b === originalObj.b);
|
ES10-flat flatMap
flat()
flat()
方法用于将嵌套数组”拉平”为一维数组。它接受一个可选的 depth
参数,指定要提取嵌套数组的深度。如果不传 depth
参数,默认深度为 1。
1 2 3 4
| const nestedArray = [1, [2, 3], [4, [5, 6]]];
console.log(nestedArray.flat()); console.log(nestedArray.flat(2));
|
在上面的例子中,第一个 flat()
调用将数组拉平一层,第二个调用将数组拉平两层。
2. flatMap()
flatMap()
是 map()
和 flat()
的组合。它首先使用提供的映射函数映射每个元素,然后将结果压缩成一个新数组。
1 2 3 4
| const arr = [1, 2, 3, 4];
const doubledAndFlattened = arr.flatMap(x => [x, x * 2]); console.log(doubledAndFlattened);
|
在上面的例子中,我们使用 flatMap()
将每个元素映射为一个包含原始值和其双倍值的数组,然后将这些数组压缩成一个新数组。
flatMap()
的好处是,它可以在一步操作中完成映射和拉平操作,相比于先使用 map()
再使用 flat()
,效率更高。
ES10-Object.fromEntries
Object.entries()
Object.entries()
方法返回一个给定对象自身可枚举字符串属性 [key, value]
对组成的数组。
1 2 3 4
| const obj = { a: 1, b: 2, c: 3 }; const entries = Object.entries(obj);
console.log(entries);
|
这个方法在需要遍历对象属性时非常有用,比如使用 for...of
循环:
1 2 3 4 5 6 7
| for (const [key, value] of Object.entries(obj)) { console.log(key, value); }
|
Object.fromEntries()
Object.fromEntries()
方法执行与 Object.entries()
逆向操作,将一个键值对列表转换为一个对象。
1 2 3 4
| const entries = [['a', 1], ['b', 2], ['c', 3]]; const obj = Object.fromEntries(entries);
console.log(obj);
|
这个方法在需要从其他数据结构(如 Map)转换为对象时非常有用:
1 2 3 4
| const map = new Map([['a', 1], ['b', 2]]); const obj = Object.fromEntries(map);
console.log(obj);
|
应用场景
![[Pasted image 20240812145943.png]]
ES10-trimStart trimEnd
trim()
, trimStart()
和 trimEnd()
是 JavaScript 中用于删除字符串两端空白字符的三个方法。
trim()
trim()
方法返回一个新的字符串,其中从字符串的两端删除了所有空白字符。
1 2 3
| const str = " Hello, world! ";
console.log(str.trim());
|
trimStart()
trimStart()
方法返回一个新的字符串,其中从字符串的开头删除了所有空白字符。
1 2 3
| const str = " Hello, world! ";
console.log(str.trimStart());
|
trimEnd()
trimEnd()
方法返回一个新的字符串,其中从字符串的末尾删除了所有空白字符。
1 2 3
| const str = " Hello, world! ";
console.log(str.trimEnd());
|
这三个方法的主要区别在于它们删除空白字符的位置:
trim()
删除字符串两端的空白字符
trimStart()
删除字符串开头的空白字符
trimEnd()
删除字符串末尾的空白字符
ES11-BigInt
要表示大于NUmber.MAX_SAFE_INTEGER的数值,需要在数值后面加上n![[Pasted image 20240812151216.png]]
ES11-Nullish Coalescing Operator (空值合并运算符)
Nullish Coalescing Operator (空值合并运算符) 是 ECMAScript 2020 (ES11) 引入的一个新的运算符,它可以帮助我们处理 null
和 undefined
值。
这个运算符的符号是 ??
它的工作原理是:
- 如果左侧的操作数不是
null
或 undefined
,则返回左侧的操作数。
- 如果左侧的操作数是
null
或 undefined
,则返回右侧的操作数。
下面是一些例子:
1 2 3 4 5 6 7 8 9 10
| const username = ''; const displayName = username ?? 'Guest';
const age = 0; const defaultAge = age ?? 30;
const username2 = ''; const displayName2 = username2 || 'Guest';
|
在上面的例子中,当 username
为空字符串时,逻辑 OR (||
) 运算符将返回 'Guest'
。但是,空字符串在 JavaScript 中是一个有效的值,我们可能不希望将其视为”falsy”。这时,Nullish Coalescing Operator 就很有用,它只会在值为 null
或 undefined
时返回右侧操作数。
另一个常见的用例是设置默认值:
1 2 3 4 5 6 7 8
| function greet(name) { const displayName = name ?? 'Guest'; console.log(`Hello, ${displayName}!`); }
greet(null); greet(''); greet('John');
|
在这个例子中,如果 name
参数是 null
或 undefined
,Nullish Coalescing Operator 会将 displayName
设置为 'Guest'
。但如果 name
是一个空字符串,它仍然会被使用,因为空字符串不是 null
或 undefined
。
ES11-Optional Chaining (可选链)
Optional Chaining (可选链) 是 ECMAScript 2020 (ES11) 引入的一个新的运算符,用于安全地访问嵌套对象的属性。它的符号是 ?.
Optional Chaining 可以帮助我们避免在访问嵌套对象属性时出现的 TypeError: Cannot read property 'x' of undefined
错误。
下面是一些例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| let adventurer = { name: 'Alice', cat: { name: 'Dinah' } };
console.log(adventurer.dog.name);
let adventurer2 = { name: 'Alice', cat: { name: 'Dinah' } };
console.log(adventurer2?.dog?.name);
|
在第一个例子中,尝试访问 adventurer.dog.name
会抛出 TypeError
,因为 adventurer.dog
是 undefined
。
但在第二个例子中,使用 Optional Chaining 运算符 ?.
可以安全地访问嵌套属性。如果 adventurer2.dog
是 undefined
,则整个表达式的结果也是 undefined
,而不会抛出错误。
Optional Chaining 也可以与函数调用一起使用:
1 2 3 4 5 6 7 8 9 10
| let customer = { name: 'Carl', address: { city: 'Seattle' } };
console.log(customer.address.city); console.log(customer.address?.city); console.log(customer.address?.getZipCode?.());
|
在最后一个例子中,即使 customer.address.getZipCode
不存在,使用 Optional Chaining 也不会抛出错误,而是返回 undefined
。
ES12-FinalizationRegistry
FinalizationRegistry 是 ECMAScript 2021 (ES12) 引入的一个新的 API,它允许在对象被垃圾回收时执行自定义的清理逻辑。
FinalizationRegistry 的主要用途是:
- 监听对象的清理:当一个对象被垃圾回收时,FinalizationRegistry 会通知注册的回调函数。这可以用于执行清理操作,比如释放资源、发送通知等。
- 避免内存泄漏:FinalizationRegistry 可以帮助开发者避免内存泄漏,因为它可以确保在对象被销毁时执行清理逻辑。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const registry = new FinalizationRegistry((value) => { console.log(`Finalized: ${value}`); });
const obj = { id: 1 }; registry.register(obj, 'object-1');
obj = null; global.gc();
|
在上述示例中:
- 我们创建了一个 FinalizationRegistry 实例,并传入一个回调函数,该函数将在对象被垃圾回收时被调用。
- 我们注册了一个对象
obj
到 FinalizationRegistry,并为其提供了一个标识符 'object-1'
。
- 我们手动将
obj
设置为 null
,然后调用 global.gc()
来触发垃圾回收(在 Node.js 中,需要使用 --expose-gc
标志来启用手动垃圾回收)。
- 当对象被垃圾回收时,FinalizationRegistry 会调用我们提供的回调函数,并输出
'Finalized: object-1'
。
FinalizationRegistry 的一个主要优点是,它不会阻止对象被垃圾回收。相反,它会在对象被回收后执行清理逻辑,这使得它比 WeakMap
和 WeakSet
更加灵活和强大。
FinalizationRegistry 的典型用例包括:
- 清理 DOM 元素和事件监听器
- 关闭数据库连接或释放其他系统资源
- 发送对象销毁的通知
ES12-WeakRefs
WeakRef 是 ECMAScript 2021 (ES12) 引入的一个新的 API,它允许创建对对象的”弱引用”。这意味着被引用的对象可以被垃圾回收器回收,即使还有 WeakRef 引用它。
主要用途:
- 避免内存泄漏: 由于 WeakRef 不会妨碍对象被垃圾回收,因此可以帮助开发者避免内存泄漏的问题。
- 缓存模式: WeakRef 可以用于实现一种”缓存模式”,在需要时重新创建对象,而不是持有永久引用。
- 观察对象生命周期: WeakRef 可以用于观察对象的生命周期,并在对象被垃圾回收时执行相应的清理操作。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const obj = { id: 1 };
const weakRef = new WeakRef(obj);
obj = null; global.gc();
const refObj = weakRef.deref(); if (refObj) { console.log(refObj.id); } else { console.log('Object has been garbage collected'); }
|
- 我们创建了一个对象
obj
。
- 我们使用
new WeakRef(obj)
创建了一个 WeakRef 实例,它持有对 obj
的引用。
- 我们手动将
obj
设置为 null
,然后调用 global.gc()
来触发垃圾回收(在 Node.js 中,需要使用 --expose-gc
标志来启用手动垃圾回收)。
- 我们尝试使用
weakRef.deref()
方法访问 WeakRef 持有的对象。如果对象还存在,则输出 1
。否则,输出 'Object has been garbage collected'
。
WeakRef 的一个主要特点是,它不会阻止被引用的对象被垃圾回收。相反,一旦对象没有其他强引用,它就可以被垃圾回收,即使还有 WeakRef 引用它。这使得 WeakRef 非常适合于缓存模式和生命周期观察等用例
WeakRef 通常与 FinalizationRegistry 配合使用,后者可以在对象被垃圾回收时执行清理逻辑。这种组合可以帮助开发者更好地管理内存和系统资源。
ES13-method.at()
method.at()
是 ECMAScript 2022 (ES13) 引入的一个新的数组方法,它允许使用索引值访问数组元素,并且可以接受负值索引。
特点:
- 支持负值索引:
at()
方法可以接受负值索引,这意味着可以从数组末尾开始计数。例如, arr.at(-1)
返回数组的最后一个元素。
- 返回 undefined 而不是抛出错误: 如果使用无效的索引值(例如索引超出数组范围),
at()
方法不会抛出错误,而是返回 undefined
。
- 更简洁的语法:
at()
方法提供了一种更简洁的语法来访问数组元素,相比于使用方括号语法 (arr[index]
) 更加直观。
示例:
1 2 3 4 5 6
| const arr = [1, 2, 3, 4, 5];
console.log(arr.at(2)); console.log(arr.at(-1));
console.log(arr.at(10));
|
ES13-Object.hasOwn(obj, prop)
Object.hasOwn()
是 ECMAScript 2022 (ES13) 引入的一个新的静态方法,它用于检查一个对象是否包含指定的属性。这个方法是 Object.prototype.hasOwnProperty()
方法的一种更简洁的替代方式。
特点:
- 更简洁的语法:
Object.hasOwn(obj, prop)
方法提供了一种更简洁的语法来检查对象是否包含指定的属性,相比于使用 obj.hasOwnProperty(prop)
更加简洁。
- 更安全的属性检查:
Object.hasOwn()
避免了原型污染问题,因为它直接在对象上检查属性,而不会受到原型链上的属性影响。这使得它更安全地用于检查对象属性。
- 支持 null 和 undefined: 与
obj.hasOwnProperty(prop)
不同, Object.hasOwn()
可以接受 null
或 undefined
作为第一个参数,并在这种情况下返回 false
。
示例:
1 2 3 4 5 6 7
| const obj = { name: 'John', age: 30 };
console.log(Object.hasOwn(obj, 'name')); console.log(Object.hasOwn(obj, 'address'));
console.log(Object.hasOwn(null, 'name')); console.log(Object.hasOwn(undefined, 'name'));
|
与 obj.hasOwnProperty(prop)
语法相比, Object.hasOwn()
的优势优势:
- 更简洁的语法,更容易阅读和理解。
- 避免了原型污染问题,更安全地检查对象属性。
- 可以处理
null
和 undefined
参数,返回 false
。
Proxy-Reflect
Proxy基本使用
- 如果我们希望 监听一个对象的相关操作,那么我们可以 先创建一个代理对象(Proxy对象)
- 之后通过对代理对象的操作来监听我们想要对原对象进行的操作
步骤:
- 首先需要 new Proxy对象,并且传入需要监听的对象以及一个处理对象,称之为handler
const p = new Proxy(target, handler)
- 其次, 我们之后的操作都是直接对Proxy的操作,而不是原有的对象,因为我们需要在handler里
Proxy的捕获器
如果想要监听某些具体的操作,就可以在handler中添加对应的 捕获器
![[Pasted image 20240812213843.png]]
- set函数有4个参数
- target:目标对象(监听的对象)
- property:将被设置的属性key
- value:新属性值
- receiver:调用的代理对象
- get函数有3个参数
- target:目标对象(监听的对象)
- property:将被设置的属性key
- receiver:调用的代理对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <script>
const obj = { name: "why", age: 18, height: 1.88 }
const objProxy = new Proxy(obj, { set: function(target, key, value) { console.log(`属性${key}被设置成${value}`) target[key] = value }, get: function(target, key) { console.log(`属性${key}被读取`) return target[key] } })
objProxy.address = "四川省" console.log(objProxy.address) objProxy.age = 20
</script>
|
![[Pasted image 20240812215441.png]]
Reflect
- 作用:Reflect 是 JavaScript 中的一个内置对象,它提供了一组方法和属性,用于更好地反映和操作对象。相比于直接使用对象的属性和方法,Reflect 提供了一些额外的功能和便利。
- 与Object的区别: Reflect 与 Object 的方法名称很相似,但 Reflect 方法会返回操作结果,而 Object 方法则会返回操作对象本身。![[Pasted image 20240813000221.png]]
- 常见方法:
- 获取属性值:
Reflect.get(target, property[, receiver])
: 获取对象 target
的属性 property
的值。
- 设置属性值:
Reflect.set(target, property, value[, receiver])
: 设置对象 target
的属性 property
的值为 value
。
- 删除属性:
Reflect.deleteProperty(target, property)
: 删除对象 target
的属性 property
。
- 检查属性是否存在:
Reflect.has(target, property)
: 检查对象 target
是否拥有属性 property
。
- 获取对象原型:
Reflect.getPrototypeOf(target)
: 获取对象 target
的原型。
- 设置对象原型:
Reflect.setPrototypeOf(target, prototype)
: 设置对象 target
的原型为 prototype
- 判断对象是否可扩展:
Reflect.isExtensible(target)
: 判断对象 target
是否可扩展。
- 冻结对象:
Reflect.preventExtensions(target)
: 让对象 target
变为不可扩展。
- 获取自身属性描述符:
Reflect.getOwnPropertyDescriptor(target, property)
: 获取对象 target
的属性 property
的描述符。
- 定义属性:
Reflect.defineProperty(target, property, descriptor)
: 在对象 target
上定义属性 property
。
- 调用函数:
Reflect.apply(target, thisArgument, argumentsList)
: 使用给定的 this
值和参数列表调用目标函数。
- 使用 new 创建实例:
Reflect.construct(target, argumentsList[, newTarget])
: 使用给定的构造函数 target
和参数列表创建一个新实例。
![[Pasted image 20240813000437.png]]
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
| <script>
const obj = { name: "why", age: 18, get info() { return this.name } }
const objProxy = new Proxy(obj, { set(target, key, value, receiver) {
console.log("Proxy中的设置方法被调用") const isSuccess = Reflect.set(target, key, value)
if(!isSuccess) { throw new Error("set failed") } }, get(target, key, receiver) { console.log("Proxy中的获取方法被调用") return Reflect.get(target, key, receiver) } })
objProxy.name = "kobe" console.log(objProxy.info)
|
Promise
异步任务的处理
- 我们调用一个函数,这个函数中发送网络请求
- 如果 发送网络请求成功了,那么告知调用者发送成功,并且返回相关数据
- 如果 发送网络请求失败了,那么告知调用者发送失败,并且告知错误信息![[Pasted image 20240816165910.png]]
Promise的基本使用
- Promise是一个类,当我们需要的时候,给予调用者一个承诺:待会儿回调函数的时候,就可以创建一个Promise对象
- 在通过new创建Promise对象时,我们需要传入一个回调函数,称为executor
- 这个回调函数会被立即执行,并且给传入另外两个回调函数resolve、reject
- 调用resolve回调函数时,会执行Promise对象的then方法传入的回调函数
- 调用reject回调函数时,会执行Promise对象的catch方法传入的回调函数![[Pasted image 20240816172919.png]]
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
| <script>
function execCode(counter) { const promise = new Promise((resolve, reject) => { if(counter > 0) { let total = 0 for(let i = 0; i < counter; i++) { total += i } resolve(total) } else { reject(`${counter}输入错误`) } }) return promise }
execCode(5).then(sucessed => { console.log("成功:", sucessed) }).catch(error => { console.log("失败:", error) })
execCode(-5).then(sucessed => { console.log("成功:", sucessed) }).catch(error => { console.log("失败:", error) })
</script>
|
resolve不同值
- 传递普通值
1 2 3 4 5 6 7 8 9
| new Promise((resolve, reject) => { resolve('success'); }) .then(result => { console.log(result); }) .catch(error => { console.error(error); });
|
- 如果
resolve()
函数传递一个普通值,这个值会作为 Promise 的 resolve 结果被传递到后续的 .then()
方法中。
- 传递 Promise 对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| new Promise((resolve, reject) => { resolve( new Promise((innerResolve, innerReject) => { setTimeout(() => { innerResolve('inner promise result'); }, 2000); }) ); }) .then(result => { console.log(result); }) .catch(error => { console.error(error); });
|
- 如果
resolve()
函数传递的是另一个 Promise 对象,那么外层 Promise 的状态会跟随内层 Promise 的状态进行变化。
- 外层 Promise 会”等待”内层 Promise 完成,然后采用内层 Promise 的状态和结果。
- 传递 thenable 对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const myThenable = { then(resolve, reject) { resolve('thenable result'); } };
new Promise((resolve, reject) => { resolve(myThenable); }) .then(result => { console.log(result); }) .catch(error => { console.error(error); });
|
- 如果
resolve()
函数传递的是一个具有 .then()
方法的对象(称为 thenable 对象),那么 Promise 会”等待”这个对象执行完 .then()
方法,并采用其返回的结果。
- 传递 throw 或 reject()
1 2 3 4 5 6 7 8 9
| new Promise((resolve, reject) => { reject(new Error('Something went wrong')); }) .then(result => { console.log(result); }) .catch(error => { console.error(error); });
|
- 如果在 Promise 执行器函数内部调用
throw
或 reject()
函数,那么 Promise 的状态会变为 rejected
。
- 后续的
.catch()
方法会捕获到这个错误,并进行处理。
then的返回值
Promise
的 then
方法的返回值是一个新的 Promise
对象。这个返回值可以用于实现链式调用。
- 成功回调的返回值:
- 如果在
then
的成功回调中返回一个值,这个值将作为下一个 then
的输入。
- 如果返回的是一个
Promise
,下一个 then
将等待该 Promise
完成。
- 失败回调的返回值:
- 如果在
then
的失败回调中返回一个值,该值将被忽略,链中的下一个 then
将继续执行。
- 如果返回的是一个
Promise
,同样会等待下一个 Promise
完成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const promise = new Promise((resolve, reject) => { resolve(42); });
promise .then(result => { console.log(result); return result + 1; }) .then(result => { console.log(result); return Promise.resolve(result + 1); }) .then(result => { console.log(result); });
|
catch的返回值
Promise
的 catch
方法的返回值也会是一个新的 Promise
对象,具有和 then
方法相似的特性。具体来说,catch
主要用于处理链中的错误,但是它的返回值可以影响后续的链式调用。
- 处理错误并返回值:
- 如果
catch
处理了错误并返回一个值,这个值将作为后续链中下一个 then
的输入。
- 返回新 Promise:
- 如果在
catch
中返回一个 Promise
,下一个 then
会等待这个 Promise
完成,然后将结果传递下去。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const promise = new Promise((resolve, reject) => { reject('出错了'); });
promise .then(result => { console.log(result); }) .catch(error => { console.error(error); return '处理完毕'; }) .then(result => { console.log(result); });
|
- 如果我们希望后续继续执行catch,那么需要抛出一个异常![[Pasted image 20240816202837.png]]
finally的回调
Promise
的 finally
方法用于在 Promise
操作结束后执行一个回调函数,无论是成功还是失败。这使得 finally
非常适合用于清理或执行一些始终需要进行的操作,比如关闭文件、清理资源等。
- 不影响链的结果:
finally
的回调不会接收 Promise
的结果或错误。
- 无论前面的
then
或 catch
是否成功,finally
始终会执行。
- 返回值:
finally
返回一个新的 Promise
,其解析方式与前面的 Promise
一致。
- 如果在
finally
中返回一个值或 Promise
,将不会影响前面 then
或 catch
的结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const promise = new Promise((resolve, reject) => { const success = true;
if (success) { resolve('成功'); } else { reject('失败'); } });
promise .then(result => { console.log(result); }) .catch(error => { console.error(error); }) .finally(() => { console.log('清理操作'); });
|
Promise类方法
Promise
的类方法提供了用于创建和操作 Promise
实例的多种功能。以下是主要的 Promise
类方法及其描述:
1. Promise.resolve(value)
- 功能: 返回一个以给定值解析后的
Promise
对象。
- 示例:
1 2
| const promise1 = Promise.resolve(42); promise1.then(value => console.log(value));
|
2. Promise.reject(reason)
- 功能: 返回一个以给定原因拒绝后的
Promise
对象。
- 示例:
1 2
| const promise2 = Promise.reject('出错了'); promise2.catch(error => console.error(error));
|
3. Promise.all(iterable)
- 功能: 接受一个可迭代对象(如数组),当所有
Promise
都已成功时,返回一个新的 Promise
,并解析为一个数组,包含所有 Promise
的结果。如果其中任何一个 Promise
被拒绝,它将立即返回拒绝状态。
- 示例:
1 2 3 4 5 6 7 8 9
| const promise3 = Promise.all([ Promise.resolve(1), Promise.resolve(2), Promise.resolve(3) ]);
promise3.then(values => { console.log(values); });
|
4. Promise.allSettled(iterable)
- 功能: 接受一个可迭代对象,返回一个新的
Promise
,当所有 Promise
的状态都已确定时(不论成功还是失败),以数组的形式解析,数组中的每个对象描述了每个 Promise
的结果。
- 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const promise4 = Promise.allSettled([ Promise.resolve(1), Promise.reject('失败'), Promise.resolve(3) ]);
promise4.then(results => { console.log(results); });
|
5. Promise.any(iterable)
- 功能: 接受一个可迭代对象,返回一个新的
Promise
。只要有一个 Promise
成功,它就会解析为那个成功的值。如果所有 Promise
都被拒绝,则返回一个拒绝的 Promise
,其理由是一个 AggregateError
。
- 示例:
1 2 3 4 5 6 7 8 9
| const promise5 = Promise.any([ Promise.reject('失败1'), Promise.resolve(3), Promise.reject('失败2') ]);
promise5.then(value => { console.log(value); });
|
6. Promise.race(iterable)
- 功能: 接受一个可迭代对象,返回一个新的
Promise
,只要有一个 Promise
完成或被拒绝,返回的 Promise
就会返回这个完成或被拒绝的值。
- 示例:
1 2 3 4 5 6 7 8
| const promise6 = Promise.race([ new Promise((resolve) => setTimeout(() => resolve('快速完成'), 100)), new Promise((resolve) => setTimeout(() => resolve('慢完成'), 200)) ]);
promise6.then(value => { console.log(value); });
|
迭代器和生成器
什么是迭代器
![[Pasted image 20240816214845.png]]![[Pasted image 20240816214905.png]]![[Pasted image 20240817154234.png]]
自定义可迭代对象
![[Pasted image 20240817155356.png]]
可迭代对象的应用
![[Pasted image 20240817162522.png]]
什么是生成器
![[Pasted image 20240817191031.png]]
生成器函数参数返回值
1 2 3 4 5 6 7 8 9 10 11 12 13
| function* generatorFunction() { yield 1; yield 2; return '结束'; yield 3; }
const gen = generatorFunction();
console.log(gen.next()); console.log(gen.next()); console.log(gen.next()); console.log(gen.next());
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <script>
function* foo(name1) { console.log('内部:111'); console.log('内部:222'); const name2 = yield "aaaa"
console.log('内部:333'); console.log('内部:444'); const name3 = yield "bbbb" console.log('内部:555'); console.log('内部:666'); yield "cccc" return undefined }
const bar = foo("next1")
console.log(bar.next()) console.log(bar.next("next2")) console.log(bar.next("next3"))
</script>
|
生成器函数提前结束
1 2 3 4 5 6 7 8 9 10 11 12 13
| function* generatorFunction() { yield 1; yield 2; return '结束'; yield 3; }
const gen = generatorFunction();
console.log(gen.next()); console.log(gen.next()); console.log(gen.next()); console.log(gen.next());
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function* generatorFunction() { try { yield 1; yield 2; } catch (e) { console.log(e); } yield 3; }
const gen = generatorFunction();
console.log(gen.next()); console.log(gen.next()); gen.throw('捕获错误'); console.log(gen.next());
|
1 2 3 4 5 6 7 8 9 10
| function* generatorFunction() { yield 1; yield 2; }
const gen = generatorFunction();
console.log(gen.next()); console.log(gen.return('提前结束')); console.log(gen.next());
|
生成器替代迭代器
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
| <script>
function* foo(start, end) { for(let i = start; i <= end; i++) { yield i } }
const gen = foo(1, 10) console.log(gen.next().value) console.log(gen.next()) console.log(gen.next()) console.log(gen.next()) console.log(gen.next()) console.log(gen.next()) console.log(gen.next()) console.log(gen.next()) console.log(gen.next()) console.log(gen.next())
const names = ['Alice', 'Bob', 'Charlie', 'David'] function* nameGenerator() { yield* names }
const nameGen = nameGenerator() console.log(nameGen.next().value) console.log(nameGen.next().value) console.log(nameGen.next().value) console.log(nameGen.next().value)
</script>
|
异步处理方案
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| <script>
function requestData(url) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(url) }, 2000); }) }
async function getData() { const res1 = await requestData("why") console.log("第一次结果:", res1) const res2 = await requestData(res1 + "aaa") console.log("第二次结果:", res2) const res3 = await requestData(res2 + "bbb") console.log("第三次结果:", res3) } getData()
</script>
|
async、await - 队列
异步函数async function
await关键字
![[Pasted image 20240819154154.png]]
处理异步请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <script>
function requestData() { return new Promise((resolve, reject) => { setTimeout(() => { reject("error message") }, 2000); }) }
async function getData() { const res1 = await requestData("why") console.log(res1) const res2 = await requestData("how") console.log(res2) }
getData().catch(err => { console.log(err) })
</script>
|
进程和线程
![[Pasted image 20240819193235.png]]
浏览器中的JavaScript线程
![[Pasted image 20240819205046.png]]
浏览器的事件循环
![[Pasted image 20240819205801.png]]
微任务和宏任务
![[Pasted image 20240819210919.png]]
Promise面试题
1
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| <script>
console.log("script start") setTimeout(function () { console.log("setTimeout1") new Promise(function (resolve) { resolve() }).then(function () { new Promise(function (resolve) { resolve() }).then(function () { console.log("then4") }) console.log("then2") }) }) new Promise(function (resolve) { console.log("promise1") resolve() }).then(function () { console.log("then1") })
setTimeout(function () { console.log("setTimeout2") })
console.log(2)
queueMicrotask(() => { console.log("queueMicrotask1") })
new Promise(function (resolve) { resolve() }).then(function () { console.log("then3") })
console.log("script end")
</script>
|
2
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 42
| <script>
async function async1 () { console.log('async1 start') await async2() console.log('async1 end') }
async function async2 () { console.log("async2") }
console.log('script start')
setTimeout(function () { console.log('setTimeout') }, 0)
async1()
new Promise (function (resolve) { console.log('promise1') resolve() }).then(function () { console.log('promise2') })
console.log('script end')
</script>
|
异常处理
throw关键字
![[Pasted image 20240820191231.png]]
Error类型
![[Pasted image 20240820191257.png]]
![[Pasted image 20240820191347.png]]
异常的捕获
![[Pasted image 20240820191801.png]]
Storage和正则表达式
认识Storage
![[Pasted image 20240821160128.png]]
Storage的基本使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <script>
let token = localStorage.getItem("token") if(!token) { console.log("从服务器获取token") token = "aaaaaaaaa" localStorage.setItem("token", token) }
let username = localStorage.getItem("username") let password = localStorage.getItem("password") if(!username || !password) { console.log("从服务器获取用户名和密码") username = "admin" password = "123456" localStorage.setItem("username", username) localStorage.setItem("password", password) }
console.log(token)
</script>
|
localStorage和sessionStorage的区别
![[Pasted image 20240821160903.png]]
Storage工具封装
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
| class Cache { constructor(isLoacl = true) { this.storage = isLocal ? localStorage : sessionStorage }
setCache(key, value) { if(!value) { throw new Error("value error: value 必须有值") }
if(value) { this.storage.setItem(key, JSON.stringify(value)) } }
getCache(key) { const res = this.storage.getItem(key) if (res) { return JSON.parse(res) } }
removeCache(key) { this.storage.removeItem(key) }
clear() { this.storage.clear() } }
const localCache = new Cache(true) const sessionCache = new Cache(false)
|
正则表达式
- 使用单个字符串来描述、匹配一系列句法规则的字符串,是一种字符串匹配利器,可以帮助我们搜索、获取、替代字符串
- 正则表达式主要由两部分组成:模式和修饰符![[Pasted image 20240821170906.png]]
正则表达式的使用方法
- JavaScript的正则表达式被用于RegExp的exec和test方法,String的match、matchAll、replace、search、split方法![[Pasted image 20240821172016.png]]![[Pasted image 20240821173022.png]]
![[Pasted image 20240821173046.png]]
修饰符flag的使用
![[Pasted image 20240821173451.png]]
正则表达式规则
字符类
![[Pasted image 20240821174044.png]]
锚点
![[Pasted image 20240821182149.png]]
转义
![[Pasted image 20240821193427.png]]
集合
![[Pasted image 20240821193816.png]]
量词
![[Pasted image 20240821195144.png]]
![[Pasted image 20240821195046.png]]
贪婪和惰性
![[Pasted image 20240821200334.png]]
![[Pasted image 20240821200355.png]]
捕获组
![[Pasted image 20240821201218.png]]
![[Pasted image 20240821201443.png]]
![[Pasted image 20240821201509.png]]
正则练习-歌词解析
![[Pasted image 20240822152859.png]]
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
| function parseLyric(lyricstring) { const lyricLineStrings = lyricstring.split("\n")
const timeRe = /\[(\d{2}):(\d{2})\.(\d{2,3})\]/i const lyricInfos = [] for (const lineString of lyricLineStrings) { const result = lineString.match(timeRe) if(!result) continue const minuteTime = result[1] * 60 * 1000 const secondTime = result[2] * 1000 const millisecondTime = result[3].length === 3? result[3] : result[3] * 10 const time = minuteTime + secondTime + millisecondTime
const content = lineString.replace(timeRe, "").trim()
lyricInfos.push({ time, content }) }
return lyricInfos }
|
正则练习-时间格式化
![[Pasted image 20240822161914.png]]
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
| <h2 class="time"></h2>
<script>
function formatTime(timestamp, fmtString) { const date = new Date(timestamp)
const dateO = { "y+": date.getFullYear(), "M+": date.getMonth() + 1, "d+": date.getDate(), "h+": date.getHours(), "m+": date.getMinutes(), "s+": date.getSeconds(), }
for (const key in dateO) { const keyRe = new RegExp(key) if(keyRe.test(fmtString)) { const value = (dateO[key] + "").padStart(2, "0") fmtString = fmtString.replace(keyRe, value) } }
return fmtString }
const timeEl = document.querySelector('.time') const productJSON = { name : "iphone", newPrice : 10000, oldPrice : 8000, endTime : 1659252290626 } timeEl.textContent = formatTime(productJSON.endTime, "yyyy/MM/dd hh:mm:ss")
</script>
|
防抖、节流、深拷贝、事件总线
防抖函数
认识防抖debounce函数
![[Pasted image 20240822171403.png]]
手动实现防抖函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <script> function yy_debounce(fn, delay) { let timer = null
const _debounce = () => { if(timer) clearTimeout(timer) timer = setTimeout(() => { fn() timer = null }, delay) } return _debounce } </script> <script>
const inputEl = document.querySelector("input")
let cnt = 1 inputEl.oninput = yy_debounce(function() { console.log(`第${cnt++}次输入`) }, 1000)
</script>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <script> function yy_debounce(fn, delay) { let timer = null
const _debounce = function(...args) { if(timer) clearTimeout(timer) timer = setTimeout(() => { fn.apply(this, args) timer = null }, delay) } return _debounce } </script> <script>
const inputEl = document.querySelector("input")
let cnt = 1 inputEl.oninput = yy_debounce(function() { console.log(`第${cnt++}次输入`) }, 1000)
</script>
|
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
| <script> function yy_debounce(fn, delay) { let timer = null
const _debounce = function(...args) { if(timer) clearTimeout(timer)
timer = setTimeout(() => { fn.apply(this, args) timer = null }, delay) }
_debounce.cancel = function() { if(timer) clearTimeout(timer) }
return _debounce } </script> <script>
const inputEl = document.querySelector("input")
let cnt = 1 inputEl.oninput = yy_debounce(function(event) { console.log(`第${cnt++}次输入`) }, 1000)
</script>
|
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 42 43 44 45 46 47 48
| <script> function yy_debounce(fn, delay, immediate = true) { let timer = null let isInvoke = false
const _debounce = function(...args) { if(timer) clearTimeout(timer)
if(immediate && !isInvoke) { fn.apply(this, args) isInvoke = true return }
timer = setTimeout(() => { fn.apply(this, args) timer = null isInvoke = false }, delay) }
_debounce.cancel = function() { if(timer) clearTimeout(timer) timer = null isInvoke = false }
return _debounce } </script> <script>
const inputEl = document.querySelector("input")
let cnt = 1 inputEl.oninput = yy_debounce(function(event) { console.log(`第${cnt++}次输入:`, this, event) }, 1000)
</script>
|
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| <script> function yy_debounce(fn, delay, immediate = true, resultCallback) { let timer = null let isInvoke = false
const _debounce = function(...args) { return new Promise((resolve, reject) => { try { if(timer) clearTimeout(timer)
let res = undefined if(immediate && !isInvoke) { res = fn.apply(this, args) if (resultCallback) resultCallback(res) resolve(res) isInvoke = true return }
timer = setTimeout(() => { res = fn.apply(this, args) if (resultCallback) resultCallback(res) resolve(res) timer = null isInvoke = false }, delay); } catch (error) { reject(error) } }) } _debounce.cancel = function() { if(timer) clearTimeout(timer) timer = null isInvoke = false } return _debounce } </script> <script>
const inputEl = document.querySelector("input")
const myDebounce = yy_debounce(function(name, age, height) { console.log(`name: ${name}, age: ${age}, height: ${height}`) return "jojo" }, 1000, false)
myDebounce("jojo", 20, 1.80).then(res => { console.log(res) })
</script>
|
可以用underscore库
节流函数
认识节流throttle函数
![[Pasted image 20240825101157.png]]
手动实现节流函数
- 实现思路
![[Pasted image 20240825103812.png]]
- 基本实现
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
| <script>
function yy_throttle(fn, interval) { let startTime = 0
const _throttle = function() { const noeTime = new Date().getTime() const waitTime = interval - (noeTime - startTime) if(waitTime <= 0) { fn() startTime = noeTime } }
return _throttle }
</script>
<script>
const inputEl = document.querySelector('input') inputEl.oninput = yy_throttle(function() { console.log('input') }, 1000)
</script>
|
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
| <script>
function yy_throttle(fn, interval) { let startTime = 0
const _throttle = function(...args) { const noeTime = new Date().getTime() const waitTime = interval - (noeTime - startTime) if(waitTime <= 0) { fn.apply(this, args) startTime = noeTime } }
return _throttle }
</script>
<script>
const inputEl = document.querySelector('input') inputEl.oninput = yy_throttle(function(event) { console.log('input:',this.value, event) }, 1000)
</script>
|
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
| <script>
function yy_throttle(fn, interval, leading = false) { let startTime = 0
const _throttle = function(...args) { const nowTime = new Date().getTime()
if(!leading && startTime === 0) { startTime = nowTime }
const waitTime = interval - (nowTime - startTime) if(waitTime <= 0) { fn.apply(this, args) startTime = nowTime } }
return _throttle }
</script>
<script>
const inputEl = document.querySelector('input') inputEl.oninput = yy_throttle(function(event) { console.log('input:',this.value, event) }, 1000)
</script>
|
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 42 43 44 45 46 47
| <script>
function yy_throttle(fn, interval, {leading = false, trailing = true} = {}) { let startTime = 0 let timer = null
const _throttle = function(...args) { const nowTime = new Date().getTime()
if(!leading && startTime === 0) { startTime = nowTime }
const waitTime = interval - (nowTime - startTime) if(waitTime <= 0) { if (timer) clearTimeout(timer) fn.apply(this, args) startTime = nowTime timer = null return }
if (trailing && !timer) { timer = setTimeout(() => { fn.apply(this, args) startTime = new Date().getTime() timer = null }, waitTime) } }
return _throttle }
</script>
<script>
const inputEl = document.querySelector('input') inputEl.oninput = yy_throttle(function(event) { console.log('input:',this.value, event) }, 1000, {trailing: true})
</script>
|
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| <script>
function yy_throttle(fn, interval, {leading = false, trailing = true} = {}) { let startTime = 0 let timer = null
const _throttle = function(...args) { const nowTime = new Date().getTime()
if(!leading && startTime === 0) { startTime = nowTime }
const waitTime = interval - (nowTime - startTime) if(waitTime <= 0) { if (timer) clearTimeout(timer) fn.apply(this, args) startTime = nowTime timer = null return }
if (trailing && !timer) { timer = setTimeout(() => { fn.apply(this, args) startTime = new Date().getTime() timer = null }, waitTime) } }
_throttle.cancel = function() { if(timer) clearTimeout(timer) startTime = 0 timer = null }
return _throttle }
</script>
<script>
const inputEl = document.querySelector('input') const buttonEl = document.querySelector('button') inputEl.oninput = yy_throttle(function(event) { console.log('input:',this.value, event) }, 1000, {trailing: true}) buttonEl.onclick = function() { inputEl.oninput.cancel() } </script>
|
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| <script>
function yy_throttle(fn, interval, {leading = false, trailing = true} = {}) { let startTime = 0 let timer = null
const _throttle = function(...args) { return new Promise((resolve, reject) => { try { const nowTime = new Date().getTime()
if(!leading && startTime === 0) { startTime = nowTime }
const waitTime = interval - (nowTime - startTime) if(waitTime <= 0) { if (timer) clearTimeout(timer) const res = fn.apply(this, args) resolve(res) startTime = nowTime timer = null return }
if (trailing && !timer) { timer = setTimeout(() => { const res = fn.apply(this, args) resolve(res) startTime = new Date().getTime() timer = null }, waitTime); } } catch (error) { reject(error) } }) }
_throttle.cancel = function() { if(timer) clearTimeout(timer) startTime = 0 timer = null }
return _throttle }
</script>
<script>
const inputEl = document.querySelector('input') const buttonEl = document.querySelector('button') inputEl.oninput = yy_throttle(function(event) { console.log('input:',this.value, event) return "jojo" }, 1000, {trailing: true}) buttonEl.onclick = function() { inputEl.oninput.cancel() } </script>
|
深拷贝
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
| <script>
function isObject(obj) { const objType = typeof obj return (obj !== null) && (objType === "object" || objType === "function") }
function deepCopy(obj) { if(!isObject(obj)) { return obj }
const newObj = {} for (const key in obj) { newObj[key] = deepCopy(obj[key]) } return newObj }
const info = { name: "jojo", age: 20, friend: { name: "小羊", age: 19, address: { name: "四川", detail: "成都市" } } }
const newObj = deepCopy(info)
console.log(newObj)
</script>
|
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
| <script>
function isObject(obj) { const objType = typeof obj return (obj !== null) && (objType === "object" || objType === "function") }
function deepCopy(obj) { if(!isObject(obj)) { return obj }
const newObj = Array.isArray(obj) ? [] : {} for (const key in obj) { newObj[key] = deepCopy(obj[key]) } return newObj }
const info = { name: "jojo", age: 20, friend: { name: "小羊", age: 19, address: { name: "四川", detail: "成都市" }, cnt: [1, 2, 3] } }
const newObj = deepCopy(info)
console.log(newObj)
</script>
|
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| <script>
function isObject(obj) { const objType = typeof obj return (obj !== null) && (objType === "object" || objType === "function") }
function deepCopy(obj) {
if (typeof obj === "symbol") { return Symbol(obj.description) }
if(!isObject(obj)) { return obj }
if(obj instanceof Set) { const newSet = new Set() for(const setItem of obj) { newSet.add(deepCopy(setItem)) } return newSet }
if(typeof obj === "function") { return obj }
const symbolKeys = Object.getOwnPropertySymbols(obj) for(const symbolKey of symbolKeys) { obj[symbolKey] = deepCopy(obj[symbolKey]) }
const newObj = Array.isArray(obj) ? [] : {} map.set(obj, newObj) for (const key in obj) { newObj[key] = deepCopy(obj[key]) } return newObj }
const set = new Set([1, 2, 3, 4, 5]) const s1 = Symbol("s1") const s2 = Symbol("s2") const info = { name: "jojo", age: 20, friend: { name: "小羊", age: 19, address: { name: "四川", detail: "成都市" }, cnt: [1, 2, 3] }, set: set, running: function() { console.log("running") }, symbolKey: Symbol, [s1]: "s1", [s2]: "s2" }
const newObj = deepCopy(info)
console.log(newObj)
</script>
|
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
| <script>
function isObject(obj) { const objType = typeof obj return (obj !== null) && (objType === "object" || objType === "function") }
function deepCopy(obj, map = new WeakMap()) {
if (typeof obj === "symbol") { return Symbol(obj.description) }
if(!isObject(obj)) { return obj }
if(obj instanceof Set) { const newSet = new Set() for(const setItem of obj) { newSet.add(deepCopy(setItem)) } return newSet }
if(typeof obj === "function") { return obj }
const symbolKeys = Object.getOwnPropertySymbols(obj) for(const symbolKey of symbolKeys) { obj[symbolKey] = deepCopy(obj[symbolKey], map) }
if(map.get(obj)) { return map.get(obj) } const newObj = Array.isArray(obj) ? [] : {} map.set(obj, newObj) for (const key in obj) { newObj[key] = deepCopy(obj[key], map) } return newObj }
const set = new Set([1, 2, 3, 4, 5]) const s1 = Symbol("s1") const s2 = Symbol("s2") const info = { name: "jojo", age: 20, friend: { name: "小羊", age: 19, address: { name: "四川", detail: "成都市" }, cnt: [1, 2, 3] }, set: set, running: function() { console.log("running") }, symbolKey: Symbol, [s1]: "s1", [s2]: "s2" }
info.self = info
const newObj = deepCopy(info)
console.log(newObj)
</script>
|
事件总线
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| class yy_EventBus { constructor() { this.eventMap = {} }
on(eventName, eventFn) { let eventFns = this.eventMap[eventName] if(!eventFns) { eventFns = [] this.eventMap[eventName] = eventFns } eventFns.push(eventFn) }
off(eventName, eventFn) { let eventFns = this.eventMap[eventName] if (!eventFns) return for(let i = 0; i < eventFns.length; i++) { const fn = eventFns[i] if(fn === eventFn) { eventFns.splice(i, 1) break } } if(eventFns.length === 0) { delete this.eventMap[eventName] } }
emit(eventName, ...args) { let eventFns = this.eventMap[eventName] if(!eventFns) return eventFns.forEach(fn => { fn(...args) }) } }
const eventBus = new yy_EventBus()
eventBus.on("navclick", (name, age, height) => { console.log(`navclick: ${name}, ${age}, ${height}`) })
const click = () => { console.log("click") } eventBus.on("navclick", click)
setTimeout(() => { eventBus.off("navclick", click) }, 2000)
const navBtnEl = document.querySelector(".nav-btn") navBtnEl.onclick = function() { console.log("navBtnEl clicked") eventBus.emit("navclick", "张三", 25, 170) }
|
JavaScript网络编程
前后端分离的优势
![[Pasted image 20240825203521.png]]
- 服务器端渲染过程![[Pasted image 20240825203823.png]]
- 前后端分离渲染过程![[Pasted image 20240825203756.png]]
Http协议
什么是Http
![[Pasted image 20240825204441.png]]![[Pasted image 20240825204459.png]]
网页中资源的获取
![[Pasted image 20240825204658.png]]
Http的组成
![[Pasted image 20240825211123.png]]
Http的版本
![[Pasted image 20240825211713.png]]
Http的请求方式
![[Pasted image 20240825212611.png]]
![[Pasted image 20240826111103.png]]
![[Pasted image 20240826110852.png]]
Response响应状态码
![[Pasted image 20240826120304.png]]
XHR发送请求
XHR发送请求的基本过程
AJAX发送请求
![[Pasted image 20240826150750.png]]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <script>
const xhr = new XMLHttpRequest()
xhr.onreadystatechange = function() { if (xhr.readyState !== XMLHttpRequest.DONE) return const resJSON = JSON.parse(xhr.response) const banner = resJSON.data.banner console.log(banner) }
xhr.open("get", "http://123.207.32.32:8000/home/multidata")
xhr.send()
</script>
|
XMLHttpRequest的state
![[Pasted image 20240826153519.png]]
![[Pasted image 20240826153458.png]]
其他事件监听
![[Pasted image 20240826154018.png]]
响应数据和类型
![[Pasted image 20240826160123.png]]
HTTP响应的状态status
![[Pasted image 20240826160737.png]]
GET/POST请求传递参数
![[Pasted image 20240826212439.png]]![[Pasted image 20240826212419.png]]
ajax网络请求封装
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
| function ajax({ url, method = "get", data = {}, success, failure } = {}) { const xhr = new XMLHttpRequest()
xhr.onload = function() { if (xhr.status >= 200 && xhr.status < 300) { success && success(xhr.response) } else { failure && failure({ status: xhr.status, message: xhr.statusText}) } }
xhr.responseType = "json"
if(method.toUpperCase() === "GET") { const queryStrings = [] for (const key in data) { queryStrings.push(`${key}=${data[key]}`) } url = url + "?" + queryStrings.join("&") xhr.open(method, url) xhr.send() } else { xhr.open(method, url) xhr.setRequestHeader("Content-Type", "application/json") xhr.send(JSON.stringify(data)) } }
|
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
| function ajax({ url, method = "get", data = {}, } = {}) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest()
xhr.onload = function() { if (xhr.status >= 200 && xhr.status < 300) { resolve(xhr.response) } else { reject({ status: xhr.status, message: xhr.statusText}) } }
xhr.responseType = "json"
if(method.toUpperCase() === "GET") { const queryStrings = [] for (const key in data) { queryStrings.push(`${key}=${data[key]}`) } url = url + "?" + queryStrings.join("&") xhr.open(method, url) xhr.send() } else { xhr.open(method, url) xhr.setRequestHeader("Content-Type", "application/json") xhr.send(JSON.stringify(data)) } }) }
|
过期时间和取消请求
![[Pasted image 20240827115020.png]]
Fetch和Fetch API
认识Fetch和API
![[Pasted image 20240827133055.png]]![[Pasted image 20240827133115.png]]
Fetch数据的响应
![[Pasted image 20240827133215.png]]
![[Pasted image 20240827134223.png]]
![[Pasted image 20240827134242.png]]
![[Pasted image 20240827134254.png]]
![[Pasted image 20240827134308.png]]
XMLHttpRequest文件上传
![[Pasted image 20240827164139.png]]![[Pasted image 20240827164146.png]]![[Pasted image 20240827164213.png]]
Fetch文件上传
![[Pasted image 20240827164955.png]]