前端阿里面试—— 实现一个深拷贝,面试完资料要拿回来吗

}

return result;

}else{

return target;

}

}

以上代码中,deepClone函数的参数 target 是要深拷贝的数据。

执行 target !== null && typeof target === 'object' 判断 target 是不是引用类型。

若不是,直接返回 target

若是,创建一个变量 result 作为深拷贝的结果,遍历 target,执行 deepClone(target[k]) 把 target 每个属性的值深拷贝后赋值到深拷贝的结果对应的属性 result[k] 上,遍历完毕后返回 result

在执行 deepClone(target[k]) 中,又会对 target[k] 进行类型判断,重复上述流程,形成了一个递归调用 deepClone 函数的过程。就可以层层遍历要拷贝的数据,不管要拷贝的数据有多少子属性,只要子属性的值的类型是引用类型,就会调用 deepClone 函数将其深拷贝后赋值到深拷贝的结果对应的属性上。

另外使用 for...in 循环遍历对象的属性时,其原型链上的所有属性都将被访问,如果只要只遍历对象自身的属性,而不遍历继承于原型链上的属性,要使用 hasOwnProperty 方法过滤一下。

在这里可以向面试官展示你的三个编程能力。

  • 对原始类型和引用类型数据的判断能力。

  • 对递归思维的应用的能力。

  • 深入理解for...in的用法。

黄金段位


白银段位的代码中只考虑到了引用类型的数据是对象的情况,漏了对引用类型的数据是数组的情况。

function deepClone(target){

if(target !== null && typeof target === ‘object’){

let result = Object.prototype.toString.call(target) === “[object Array]” ? [] : {};

for (let k in target){

if (target.hasOwnProperty(k)) {

result[k] = deepClone(target[k])

}

}

return result;

}else{

return target;

}

}

以上代码中,只是额外增加对参数 target 是否是数组的判断。执行 Object.prototype.toString.call(target) === "[object Array]" 判断 target 是不是数组,若是数组,变量result 为 [],若不是数组,变量result 为 {}

在这里可以向面试官展示你的两个编程能力。

  • 正确理解引用类型概念的能力。

  • 精确判断数据类型的能力。

铂金段位


假设要深拷贝以下数据 data

let data = {

a: 1

};

data.f=data

执行 deepClone(data),会发现控制台报错,错误信息如下所示。 image

这是因为递归进入死循环导致栈内存溢出了。根本原因是 data 数据存在循环引用,即对象的属性间接或直接的引用了自身。

function deepClone(target) {

function clone(target, map) {

if (target !== null && typeof target === ‘object’) {

let result = Object.prototype.toString.call(target) === “[object Array]” ? [] : {};

if (map[target]) {

return map[target];

}

map[target] = result;

for (let k in target) {

if (target.hasOwnProperty(k)) {

result[k] = deepClone(target[k])

}

}

return result;

} else {

return target;

}

}

let map = {}

const result = clone(target, map);

map = null;

return result

}

以上代码中利用额外的变量 map 来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,先去 map 中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话继续拷贝,这样就巧妙化解的循环引用的问题。最后需要把变量 map 置为 null ,释放内存,防止内存泄露。

在这里可以向面试官展示你的两个编程能力。

  • 对循环引用的理解,如何解决循环引用引起的问题的能力。

  • 对内存泄露的认识和避免泄露的能力。

砖石段位


该段位要考虑性能问题了。在上面的代码中,我们遍历数组和对象都使用了 for...in 这种方式,实际上 for...in 在遍历时效率是非常低的,故用效率比较高的 while 来遍历。

function deepClone(target) {

/**

  • 遍历数据处理函数

  • @array 要处理的数据

  • @callback 回调函数,接收两个参数 value 每一项的值 index 每一项的下标或者key。

*/

function handleWhile(array, callback) {

const length = array.length;

let index = -1;

while (++index < length) {

callback(array[index], index)

}

}

function clone(target, map) {

if (target !== null && typeof target === ‘object’) {

let result = Object.prototype.toString.call(target) === “[object Array]” ? [] : {};

if (map[target]) {

return map[target];

}

map[target] = result;

const keys = Object.prototype.toString.call(target) === “[object Array]” ? undefined : Object.keys(

target);

function callback(value, key) {

if (keys) {

// 如果keys存在则说明value是一个对象的key,不存在则说明key就是数组的下标。

key = value;

}

result[key] = clone(target[key], map)

}

handleWhile(keys || target, callback)

return result;

} else {

return target;

}

}

let map = {}

const result = clone(target, map);

map = null;

return result

}

用 while 遍历的深拷贝记为 deepClone,把用 for ... in 遍历的深拷贝记为 deepClone1。利用 console.time() 和 console.timeEnd() 来计算执行时间。

let arr = [];

for (let i = 0; i < 1000000; i++) {

arr.push(i)

}

let data = {

a: arr

};

console.time();

const result = deepClone(data);

console.timeEnd();

console.time();

const result1 = deepClone1(data);

console.timeEnd();

从上图明显可以看到用 while 遍历的深拷贝的性能远优于用 for ... in 遍历的深拷贝。

在这里可以向面试官展示你的四个编程能力。

  • 具有优化代码运行性能的能力。

  • 了解遍历的效率的能力。

  • 了解 ++i 和 i++ 的区别。

  • 代码抽象的能力。

星耀段位


