}
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)
,会发现控制台报错,错误信息如下所示。
这是因为递归进入死循环导致栈内存溢出了。根本原因是 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前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
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行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
而不是两次、四次?
- 三次握手过程中可以携带数据么?
- 说说 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)]