以下是本人在 js 学习过程中个人总结和积累的一些笔记,我学习或参考过 黑马js、渡一、JavaScript高级程序设计、MDN、al 交流等课程或资料,希望可以帮助到读者,欢迎读者纠偏
本篇是 JS 进阶篇 一,包括深入类和对象等
原型(prototype)
- 含义
每个 JavaScript 函数(包括构造函数)都有一个特殊的属性叫
prototype
构造函数
-
含义 是一种特殊的函数,专门用于创建并初始化具有特定属性和方法的对象实例(可以当作“对象工厂”)
-
使用场景 快速创建多个类似的对象
-
约定
- 构造函数的函数名首字母大写(用于区分普通函数)
- 必须使用
new操作符来调用构造函数 - 在函数内部使用
this关键字来引用即将创建的新对象,并给这个新对象添加属性和方法
-
示例
// 以下列对象为例
// 采用构造函数,将实例方法定义在构造函数的 `prototype` 属性中
function User(name, email, password) {
// 定义实例属性
this.name = name
this.email = email
this.password = password
}
// 定义实例方法
User.prototype.login = function() {
console.log(`${this.name} (${this.email}) 登录成功!`);
}
// 定义静态方法
User.submit = function () {}
// 定义静态属性
User.age = 22
// 构造函数创建实例对象
const user1 = new User('小明', 'xiaoming@example.com', 'secret123')
// 通过原型链访问方法
user1.login()
- 注意
- 直接将对象方法放置在构造函数内部,会造成一个问题,每次调用构造函数时都会在内存中为对象创建一个全新的方法副本,多次调用会浪费大量内存,可以通过将方法定义在构造函数的
prototype属性中,放置在该属性中的方法和属性会被通过该构造函数创建的对象实例共享(通过原型链访问) - 实例化构造函数时,没有参数可以省略(),但不建议省略
- 构造函数内部无需写
return,返回值为新创建的对象 - 构造函数内部写的
return返回的值无效,因此不要在里面写return - new Object()、new Date() 也是实例化构造函数
- 直接将对象方法放置在构造函数内部,会造成一个问题,每次调用构造函数时都会在内存中为对象创建一个全新的方法副本,多次调用会浪费大量内存,可以通过将方法定义在构造函数的
[!tips] 1.构造函数创建的实例对象彼此独立互不影响 2. 不要在构造函数内部书写静态方法和属性,这是伪静态,每次实例化都会运行一次,应该在外部直接使用添加属性的方式,实例方法应在外部添加到 prototype 上 3. 无特殊情况不要自己写 return 会丢失 this 值(绑定 this 在实例化时要前于返回值) 4. 实例化时无参数可以省略 () ,但不建议
实例化执行过程**
- 在使用
new关键字调用构造函数时,发生以下过程- 创建一个全新的空对象(临时对象,存储在堆空间中)
- 将空对象的
[[prototype]]指向构造函数的portottype属性中 - JS引擎将构造函数的
this绑定到该空对象 - 调用函数并传入预设的
this - 检查构造函数是否返回对象,如果是对象,则返回此对象,否则将返回前面创造的新对象
如
const user1 = new User('小明', 'xiaoming@example.com', 'secret123'),右边的代码相当于前面创造的新对象,式子实际相当于const user1 = temobj,意思是在user1中存储指向temobj的地址,相当于引用temboj因此该临时对象会一直存在 ,不会被垃圾回收机制回收 如果返回的是对象,那么第一步创建的临时对象引用为0,会被垃圾回收机制回收
[!tips] 整个过程是在JS引擎中自动实现的,采用的是C++
构造函数——bind()方法
-
作用 将构造函数
this关键字设置为给定的值,同时,还可以传入一系列指定的参数,这些参数会插入到调用新函数时传入的参数的前面 -
语法
构造函数.bind(thisArg, 参数1, 参数2)
class —— 类
-
含义 本质上只是构造函数的语法糖,底层仍然基于原型继承
-
语法
class 类名 {
// 1. 构造函数 (初始化实例——实例属性)
constructor(形参1, 形参2) {
// 一般属性名与形参名相同
this.属性名 = 形参1;
this.属性名 = 形参2;
}
// 2. 实例方法 (添加到原型链)
方法名() {
函数体
}
// 3. 静态属性
static 属性名 = 属性值
// 4. 静态方法 (直接绑定到类本身)
static 方法名() {
函数体
}
}
// 通过类创造实例对象,实参则为属性值,对应 constructor 函数的形参
const 对象名 = new 类名(实参)
// 调用实例方法
对象名.方法名()
// 调用静态方法
类名.方法名()
// 访问静态属性
类名.属性名
-
类的组成
- 静态成员—— 使用 static 关键字
属于类级别的概念
- 静态属性
- 静态方法
- 实例成员
属于对象级别的概念
- 实例属性(写在
constructor() {}构造函数中 - 实例方法(直接写在类中)
- 实例属性(写在
- 静态成员—— 使用 static 关键字
属于类级别的概念
-
特殊情况——在实例方法中访问静态成员
class ID {
static id = 1
static getSum(a, b) {
return a + b
}
// 利用实例方法访问静态属性
getId() {
return ID.id
}
// 利用实例方法访问静态方法
canWay() {
return ID.getSum
}
}
- 注意
- 静态成员储存在类本身,通过类创建的对象并不会继承其静态成员,只能通过类名来访问这些属性/方法,可以理解为静态成员是类层面的工具
- 实例成员中,实例属性会通过
constructor() {}构造函数,在new,调用时自动执行,为所有实例对象添加该属性,类中直接定义的实例方法会添加到类的原型对象(prototype)上,实例对象可通过原型链共享 - 如果需要实例共享某个类的静态属性,不建议将该静态属性写进
constructor() {}构造函数中,可以使用静态属性+实例方法的方式共享
原型
prototype —— 原型 函数特有
[!tip] 注意 1. 箭头函数没有 prototype
-
含义 JS规定,每个函数(构造函数)都有一个 prototype 属性,指向另一个对象,我们称之为原型对象 原型对象默认只会获得 constructor 属性,其他方法全部继承自 object
-
作用 可以挂载函数,使对象在实例化过程中不会多次创建原型上的函数,从而节约内存
-
做法 将不变的方法,直接定义在 prototype 对象上,示例化对象通过原型继承使用这些方法
-
示例
function Star(uname, age) {
// 将实例方法定义在 prototype 对象中,节约内存
Star.prototype.sing = function () {
console.log('唱歌)
}
}
- 注意
- 构造函数和原型对象中的
this都指向实例化对象 - 示例函数定义在
prototype对象中,可以写在构造函数内,也可以写在构造函数外
- 构造函数和原型对象中的
constructor 属性 —— 构造器
[!tip] 注意
- prototype.constructor 属性指回与之关联的构造函数本身
-
位置 每个原型
prototype和 隐式原型__proto__都有constructor属性 -
作用 指向该原型对象或隐式原型的构造函数
![[z_attachments/96cf1decff287f78c1fe1416f6db36b.jpg]]
- 示例
function User() {
}
const lcl = new User()
// 1. 原型对象对象的 constructor 属性指回构造函数本身
console.log(User.prototype.constuctor === User // true
console.log(lcl.__proto__.constuctor === User // true
- 问题与解决方法
// 通过下面语法为 User.prototype 赋值时会造成覆盖,导致原型对象的 constructor 不再指向当前构造函数
User.prototype = {
sing: function () {
console.log('唱歌')
},
dance: function () {
console.log('跳舞')
}
}
// 解决方法——在prototype中添加constuctor: User
User.prototype = {
constructor: User,
sing: function () {
console.log('唱歌')
},
dance: function () {
console.log('跳舞')
}
}
[!tip] 注意
- constructor 是不可枚举的,可写的
- 重写 prototype 时会丢失原本的 constructor(例如使用自定义构造函数 User 构造实例,User 的 prototype 被整个替换后,变成了一个普通对象,普通对象是 Object 的实例,因此普通对象
prototype.__proto__.constructor === Object,而由于 prototype.constructor 属性已丢失,会在原型链上找,找到了 Object.prototype 上的 constructor,因此事实上prototype.constructor要按照实例属性来理解,所以prototype.constructor === Object.prototype.consturctor === Object
隐式原型 __proto__ 所有对象都有
- 含义
- 每个对象都有属性
__proto__,指向构造函数的prototype原型对象,导致该对象可以使用构造函数prototype的属性和方法 - 使用构造函数创建一个新实例时,新实例会有一个指针指向构造函数的原型对象(prototype),这个指针叫做
[[prototype]],这是一个内部属性,无法被脚本直接访问,但浏览器在每个对象上暴露了_proto_属性,通过它可以访问到对象的原型(即构造函数的prototype)
- 每个对象都有属性
[!tip] 注意
- 从上面这一点可以看出来,实例通过
_proto_与构造函数的 prototype 产生直接联系,但实例与构造函数之间没有
- 注意
- __proto__ 是只读的
- __proto__ 是JS非标准属性
[[prototype]]和 __proto__ 意义相同- 用来表明当前实例对象指向哪个原型对象prototype
- __proto__ 隐式原型里面也有 constructor 属性,指向创建该实例对象的构造函数
![[z_attachments/950f96925c33e3d9bb3de373c07af29.jpg]]
⭐⭐ prototype __proto__ constructor 构造函数与实例⭐⭐
原型相关概念的关系
function User () {}
// 1. 实例通过对构造函数使用 new 操作符创建
const person1 = new User()
// 2. 每一个函数(包括构造函数)都拥有 prototype
console.log(User.prototype)
// {
//constructor: ƒ User()
// }
// 3. 所有函数(包括构造函数)的 prototype 属性中都有 __proto__ 属性,指向它的构造函数的原型对象(prototype)
// 解释一下上面的表述,这是因为 函数的 prototype 属性是一个对象,而所有的对象都有 __proto__ 属性,而普通对象的 __proto__ 属性自然指向 Object.prototype 啊,太妙了!!!
// 自定义构造函数的隐式原型是 Object 的原型
console.log(User.prototype.__proto__ === Object.prototype) // true
// 原型链终止于 Object 的原型
console.log(Object.prototype.__proto__) // null
// 4. 所有函数的隐式原型(__proto__)都指向 Function 的原型(prototype),即所有函数都是 Function 构造函数的实例,包括 Function 自身)
console.log(User.__proto__ === Function.prototype) // true
console.log(Function.__proto__ === Function.prototype) // true
// Object 也是构造函数,由于函数也是对象,所以它具有 __proto__ 指向函数的构造对象的 prototype
console.log(Object.__proto__ === Function.prototype) // true
// 5. 若实例不是函数,则没有 prototype 属性
console.log(person1.prototype) // undefined
// 6. 实例是对象,具有 _proto_ 属性,该属性指向构造函数的原型对象(prototype)
console.log(person1.__proto__)
// {
//constructor: ƒ User()
// }
console.log(person1.__proto__ === User.prototype) // true
// 7. prototype 上的属性和方法可以被实例共享
User.prototype.name = '廖成林'
User.prototype.add = (a, b) => a + b
console.log(person1.name) // '廖成林'
console.log(person1.add(1, 9)) // 10
// 8. 不同实例之间共享 prototype
const person2 = new User()
console.log(person1.__proto__ === person2.__proto__) // true
// 9. prototype 默认含有属性 constructor
console.log('constructor' in User.prototype) // true
// 由于实例的 __proto__ 指向构造函数的原型对象,因此以下为 true
console.log('constructor' in person1.__proto__) // true
// 10. prototype 的 constructor 属性指向构造函数本身
console.log(User === User.prototype.constructor) //true
// 因此实例的 __proto__.constructor 也指向构造函数本身
console.log(User === person1.__proto__.constructor) // true
![[z_attachments/Pasted image 20251107174807.png|600]]
原型继承
-
含义 继承是面向对象编程的另一个特征,通过继承进一步提高代码的封装程度,JS中大多数是借助原型对象实现继承的
-
示例
// 让 woman 和 man 继承 person的属性和方法
function Person() {
this.eye = 2,
this.head = 1
}
function Man() {
}
// 原型继承
Man.prototype = new Person()
// 指回原构造函数
Man.prototype.constructor = Man
function Woman() {
}
// 原型继承
Woman.prototype = new Person()
// 指回原构造函数
Woman.constructor = Person
// 添加 Woman 独有的方法
Woman.prototype.baby = function () {}
-
注意 原型继承并没有节省内存,只是节省了代码量,提高了代码的封装程度
-
问题 这种情况会导致所有 Man 实例共享一个 Person 实例
原型的问题
-
描述 对于引用数据类型,原型容易导致问题,由下可见
-
示例
function Parent () {}
Parent.prototype.name = ['lcl']
const son1 = new Parent()
son1.name.push('dhh')
console.log(son1.name) // ['lcl', 'dhh']
// 实例 son1 修改了原型上的属性,son2 这里想要访问时也受到了影响,有时这并非我们期望的结果
const son2 = new Parent()
console.log(son2.name) // ['lcl', 'dhh']
原型链
[!tip] 注意
- 原型链的重要特性是,实例可以通过原型链读取原型对象上的值,但是实例不能修改这些值,例如实例添加了一个与原型对象同名的属性,会在实例上创建这个属性,而不是修改了原型对象上属性的值
- 只要设置了同名属性,在这个实例中,就会遮盖掉原型对象上的同名属性,即屏蔽了对原型对象上同名属性的访问,即使将实例对象中该属性设置为 null 或 undefined 也不能恢复,除非使用 delete 完全删除掉实例上的同名属性
- 含义 基于原型的继承使得不同构造函数的原型对象关联在一起,这种关联的关系是一种链状的结构,我们将原型的链状结构关系称为原型链
![[z_attachments/64af720bbc5d916b2bb5e37d92f15df.jpg]]
- 示例
// 接原型继承的例子,实例化 Man
const lcl = new Man()
console.log(lcl.__proto__ === Man.prototype) // true
console.log(Man.prototype.__proto__ === Person.prototype) // true
console.log(Person.prototype.__proto__ === Object.prototype) // true
console.log(Object.prototype.__proto__) // null
- 查找规则
- 当访问一个对象的属性(或方法)时,首先查找这个对象自身有没有该属性
- 如果没有,则查找它的原型(即 __proto__ 指向的 prototype 原型对象)
- 如果还没有就查找原型对象的原型
- 依此类推直到 Object 为止(null)
- __proto__ 隐式原型的意义就在于为对象成员查找机制提供了一个方向,或者一条路线
- 可以使用
instanceof运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上
JavaScript继承方式演进与核心机制
原型链继承
核心代码:Child.prototype = new Parent()
优点:实现简单,可继承父类实例属性和原型方法
缺点: 一、引用类型属性共享:所有子类实例共享父类实例中的引用类型属性 二、无法向父类传参:创建子类实例时,无法向父类构造函数传递参数
// 原型链继承示例
function Person(name) {
this.name = name
this.hobbies = ['reading', 'music'] // 引用类型属性
}
Person.prototype.sayName = function() {
console.log('My name is ' + this.name)
}
function Man() {
// 无法向Person传递参数
}
// 核心:设置原型为父类实例
Man.prototype = new Person('Default')
// 测试
const man1 = new Man()
const man2 = new Man()
man1.hobbies.push('sports')
console.log(man1.hobbies) // ['reading', 'music', 'sports']
console.log(man2.hobbies) // ['reading', 'music', 'sports'] - 被共享了!
console.log(man1.name) // 'Default' - 无法自定义
构造函数继承
核心代码:在子类构造函数中调用 Parent.call(this, ...args)
优点: 一、解决了引用类型属性共享问题 二、支持向父类构造函数传递参数
缺点:
一、方法无法复用:方法需在构造函数中定义,每次实例化都会重新创建函数
二、无法继承原型方法:无法继承父类 prototype 上定义的方法
// 构造函数继承示例
function Person(name) {
this.name = name
this.hobbies = ['reading', 'music']
// 方法定义在构造函数内
this.sayName = function() {
console.log('My name is ' + this.name)
}
}
function Man(name, age) {
// 核心:借用父类构造函数
Person.call(this, name) // 可以传递参数
this.age = age
}
// 测试
const man1 = new Man('John', 25)
const man2 = new Man('Mike', 30)
man1.hobbies.push('sports')
console.log(man1.hobbies) // ['reading', 'music', 'sports']
console.log(man2.hobbies) // ['reading', 'music'] - 不共享
console.log(man1.sayName === man2.sayName) // false - 方法被重复创建
组合继承
核心代码:结合原型链继承与构造函数继承
优点:集成了两者的优点,是ES5中最常用的继承模式
缺点:效率问题。父类构造函数被调用了两次(一次用于构建子类原型,一次在子类构造函数内部),导致子类原型上存在冗余的父类实例属性
// 组合继承示例
function Person(name) {
this.name = name
this.hobbies = ['reading', 'music']
console.log('Person构造函数被调用,name:', name)
}
Person.prototype.sayName = function() {
console.log('My name is ' + this.name)
}
function Man(name, age) {
// 第一次调用Person构造函数
Person.call(this, name)
this.age = age
}
// 第二次调用Person构造函数
Man.prototype = new Person() // 注意:这里没有传递参数或使用默认值
// 修正constructor指向
Man.prototype.constructor = Man
Man.prototype.sayAge = function() {
console.log('I am ' + this.age + ' years old')
}
// 测试
const man = new Man('John', 25)
console.log(man.name) // 'John' - 来自实例属性
console.log(man.hasOwnProperty('name')) // true
console.log(man.hasOwnProperty('hobbies')) // true
// 问题:Man原型上也有name和hobbies属性(但值为undefined/default)
console.log(Man.prototype.hasOwnProperty('name')) // true
console.log(Man.prototype.name) // undefined
寄生组合式继承
核心代码:Child.prototype = Object.create(Parent.prototype) 与 Parent.call(this) 结合
优点:被广泛认为是ES5中最理想的继承范式。它只调用一次父类构造函数,避免了在子类原型上创建不必要的属性,同时保持原型链不变
缺点:实现相对繁琐,且无法自动继承父类的静态属性和方法(需手动处理)
// 寄生组合式继承示例
function Person(name) {
this.name = name
this.hobbies = ['reading', 'music']
}
Person.prototype.sayName = function() {
console.log('My name is ' + this.name)
}
// 静态方法
Person.staticMethod = function() {
console.log('This is a static method')
}
function Man(name, age) {
// 调用父类构造函数
Person.call(this, name)
this.age = age
}
// 核心:创建以父类原型为原型的新对象,不调用父类构造函数
Man.prototype = Object.create(Person.prototype)
// 修正constructor指向
Man.prototype.constructor = Man
Man.prototype.sayAge = function() {
console.log('I am ' + this.age + ' years old')
}
// 测试
const man = new Man('John', 25)
man.sayName() // 'My name is John'
man.sayAge() // 'I am 25 years old'
// 静态方法不会被继承
// Man.staticMethod() // 报错:除非手动复制
console.log(Object.getPrototypeOf(Man.prototype) === Person.prototype) // true
ES6类继承
class与extends不仅仅是语法糖,它改变了对象创建的底层逻辑,从而解决了ES5继承的固有缺陷
优点:
一、语法简洁直观:extends、super语义明确
二、完美继承内置类:可以正确继承Array、Error等原生类的行为
三、自动继承静态成员:子类会自动继承父类的静态方法和属性
四、内置new.target等元属性,提供更强大的内省能力
// ES6类继承示例
class Person {
constructor(name) {
this.name = name
this.hobbies = ['reading', 'music']
}
sayName() {
console.log(`My name is ${this.name}`)
}
// 静态方法
static staticMethod() {
console.log('This is a static method')
}
}
class Man extends Person {
constructor(name, age) {
super(name) // 必须调用super
this.age = age
}
sayAge() {
console.log(`I am ${this.age} years old`)
}
}
// 测试
const man = new Man('John', 25)
man.sayName() // 'My name is John'
man.sayAge() // 'I am 25 years old'
// 静态方法被自动继承
Man.staticMethod() // 'This is a static method'
// 可以正确继承内置类
class MyArray extends Array {
getFirst() {
return this[0]
}
}
const arr = new MyArray(1, 2, 3)
console.log(arr.length) // 3
console.log(arr.getFirst()) // 1
ES5继承的固有缺陷
核心缺陷:包括寄生组合式在内的所有ES5继承方式,都存在一个无法绕过的根本性缺陷——无法真正继承内置原生类(如Array, Error, Map)的内部槽
根本原因:对象创建顺序
一、ES5模式(子类优先):先创建子类的this(一个普通对象),再通过Parent.call(this)将父类的属性"附加"上去。原生类的内部机制(如Array的length与索引的绑定关系)无法被"注入"到这个事后创建的普通对象中
二、ES6模式(父类优先):通过super()先由父类创建并返回一个具备完整内部槽的实例,子类再在此基础上进行修饰
具体表现:用ES5方式继承Array后,子类实例的length属性与索引操作不会自动联动,Array的原生行为完全失效。此缺陷无法通过模拟或Babel转译修复
// ES5方式尝试继承Array
function MyArrayES5() {
Array.call(this) // 这行代码实际上没有效果
}
MyArrayES5.prototype = Object.create(Array.prototype)
MyArrayES5.prototype.constructor = MyArrayES5
const arr5 = new MyArrayES5()
arr5[0] = 'a'
console.log(arr5.length) // 0 - 不正常!
console.log(arr5 instanceof Array) // true
// ES6方式继承Array
class MyArrayES6 extends Array {
constructor(...args) {
super(...args)
}
}
const arr6 = new MyArrayES6()
arr6[0] = 'a'
console.log(arr6.length) // 1 - 正常!
继承方式对比总结
| 继承方式 | 核心原理 | 优点 | 缺点 |
|---|---|---|---|
| 原型链继承 | Child.prototype = new Parent() | 简单,可继承原型方法 | 引用属性共享,无法传参 |
| 构造函数继承 | 在子类中Parent.call(this) | 解决属性共享,支持传参 | 方法不能复用,无法继承原型方法 |
| 组合继承 | 原型链 + 构造函数 | 功能完整,实例属性独立,可复用方法 | 父类构造函数被调用两次,效率稍低 |
| 寄生组合式继承 | Object.create() + 构造函数 | ES5最佳实践,高效,原型链纯净 | 写法繁琐,无法继承静态成员 |
| ES6类继承 | class Child extends Parent | 语法简洁,完美继承内置类,自动继承静态成员 | 本质上仍是原型继承的语法糖 |
深入类
[!tip] 注意
- 可以使用生物学的例子来理解类,以兔子为例,类是兔子这种生物,关注兔子这个物种的整体,而实例聚焦于一只特定的兔子
定义类
- 类声明
class Person {}
- 类表达式
const Animal = class {}
[!tip] 注意
- 要求类的首字母大写,以区别通过它创建的实例
类的特点
- 类的声明不能提升
console.log(ClassExpression) // ReferenceError: ClassExpression is not defined
class ClassExpression {}
console.log(ClassExpression) // class ClassExpression {}
-
函数声明具有函数作用域,类声明具有块作用域
-
类是一种特殊的构造函数
class Person {}
// 1. 通过 typeof 检测,发现类是函数
console.log(typeof Person) // function
// 2. 类标识具有 prototype 属性,其 constructor 指向自身
console.log(Person === Person.prototype.constructor) // true
// 3. 与普通构造函数相同,可以使用 instanceof 操作符检查 构造函数原型 是否存在于实例的原型链中
const p = new Person()
console.log(p instanceof Person) // true
类的构成
-
构成 类可以包含构造函数方法、实例方法、获取函数、设置函数、私有属性、私有方法和静态类方法
-
关于类的表达式名称 这是可选的,在把类表达式赋值给变量后,可以通过 name 属性获取类表达式名称的字符串,但不能在类表达式作用域外部访问这个标识符
// 示例
const Person = class PersonName {
identify () {
console.log(Person.name, PersonName.name)
}
}
const lcl = new Person()
lcl.identify // PersonName PersonName
console.log(Person.name) // PersonName
// 不能在类表达式作用域外部访问这个标识符
console.log(PersonName) // ReferenceError: PersonName is not defined
console.log(PersonName.name) // ReferenceError: PersonName is not defined
constructor —— 类构造函数
- 作用 在类定义块内部创建类的构造函数,使用 new 操作符创建类的新实例时,会调用 constructor 方法,若不定义该方法则相当于将构造函数定义为空函数
实例化
-
对类使用 new 操作符 使用 new 操作符实例化类的操作等价于使用 new 操作符调用其构造函数
-
步骤
- 在内存中创建一个新的对象
- 这个新对象内部的
[[prototype]]指针被赋值为构造函数的 prototype 属性 - 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)
- 执行构造函数内部代码(给新对象添加属性)
- 如果构造函数返回非空对象,则返回该对象,否则,返回刚刚创建的对象
[!tip] 注意
- 类实例化时传入的参数会作为构造函数的参数,若不传参,可以省略类名后面的 ()
- 类与构造函数的主要区别是,调用类时必须使用 new 操作符(不使用 new 会抛出错误),而普通的构造函数如果不适用 new 调用,则以全局的 this (通常是 window) 作为内部对象
- 示例
class Person {
constructor (name) {
console.log(arguments.length)
this.name = name || null
}
}
let p1 = new Person() // 0
console.log(p1.name) // null
// 下面省略了 () 但效果是一样的
let p2 = new Person // 0
console.log(p2.name) // null
类构造函数与类的关系
- 首先应明确
- 在类内部,它的 this 指向新创建的实例,因此,this 是类的实例
- constructor 构造器相当于原来的构造函数整体
- constructor 并不是 Person 的自有属性
class Person {
// 2. constructor 构造器等价于下方列的构造函数
constructor () {
// 1. 在类内部,它的 this 指向新创建的实例,因此,this 是类的实例
console.log(this instanceof Person) // true
// 实例无 constructor 属性,向原型链查找,最终在 Person.prototype.construtor 找到,因此下列等式为true
console.log(this.constructor === Person) // true
}
}
const p = new Person()
// 3. constructor 并不是 Person 的自有属性
console.log(Person.hasOwnProperty(constructor)) // false
// 构造函数
function Person () {
console.log(this instanceof Person) // true
console.log(this.constructor === Person) // true
}
深入解析类中的 constructor 构造函数和 类.prototype.constructor
-
前提 —— 了解类的定义过程,以定义类 F 为例
- 产出一个构造函数 F (携带
[[Call]] 和 [[Construct]]) - 创建原型对象 proto ,并在 F 上定义不可枚举属性 prototype,值为 proto
- 在 proto 上定义不可枚举数据属性 "constructor",值为 F(回指)
- 根据源代码顺序
- 实例方法定义到 proto 上(writable: true, configurable: true, enumerable: false)
- 静态方法/静态属性定义到 F 上(同样的特性,静态字段初始化以 F 为接收者)
- 静态初始化块(static block)在类定义完成后立即执行一次,作用域绑定到 F
- 产出一个构造函数 F (携带
-
前提 —— 了解类的实例化过程,以实例化类 F 为例
- 分配一个新对象 O
- 设定
O.[[prototype]] = F.prototype - 调用
F.[[Construct]]即类中的 constructor 构造函数,把 this 绑定到 O - 如果 constructor 方法返回对象 R 则返回 R ,否则返回 O
[!tip] 注意
- 类定义中的 constructor 代码块,正是 构建类 F 本身(即构造函数)的源代码
- 引擎使用这段代码创建了函数对象 F
- F.prototype.constructor 只是一个属性引用,它指向的正是这个函数对象 F
- 所以, 它们在内存中指向的是同一个函数实体 ,只是看待的角度不同(一个是源码定义视角,一个是原型链引用视角)
类中的访问器
-
描述 在类中,也可以获取和设置访问器,如下所示
-
示例
// 添加访问器
class Person {
get name () {
return this._name
}
set name (newVal) {
console.log('设置了值', newVal)
this._name = newVal
}
}
const p = new Person()
// undefined 因为此时未设置 name 的值,直接获取返回 this._name ,但它需要使用 set 设置,未设置为 undefined
console.log(p.name)
p.name = '廖成林' // 设置了值 廖成林
console.log(p.name) // 廖成林
类的静态成员
-
静态成员 通常用于执行不特定于实例的操作,在类中使用 static 作为前缀,在静态方法中,this 指向类本身
-
作用
- 初始化块,记录块的信息(记录兔子这个物种的信息,例如有四条腿,两个眼镜)
- 作为实例工厂
- 使用类的静态方法批量调用 constructor 构造器生成实例
- 迭代器和生成器方法
类的私有成员
[!tip] 注意
- 分类,私有成员不属于静态成员,私有成员也有实例私有成员和静态私有成员
- 真正的封装(对比访问器的
_约定),外部不能直接访问私有成员,只能通过内部方法间接访问- 独立性,对于实例私有成员,即使它的值是一个对象,每个实例之间的引用也并不相同(即每个实例之间的实例私有成员完全独立,其独立性可以类比为 constructor 中使用 this. 一样的成员)
- 私有成员可以添加访问器,但其访问器也要是私有的
- 私有成员不能被子类继承,仅限于定义它们的类使用
- 私有成员不能由派生类的同名方法和属性访问或覆盖
- 构造函数不能私有
- 私有成员必须在类体内声明,不能在构造函数被调用期间或调用后添加
-
描述 私有成员是指,只能在类内部进行访问和修改的成员,在成员名前面添加 # 就可以将成员私有化
-
示例
class Persons {
// 实例私有成员,每个实例都是独一份的
#name = '初始值'
#add () {}
// 私有成员添加访问器
get #sub () {}
set #sub () {}
constructor(name) {
if (name) {
this.#name = name; // 设置私有字段
}
}
// 通过内部方法在外部访问私有成员
getName() {
return this.#name; // 访问私有字段
}
}
const p1 = new Persons('张三');
const p2 = new Persons('李四');
// 两个实例的 #name 值不同
console.log(p1.getName()); // "张三"
console.log(p2.getName()); // "李四"
// 每个实例有自己独立的 #name
类的成员 —— 各种成员的位置
-
声明 在这里,所谓的实例属性,如果写在 constructor 构造器中,那么每个实例都不同,如果写在类的 prototype 上,则所有实例共享了该属性,这并非我们期望的(除非某些情况下的特殊需要,但这又跟静态属性的行为重复了,既然所有实例共享某个属性,那为什么我不定义为静态属性呢),因此,实例属性通常指写在 constructor 中的属性,我更愿意称它为实例的自有属性
-
示例
class Person {
// 1. 构造器,用于构造实例的自有属性,每次实例化会执行,它是一个函数作用域
constructor(name, age) {
// 1.1 这是一个实例自有属性
this.person = { name, age }
// 1.2 这是一个实例自有方法,不同实例之间的该方法不相等
this.sub = () => { console.log('dhh') }
// 1.3 函数作用域内不声明会报错
// xyz = () => {}
// 1.3 必须使用关键字进行声明,但这样相当于声明了一个变量,并不会让 prototype 或 实例获得这个属性,无效写法
let xyz = () => { }
}
// 2. 实例方法,定义在类的原型(prototype)上
add() { console.log('lcl') }
// 3. 静态属性和静态方法,使用 static 关键字声明
static isHuman = true
static fun() { }
// 4. 获取访问器和设置访问器
get height() {
console.log('身高是', this.#height)
return this.#height
}
set height(newVal) {
this.#height = newVal
}
// 5.私有成员
// 私有实例成员
#height = ''
#sub() { }
// 私有静态成员
static #weight = ''
static #find() { }
// 私有访问器
get #mouse() { }
set #mouse(newVal) { }
}
静态块 —— 类的初始化
- 描述 这是 ECMAScript 2022 加入的新特性,该静态块会在类初始化时执行一次,其 this 指向类本身,补齐了类的最后一块短板,可以用于复杂静态属性的初始化,使得类的静态端初始化能够像实例端一样灵活和强大
// 打印了 666
class Lcl {
static {
console.log(666)
console.log(this === Lcl) // true
}
}
类的继承
- 继承方式
JS 类只支持单继承,即只能有一个父类,使用 extend 关键字可以继承任何具有
[[Construct]]和原型(prototype)的对象,这意味着,类不仅能继承类,也能继承构造函数
extends 关键字 —— 类继承
-
作用 定义一个类为另一个类的子类
-
语法
// {} 内为子类的定义这个 {} 的内容相当于 a { constructor () {}...... } 中 {} 的内容
class 子类 extends 父类 {}
const 子类 = class extends 父类 {}
- 原型链关系 extends 建立了完整的原型链关系
一、构造函数原型链(继承静态方法与属性)
Child.__proto__ === Parent
二、实例原型链(继承实例方法与属性)
Child.prototype.__proto__ === Parent.prototype
class Animal {}
class Dog extends Animal {}
const d = new Dog()
console.log(d.__proto__ === Dog.prototype) // true
console.log(Dog.__proto__ === Animal) // true
console.log(Dog.prototype.__proto__ === Animal.prototype) // true
console.log(Animal.prototype.__proto__ === Object.prototype) // true
- 示例
// 继承自类
class Father {}
// 写法一
const Son = class extends Father {}
// 写法二
class Son extends Father {}
const s = new Son()
console.log(s instanceof Son) // true
console.log(s instanceof Father) // true
// 继承自构造函数
function Mother () {}
// 写法一
const daugther = class extends Mother {}
// 写法二
class daugther extends Mother {}
const d = new daugther()
console.log(d instanceof daugther) // true
console.log(d instanceof Mother) // true
[[HomeObject]] 内部特性
[!tip] 注意
- 该内部槽只存在于 简洁方法中,即对象中简写的方法,如
{ lcl() {} }中的 lcl 就是简洁方法,而普通函数,箭头函数都不是简洁方法,全部没有[[HomeObject]]内部槽
- 含义
JS 在给类构造函数(constructor),静态方法,实例方法,私有方法,访问器(这些都是简洁方法)定义时,会自动添加
[[HomeObject]],这是一个指针,指向定义该方法的对象,它只能在 JS 引擎内部访问
由于它的特性,我们可以知道,类的静态方法的 [[HomeObject]] 指向类本身,而类 Animal.prototype.method 中的 [[HomeObject]] 指向 Animal.prototype
- 示例
class Animal {
// run 方法的 [[HomeObject]] 指向 Animal
static run() {
}
}
子类(派生类)的特殊标记
-
对于基类(Base Class) 没有 extends 的类。其构造器行为与普通函数类似,直接创建 this 对象(通常是一个空对象,原型指向 Base.prototype )
-
对于派生类(Derived Class) 有 extends 的类。其构造器有一个特殊的内部属性
[[ConstructorKind]]: "derived"
这个标记意为着派生类不能自己创造 this ,必须能基类将构造函数运行完之后让基类将其 this 传递过来,才能使用 this ,而这个过程通过 super() 实现
super 关键字
[!tip] 注意
- super 可以用于任何简洁方法([[#[ [HomeObject 内部特性]|简洁方法]])中,指向简洁方法的
[[HomeObject]]的隐式原型
-
作用 调用父类构造函数
-
值 super 始终会定义为
[[HomeObject]]的隐式原型
super === [[HomeObject]].prototype
// 如 Child.prototype.method,它的 super 为 [[HomeObject]] 的隐式原型,即 Child.prototype 的隐式原型,即 Parent.prototype
- 场景
| 场景 | super 指向的目标 | this 指向 | 备注 |
|---|---|---|---|
| constructor | 父类构造函数 | 当前正在创建的实例(但必须在 super() 调用后才能使用) | 必须在使用 this 前调用 super() |
| 实例方法 | 父类原型 (Parent.prototype) | 调用该方法的实例 | 可访问父类原型方法,但无法直接访问父类实例属性 |
| 静态方法 | 父类本身 (Parent) | 子类本身 (Child) | 用于继承静态逻辑,如静态方法调用 |
| 对象字面量 | 原型对象 | 当前对象 | ES6 增强的对象方法语法,super 指向对象的原型 |
针对对象字面量的增强,通常为一个 parent 对象和 child 对象,其中 child 的隐式原型为 parent,类似的,能通过 super 指向 parent ,进而调用其方法,还会将 this 绑定为 child
// 关于对象字面量的场景
const parent = {
print() {
console.log('来自parent的方法')
}
}
const child = {
__proto__: parent,
ok() {
super.print()
console.log(this === child)
}
}
child.ok()
// 来自parent的方法
// true
// 报错,super 关键字不能用于普通函数中
child.method = function () {
super.print()
console.log(this)
}
super 特性
- 在构造器中使用
super()调用基类构造函数并传递 this ,这是super()必须在 派生类this之前使用的原因(下面的 Reflect 方法详见 [[#Reflect.construct() —— 调用构造函数]])
// 在 Child 的 constructor 构造器中
// 这个操作相当于对基类使用 new Parent() 但是将生成的实例对象的 this 设置为 Child.prototype
super() 等价于 Reflect.construct(Parent, args, Child)
- 不能单独使用 super 关键字
class Parent {
constructor() {
}
}
// 没有定义构造器
class Child extends Parent {
constructor() {
// 单独使用 super
super // SyntaxError: 'super' keyword unexpected here
}
}
-
在 constructor 中调用 super() 会调用父类构造函数,并将返回的实例赋值给 this
-
在 constructor 中调用 super() ,其参数作为父类构造函数的参数传入
-
没有定义构造函数的派生类,在实例化时会隐式调用 super() 并且会传入所有传给派生类的参数
// 示例
class Parent {
constructor() {
}
}
// 没有定义构造器
class Child extends Parent {
}
// 相当于构造器为 constructor(...args) { super(...args) }
new Child(...args)
6.派生类中显式定义了构造函数,则必须要在其中调用 super() ,否则必须在其中返回一个对象,返回的对象会取代默认流程下的实例(即基类创建作为this传递给派生类最后作为 new 派生类()值得实例)
⭐派生类实例化过程与普通类实例化过程对比⭐
普通类(基类)的实例化过程
-
场景 class Parent { ... } ,执行 new Parent()
-
步骤 一、
Call [[Construct]]引擎调用 Parent 的构造器方法
二、确定 new.target
此时 new.target 就是 Parent 自己
三、分配内存 (Allocate) 引擎根据 new.target.prototype (即 Parent.prototype )创建一个全新的空对象,将空对象的隐式原型绑定到 new.target.prototype 上,此时有了 this
四、执行构造逻辑 将 this 绑定到刚刚创建的对象,执行 constructor 函数体内的代码
五、返回 如果没有显式返回对象,则返回 this
派生类的实例化过程
-
场景 class Child extends Parent { ... } ,执行 new Child()
-
步骤 一、
Call [[Construct]]:引擎调用 Child 的构造器方法 引擎发现 Child 是一个派生类([[ConstructorKind]]: "derived"),暂停创建 this 对象的操作。它知道 Child 没资格创建。
二、确定 new.target 此时 new.target 是 Child
三、执行构造逻辑 (Child) 进入 constructor 代码块,此时 this 处于暂时性死区(TDZ),不能访问
四、执行 super() JS 引擎调用父类构造器 Parent ,重点——它把当前的 new.target(即 Child)传给了 Parent
五、进入 Parent 构造器 Parent 像普通类一样开始工作,它开始进行内存分配,Parent 准备创建对象,它发现此时的 new.target 是 Child ,于是将新对象的隐式原型绑定到 new.target.prototype 上,随后继续执行自己的初始化代码(即 Parent constructor),执行完毕后返回这个对象
六、回到 Child 构造器 super() 返回了刚才那个对象,Child 将这个对象绑定为自己的 this ,这个时候 TDZ(暂时性死区)解除, Child 开始执行自己的初始化代码(即 Child constructor)
七、返回 返回最终装修好的 this,即实例对象
深入对象
对象的两种属性
- 数据属性
const obj = {
a: 1,
b: '1',
c: [],
d: {},
// 方法是值为函数的数据属性
e () {}
......
}
- 访问器属性
const obj = {
get a () {},
set a (newVal) {}
}
[!tip] 注意 1. 属性具有唯一性,它只能是上面三种属性中的一种 2. 方法是值为函数的数据属性
修改属性配置
- 方法
Object.defineProperty(target, property, descriptor)
Object.defineProperties(target, {
property: descriptor
})
创建对象的三种方式
- 方式
- 利用对象字面量
{}创建对象 - 利用
new Object创建对象 - 利用构造函数创建对象
- 利用对象字面量
[!tips] 注意 在JS中,所有对象最终都继承自
Object.prototype,因此以上三种方法都能够使用Object()构造函数的静态方法和静态属性
利用 new Object 创建对象
- 语法
const 对象名 = new Object()
// 初始化对象时赋值,只有一个键值对可以不加 ,
const 对象名 = new Object({ key: value, })
[!tips] 注意 使用 new Object() 创建对象,实际上也是利用构造函数,只不过该函数为JS内置的 Object() 函数
利用构造函数创建对象
详见[[#构造函数|构造函数]]
原始值包装类型
[!tip] 注意 只有读取原始值的属性或者方法时,才会触发 JS ,将原始值临时包装为对象
-
包装类型 JS 中,会将基本数据类型通过专门的构造函数包装为复杂数据类型,利用 JS 内置的构造函数进行实例化,使基本数据类型也能拥有实例方法和实例属性
-
示例
// 声明普通字符串
const str1 = 'pink'
const str2 = str1.length
// 在第 2 行读取 str1.length 的时候,JS 引擎执行了以下三步,换成 Number, Boolean 类型步骤相同,只不过包装类型从 String 换成了二者
// 原始类型字符串
let str ='hello';
// 当访问str.length时,Javascript引擎做了以下工作:
let size =(function(){
// 1.自动装箱:创建一个临时的string对象包装原始字符串
let tempStringobject =new String(str);
//2.访问string对象的length属性
let lengthValue = tempstringobject.length;
//3.销毁临时对象,返回长度值//(Javascript引擎自动处理对象销毁,开发者无感知)
return lengthValue,
})();
console.log(size);//输出:4
- 内置构造函数
| 类型 | 构造函数 |
|---|---|
| 复杂数据类型 | Object , Array , RegExp , Date 等 |
| 包装类型 | String , Number , Boolean |
Object() 构造函数——静态方法
Object.keys —— 返回对象自有可枚举非符号属性名的数组
-
作用 返回对象自有可枚举属性名的数组,单元值为字符串
-
语法
Object.keys(obj)
Object.values —— 返回对象自有可枚举属性值的数组
-
作用 返回对象自有可枚举属性值的数组
-
语法
Object.values(obj)
Object.entries —— 返回对象自身可枚举属性的键值对数组
-
作用 返回对象自身可枚举属性的键值对数组
-
语法
Object.entries(obj)
- 示例
const obj = { foo: "bar", baz: 42 }
console.log(Object.entries(obj)) // [ ['foo', 'bar'], ['baz', 42] ]
Object.fromEntries —— 将键—值对可迭代对象返回为新对象
-
作用 基于传入的键—值对可迭代对象(数组或映射等)返回一个新对象
-
语法
Object.fromEntries(iterable)
- 示例
const entries = new Map([
["foo", "bar"],
["baz", 42],
]);
const obj = Object.fromEntries(entries);
console.log(obj);
// Expected output: Object { foo: "bar", baz: 42 }
Object.assign —— 复制对象可枚举属性值到目标对象
-
作用(可以理解为将 sources 的内容添加到 target 中)
- 属性覆盖
- 给对象添加属性
- 合并对象(使用 {} 作为 target )
-
返回值 返回被修改后的目标对象(并非返回一个新对象,而是在地修改目标对象)
-
原理 该方法将会使用源对象上的
[[Get]]获取属性值,然后使用目标对象上的[[Set]]设置属性的值 -
语法
Object.assign(target, sources)
- 示例
// 对象拷贝
const o = { name: '佩奇', age: 6 }
const obj = {}
Object.assign(obj, o)
// 给对象添加属性
const oo = { gender: '女' }
Object.assign(obj, o, oo)
[!tips] 注意
- 如果 target 和 sources 之间有相同的属性名,sources 的属性值会覆盖掉 target 中的属性值,同样的,后面的 source 会覆盖前面的 source
- 该方法是一个浅复制,遇到对象时会复制其引用
- 如果赋值期间出错,该方法会中止并退出,这导致过程中报错只会完成部分复制
Object.defineProperty —— 定义或修改属性特性
- 语法
// descriptor 为属性描述符,有数据属性和访问器属性,只能配置其中一个
Object.defineProperty(target, property, descriptor)
[!tips] 配置差异
- 与直接定义或修改对象不同,使用 defineProperty 定义的属性,configurable , writable , enumerable 默认为 false ,get , set , value 未设置时默认为 undefined
- 但是,如果该属性已被定义,再使用 defineProperty 修改属性,描述符传入空对象时则不会改变其属性描述符配置
Object.defineProperties —— 定义或修改多个属性的特性
- 语法
// props 为一个对象,对象内部的属性名为需要定义的属性名,其值为属性描述符对象
Object.defineProperties(target, props)
// 示例
const props = {
name: {
value: 'lcl',
writable: true,
configurable: flase,
enumerable: flase
},
age: {
get () {},
set () {},
configurable: false,
enumerable: true
}
}
[!tips] 配置差异
- 与直接定义或修改对象不同,使用 defineProperties 定义的属性,configurable , writable , enumerable 默认为 false ,get , set , value 未设置时默认为 undefined
Object.getOwnPropertyDescriptor —— 查询指定描述符
-
作用 返回描述对象上指定属性配置的描述符对象
-
语法
Object.getOwnPropertyDescriptor(target, property)
- 示例
const o = { get foo() { return 17; }, }
d = Object.getOwnPropertyDescriptor(o, "foo")
console.log(d)
// {
// configurable: true,
// enumerable: true,
// get: [Function: get foo],
// set: undefined
// }
Object.getOwnPropertyDescriptors —— 查询对象所有属性的属性描述符
-
作用 返回描述对象上所有属性配置的描述符对象
-
语法
Object.getOwnPropertyDescriptors(obj)
- 示例
const o = { get foo() { return 17; }, name: 'lcl'}
d = Object.getOwnPropertyDescriptosr(o)
console.log(d)
// {
// 'foo': {
// configurable: true,
// enumerable: true,
// get: [Function: get foo],
// set: undefined
// },
// 'name': {
// configurable: true,
// enumerable: true,
// value: 'lcl',
// writable: true
// }
// }
Object.getOwnPropertyNames —— 返回对象自有属性名数组
-
作用 返回一个数组,其包含给定对象中所有自有属性(包括不可枚举属性,但不包括使用 symbol 值作为名称的属性)
-
语法
Object.getOwnPropertyNames(obj)
Object.getOwnPropertySymbols —— 返回对象自有符号属性名数组
-
作用 返回一个包含给定对象所有自有 Symbol 属性的数组
-
语法
Object.getOwnPropertySymbols(obj)
Object.getPrototypeOf —— 返回指定对象的原型
-
作用 返回指定对象的原型(即内部
[[Prototype]]属性的值),即对象的__proto__的值 -
语法
Object.getPrototypeOf(obj)
- 示例
const obj = {}
console.log(Object.getPrototypeOf(obj) === Object.prototype) // true
Object.seal —— 封存指定对象
-
作用 禁止对象扩展、修改属性配置、添加、删除属性,对于现存的可写属性,可以修改赋值(即禁用内部
[[defineProperty]] 方法,但允许[[set]] 方法修改赋值) -
范围 只作用于对象的自有属性
-
返回值 返回传递给函数的对象(在地将对象冻结)
-
语法
Object.freeze(obj)
Object.freeze —— 冻结指定对象
-
作用 禁止对对象实行任何修改操作,如扩展,修改属性配置,添加、删除属性,禁止修改属性值即禁用内部
[[defineProperty]] 和 [[set]] 方法)......,冻结一个对象是 JavaScript 提供的最高完整性级别保护措施 -
范围 **只作用于对象的自有属性
-
返回值 返回传递给函数的对象(在地将对象冻结)
-
语法
Object.freeze(obj)
Object.setPrototypeOf —— 设置隐式原型
-
返回值 将一个指定对象的原型(即内部的
[[Prototype]]属性)设置为另一个对象或者 null,同时返回被指定的对象 -
语法
Object.setPrototypeOf(obj, prototype)
[!tip] 注意
- 修改隐式原型会导致很深远的影响,浏览器对此负担较大,因此尽量不要使用 setPrototypeOf 设置隐式原型,应该使用 Object.create 创造一个指定了原型的新对象
- 示例
const obj = { a: 1 }
const n = {}
Object.setPrototypeOf(n, obj)
console.log(n.__proto__ === obj) // true
console.log(Object.getPrototypeOf(n) === obj) // true
Object.hasOwn —— 确定对象是否拥有指定的自有属性
-
返回值 如果对象的自有属性(包括不可枚举属性)中包含指定属性,则返回 true , 如果没有该属性或者该属性是继承的,则返回 false
-
语法
// prop 为要测试的字符串属性名或 Symbol
Object.hasOwn(obj, prop)
Object.is —— 确定两个值是否相同
-
返回值 两个 NaN 判定为 true, +0 和 -0 判定为 fallse, 0 和 -0 判定为 fallse, 其他行为跟严格相等相同
-
语法
Object.is(value1, value2)
Object.preventExtensions —— 设置对象为不可扩展
-
返回值 设置对象为不可扩展,即无法添加新属性,重新指定原型,返回已经不可扩展的对象
-
语法
Object.preventExtensions(obj)
Object.isExtensible —— 对象是否可扩展
-
返回值 返回一个布尔值,表示对象是否可以被扩展(指添加新属性,或改变原型)
-
语法
Object.isExtensible(obj)
Object.isSealed —— 对象是否被封存
-
返回值 返回一个布尔值,表示对象是否被封存,
-
语法
Object.isSealed(obj)
Object.isFrozen —— 对象是否被冻结
-
返回值 返回一个布尔值,表示对象是否被冻结,即是否不可扩展,所有属性都不可配置,且不可添加、删除、修改属性
-
语法
Object.isFrozen(obj)
Object.create —— 已现有对象为原型创造新对象
- 描述 接受一个对象作为参数,返回新对象,新对象的原型为这个参数对象
Object.create(obj)
- 示例
const obj = {}
const son = Object.create(obj)
console.log(Object.getPrototypeOf(son) === obj) // true
Object() 构造函数 —— 实例方法
obj.hasOwnProperty —— 是否有该属性
-
作用 返回一个布尔值,如果对象的含有该自有属性,则返回 true,若无该属性或该属性是继承来的,则返回 false
-
语法
// property 为字符串属性名或 symbol
obj.hasOwnProperty(property)
[!tip] 注意
- 该方法是 [[#Object.hasOwn —— 确定对象是否拥有指定的自有属性|Object.hasOwn()]] 的代替简写方法
- 有意思的是 in 操作符,无论是自有属性还是原型属性,都返回 true,而 hasOwnProperty 只有为自有属性时才返回 true,in 操作符返回 true 而 hasOwnProperty 返回 false ,就可以说明这是一个原型属性
Array() 构造函数
[!tip] 注意
- 比如
reverse(),sort()等方法,会直接修改原数组的,可以在方法名前加 to ,后缀 ed,会有新方法,不修改原数组,而是返回新数组,但功能相同- 有回调函数,且回调函数带有测试功能的方法,通过返回值判断
常见实例方法
| 方法 | 作用 | 说明 |
|---|---|---|
forEach() | 遍历数组 | 不返回数组,经常用于查找遍历数组元素 |
filter() | 过滤数组 | 返回新数组,返回的使筛选满足条件的数组元素 |
map() | 迭代数组 | 返回新数组,返回的使处理之后的数组元素,想要使用返回的新数组 |
reduce() | 累计器 | 返回累计处理的结果,经常用于求和等 |
join() | 拼接数组元素 | 拼接数组元素为字符串,并返回该字符串 |
find() | 从数组中查找 | 返回符合测试条件的第一个数组元素,如果没有符合条件的则返回 undefined |
every() | 检测数组所有元素是否符合条件 | 检测数组的所有元素是否都符合条件,如果所有元素都通过检测则返回 true ,反之返回 false |
some() | 检测数组是否有元素符合条件 | 如果有元素符合条件则返回 true ,没有则返回 false |
concat() | 合并数组 | 合并两个或多个数组并返回生成的新数组 |
sort() | 数组排序 | 对原数组单元值排序,并返回新数组 |
splice() | 删除或替换原数组单元 | 就地删除/添加数组元素,返回被删除项组成的数组,如果没有删除,则返回一个空数组 |
toSpliced() | 删除或替换数组单元 | 返回被修改后的新数组 |
reverse() | 反转原数组 | 将原数组的元素顺序转换,直接改变原数组 |
findIndex() | 查找元素索引值 | |
includes() | 包含某值 | 判断一个数组是否包含一个指定的值,并返回 true 或 false |
[[#实例方法 —— findIndex 和 indexOf|indexOf()]] | 查找元素下标 | 返回数组中第一次出现给定元素的下标,如果不存在则返回 -1 |
[[#实例方法 —— findIndex 和 indexOf|findIndex()]] | 查找元素下标 | 返回数组中满足提供的测试函数的第一个元素的索引。若没有找到对应元素则返回 -1 |
slice() | 浅拷贝数组的某一部分 | 返回一个新的数组对象,,其中 start (包括) 和 end (代表了数组元素的索引。原始数组不会被改变 |
flat() | 扁平化数组 | 返回一个扁平化处理后的新数组,参数为要提取嵌套数组的结构深度,默认为 1 |
pop() | 删除数组最后一个元素 | 它返回被删除元素的值,有时候可以用来获取数组末尾项 |
push() | 向数组末尾添加元素 | 返回新数组的长度 |
shift() | 删除数组第一个元素 | 返回被删除元素的值 |
unshift() | 向数组开头添加元素 | 返回新数组的长度 |
fill() | 以固定值填充数组 | 返回被修改后的数组 |
^3ecc78
实例方法——forEach()
[[#常见实例方法|回到实例表]]
-
作用
forEach()方法是一个迭代方法。它按索引升序地为数组中的每个元素调用一次提供的callbackFn函数。与map()不同,forEach()总是返回undefined,而且不能继续链式调用。其典型的用法是在链式调用的末尾执行某些操作。 -
语法
数组.forEach(function() {
函数体
})
- 注意
forEach()方法无返回值forEach()适合遍历数组对象
实例方法——map()
[[#常见实例方法|回到实例表]]
-
阐释 map 也称为映射,指两个元素的集合之前元素相互对应的关系
-
作用 遍历数组处理数据,并返回新的数组(创建一个新数组,这个新数组由原数组中的每个元素都调用一次提供的函数后的返回值组成)
-
语法
数组.map(function(callbackFn, thisArg)
- 示例
const array1 = [1, 4, 9, 16];
// 使用map方法处理
const map1 = array1.map(function (x) {
return x*2
});
console.log(map1);
// 输出结果: Array [2, 8, 18, 32]
- 注意
map()方法有返回值,其返回值构成新数组,可以进行链式传递
实例方法——filter()
[[#常见实例方法|回到实例表]]
-
含义 创建给定数组一部分的浅拷贝,其包含通过所提供函数实现的测试的所有元素
-
作用 按照给定条件筛选数组,将符合条件的元素编入新数组中
-
语法
数组.filter(callbackFn, thisArg)
实例方法——every()
[[#常见实例方法|回到实例表]]
-
作用 测试一个数组内的所有元素是否都能通过指定函数的测试,如通过则返回
true, 不通过则返回false -
语法
array.every(callbackFn, thisArg)
实例方法——some()
[[#常见实例方法|回到实例表]]
-
作用 测试数组中是否至少有一个元素通过了由提供的函数实现的测试。如果在数组中找到一个元素使得提供的函数返回 true,则返回 true;否则返回 false。它不会修改数组
-
语法
array.some(callbackFn, thisArg)
实例方法——find()
[[#常见实例方法|回到实例表]]
-
作用 回数组中满足提供的测试函数的第一个元素。否则返回
undefined -
语法
array.find(callbackFn, thisArg)
- 示例
const inventory = [
{ name: "apples", quantity: 2 },
{ name: "bananas", quantity: 0 },
{ name: "cherries", quantity: 5 },
];
function isCherries(fruit) {
return fruit.name === "cherries";
}
console.log(inventory.find(isCherries));
// { name: 'cherries', quantity: 5 }
实例方法——findIndex()
[[#常见实例方法|回到实例表]]
-
作用 返回数组中满足提供的测试函数的第一个元素的索引。若没有找到对应元素则返回 -1
-
语法
array.findIndex(callbackFn, thisArg)
实例方法 —— findIndex 和 indexOf
[[#^3ecc78|返回实例表]]
- 语法
// fn 为测试函数,返回符合测试条件的第一个元素的下标,若没有则返回 -1
array.findIndex(fn)
// element 为要查找的元素,采用 '===' 的方式查找,返回第一个符合条件元素的索引,若没有则返回 -1
array.indexOf(element)
[!tips] 注意
- indexOf() 适合用来查找简单数据类型, findIndex() 可以定制测试条件,适合复杂数据类型
以上方法的callbackFn(回调函数)参数
| 参数 | 类型 | 是否必选 | 说明 |
|---|---|---|---|
1. element | 任意 | 必选 | 数组中当前正在处理的元素 |
2. index | Number | 可选 | 当前元素在数组中的索引(从 0 开始) |
3. array | Array | 可选 | 调用该方法的原始数组本身 |
以上方法——thisArg
-
含义 为回调函数指定
this的值 -
示例
const obj = { factor: 100 };
// 正确写法:thisArg是map的第二个参数
[1, 2].map(function(n) {
return n * this.factor;
}, obj); // ✅ 输出 [100, 200]
// 错误尝试(不会生效)
[1, 2].map((n, thisArg) => { ... }) // ❌ thisArg成了索引参数
实例方法——reduce()
[[#常见实例方法|回到实例表]]
- 作用 返回累计处理的结果,经常用于求和等
- 语法
arr.reduce(callbackFn, initialValue)
- 回调函数参数 按顺序排列,依次为
| 参数 | 说明 |
|---|---|
accumulator | 上一次调用 callbackFn 的结果。在第一次调用时,如果指定了 initialValue 则为指定的值,否则为 array[0] 的值 |
currentValue | 当前元素的值。在第一次调用时,如果指定了 initialValue,则为 array[0] 的值,否则为 array[1] |
currentIndex | currentValue 在数组中的索引位置。在第一次调用时,如果指定了 initialValue 则为 0,否则为 1 |
array | 调用了 reduce() 方法的数组本身 |
[!tips] 注意 若指定了
initialValue,则循环次数为数组长度,反之循环次数为数组长度 - 1
- 特殊情况——对象数组累加
const arr = [{
name: '张三',
salary: 10000
},{
name: '李四',
salary: 10000
},{
name: '王五',
salary: 10000
}
]
const result = arr.reduce((pre,current) => return pre + current.salary, 0)
console.log(result);
[!tips] 注意 必须指定初始值,pre是上一次的运算结果,为数字,而
current是当前元素,是一个对象,要从里面取工资出来,才能参与累加计算
实例方法——join()
[[#常见实例方法|回到实例表]]
-
作用 可以将数组内容进行拼接,并返回结果字符串
-
语法
array.join(separator)
// 直接拼接
array.join('')
// 逗号分隔
array.join()
// |分隔
array.join('|')
- 注意
- 各元素之间会以
'separator'分隔 - 如果 separator省略,则各元素之间使用英文逗号
,分隔 - 如果separator为空字符串
'',则数组元素之间没有任何字符,直接拼接
- 各元素之间会以
实例方法——concat()
[[#常见实例方法|回到实例表]]
-
作用 合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组
-
语法
array.concat(value1, value2, valueN)
[!tips] 注意
value可以为数组或值,也可以填写展开数组...arr
实例方法——sort()
[[#常见实例方法|回到实例表]]
-
作用 就地对数组的元素进行排序,并返回对相同数组的引用。默认排序是将元素转换为字符串,然后按照它们的 UTF-16 码元值升序排序组
-
语法
array.concat(compareFn)
- compareFn 参数
定义排序顺序的函数。返回值应该是一个数字,其符号表示两个元素的相对顺序:如果
a小于b,返回值为负数,如果a大于b,返回值为正数,如果两个元素相等,返回值为0。NaN被视为0
| 参数 | 说明 |
|---|---|
a | 第一个用于比较的元素,不会是 undefined |
b | 第二个用于比较的元素,不会是 undefined |
compareFn(a, b) 返回值 | 排序顺序 |
|---|---|
| > 0 | a 在 b 后,如 [b, a] |
| < 0 | a 在 b 前,如 [a, b] |
| === 0 | 大部分情况下是稳定排序,保持 a 和 b 在原始数组中的顺序 |
- 示例
let arr = [5, 4, 3, 2, 1]
arr.sort()
// 完整写法——升序
arr.sort(function (a, b) {
return a - b
})
// 完整写法——降序
arr.sort(function (a, b) {
return b - a
})
// 对对象数组排序,使用对象中的某个属性进行排序
const arr = [{
name: '张三',
salary: 10000
},{
name: '李四',
salary: 15000
},{
name: '王五',
salary: 20000
}
]
//降序
arr.sort((a, b) => {
return b.salary - a.salary
})
[!tips] 注意
- 此方法会直接修改原数组,若想要不修改原数组排序,可以使用
toSorted()方法- 返回值大于0时,a,b交换顺序
- a和b并不是按照数组 arr[0] arr[1] 的顺序来选取的,而是由 JS 引擎根据算法所选择的
实例方法 —— splice()
-
作用 移除或者替换已存在的元素和/或添加新的元素
-
返回值 一个包含了删除的元素的数组。 如果只移除一个元素,则返回一个元素的数组。 如果没有删除任何元素,则返回一个空数组。
-
语法
splice(start)
splice(start, deleteCount)
splice(start, deleteCount, item1)
splice(start, deleteCount, item1, item2)
splice(start, deleteCount, item1, item2, /* …, */ itemN)
- 参数
| 参数 | 说明 |
|---|---|
| start | 从 0 开始计算的索引,表示要开始改变数组的位置 |
deleteCount | 一个整数,表示数组中要从 start 开始删除的元素数量 |
item1, item2, /* …, */ itemN | 从 start 开始要加入到数组中的元素。如果不指定任何元素, splice() 将只从数组中删除元素 |
- 示例
console.log([1, 2].splice(0, 1)) // 1
实例方法——reverse()
[[#常见实例方法|回到实例表]]
-
作用 反转原数组
-
语法
array.reverse()
- 示例
const arr1 = [0, 1, 2]
arr1.reverse()
console.log(arr1) // [2, 1, 0]
实例方法 —— includes()
-
作用 从数组中查找某值,并返回一个布尔值
-
语法
array.includes(searchElement, fromIndex)
实例方法 —— slice()
-
作用 浅拷贝数组(的某一部分),返回一个新数组
-
语法
// 拷贝的内容为原数组的 start 索引(包括 start )到 end 索引(不包括 end)的内容
array.slice(start, end)
[!tip] 注意
- start 小于 0 则为 startIndex + array.length
- end 大于数组长度,相当于数组末尾
unshift() —— 向数组开头添加元素
[[#常见实例方法|回到实例表]]
-
返回值 返回新数组的长度
-
语法
arr.unshift(element1, element2...)
shift() —— 删除数组第一个元素
[[#常见实例方法|回到实例表]]
-
返回值 返回该元素的值
-
语法
arr.shift()
push() —— 向数组末尾添加元素
[[#常见实例方法|回到实例表]]
-
返回值 返回新数组的长度
-
语法
arr.push(element1, element2...)
pop() —— 删除数组末尾元素
[[#常见实例方法|回到实例表]]
-
返回值 返回该元素的值
-
语法
arr.pop()
fill() —— 以固定值填充数组
[[#常见实例方法|回到实例表]]
-
返回值 返回被修改后的数组
-
语法
// start 为起始索引,默认值为 0 ,end 为结束索引(不包含),缺省则填充到数组末尾
arr.fill(value[, start, end])
静态方法——Array.from()
[[#常见实例方法|回到实例表]]
-
作用 将伪数组转化为真数组
-
作用对象
- 任何可迭代的结构
- 有一个 length 属性和可索引的结构
-
语法
Array.from(arrayLike, mapFn, thisArg)
[!tips] 注意 如果不需要处理伪数组,mapFn和thisArg可以省略不写
- 示例
// 返回伪数组
const lis = document.querySelectorAll('li')
// 转为真数组
const liss = Array.from(lis)
// 将生成器对象转换为数组,实现填充数组
function* fn(time) {
for(let i = 0; i < time; i++) {
yield i
}
}
const arr = Array.from(fn(4))
console.log(arr) // [0, 1, 2, 3]
mapFn参数 按顺序分别为
| 参数 | 说明 |
|---|---|
element | 当前元素 |
index | 当前元素的索引号 |
数组扩展——伪数组与真数组
伪数组
- 含义
伪数组(Array-like Objects) 是指具有
length属性,并可通过索引访问元素(如obj[0]),但不具备数组方法(如push、pop、forEach等)的对象
常见伪数组
-
arguments对象 -
DOM 操作返回的结果
document.getElementsByClassName()返回的HTMLCollectiondocument.getElementsByTagName()返回的HTMLCollectiondocument.querySelectorAll()返回的NodeList
-
字符串(Strings) 字符串可通过索引访问字符且有
length属性,但不可变且无数组方法 -
其他 API 返回的对象 如
FileList(文件上传控件返回的文件列表)
伪数组转换为真数组
Array.from()方法
const argsArray = Array.from(arguments);
const divsArray = Array.from(document.querySelectorAll('div'));
- 扩展运算符
const argsArray = [...arguments];
const divsArray = [...document.querySelectorAll('div')];
- 手动循环(兼容性最佳)
const arr = [];
for (let i = 0; i < 伪数组.length; i++) {
arr.push(伪数组[i]);
}
- 传统方法——
Array.prototype.slice.call()
const argsArray = Array.prototype.slice.call(arguments);
// 简写
const divsArray = [].slice.call(document.querySelectorAll('div'));
String() 构造函数
- 说明
在JS中,字符串被
String()构造函数包装成对象,具有属性和方法,同时,字符串还是伪数组
常见实例方法与属性
| 实例方法/属性 | 说明 | 注意 |
|---|---|---|
length | 字符串长度 | |
| [[#实例方法——split()|split(separator[, limit])]] | 拆分字符串,并返回一个数组 | separaotor 支持正则匹配 |
| [[#实例方法——substring()|substring(indexStart[, indexEnd])]] | 截取字符串 | 截取内容不包含结束索引的部分 |
| slice(indexStart, indexEnd)) | 截取字符串 | 截取内容不包含结束索引的部分 |
| [[#实例方法——startsWith()|startWith(searchString[, position])]] | 检测是否以某字符开头 | position 为索引号,默认为0 |
| [[#实例方法——endsWith()|endsWith(searchString[, endPosition])]] | 检测是否以某字符结尾 | |
| [[#实例方法——includes()|includes(searchString[, position])]] | 判断字符串是否包含另一字符串 | 返回 true oe false |
| [[#实例方法——toUpperCase()|toUpperCase()]] | 将字母转换成大写 | |
| [[#实例方法——toLowerCase()|toLowerCase()]] | 将字母转换成小写 | |
| [[#实例方法——indexOf()|indexOf(searchString[, position])]] | 检测是否包含某字符串 | |
| [[#实例方法——replace()|replace(pattern, replacement)]] | 替换字符串 | 支持正则匹配 |
| [[#实例方法——match()|str.match(regexp)]] | 查找字符串 | 支持正则匹配 |
| [[#padStart() —— 将数字转换为固定长度的字符串|padStart(targetLength, padString)]] | 开头填充字符串 | |
| padEnd(targetLength, padString` | 结尾填充字符串 | |
| includes(searchString, position) | 查找字符串,返回布尔值 |
实例方法——split()
[[#常见实例方法与属性|回到实例表]] [[#实例方法——join()|同 join() 相反]]
-
作用 通过搜索模式将字符串分割成一个有序的子串列表,将这些子串放入一个数组,并返回该数组
-
语法
str.split(separator[, limit])
- 参数
| 参数 | 说明 |
|---|---|
separator | 分隔符,字符串型或正则表达式 |
limit | 可选,数字型,表示数组的长度 |
- 实例
const str = '廖成林-丁换换'
console.log(str.split('-')) // ['廖成林', '丁换换']
- 特殊情况
const emptyString = "";
// 字符串是空的,分隔符是非空的
console.log(emptyString.split("a"));
// [""]
// 字符串和分隔符都是空的
console.log(emptyString.split(emptyString));
// []
实例方法——substring()
[[#常见实例方法与属性|回到实例表]]
[!tip] substring 面对负参数或者 NaN 时会将他们识别为 0 ,传入的两个索引的话,不用按大小排序,能智能识别
-
作用 返回该字符串从起始索引到结束索引(不包括)的部分,如果未提供结束索引,则返回到字符串末尾的部分
返回值为一个新字符串
-
语法
str.substring(indexStart[, indexEnd])
- 参数
| 参数 | 说明 |
|---|---|
indexStart | 起始索引(第一个被截取的字符串索引) |
indexEnd | 结束索引(第一个被排除的字符串索引) |
实例方法——slice()
[[#常见实例方法与属性|回到实例表]]
[!tip] 接受负数索引,如果参数为 NaN 将识别为 0 ,第一个参数为起始索引,第二个为结束索引,起始索引大于结束索引时返回空字符串
-
作用 返回该字符串从起始索引到结束索引(不包括)的部分,如果未提供结束索引,则返回到字符串末尾的部分
返回值为一个新字符串
-
语法
str.slice(indexStart[, indexEnd])
- 参数
| 参数 | 说明 |
|---|---|
indexStart | 起始索引(第一个被截取的字符串索引) |
indexEnd | 结束索引(第一个被排除的字符串索引) |
实例方法——startsWith()
[[#常见实例方法与属性|回到实例表]]
-
作用 判断当前字符串是否以另外一个给定的子字符串开头,并根据判断结果返回
true或false -
语法
str.startsWith(searchString[, position])
- 参数
| 参数 | 说明 |
|---|---|
searchString | 要在该字符串开头搜索的字符串,参数值强制转换为字符串,若不设置或设置为undefined,将查找 'undefined' 字符串 |
position | searchString 期望被找到的起始位置(索引号)默认为 0 |
- 示例
const str = '廖成林 丁换换'
console.log(str.startsWith('丁换换', 4)); // true
实例方法——endsWith()
[[#常见实例方法与属性|回到实例表]]
-
作用 判断一个字符串是否以指定字符串结尾,如果是则返回
true,否则返回false -
语法
str.endsWith(searchString[, endPosition])
- 参数
| 参数 | 说明 |
|---|---|
searchString | 要搜索的作为结尾的字符串,参数值强制转换为字符串,若不设置或设置为undefined,将查找 'undefined' 字符串 |
endPosition | 预期找到 searchString 的末尾位置(即 searchString 最后一个字符的索引加 1)。默认为 str.length |
实例方法——includes()
[[#常见实例方法与属性|回到实例表]]
-
作用 执行区分大小写的搜索,以确定是否可以在一个字符串中找到另一个字符串,并根据情况返回
true或false -
语法
str.includes(searchString[, position])
- 参数
| 参数 | 说明 |
|---|---|
searchString | 要在 str 中查找的字符串 |
position | 在字符串中开始搜索 searchString 的位置(索引号)。默认值为 0 |
实例方法——toUpperCase()
[[#常见实例方法与属性|回到实例表]]
-
作用 将该字符串转换为大写形式,并返回一个新的字符串(原字符串的值不变)
-
语法
str.toUpperCase()
实例方法——toLowerCase()
[[#常见实例方法与属性|回到实例表]]
-
作用 将该字符串转换为小写形式,并返回一个新的字符串(原字符串的值不变)
-
语法
str.LowerCase()
实例方法——indexOf()
[[#常见实例方法与属性|回到实例表]]
-
作用 在字符串中搜索指定子字符串,并返回其第一次出现的位置索引。它可以接受一个可选的参数指定搜索的起始位置,如果找到了指定的子字符串,则返回的位置索引大于或等于指定的数字
-
返回值 返回查找的字符串
searchValue的第一次出现的索引,如果没有找到,则返回-1如果position大于调用字符串的长度,则该方法根本不搜索调用字符串,而是返回调用字符串的长度 如果position小于零,该方法的行为就像position为0时一样 -
语法
str.indexOf(searchString[, position])
- 参数
| 参数 | 说明 |
|---|---|
searchString | 要搜索的子字符串,所有传入值都会被强制转换为字符串 |
position | 定子字符串在大于或等于 position 位置的第一次出现的索引,默认为 0 |
- 示例
const str = textjshdnf
sonsole.log(str.indexOf('text') === 0) // true
实例方法——replace()
[[#常见实例方法与属性|回到实例表]]
[!tip] 注意
- 如果 pattern 是一个字符串,则只会匹配第一个值
- 如果 pattern 是一个正则表达式,则可以使用修饰符 g 匹配多个值
-
作用 返回一个新字符串,其中一个、多个或所有匹配的
pattern被替换为replacement -
语法
str.replace(pattern, replacement)
- 参数
| 参数 | 说明 |
|---|---|
pattern | 要匹配的字符串,支持正则匹配 |
replacement | 替换 parttern 的字符串或函数,为函数时,将为每个匹配调用该函数,并将函数返回值作为替换文本 |
[!tips] 注意
- 字符串模式只替换第一个匹配的文本,正则匹配添加/g可以替换所有匹配项
- 可以使用
replaceAll()方法替换所有匹配项
实例方法——replaceAll()
[[#常见实例方法与属性|回到实例表]]
[!tip] 注意
- 使用该方法时,如果 pattern 是正则表达式,必须添加 g 修饰符
-
作用 返回一个新字符串,其中所有匹配的
pattern被替换为replacement -
语法
str.replaceAll(pattern, replacement)
实例方法——match()
[[#常见实例方法与属性|回到实例表]]
-
作用 检索字符串与正则表达式进行匹配的结果
-
返回值 返回一个数组,其内容取决于是否存在全局(
g)标志,如果没有匹配,则返回null- 如果使用
g标志,则将返回与完整正则表达式匹配的所有结果,但不会返回捕获组 - 如果没有使用
g标志,则只返回第一个完整匹配及其相关捕获组。在这种情况下,match()方法将返回与RegExp.prototype.exec()相同的结果(一个带有一些额外属性的数组)
- 如果使用
-
语法
str.match(regexp)
[!tips] 注意 参数只能填写正则表达式
padStart() —— 将数字转换为固定长度的字符串
[[#常见实例方法与属性|回到实例表]]
- 作用 用另一个字符串填充当前字符串(如果需要会重复填充),直到达到给定的长度。填充是从当前字符串的开头开始的
//两个参数分别为 填充后的长度,填充字符串
//原数据为字符串
原字符串.padStart(targetLength, padString)
//原数据不是字符串
原数据.toString().padStart(targetLength, padString)
-
注意
padString的默认值为unicode空格字符(U+0020)- 若原字符串长度已满足
targetLength则直接返回原字符串 - 若
padString太长,padString中超出长度的部分不填充 - 若填充一次
padString达不到targetLength,会再次填充到目标长度 - 如果原数据不是字符串,需要使用
.toString()转换为字符串格式
-
示例
console.log('str'.padStart(5, 'abc')) // abstr
Number() 构造函数
实例方法—— toFixed()
- 作用 设置保留的小数位数(遵循四舍五入原则),返回值为字符串
[!tips] 注意
- 如果数字的末尾是5的话,返回的值有可能会有问题(因为双精度数字显示不完全精准,会导致误差)
- 负数向负无穷四舍五入
- 语法
num.toFixed(digits)
- 参数
digits:小数位数,默认值为0
面向对象编程(oop)
-
划分依据 以对象功能来划分问题,而不是步骤
-
优点 面向对象编程具有灵活、代码可复用、容易维护和开发的优点,更适合多人合作的大型软件项目
-
缺点 性能比面向过程低
-
特性
- 封装性
- 继承性
- 多态性
面向过程编程
-
优点 性能比面向对象高,适合跟硬件联系紧密的东西,例如单片机采用面向过程编程
-
缺点 没有面向对象易维护、易复用、易扩展
深浅拷贝
- 对象 浅拷贝和深拷贝针对的是引用类型
浅拷贝
-
内容 最外层属性值为简单数据类型则拷贝值,为引用类型则拷贝地址
-
常见方法
// 拷贝对象
const obj = {
name: '廖成林',
age: 22
}
// 方法1 扩展操作符(展开运算符)
const o = {...obj}
// 方法2
const oo = {}
Object.assign(oo, obj)
// 拷贝数组
const arr = [1, 2, 3]
// 方法1 扩展操作符(展开运算符)
const array1 = [...arr]
// 方法2
const array2 = []
const newArr = array2.concat(arr)
- 问题 浅拷贝只拷贝最外层数据,内层数据仍然拷贝地址
// 以对象为例,浅拷贝会拷贝最外层数据,即name和age,而内部嵌套的family对象,只拷贝其地址,因此通过assign或扩展操作符(展开运算符)拷贝后修改family的属性会导致原对象被修改
const obj = {
name: '廖成林',
age: 22
family: {
baby: '廖某'
}
}
const o = {...obj}
o.family.baby = '黄某'
console.log(o) // 黄某
console.log(obj) // 黄某
深拷贝
-
内容 拷贝对象,而不是其地址
-
常见方法
- 通过递归实现深拷贝
- lodash库的cloneDeep方法
- 通过 JSON.stringify() 实现
- 通过
structuredClone()方法实现深拷贝
递归函数
- 含义 如果在一个函数内部可以条用其本身,那么这个函数就是递归函数
[!tips] 注意
- 可以简单理解为函数再内部自己调用自己,则该函数为递归函数
- 递归函数的作用和循环效果类似
- 递归很容易发生“栈溢出”错误(stack overflow),因此必须添加推出条件 return
- 使用递归函数实现深拷贝
const obj = {
uname: '廖成林',
age: 22,
hobby: [{ unames: 'HTML' }, '乒乓球', '羽毛球', '游泳', '打游戏']
}
const o = {}
// 简易写法
function deepClone(target) {
const map = new WeakMap()
function _deepClone(target) {
if (typeof target !== 'object' || target === null) {
return target
}
if (map.has(target)) {
return map.get(target)
}
const result = Array.isArray(target) ? [] : {}
map.set(target, result)
for (const i in target) {
result[i] = _deepClone(target[i])
}
return result
}
return _deepClone(target)
}
deepCopy(o, obj)
[!tips] 注意
- 必须把Array写在Object之前,因为数组也是对象
- 可以使用 typeof 替代instanceof
-
优点
- 可定制性强(支持循环引用、特殊类型)
- 性能可控
-
缺点
- 需手动处理所有数据类型(如
Date、RegExp需额外判断) - 递归可能栈溢出(极深嵌套时)
- 未考虑原型链(复制后对象的原型为
Object)
- 需手动处理所有数据类型(如
使用 lodash 库实现深拷贝
- 示例
// 引用lodash库
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
<script>
const obj = {
uname: '廖成林',
age: 22,
hobby: [{ unames: 'HTML' }, '乒乓球', '羽毛球', '游泳', '打游戏']
}
const o = _.cloneDeep(obj)
</script>
-
优点:
- 功能全面(支持循环引用、所有内置类型)
- 经过充分测试,健壮性强
- 避免重复造轮子
-
缺点:
- 增加项目体积(需引入整个库或按需打包)
⭐利用[[#^78c9bb|JSON.stringify()]]实现深拷贝⭐
-
原理 通过
JSON.stringify()将对象转换为字符串,从而直接拷贝值 -
示例
const obj = {
uname: '廖成林',
age: 22,
hobby: [{ unames: 'HTML' }, '乒乓球', '羽毛球', '游泳', '打游戏']
}
// 深拷贝操作
const o = JSON.parse(JSON.stringify(obj))
-
优点:
- 简单易用,一行代码实现
- 兼容性好(支持所有现代浏览器)
-
缺点:
- 无法处理特殊类型:
- 函数、
Symbol、undefined会被忽略。 Date对象转为字符串(失去 Date 类型)。RegExp、Error对象转为空对象{}。Map/Set等 ES6 新类型会丢失。
- 函数、
- 循环引用报错:
- 性能问题:大数据量时较慢
- 无法处理特殊类型:
通过structuredClone()实现深拷贝
- 示例
const obj = {
uname: '廖成林',
age: 22,
hobby: [{ unames: 'HTML' }, '乒乓球', '羽毛球', '游泳', '打游戏']
}
// 深拷贝操作
const copy = structuredClone(obj)
各种拷贝方式的优劣
| 方法 | 循环引用 | 函数 | 特殊类型¹ | 性能 | 复杂度 |
|---|---|---|---|---|---|
JSON 序列化 | ❌ | ❌ | ❌ | 中 | 低 |
| 递归实现 | ✅ | ✅² | ✅² | 中高 | 高 |
lodash.cloneDeep | ✅ | ✅ | ✅ | 高 | 低 |
structuredClone() | ✅ | ❌ | ⚠️³ | 高 | 低 |
MessageChannel | ✅ | ❌ | ⚠️³ | 低⁴ | 中 |
¹ 特殊类型:
Date/RegExp/Map/Set等 ² 需手动实现特殊类型的支持 ³ 支持部分类型(如Date、Map),但不支持函数、DOM 节点 ⁴ 异步操作可能有延迟
异常处理
- 含义 异常处理是指预估代码执行过程中可能发生的错误,然后最大程度的避免错误的发生导致整个程序无法继续运行
throw 抛异常
- 示例
function fn(x, y) {
if (!x || !y) {
// 使用抛异常
// 方法1
throw '未传递参数'
// 方法2
throw new Error('未传递参数')
}
return x + y
}
console.log(fn())
[!tips] 注意
- 若没有给出抛异常处理,则会输出 NAN
- 抛出异常后,程序会终止执行
- throw 后面跟的是错误提示信息
- Error 对象配合 throw 使用,能够设置更详细的错误信息
- 给出抛异常之后,会在控制台输出抛异常,以上代码运行结果如下
方法1 ![[z_attachments/Pasted image 20250715175958.png|600]]
方法2 ![[z_attachments/Pasted image 20250715175942.png|600]]
try/catch 捕获错误信息
[!tip] 注意,try... catch... 并不是函数,只是代码块,因此在 catch 中 return 会终止代码块所在函数的运行(26.1.7)
-
使用 可能发生错误的代码写到
try{}中 -
示例
// try catch 捕获错误信息
function fun() {
// 可能发送错误信息的代码,写到try里面
try {
const p = document.querySelector('p')
p.style.color = 'red'
} catch (err) {
// 拦截错误,提示浏览器提供的错误信息,但是不中断程序执行
console.log((err.message));
} finally {
console.log(111)
}
// 若需要中断程序
return
}
fun()
[!tips] 注意
- try,catch不会中断程序运行,如果需要中断,可以添加 return 中断函数
- 如果try 内部出现错误,try 内部之后的代码不会运行,try之外的代码不受影响
- 可以跟 throw 配合使用,中断程序运行,就不用写 return 了
- try, catch, finnally 为三个块作用域
- catch() 的第一个形参为异常值信息,第一个形参.message是详细的错误信息
- 先执行 try ,如果没错误执行 finally ,如果有错误先执行 catch ,再执行 finally
debugger
-
使用 在代码中添加
debugger相当于在此处添加了一个断点,方便进行断点调试 -
示例
const arr = [0, 1, 2]
debugger
const array = [...arr]
严格模式
- 开启 在作用域添加以下代码,该作用域将开启严格模式
'use strict'
[!tips] 注意
- 严格模式下,普通函数没有调用者时, this 的值为 undefined
处理 this
this 指向
-
普通函数 谁调用,指向谁,
this是一个动态的值,会随着调用者的改变而改变 -
箭头函数 箭头函数没有自己的
this,声明箭头函数时,箭头函数与最近作用域的this一致,若最近作用域没有this,沿着作用域链依次查找,其this值是静态的,在声明的时被固定
[!tips] 注意
- 事件回调函数使用箭头函数时,
this为全局的window因此DOM事件回调函数中如果需要 DOM 对象的this,不推荐使用箭头函数- 在使用构造函数时,在构造函数内部的箭头函数,
this能正确指向实例对象,而写在构造函数外的箭头函数(如在prototype上添加实例方法,这个this指向window
改变 this
-
描述 在JS中,允许我们通过指定函数改变
this的指向,有3个方法可以动态指定普通函数中的this指向 -
函数
call()
apply()
bind()
[!tips] 注意
- 一般来说,只有函数具有
this,但是通过这三种方法可以将普通函数的this值绑定到任何对象上(包括自定义对象、数组、函数等)
call()
-
作用 调用函数,同时指定被调用函数中的
this的值 -
语法
fn.call(thisArg, arg1, arg2, ...)
- 参数
| 参数 | 说明 |
|---|---|
thisArg | 在函数运行时的 this 值,默认为调用者,可以自己设定 |
arg1, arg2 | 传递的其他参数 |
- 示例
function fn(x, y) {
console.log(this); // sum
return console.log(x + y); // 5
}
function sum() {
}
fn.call(sum, 2, 3)
- 返回值 返回值为函数的返回值
apply()
-
作用 调用函数,同时指定被调用函数的
this的值 -
语法
fn.apply(thisArg, [argsArray])
- 参数
| 参数 | 说明 |
|---|---|
thisArg | 在函数运行时指定的 this 值,默认为调用者,可以自己设定 |
[argsArray] | 传递的值,必须包含在数组中 |
[!tips] 注意
- 返回值为函数的返回值
- 该方法主要与数组有关,例如使用 Math.max() 求数组的最大值
- 数组中的元素按顺序对应函数的形参
[[#构造函数——bind()方法|bind()]]
-
作用 将构造函数
this关键字设置为给定的值,同时,还可以传入一系列指定的参数,这些参数会插入到调用新函数时传入的参数的前面 -
语法
fn.bind(thisArg, arg1, arg2)
- 参数
| 参数 | 说明 |
|---|---|
thisArg | 在函数运行时指定的 this 值 |
arg1, arg2 | 传递的其他参数 |
- 返回值 返回指定的 this 值和初始化参数改造的原函数拷贝(新函数)
[!tips] 注意
- 只想改变
this指向,而不想调用该函数时,可以使用 bind(),例如改变定时器内部的this指向- bind() 方法不会改变原函数的 this 指向,而是返回一个新函数(拷贝函数),永久绑定拷贝函数的 this指向
- 当写下
fn.bind(thisArg, arg1, arg2)时,这句话等价于这里是一个复制的fn,并已经绑定了this值,这段代码是一个整体,如果后面加()就可以直接调用新函数了
call() apply() bind()总结
-
相同点 都可以改变函数内部的this指向
-
不同点
- call 和 apply 会调用函数,并且改变函数内部的 this 指向
- call 和 apply 传递的参数不一样, call 传递参数 arg1, arg2... 形式,而 apply 必须为数组[arg1, arg2...]
- bind 不会调用函数,但可以改变函数内部 this 的指向
- call 和 apply 为临时改变函数 this 值,仅当次有效, 而 bind 创建的新函数 this 值被永久绑定
-
主要应用场景
- call 调用函数并且可以传递参数
- apply 经常跟数组有关系,比如借助数学对象实现数组最大值最小值
- bind 不调用函数,但是改变 this 指向, 比如改变定时器内部的 this 指向
防抖(debounce)
- 含义 单位时间内,频繁触发事件,只执行最后一次 类似于王者的回城,执行过程中被打断需要重新来
![[z_attachments/e5ac82ee0e4ed1116a7284b02bdc6c14.jpg|400]]
手写防抖函数
- 示例
const div = document.querySelector('div');
let i = 0
// fn 为业务的执行代码
function fn() {
div.innerHTML = i++
}
// 防抖 通过闭包的形式记 timer 的状态
function doundebounce(fn, duration, immediate) {
let timer
const doundebounced = function (...args) {
const context = this
// 理解立即执行的关键是,这里只清除了定时器,但是 timer 变量并未被清除,还存储着定时器 id
if (timer) clearTimeout(timer)
if (immediate) {
const callNow = !timer
timer = setTimeout(() => {
timer = null
}, duration)
if (callNow) return fn.apply(context, args)
} else {
timer = setTimeout(() => {
fn.apply(context, args)
}, duration)
}
}
doundebounced.cancel = function () {
clearTimeout(timer)
timer = null
}
return doundebounced
}
// 这个回调函数加了() 会在浏览器添加事件监听器的时候立即执行,并返回匿名函数,之后触发事件会执行匿名函数
div.addEventListener('mousemove', debounce(fn, 50))
lodash 库实现
-
lodash 库 #lodash库 cdn.jsdelivr.net/npm/lodash@…
-
语法
// 参数分别为业务函数,延迟时间,其他可选参数
_.debounce(fn, [wait = 0], [options = ])
- 示例——以上面的例子书写
div.addEventListener('mousemove', _.debounce(fn, 500))
- options 的参数
options是一个对象,有leading,trailing,maxWait三个参数
| 参数 | 含义 |
|---|---|
leading | 前缘执行,默认值为 false ,在指定延迟的开始处立即执行 |
trailing | 后缘执行,默认值为 true ,在指定延迟结束后立即执行 |
maxWait | fn的最大延迟时间 |
[!tips] 注意
leading和trailing都为true时,在节流时间段内多次触发函数,会记录最后一次触发,并在节流时间段结束时执行,如果节流时间段内只有一次触发动作,则不触发后缘执行- 如果设置了
maxWait,如果频繁触发事件,则在该时间段结束时触发一次
节流——throttle
- 含义 单位时间内,频繁触发事件,只执行一次,即触发事件后,如果该事件正在进行,则取消重新触发,直到该事件进行完后可以再次触发 eg.王者的平a,无论按多少次按键,都要等待当前平a行为结束后才能再次平a
![[z_attachments/9aefcaf80df3295f09ee78525b7a7252 1.jpg|400]]
- 使用场景
高频事件:鼠标移动
mousemove,页面尺寸缩放resize,滚动条滚动scroll等等
lodash 库实现
- 语法
_.throttle(func, [wait=0], [options=])
- 示例——以上一节防抖函数的例子为例
div.addEventListener('mousemove', _.throttle(fn, 500))
- options 的参数
options是一个对象,有leading,trailing两个参数,默认值为true
| 参数 | 含义 |
|---|---|
leading | 前缘执行,在节流时间段的开始处立即执行 |
trailing | 后缘执行,在节流时间段的结束处立即执行 |
[!tips] 注意
leading和trailing都为true时,在节流时间段内多次触发函数,会记录最后一次触发,并在节流时间段结束时执行,如果节流时间段内只有一次触发动作,则不触发后缘执行- 在 lodash 库中,节流函数本质上是对防抖函数的封装,其中
maxwait = wait
手写节流函数
- 示例——以上一节防抖函数的例子为例
// fn 为业务代码
function throttle(fn, t) {
let timer = null
return function () {
if (!timer) {
timer = setTimeout(function () {
fn()
// 清空定时器
timer = null
}, t)
}
}
}
div.addEventListener('mousemove', throttle(fn, 500))
[!tips] 原理 如果 timer 为 false ,则触发定时器,定时器内部先调用核心业务函数,核心业务处理完成后,再将定时器 id
节流与防抖总结
| 性能优化 | 说明 | 适用场景 |
|---|---|---|
| 防抖 | 一定时间内,频繁触发事件,只执行最后一次 | 搜索框搜索输入、手机号、邮箱验证输入检测 |
| 节流 | 一定时间内,频繁触发事件,只执行一次 | 高频事件:鼠标移动 mousemove ,页面尺寸缩放 resize ,滚动条滚动 scroll 等等 |
DOMParser构造函数——将文本解析为HTML节点
-
含义
DOMParser()构造函数用于创建一个新的DOMParser对象。该对象可用于通过parseFromString()方法解析文档的文本内容 -
实例方法
// 两个参数分别为 要转换的字符串 text/html
构造函数.parseFromString(string, mimeType)
- 示例
const parser = new DOMParser();
const htmlString = "<strong>Beware of the leopard</strong>";
const doc3 = parser.parseFromString(htmlString, "text/html");
// HTMLDocument
可选链操作符(?.)
-
含义 用于访问对象的属性或调用函数。如果使用此运算符访问的对象或调用的函数是
undefined或null,则表达式会短路并计算为undefined,而不是抛出错误 -
作用 如果引用是空值(
null或undefined),它不会导致错误,而是使表达式短路并返回undefined。当用于函数调用时,如果给定函数不存在,它也会返回undefined -
示例
error?.response?.status
// [] 方法中使用
const obj = {}
const key = ab
obj?.name?.[key]
- 解读 如果 error 有值,则访问 error.response ,如果 response 有值,则访问 error.response.status,防止出现 undefined.response 或 undefined.status 从而报错
[!tips] 注意
- 空数组使用可选链后访问数组索引会触发可选链导致 undefined
- 数组中范围不存在的索引也会触发可选链
- 空对象同理
空值合并操作符(??)
-
作用 左边为 null 或 undefined 时,返回右边的值
-
示例
let a
let b = 1
console.log(a ?? b) // 1
[!tips] 注意
- 跟 || 做区分, || 左侧为 NAN , null , undefined , false 时会返回右侧的值
- ?? 的优先级较低,使用时可以添加括号提高优先级
in 操作符
-
作用 判断指定的属性是否在指定的对象或其原型链中,返回一个布尔值
-
语法
property in objectName
- 参数
| 参数 | 含义 |
|---|---|
| property | 一个字符串类型或者 symbol 类型的属性名或者数组索引(非 symbol 类型将会强制转为字符串) |
| objectName | 检查它(或其原型链)是否包含具有指定名称的属性的对象 |
- 示例
// 1. 自定义对象
const obj = { name: '廖成林' }
console.log('name' in obj) // true
// 2. 内置对象
console.log('PI' in Math) // true
// 3. 数组
var trees = new Array("redwood", "bay", "cedar", "oak", "maple");
0 in trees // 返回 true
3 in trees // 返回 true
6 in trees // 返回 false
获取焦点
- 语法
DOM元素.focus()
[!tips] 影响
- 对应的,失去焦点为 blur()
html 的属性 attribute 和 dom 对象的属性 property 详解
-
attribute Attribute 是写在 HTML 标签上的键值对 ,它的 值为字符串
-
示例 下面例子的 src , alt , width , class , id 都是 attribute
<img src="https://example.com/image.jpg" alt="A beautiful landscape" width="500" class="my-image" id="pic1">
-
property Property 是 JavaScript 内存中代表 DOM 元素的【对象】所拥有的【属性】
-
区别
| Attribute (属性) | Property (属性) | |
|---|---|---|
| 存在于 | HTML 代码/标签 中 | DOM 对象 中(浏览器在内存中创建的 JavaScript 对象) |
| 值类型 | 总是字符串 | 可以是任意 JavaScript 类型(布尔、数字、对象...) |
| 同步 | 在 HTML 解析阶段初始化对应的 DOM property | 是当前 DOM 对象的状态,会随着用户操作或JS代码改变 |
| 实时性 | 初始值、默认值 | 当前值、动态状态 |
| 标准性 | 可以是任意自定义的(如 data-*, my-attr) | 通常是标准化的,遵循 DOM 规范(如 id, className, value) |
- 联系 —— 主要体现在初始化阶段:
-
初始化同步:
当浏览器解析 HTML 并创建 DOM 对象时,会根据标签上的 Attribute 来初始化对应的 Property 例如,<input value="Hello">会让input.value这个 property 的初始值被设置为字符串"Hello" -
有限的双向同步:
对于某些 非值的 Attribute(如id,class,style,data-*等),它们在 Attribute 和 Property 之间存在双向同步关系 例如,更改element.setAttribute('class', 'red')会更新element.className反之,更改element.className = 'blue'也会更新class这个 attribute
-
[!tips] 注意
- 对于值的 Attribute(如
input的value,checkbox的checked),这种同步是单向的(仅初始化时)。Property 的值代表当前状态,而 Attribute 的值代表默认值
在事件监听中检测是否按下 shift 键
- 语法
// 如果按下了 shift 则返回 true 反之返回 false
e.shiftKey
requestAnimationFrame(callback)
-
作用 要求浏览器在下一次重绘之前,调用用户提供的回调函数,即每帧渲染前调用一次回调函数
-
原理 将回调函数推入一个队列,在下一帧渲染前执行该队列
-
语法
requestAnimationFrame(callback)
-
优点 可以代替 setTimeout 成为更流畅更优秀的动画节流
-
取消 requestAnimationFrame
cancelAnimationFrame(timer)
[!tips] 注意 cancelAnimationFrame 可以接收如 null undefined 等无效参数
- 示例
// 实现流畅的动画效果
cancelAnimationFrame(timer)
timer = requestAnimationFrame(() => {})
不变更新
-
作用 面对引用数据类型时,使用扩展操作符(展开运算符) + 属性覆盖的形式可以在不修改原对象的情况下创建一个新的对象并修改其中的特定属性
-
示例
// 这种方式只能处理展开后为简单数据类型的数据,反之则要继续展开
const obj = {
a: {
uid: 10086
},
b: 'lcl'
}
// 需要用 obj 但是得处理一下,把 uid 改成 041128 才能使用
conse newObj = {
// 展开 a 对象
...obj,
// 添加新的 a 对象覆盖掉前面的
a: {
// a 对象中通过扩展操作符(展开运算符)获得 obj.a 的所有属性
...boj.a,
// 手动添加需要覆盖的属性
uid: 222
}
}
互斥锁
-
作用 确保一段代码在同一时刻只能被一个执行上下文进入
-
组成
- 上锁
- 执行核心代码
- 解锁
-
示例
let flag = false
function fn () {
// 上锁
if (flag) {
return
}
flag = true
// 执行核心代码
console.log(flag)
// 执行完毕,解锁
flage = false
}
[!tips] 注意
- 如果核心代码中含有异步操作,需要把解锁放在异步操作中,否则会导致上锁后马上解锁,失去互斥锁作用
- 应用场景 任何需要防止重复触发的场景
条件扩展运算符
-
作用 在配置对象时,如果某个条件存在则配置该属性,否则不配置
-
语法
// 示例
config: {
// 利用逻辑与,如果左边存在则返回右侧值,即右侧的对象,然后右侧对象被 ... 展开,如果左侧不存在则返回 false ,...false
...(this.coordinates && { location: this.coordinates, radius: 100000 })
}
重命名导入
-
描述 在导入时遇到命名冲突,可以使用重命名导入方法规避
-
示例
// setDefaultAddress 冲突了,使用改语法将命名改为 setDefaultAddressApi
import { setDefaultAddress as setDefaultAddressApi } from '@/api/address'
ESlint
// eslint-disable-line
- 作用 添加该注释的行将忽略该行警告
IntersectionObserver API
-
含义 是一种异步观察目标元素与其祖先元素或顶级文档视口(viewport)交叉状态的方法。其祖先元素或视口被称为根(root)
-
创建 IntersectionObserver 实例
const options = {
root: document.querySelector("#scrollArea"),
rootMargin: "0px",
threshold: [1],
};
const observer = new IntersectionObserver((entries) => {
console.log(entries)
},
{
root: document.querySelector("#scrollArea"),
rootMargin: "0px",
threshold: [1],
}
);
实例属性
| 属性 | 含义 |
|---|---|
| root | 测试交叉时,用作边界盒的元素或文档。如果构造函数未传入 root 或其值为null,则默认使用顶级文档的视口 |
| rootMargin | 改变交叉的范围,以边界盒为基准,正数扩大,反之缩小,默认值为“0px 0px 0px 0px”,写法同 margin ,可使用 px 或 % |
| thresholds | 表示被监视元素进入监视区域后会触发重叠回调的比例,介于0-1之间,写入一个数组,默认为 [0] |
[!tips] 注意
- 上面三种写法是在配置项中的写法,在实例外可以使用(以上述创建的实例为例) observer.root , observer.rootMargin , observer.thresholds 表示
实例方法
- 以上述创建的实例为例
// 开始监听一个目标元素,重点是一个元素,无返回值
observer.observe(targetElement)
// 停止监听目标,无参数和返回值,该方法停止监听所有目标元素
observer.discounnect()
// 返回所有观察目标的 IntersectionObserverEntry 对象数组
observer.takeRecords()
// 停止监听特定目标元素,该方法一次停止监听一个元素,无返回值
observer.unobserve(targetElement)
回调函数之参数 —— entries
-
含义 包括所有这次回调时状态发生改变的被观察元素
-
属性
| 属性 | 类型 | 描述 | 使用频次 | 使用场景 |
|---|---|---|---|---|
isIntersecting | Boolean | 目标元素是否与根元素相交 | ⭐⭐⭐⭐⭐ | 判断元素是否进入/离开视口 |
intersectionRatio | Number | 相交区域的比例 (0-1) | ⭐⭐⭐⭐ | 精确控制懒加载、动画触发点 |
target | Element | 被观察的目标元素 | ⭐⭐⭐⭐ | 获取具体是哪个元素触发了回调 |
boundingClientRect | DOMRectReadOnly | 目标元素的边界矩形 | ⭐⭐⭐ | 获取元素位置和尺寸信息 |
intersectionRect | DOMRectReadOnly | 相交区域的边界矩形 | ⭐⭐ | 获取实际相交区域信息 |
rootBounds | DOMRectReadOnly | 根元素的边界矩形 | ⭐ | 获取根元素(通常是视口)信息 |
time | Number | 变化发生的时间戳 | ⭐ | 性能分析、调试时序问题 |
ResizeObserver API
[!tip] 注意
- 该观察者不会触发重排,因为它是异步地订阅浏览器布局数据,在浏览器自然地进行布局计算后,它会读取这个计算地结果,并且只有在被观察元素的实际尺寸发生变化时,才会触发回调
- 与它不同的是,直接读取元素尺寸(clientWidth , offsetWidth , getBoundingClientRect等),由于要获得最新布局属性,会强制重排
-
描述 可以监听指定 Element 元素内容盒或边框盒或者 SVGElement 边界尺寸的大小,并执行相应操作
-
创建 ResizeObserver 实例
const resizeObserver = new ResizeObserver(callback)
// 示例
function callback(entries, observer) {
console.log(observer === resizeObserver) // true
for (const entry of entries) {
// Do something to each entry
// and possibly something to the observer itself
}
}
- 回调函数的参数
| 参数 | 含义 |
|---|---|
| entries | 一个 resizeObserverEntry 对象数组,可以用于获取每个元素改变后的新尺寸 |
| observer | 对实例的引用,如上述示例 |
disconnect() —— 取消所有元素监听
-
作用 取消所有的对 Element 或 SVGElement 目标的监听
-
语法
observer.disconnect()
observe() —— 监听指定的 Element 或 SVGElement
- 语法
const observer = new ResizeObserver(callback)
observer.observe(target)
observer.observe(target, options)
- 参数
| 参数 | 含义 |
|---|---|
| target | 对要监听的 Element 或 SVGElement 的引用 |
| options | 一个可选的对象,允许你为监听的对象设置参数,目前只有 box 一个参数 |
- options 参数
options: {
// 设置 observer 将监听的盒模型
// 默认值,CSS 中定义的内容区域的大小
box: content-box
// CSS 中定义的边框区域的大小
box: border-box
// 在对元素或其祖先应用任何 CSS 转换之前,CSS 中定义的内容区域的大小,以设备像素为单位
box: device-pixel-content-box
}
unobserve() —— 结束对指定的 Element 或 SVGElement 的监听
- 语法
const observer = new ResizeObserver(callback)
unobserve(target)
定型数组 —— TypedArray
历史
-
描述 随着浏览器的流行,人们期待通过它来运行复杂的 3D 应用程序,在这个基础上,浏览器基于 OpenGL ES 开发出来一套新 API —— WebGL 在早期的 WebGL 版本中,由于 JS 数组与 原生数组之间不匹配,图形驱动程序 API 通常不需要以 JS 默认双精度浮点格式传递给它们的数值,而这是 JS 数组在内存中的格式,因此 WebGL 和 JS 运行之间传递数组时,需要进行格式转换,出现了性能问题
-
定型数组 为了解决这个问题,浏览器提供了一个 JS 接口,但 C 语言风格的浮点值数组,使得上述交流不必进行格式转换,这就是 Float32Array
ArrayBuffer
[!tips] 注意
- ArrayBuffer 一经创建就不能再调整大小
- ArrayBuffer 在分配失败时会抛出错误
- ArrayBuffer 分配的内存不能超过 字节
- ArrayBuffer 会将所有二进制位初始化为 0
- ArrayBuffer 分配的堆内存可以被当作垃圾回收,不用手动释放
-
描述 ArrayBuffer 是所有定型数组及视图引用的基本单位 只读的,空间连续的、定长字节数组
-
创建 ArrayBuffer 传入一个参数,表示在内存中分配特定数量的字节空间
// 创建一个占据 16 字节内存空间的 ArrayBuffer
const buf = new ArrayBuffer(16)
byteLength 属性—— ArrayBuffer 的内存大小
// 创建一个占据 16 字节内存空间的 ArrayBuffer
const buf = new ArrayBuffer(16)
console.log(buf.byteLength) // 16
slice() —— 分割或复制 ArrayBuffer
- 示例
const buf = new ArrayBuffer(16)
const buf2 = buf.slice(4, 12)
console.log(buf2.byteLength) // 8