ECMA
数组
类数组转换为数组
Array.prototype.slice.call(arrayLike)
Array.from(arrayLike)
[...arrayLike]
[].concat(...arrayLike)
isArray
isArray 的前身是 Object.prototype.toString.call(value) === "[object Array]"
类型判断
typeof
A instanceof Array
toString.call
splice
功能:删除、替换、插入
参数:开始位置;删除个数;添加到数组的元素;
reduce
// 去重
const myArray = ["a", "b", "a", "b", "c", "e", "e", "c", "d", "d", "d", "d"];
const myArrayWithNoDuplicates = myArray.reduce((acc, cur) => {
if (!acc.includes(cur)) {
acc.push(cur);
}
return acc;
}, []);
改变原数组的方法
push、pop、shift、unshift;sort、reverse;splice;
其他:concat、slice、join、indexOf、lastIndexOf;forEach、map、filter;some、every、find、findIndex;
对象
Object.create(proto)
创建新对象,新对象的 prototype 【由传入的参数指定】,参数 proto 必须是 null 或对象,否则会报错;
Object.create() 和 new Constructor() 的区别
Object.create() 创建的对象实例【具有独立的原型链】,可以独立地管理和修改属性;
new Constructor() 创建的对象实例共享相同的原型链,可能导致意外的副作用和属性共享问题;
代码解释 ↓↓
function Person(name) {
this.name = name;
}
Person.prototype.greet = function () {
console.log(`Hello, my name is ${this.name}.`);
};
// 使用 new Constructor() 创建两个对象实例
const person1 = new Person("Alice");
const person2 = Object.create(Person);
// 修改 Person 的原型上的属性
Person.prototype.age = 30;
// person2 不会被影响到
console.log(person1.age); // 输出: 30
console.log(person2.age); // 输出: undefined
继承
function Parent() {
this.name = "parent";
}
function Children() {
Parent.call(this); // 调用父类构造函数
this.type = "Child";
}
Children.prototype = Object.create(Parent.prototype);
Children.prototype.constructor = Children;
为对象创建原型
const proto = { foo: "bar" };
const obj = Object.create(proto);
obj.__proto__ === proto;
克隆对象
function clone(origin) {
return Object.create(Object.getPrototypeOf(origin));
}
DefineProperty
在对象上定义一个新属性,或修改其现有属性,并返回此对象;
优点:更精确,添加更多细节(可写、可枚举、可配置、自定义 get、set 等,修改默认行为);
configurable 和 writable 的区别
configurable 是否可配置,不能再次使用 defineProperty 重新定义之前已经定义过的属性;
writable 是否可写,是否能修改属性值 obj.x = 20
Proxy
请解释一下 Proxy 对象的作用和用法?
用于拦截并控制目标对象的访问,允许在访问对象之前和之后执行自定义的操作。它接受两个参数:目标对象和一个处理程序对象(handler),其中处理程序对象定义了拦截的行为。通过 Proxy,可以对目标对象的属性访问、属性赋值、函数调用等进行拦截和处理。
请说明 Proxy 中的 get 和 set 拦截器的作用和用法。
get 拦截器:用于拦截对象属性的访问操作,当访问代理对象的属性时触发。
set 拦截器:用于拦截对象属性的赋值操作,给代理对象的属性赋值时触发。
通过在处理程序对象中定义 get 和 set 方法,可以自定义拦截的行为,并在拦截发生时执行相应的操作,如打印日志、进行验证等。
let person = {
name: "Alice"
}
let proxy = new Proxy(person, {
get: function(target, prop) {
console.log(`访问属性:${prop}`);
return target[prop];
}
set: function(target, prop, value) {
console.log(`设置属性:${prop} = ${value}`);
target[prop] = value;
}
})
const proxy = new Proxy(target, handler);
console.log(proxy.name); // 输出: 访问属性:name,Alice
proxy.age = 35; // 输出: 设置属性:age = 35
console.log(proxy.age); // 输出: 访问属性:age,35
// receiver:proxy 是真实对象的替身,如果想要【获取真实对象】的属性和状态,需要将真实对象传递给代理对象作为参数,receiver 就是这个参数,它告诉代理对象要去向谁获取属性值。也相当于上下文。
Proxy 中的 has 方法的作用是什么?
用于拦截使用 propKey in proxy 的操作,判断属性是否存在于代理对象中。当在代理对象上进行属性存在性检查时,has 方法会被触发,并返回一个布尔值,表示属性是否存在。通过定义 has 方法,可以自定义拦截操作并返回相应的结果。
请解释一下 Proxy 中的 apply 和 construct 拦截器的用途?
apply 拦截器用于拦截 Proxy 实例作为函数调用时的操作,即当代理对象被调用时触发。通过定义 apply 方法,可以自定义拦截函数调用的行为,并在调用发生时执行自定义的操作,如参数验证、记录日志等。
construct 拦截器用于拦截 Proxy 实例作为构造函数调用时的操作,即当使用 new 关键字创建代理对象实例时触发。通过定义 construct 方法,可以自定义拦截构造函数调用的行为,并在构造函数调用发生时执行自定义的操作,如创建实例前的准备工作、实例化对象的类型检查等。
Proxy 还支持哪些其他的拦截器方法?
deleteProperty:用于拦截 delete proxy[propKey]
的操作,当删除代理对象的属性时触发。
defineProperty:用于拦截 Object.defineProperty(proxy, propKey, propDesc)
的操作,当在代理对象上定义属性时触发。
getPrototypeOf:用于拦截 Object.getPrototypeOf(proxy) 的操作,当获取代理对象的原型时触发。
setPrototypeOf:用于拦截 Object.setPrototypeOf(proxy, newProto) 的操作,当设置代理对象的原型时触发。
高级功能
属性验证和保护;
数据绑定(拦截 get 和 set 操作,构建响应式界面);
缓存(调用拦截方法并缓存结果);
代理远程服务(调用拦截方法,把方法调用转发到远程服务,获取响应结果)
Reflect
提供了一组访问对象底层操作的方法,和 Proxy 提供的方法是完全一致的,Proxy 是代理对象,它不修改源对象,因此 Reflect 可以和 Proxy 配合使用,让源对象也发生改变。它的优势是比较优雅,写法和 Proxy 保持一致,而且更安全(this 指向问题)
浅克隆
1、Object.assign
2、展开运算符
3、arr.slice(数组)
深克隆
1、JSON.parse(JSON.stringify(obj))
2、递归
function deepClone(obj, memo = new WeakMap()) {
// 如果是基本数据类型或 null,则直接返回
if (obj === null || typeof obj !== "object") {
return obj;
}
// 如果对象被拷贝过,直接返回之前的拷贝结果,避免循环引用
// 循环引用,一个对象包含自身的引用或者多个对象互相引用的情况
// 如果不处理循环引用,深拷贝可能会陷入无线递归
// 比如 objA.prop = objB; objB.prop = objA;
// 一个对象引用了另一个对象,后者又直接或间接引用了前者
if (memo.has(obj)) {
return memo.get(obj);
}
// 检查memo中是否已经存在了obj。这是通过memo.has(obj)来判断的。
// 如果memo中已经有了obj的记录,说明这个对象已经被拷贝过,就直接返回memo中对应的拷贝结果,
// 而不再递归深拷贝这个对象
// 特殊对象处理,如 Date 和 RegExp
if (obj instanceof Date) {
return new Date(obj);
}
if (obj instanceof RegExp) {
return new RegExp(obj);
}
// 处理数组和对象
const newObj = Array.isArray(obj) ? [] : {};
memo.set(obj, newObj);
// 递归拷贝子对象
for (let key in obj) {
newObj[key] = deepClone(obj[key], memo);
}
return newObj;
}
/**
1. 如果是基本数据类型或 null,则直接返回
2. 特殊对象处理(date RegEXP等),instanceof 判断,然后返回 new Date(obj)
3. 数组和对象的处理,判断是不是数组,是数组就新建一个[],否则就是{}
4. 递归拷贝子对象,重复之前的步骤
5. 循环引用的问题:如果对象被拷贝过,直接返回之前的拷贝结果,避免循环引用。这时需要用一个 weakmap 来管理这些数据。
*/
函数
参数柯里化
将接受多个参数的函数,转化为一系列【接受单一参数的函数】的过程
function add(x, y) {
return x + y;
}
// 柯里化
function add(x) {
return function (y) {
return x + y;
};
}
function add() {
// 取得所有参数
const args = [...arguments];
if (args.length >= 2) {
// 如果参数足够,则直接返回它们的和
return args.reduce((a, b) => a + b);
} else {
return function () {
// 参数不足,返回新函数,等待更多参数
return add(...args, ...arguments);
};
}
}
Call / Apply / Bind
用途:函数调用和上下文绑定
call - 显式指定函数的上下文(即 this 值)和参数,我们可以使用 call 方法把一个对象传递给函数,让函数在该对象的上下文中执行。
apply - 类似 call 方法,不同点是参数必须以数组的形式传递给函数,而不是逐个传入
bind - bind 方法不会立即执行函数,而是创建一个新的函数,该函数的 this 值被绑定到指定的对象。此外,我们可以在 bind 方法中指定参数,这些参数将作为绑定函数的预设参数,不会被改变。
// 绑定 this
this.x = 9
const moduleS = {
x: 81,
getX() {
return this.x
}
}
console.log(moduleS.getX())
const retrieveX = moduleS.getX
console.log(retrieveX())
const boundGetX = retrieveX.bind(moduleS)
console.log(boundGetX())
// 预设函数的参数
function add(x, y){
return x + y;
}
const add5 = add.bind(null, 5)
console.log(add5(3)) // 输出8
// 柯里化
function mutiply(a, b) {
return a * b;
}
const multiplyByTwo = multiply.bind(null, 2);
console.log(multiplyByTwo(4))
立即执行函数
声明时立即执行的函数;
(function(){
// 代码块
}){}
特点:
1、独立作用域
2、保护内部变量(对外不可见)
3、实现模块模式(返回公共接口,隐藏内部实现细节)
4、执行一些初始化操作
ES6
Symbol
值唯一且不可变,通过 Symbol 函数生成,每个 Symbol 值不存在相等性比较
主要用途
给对象定义唯一的属性名(防止属性名冲突),定义类的私有属性和方法(不可迭代)
Symbol 转换
Symbol(’foo’).toString
Symbol(’foo’).description
Set / Map / WeakMap / WeakSet
Set
集合;值的合集;集合中的元素只会出现一次(唯一性);
迭代 - 会按照插入顺序访问集合中的元素;
array 转 set:new Set(arr)
set 转 array:[...setArr]
方法
add;clear;delete;has;size;keys/values/entries;forEach;
Set 和 Array 的区别
- 无重复值
- 没有索引,不能通过下标访问
- 没有 push 和 pop 方法,但有 add 和 delete 方法;
比较
需要存储唯一值,且不关心顺序,可以使用 Set;需要有序的、可以重复的集合,并且需要频繁的执行操作(两端添加或删除),可以使用 Array;
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([3, 4, 5, 6]);
const intersection = new Set([...setA].filter((x) => setB.has(x)));
const union = new Set([...setA, ...setB]);
const difference = new Set([...setA].filter((x) => !setB.has(x)));
Map
键值对集合;集合中的每个键只能出现一次;
方法
set;get;clear;delete;has;size;keys/values/entries;forEach
Object 和 Map 的区别
- Map 的键可以是任何值,Object 只能是 String 或 Symbol
- Map 的键按照插入顺序迭代
- Map 的项目数量通过 size 属性获取,Object 需要通过 Object.key().length
- Map 是可迭代对象,用 forEach、for-of 来遍历,Object 可以使用 Object.entries 或 for-in 迭代;
- Map 的性能更好,赋值和搜索的复杂度是 O(n)
for-in 和 for-of 的区别
for-in:遍历对象的可枚举属性,可以遍历对象的键(输出属性名),遍历数组时,会遍历数组的可枚举属性,包括原型链上的属性;
for-of:遍历可迭代对象的元素,包括数组、字符串、set、map,不支持遍历 Object(不是可迭代对象)
WeakMap
弱引用(如果没有其他地方引用该对象,它可以被垃圾回收器回收)
键必须是对象,不能是原始类型的值;
特点:键是弱引用的,当键对象失去其他引用时,它可以被垃圾回收机制回收
原理
Map 在 Javascript 中的实现相当于使用了两个数组(存放键,存放值),缺点是会导致内存泄露,因为数组会一直引用每个键和值。这种引用会使垃圾回收算法不能回收处理它们。WeakMap 就是解决这个问题的,如果一个键没有在其他地方使用,它和值之间的关系就是弱引用,可以被垃圾回收掉。
WeakMap 不可遍历,也没有 size 属性,因为其中的元素可能会突然消失。
场景
WeakMap 适合临时数据、对象状态等短期存储(临时缓存)
DOM 节点,DOM 节点被删除时,元数据也会被垃圾回收
登录状态,退出登录后被垃圾回收
Map 适合持久化的、需要快速查找的数据
用户信息;键可以是另一个对象(表达关系);时间线(保持顺序)
Object:更自由的操作数据,适合频繁操作的小规模数据