在这个阶段应该考虑代码逻辑的严谨性。在上面段位的代码虽然已经满足平时开发的需求,但是还是有几处逻辑不严谨的地方。

  • 判断数据不是引用类型时就直接返回 target,但是原始类型中还有 Symbol 这一特殊类型的数据,因为其每个 Symbol 都是独一无二,需要额外拷贝处理,不能直接返回。

  • 判断数据是不是引用类型时不严谨,漏了 typeof target === function' 的判断。

  • 只考虑了 Array、Object 两种引用类型数据的处理,引用类型的数据还有Function 函数、Date 日期、RegExp 正则、Map 数据结构、Set 数据机构,其中 Map 、Set 属于 ES6 的。

废话不多说,直接贴上全部代码,代码中有注释。

function deepClone(target) {

// 获取数据类型

function getType(target) {

return Object.prototype.toString.call(target)

}

//判断数据是不是引用类型

function isObject(target) {

return target !== null && (typeof target === ‘object’ || typeof target === ‘function’);

}

//处理不需要遍历的应引用类型数据

function handleOherData(target) {

const type = getType(target);

switch (type) {

case “[object Date]”:

return new Date(target)

case “[object RegExp]”:

return cloneReg(target)

case “[object Function]”:

return cloneFunction(target)

}

}

//拷贝Symbol类型数据

function cloneSymbol(targe) {

const a = String(targe); //把Symbol字符串化

const b = a.substring(7, a.length - 1); //取出Symbol()的参数

return Symbol(b); //用原先的Symbol()的参数创建一个新的Symbol

}

//拷贝正则类型数据

function cloneReg(target) {

const reFlags = /\w*$/;

const result = new target.constructor(target.source, reFlags.exec(target));

result.lastIndex = target.lastIndex;

return result;

}

//拷贝函数

function cloneFunction(targe) {

//匹配函数体的正则

const bodyReg = /(?<={)(.|\n)+(?=})/m;

//匹配函数参数的正则

const paramReg = /(?<=().+(?=)\s+{)/;

const targeString = targe.toString();

//利用prototype来区分下箭头函数和普通函数,箭头函数是没有prototype的

if (targe.prototype) { //普通函数

const param = paramReg.exec(targeString);

const body = bodyReg.exec(targeString);

if (body) {

if (param) {

const paramArr = param[0].split(‘,’);

//使用 new Function 重新构造一个新的函数

return new Function(…paramArr, body[0]);

} else {

return new Function(body[0]);

}

} else {

return null;

}

} else { //箭头函数

//eval和函数字符串来重新生成一个箭头函数

return eval(targeString);

}

}

/**

  • 遍历数据处理函数

  • @array 要处理的数据

  • @callback 回调函数,接收两个参数 value 每一项的值 index 每一项的下标或者key。

*/

function handleWhile(array, callback) {

let index = -1;

const length = array.length;

while (++index < length) {

callback(array[index], index);

}

}

function clone(target, map) {

if (isObject(target)) {

let result = null;

if (getType(target) === “[object Array]”) {

result = []

} else if (getType(target) === “[object Object]”) {

result = {}

} else if (getType(target) === “[object Map]”) {

result = new Map();

} else if (getType(target) === “[object Set]”) {

result = new Set();

}

//解决循环引用

if (map[target]) {

return map[target];

}

map[target] = result;

if (getType(target) === “[object Map]”) {

target.forEach((value, key) => {

result.set(key, clone(value, map));

});

return result;

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
img

TCP协议

  • TCP 和 UDP 的区别?
  • TCP 三次握手的过程?
  • 为什么是三次而不是两次、四次?
  • 三次握手过程中可以携带数据么?
  • 说说 TCP 四次挥手的过程
  • 为什么是四次挥手而不是三次?
  • 半连接队列和 SYN Flood 攻击的关系
  • 如何应对 SYN Flood 攻击?
  • 介绍一下 TCP 报文头部的字段
  • TCP 快速打开的原理(TFO)
  • 说说TCP报文中时间戳的作用?
  • TCP 的超时重传时间是如何计算的?
  • TCP 的流量控制
  • TCP 的拥塞控制
  • 说说 Nagle 算法和延迟确认?
  • 如何理解 TCP 的 keep-alive?

浏览器篇
  • 浏览器缓存?
  • 说一说浏览器的本地存储?各自优劣如何?
  • 说一说从输入URL到页面呈现发生了什么?
  • 谈谈你对重绘和回流的理解
  • XSS攻击
  • CSRF攻击
  • HTTPS为什么让数据传输更安全?
  • 实现事件的防抖和节流?
  • 实现图片懒加载?

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
img

而不是两次、四次?

  • 三次握手过程中可以携带数据么?
  • 说说 TCP 四次挥手的过程
  • 为什么是四次挥手而不是三次?
  • 半连接队列和 SYN Flood 攻击的关系
  • 如何应对 SYN Flood 攻击?
  • 介绍一下 TCP 报文头部的字段
  • TCP 快速打开的原理(TFO)
  • 说说TCP报文中时间戳的作用?
  • TCP 的超时重传时间是如何计算的?
  • TCP 的流量控制
  • TCP 的拥塞控制
  • 说说 Nagle 算法和延迟确认?
  • 如何理解 TCP 的 keep-alive?

[外链图片转存中…(img-pHhME0ii-1712941201533)]

浏览器篇
  • 浏览器缓存?
  • 说一说浏览器的本地存储?各自优劣如何?
  • 说一说从输入URL到页面呈现发生了什么?
  • 谈谈你对重绘和回流的理解
  • XSS攻击
  • CSRF攻击
  • HTTPS为什么让数据传输更安全?
  • 实现事件的防抖和节流?
  • 实现图片懒加载?

[外链图片转存中…(img-5fkhySwG-1712941201533)]

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-F96JuGHE-1712941201534)]

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值