目录
前言
往期回顾:
JS篇
1.深拷贝和浅拷贝的区别
浅拷贝:只拷贝对象的第一层属性
- 如果属性是基本类型(number、string),拷贝的是值
- 如果属性是引用类型,拷贝的是内存地址(指针)
由此可见,浅拷贝一个基本类型数据时,不会产什么什么问题。但是拷贝一个对象类型数据时,拷贝得到的仅仅是对象类型数据的一个引用,所以对该引用的修改也会影响到原对象。
浅拷贝常见方式:
- 展开运算符(...)
- 数组方法slice()、concat()
javascript
let obj1 = {
a: 1,
b: {
c: 2
}
}
let obj2 = { ...obj1 };
obj2.a = 999; // 修改第一层属性,原对象不被影响
obj2.b.c = 111; // 修改第二层属性,原对象被影响
console.log(obj1);
console.log(obj2);
// { a: 1, b: { c: 111 } }
// { a: 999, b: { c: 111 } }
深拷贝:递归拷贝素所有层级的属性,新对象和旧对象是两个完全独立的个体
常见方式:
- JSON:JSON.parse(JSON.stringify(obj)),但是会丢失undefined、Function、Symbol属性;Date对象会被拷贝成String,不再是Date对象;RegExp对象会被转换成空对象;NaN/Infinity会被转换成null
- 递归函数
- 第三方库函数:lodash.cloneDeep()
当时用JSON方法时,会丢失undefined、Function、Symbol类型的数据:
javascript
let obj1 = {
a: 1,
b: {
c: 2
},
d: undefined,
e: function() {},
f: Symbol("f")
}
let obj2 = JSON.parse(JSON.stringify(obj1));
for (let key in obj1) {
console.log("obj1[" + key + "] = ", obj1[key]);
}
for (let key in obj2) {
console.log("obj2[" + key + "] = ", obj2[key]);
}
// 输出:
//obj1[a] = 1
// obj1[b] = { c: 2 }
// obj1[d] = undefined
// obj1[e] = [Function: e]
// obj1[f] = Symbol(f)
// obj2[a] = 1
// obj2[b] = { c: 2 }
一个推荐的深拷贝递归函数写法:
javascript
function deepClone(target, map = new WeakMap()) {
// 1.基本类型和null直接返回就行
if (typeof target !== 'object' || target === null) {
return target;
}
// 2.Date和RegExp需要重建
if (target instanceof Date) {
return new Date(target);
}
if (target instanceof RegExp) {
return new RegExp(target);
}
// 3.循环引用处理
if (map.has(target)) {
return map.get(target);
}
// 4/创建新容器
const newObj = new target.constructor();
// 5.将本次递归的属性存入Map
map.set(target, newObj);
for (const key in target) {
if (target.hasOwnProperty(key)) {
newObj[key] = deepClone(target[key], map);
}
}
return newObj;
}
2.call、apply、bind区别
| 特性 | call | apply | bind |
|---|---|---|---|
| 传参方式 | 参数列表(arr1,arr2,arr3) | 一个数组或类数组 | 参数列表(arr1,arr2,arr3) |
| 执行时机 | 立即执行 | 立即执行 | 不执行,返回新函数 |
| 作用 | 改变this指向 | 改变this指向 | 改变this指向,柯里化 |
一个示例代码:
javascript
const person = {
name: '张三'
}
function say(age, hobby) {
console.log(`我叫${this.name}, 我今年${age}岁, 我的爱好是${hobby}`);
}
// call方法
say.call(person, 18, '打篮球');
// apply方法
say.apply(person, [18, '打篮球']);
// bind方法
const sayBound = say.bind(person, 18);
sayBound('打篮球'); // 补齐剩余参数调用
3.什么是"类数组"?如何转为真数组?
类数组也是一个普通对象,它有数值索引 ,有length属性
但是类数组不能调用数组方法(push、map、forEach)
常见的类数组:
- arguments对象
- DOM返回的NodeList(使用document.querySelectorAll('div'))
转换方法:
- 使用Array.from方法
- 使用展开运算符(...),arguments和NodeList可以用,但是自定义对象不能使用
- Array.prototype.slice.call(),借用数组的slice方法,强行切割出一个新数组,这是ES5老方法
Vue篇
1.vue3响应式原理
这里简单说一vue3响应式原理,后面会单独开一篇文章来写vue3响应式原理
vue3的响应式总结为一句话:"拦截 -> 追踪 -> 触发"三大步骤:
首先是拦截,拦截指的是拦截器,它有两种分别是:"reactive "和"ref"
reactive 基于proxy实现,它只能用来包装对象,因为proxy只能包装对象,一个reactive伪代码:
ref 基于proxy 和get/set(Object.defineProperty)实现,它可以用来包装任何数据类型
追踪用于读取值时收集依赖,主要保存谁使用了这个值
触发用于修改值时派发更新,将更新同步到之前追踪时保存的依赖处
2.组合式API解决了什么问题
组合式API将同一个功能的代码写在一起,更加容易阅读。同时利用组合式API构建Hooks可以更方便的复用逻辑。
3.Reflect是做什么的?为什么要配合Proxy
Reflect是一个内置对象,它提供了一套与Proxy的handler相对应的方法(Reflect.get、Reflect.set、Reflect.has等),它的作用是执行对象的默认行为且可以被proxy拦截
使用Reflect的原因在于this的指向:
看下面一段示例代码:
javascript
const target = {
firstName: "张三",
get fullName() {
console.log("👉正在执行 getter,读取 firstName");
return this.firstName + "先生";
}
};
const proxy = new Proxy(target, {
get(target, key, receiver) {
console.log(`📡 [Proxy拦截] 拦截到了: ${key}`);
// 错误!直接返回原对象的属性
return target[key];
}
});
console.log("--- 开始测试 ---");
proxy.fullName;
结果:

可以看到,fullName里面获取this.firstName时没有被proxy拦截到,此时就需要使用Reflect
javascript
const target = {
firstName: "张三",
get fullName() {
console.log("👉正在执行 getter,读取 firstName");
return this.firstName + "先生";
}
};
const proxy = new Proxy(target, {
get(target, key, receiver) {
console.log(`📡 [Proxy拦截] 拦截到了: ${key}`);
return Reflect.get(target, key, receiver);
}
});
console.log("--- 开始测试 ---");
proxy.fullName;
结果:

总结:Relfect用来使Proxy的拦截不会被this指针绕过。当使用Proxy时,直接无脑搭配Reflect返回数据即可
代码篇
1.写一个深拷贝递归函数
javascript
function deepClone(obj, map = new WeakMap()) {
if (typeof obj !== 'object' || obj === null) {
return obj;
}
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (map.has(obj)) {
return map.get(obj);
}
const newObj = new obj.constructor();
map.set(obj, newObj);
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = deepClone(obj[key], map);
}
}
return newObj;
}
2.手写bind函数
javascript
function myBind(context, ...args) {
const fn = this;
return function(...newArgs) {
return fn.apply(context, [...args, ...newArgs]);
}
}