前端面试基础知识整理【Day-2】

目录

前言

JS篇

1.深拷贝和浅拷贝的区别

2.call、apply、bind区别

3.什么是"类数组"?如何转为真数组?

Vue篇

1.vue3响应式原理

2.组合式API解决了什么问题

3.Reflect是做什么的?为什么要配合Proxy

代码篇

1.写一个深拷贝递归函数

2.手写bind函数


前言

往期回顾:

前端面试基础知识整理【Day-1】-CSDN博客

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'))

转换方法:

  1. 使用Array.from方法
  2. 使用展开运算符(...),arguments和NodeList可以用,但是自定义对象不能使用
  3. Array.prototype.slice.call(),借用数组的slice方法,强行切割出一个新数组,这是ES5老方法

Vue篇

1.vue3响应式原理

这里简单说一vue3响应式原理,后面会单独开一篇文章来写vue3响应式原理

vue3的响应式总结为一句话:"拦截 -> 追踪 -> 触发"三大步骤:

首先是拦截,拦截指的是拦截器,它有两种分别是:"reactive "和"ref"

reactive 基于proxy实现,它只能用来包装对象,因为proxy只能包装对象,一个reactive伪代码:

ref 基于proxyget/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]);
    }
}
相关推荐
mCell4 小时前
如何零成本搭建个人站点
前端·程序员·github
mCell5 小时前
为什么 Memo Code 先做 CLI:以及终端输入框到底有多难搞
前端·设计模式·agent
恋猫de小郭5 小时前
AI 在提高你工作效率的同时,也一直在增加你的疲惫和焦虑
前端·人工智能·ai编程
少云清5 小时前
【安全测试】2_客户端脚本安全测试 _XSS和CSRF
前端·xss·csrf
银烛木5 小时前
黑马程序员前端h5+css3
前端·css·css3
m0_607076605 小时前
CSS3 转换,快手前端面试经验,隔壁都馋哭了
前端·面试·css3
听海边涛声6 小时前
CSS3 图片模糊处理
前端·css·css3
IT、木易6 小时前
css3 backdrop-filter 在移动端 Safari 上导致渲染性能急剧下降的优化方案有哪些?
前端·css3·safari
0思必得06 小时前
[Web自动化] Selenium无头模式
前端·爬虫·selenium·自动化·web自动化
anOnion6 小时前
构建无障碍组件之Dialog Pattern
前端·html·交互设计