程度垂直居中的实现
- 利用相对定位,先将元素的左上角通过top:50%和left:50%定位到页面的核心,而后再通过translate来调整元素的中心点到页面的核心。该办法须要思考浏览器兼容问题。
.parent { position: relative;} .child { position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%);}
- 利用相对定位,设置四个方向的值都为0,并将margin设置为auto,因为宽高固定,因而对应方向实现平分,能够实现程度和垂直方向上的居中。该办法实用于盒子有宽高的状况:
.parent { position: relative;}.child { position: absolute; top: 0; bottom: 0; left: 0; right: 0; margin: auto;}
- 利用相对定位,先将元素的左上角通过top:50%和left:50%定位到页面的核心,而后再通过margin负值来调整元素的中心点到页面的核心。该办法实用于盒子宽高已知的状况
.parent { position: relative;}.child { position: absolute; top: 50%; left: 50%; margin-top: -50px; /* 本身 height 的一半 */ margin-left: -50px; /* 本身 width 的一半 */}
- 应用flex布局,通过align-items:center和justify-content:center设置容器的垂直和程度方向上为居中对齐,而后它的子元素也能够实现垂直和程度的居中。该办法要思考兼容的问题,该办法在挪动端用的较多:
.parent { display: flex; justify-content:center; align-items:center;}
寄生组合继承
题目形容:实现一个你认为不错的 js 继承形式
实现代码如下:
function Parent(name) { this.name = name; this.say = () => { console.log(111); };}Parent.prototype.play = () => { console.log(222);};function Children(name) { Parent.call(this); this.name = name;}Children.prototype = Object.create(Parent.prototype);Children.prototype.constructor = Children;// let child = new Children("111");// // console.log(child.name);// // child.say();// // child.play();
代码输入后果
Promise.resolve('1') .then(res => { console.log(res) }) .finally(() => { console.log('finally') })Promise.resolve('2') .finally(() => { console.log('finally2') return '我是finally2返回的值' }) .then(res => { console.log('finally2前面的then函数', res) })
输入后果如下:
1finally2finallyfinally2前面的then函数 2
.finally()
个别用的很少,只有记住以下几点就能够了:
.finally()
办法不论Promise对象最初的状态如何都会执行.finally()
办法的回调函数不承受任何的参数,也就是说你在.finally()
函数中是无奈晓得Promise最终的状态是resolved
还是rejected
的- 它最终返回的默认会是一个上一次的Promise对象值,不过如果抛出的是一个异样则返回异样的Promise对象。
- finally实质上是then办法的特例
.finally()
的谬误捕捉:
Promise.resolve('1') .finally(() => { console.log('finally1') throw new Error('我是finally中抛出的异样') }) .then(res => { console.log('finally前面的then函数', res) }) .catch(err => { console.log('捕捉谬误', err) })
输入后果为:
'finally1''捕捉谬误' Error: 我是finally中抛出的异样
过程与线程的概念
从实质上说,过程和线程都是 CPU 工作工夫片的一个形容:
- 过程形容了 CPU 在运行指令及加载和保留上下文所需的工夫,放在利用上来说就代表了一个程序。
- 线程是过程中的更小单位,形容了执行一段指令所需的工夫。
过程是资源分配的最小单位,线程是CPU调度的最小单位。
一个过程就是一个程序的运行实例。具体解释就是,启动一个程序的时候,操作系统会为该程序创立一块内存,用来寄存代码、运行中的数据和一个执行工作的主线程,咱们把这样的一个运行环境叫过程。过程是运行在虚拟内存上的,虚拟内存是用来解决用户对硬件资源的有限需要和无限的硬件资源之间的矛盾的。从操作系统角度来看,虚拟内存即交换文件;从处理器角度看,虚拟内存即虚拟地址空间。
如果程序很多时,内存可能会不够,操作系统为每个过程提供一套独立的虚拟地址空间,从而使得同一块物理内存在不同的过程中能够对应到不同或雷同的虚拟地址,变相的减少了程序能够应用的内存。
过程和线程之间的关系有以下四个特点:
(1)过程中的任意一线程执行出错,都会导致整个过程的解体。
(2)线程之间共享过程中的数据。
(3)当一个过程敞开之后,操作系统会回收过程所占用的内存, 当一个过程退出时,操作系统会回收该过程所申请的所有资源;即便其中任意线程因为操作不当导致内存透露,当过程退出时,这些内存也会被正确回收。
(4)过程之间的内容互相隔离。 过程隔离就是为了使操作系统中的过程互不烦扰,每一个过程只能拜访本人占有的数据,也就避免出现过程 A 写入数据到过程 B 的状况。正是因为过程之间的数据是严格隔离的,所以一个过程如果解体了,或者挂起了,是不会影响到其余过程的。如果过程之间须要进行数据的通信,这时候,就须要应用用于过程间通信的机制了。
Chrome浏览器的架构图: 从图中能够看出,最新的 Chrome 浏览器包含:
- 1 个浏览器主过程
- 1 个 GPU 过程
- 1 个网络过程
- 多个渲染过程
- 多个插件过程
这些过程的性能:
- 浏览器过程:次要负责界面显示、用户交互、子过程治理,同时提供存储等性能。
- 渲染过程:外围工作是将 HTML、CSS 和 JavaScript 转换为用户能够与之交互的网页,排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该过程中,默认状况下,Chrome 会为每个 Tab 标签创立一个渲染过程。出于平安思考,渲染过程都是运行在沙箱模式下。
- GPU 过程:其实, GPU 的应用初衷是为了实现 3D CSS 的成果,只是随后网页、Chrome 的 UI 界面都抉择采纳 GPU 来绘制,这使得 GPU 成为浏览器广泛的需要。最初,Chrome 在其多过程架构上也引入了 GPU 过程。
- 网络过程:次要负责页面的网络资源加载,之前是作为一个模块运行在浏览器过程外面的,直至最近才独立进去,成为一个独自的过程。
- 插件过程:次要是负责插件的运行,因插件易解体,所以须要通过插件过程来隔离,以保障插件过程解体不会对浏览器和页面造成影响。
所以,关上一个网页,起码须要四个过程:1 个网络过程、1 个浏览器过程、1 个 GPU 过程以及 1 个渲染过程。如果关上的页面有运行插件的话,还须要再加上 1 个插件过程。
尽管多过程模型晋升了浏览器的稳定性、流畅性和安全性,但同样不可避免地带来了一些问题:
- 更高的资源占用:因为每个过程都会蕴含公共根底构造的正本(如 JavaScript 运行环境),这就意味着浏览器会耗费更多的内存资源。
- 更简单的体系架构:浏览器各模块之间耦合性高、扩展性差等问题,会导致当初的架构曾经很难适应新的需要了。
事件是什么?事件模型?
事件是用户操作网页时产生的交互动作,比方 click/move, 事件除了用户触发的动作外,还能够是文档加载,窗口滚动和大小调整。事件被封装成一个 event 对象,蕴含了该事件产生时的所有相干信息( event 的属性)以及能够对事件进行的操作( event 的办法)。
事件是用户操作网页时产生的交互动作或者网页自身的一些操作,古代浏览器一共有三种事件模型:
- DOM0 级事件模型,这种模型不会流传,所以没有事件流的概念,然而当初有的浏览器反对以冒泡的形式实现,它能够在网页中间接定义监听函数,也能够通过 js 属性来指定监听函数。所有浏览器都兼容这种形式。间接在dom对象上注册事件名称,就是DOM0写法。
- IE 事件模型,在该事件模型中,一次事件共有两个过程,事件处理阶段和事件冒泡阶段。事件处理阶段会首先执行指标元素绑定的监听事件。而后是事件冒泡阶段,冒泡指的是事件从指标元素冒泡到 document,顺次查看通过的节点是否绑定了事件监听函数,如果有则执行。这种模型通过attachEvent 来增加监听函数,能够增加多个监听函数,会按程序顺次执行。
- DOM2 级事件模型,在该事件模型中,一次事件共有三个过程,第一个过程是事件捕捉阶段。捕捉指的是事件从 document 始终向下流传到指标元素,顺次查看通过的节点是否绑定了事件监听函数,如果有则执行。前面两个阶段和 IE 事件模型的两个阶段雷同。这种事件模型,事件绑定的函数是addEventListener,其中第三个参数能够指定事件是否在捕捉阶段执行。
手写题:数组扁平化
function flatten(arr) { let result = []; for (let i = 0; i < arr.length; i++) { if (Array.isArray(arr[i])) { result = result.concat(flatten(arr[i])); } else { result = result.concat(arr[i]); } } return result;}const a = [1, [2, [3, 4]]];console.log(flatten(a));
参考 前端进阶面试题具体解答
事件是如何实现的?
基于公布订阅模式,就是在浏览器加载的时候会读取事件相干的代码,然而只有理论等到具体的事件触发的时候才会执行。
比方点击按钮,这是个事件(Event),而负责处理事件的代码段通常被称为事件处理程序(Event Handler),也就是「启动对话框的显示」这个动作。
在 Web 端,咱们常见的就是 DOM 事件:
- DOM0 级事件,间接在 html 元素上绑定 on-event,比方 onclick,勾销的话,dom.onclick = null,同一个事件只能有一个处理程序,前面的会笼罩后面的。
- DOM2 级事件,通过 addEventListener 注册事件,通过 removeEventListener 来删除事件,一个事件能够有多个事件处理程序,按程序执行,捕捉事件和冒泡事件
- DOM3级事件,减少了事件类型,比方 UI 事件,焦点事件,鼠标事件
代码输入后果
var length = 10;function fn() { console.log(this.length);}var obj = { length: 5, method: function(fn) { fn(); arguments[0](); }};obj.method(fn, 1);
输入后果: 10 2
解析:
- 第一次执行fn(),this指向window对象,输入10。
- 第二次执行arguments[0],相当于arguments调用办法,this指向arguments,而这里传了两个参数,故输入arguments长度为2。
localStorage sessionStorage cookies 有什么区别?
localStorage:以键值对的形式存储 贮存工夫没有限度 永恒失效 除非本人删除记录sessionStorage:当页面敞开后被清理与其余相比不能同源窗口共享 是会话级别的存储形式cookies 数据不能超过4k 同时因为每次http申请都会携带cookie 所有cookie只适宜保留很小的数据 如会话标识
代码输入后果
var a = 10; var obt = { a: 20, fn: function(){ var a = 30; console.log(this.a) } } obt.fn(); // 20 obt.fn.call(); // 10 (obt.fn)(); // 20
输入后果: 20 10 20
解析:
- obt.fn(),fn是由obt调用的,所以其this指向obt对象,会打印出20;
- obt.fn.call(),这里call的参数啥都没写,就示意null,咱们晓得如果call的参数为undefined或null,那么this就会指向全局对象this,所以会打印出 10;
- (obt.fn)(), 这里给表达式加了括号,而括号的作用是扭转表达式的运算程序,而在这里加与不加括号并无影响;相当于 obt.fn(),所以会打印出 20;
事件委托的应用场景
场景:给页面的所有的a标签增加click事件,代码如下:
document.addEventListener("click", function(e) { if (e.target.nodeName == "A") console.log("a");}, false);
然而这些a标签可能蕴含一些像span、img等元素,如果点击到了这些a标签中的元素,就不会触发click事件,因为事件绑定上在a标签元素上,而触发这些外部的元素时,e.target指向的是触发click事件的元素(span、img等其余元素)。
这种状况下就能够应用事件委托来解决,将事件绑定在a标签的外部元素上,当点击它的时候,就会逐级向上查找,晓得找到a标签为止,代码如下:
document.addEventListener("click", function(e) { var node = e.target; while (node.parentNode.nodeName != "BODY") { if (node.nodeName == "A") { console.log("a"); break; } node = node.parentNode; }}, false);
如何提⾼webpack的构建速度?
- 多⼊⼝状况下,使⽤ CommonsChunkPlugin 来提取公共代码
- 通过 externals 配置来提取常⽤库
- 利⽤ DllPlugin 和 DllReferencePlugin 预编译资源模块 通过 DllPlugin 来对那些咱们引⽤然而相对不会批改的npm包来进⾏预编译,再通过 DllReferencePlugin 将预编译的模块加载进来。
- 使⽤ Happypack 实现多线程减速编译
- 使⽤ webpack-uglify-parallel 来晋升 uglifyPlugin 的压缩速度。 原理上 webpack-uglify-parallel 采⽤了多核并⾏压缩来晋升压缩速度
- 使⽤ Tree-shaking 和 Scope Hoisting 来剔除多余代码
PWA应用过吗?serviceWorker的应用原理是啥?
渐进式网络应用(PWA)
是谷歌在2015年底提出的概念。基本上算是web应用程序,但在外观和感觉上与原生app
相似。反对PWA
的网站能够提供脱机工作、推送告诉和设施硬件拜访等性能。
Service Worker
是浏览器在后盾独立于网页运行的脚本,它关上了通向不须要网页或用户交互的性能的大门。 当初,它们已包含如推送告诉和后盾同步等性能。 未来,Service Worker
将会反对如定期同步或天文围栏等其余性能。 本教程探讨的外围性能是拦挡和解决网络申请,包含通过程序来治理缓存中的响应。
冒泡排序--工夫复杂度 n^2
题目形容:实现一个冒泡排序
实现代码如下:
function bubbleSort(arr) { // 缓存数组长度 const len = arr.length; // 外层循环用于管制从头到尾的比拟+替换到底有多少轮 for (let i = 0; i < len; i++) { // 内层循环用于实现每一轮遍历过程中的反复比拟+替换 for (let j = 0; j < len - 1; j++) { // 若相邻元素后面的数比前面的大 if (arr[j] > arr[j + 1]) { // 替换两者 [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]; } } } // 返回数组 return arr;}// console.log(bubbleSort([3, 6, 2, 4, 1]));
Promise 以及相干办法的实现
题目形容:手写 Promise 以及 Promise.all Promise.race 的实现
实现代码如下:
class Mypromise { constructor(fn) { // 示意状态 this.state = "pending"; // 示意then注册的胜利函数 this.successFun = []; // 示意then注册的失败函数 this.failFun = []; let resolve = (val) => { // 放弃状态扭转不可变(resolve和reject只准触发一种) if (this.state !== "pending") return; // 胜利触发机会 扭转状态 同时执行在then注册的回调事件 this.state = "success"; // 为了保障then事件先注册(次要是思考在promise外面写同步代码) promise标准 这里为模仿异步 setTimeout(() => { // 执行以后事件外面所有的注册函数 this.successFun.forEach((item) => item.call(this, val)); }); }; let reject = (err) => { if (this.state !== "pending") return; // 失败触发机会 扭转状态 同时执行在then注册的回调事件 this.state = "fail"; // 为了保障then事件先注册(次要是思考在promise外面写同步代码) promise标准 这里模仿异步 setTimeout(() => { this.failFun.forEach((item) => item.call(this, err)); }); }; // 调用函数 try { fn(resolve, reject); } catch (error) { reject(error); } } // 实例办法 then then(resolveCallback, rejectCallback) { // 判断回调是否是函数 resolveCallback = typeof resolveCallback !== "function" ? (v) => v : resolveCallback; rejectCallback = typeof rejectCallback !== "function" ? (err) => { throw err; } : rejectCallback; // 为了放弃链式调用 持续返回promise return new Mypromise((resolve, reject) => { // 将回调注册到successFun事件汇合外面去 this.successFun.push((val) => { try { // 执行回调函数 let x = resolveCallback(val); //(最难的一点) // 如果回调函数后果是一般值 那么就resolve进来给下一个then链式调用 如果是一个promise对象(代表又是一个异步) 那么调用x的then办法 将resolve和reject传进去 等到x外部的异步 执行结束的时候(状态实现)就会主动执行传入的resolve 这样就管制了链式调用的程序 x instanceof Mypromise ? x.then(resolve, reject) : resolve(x); } catch (error) { reject(error); } }); this.failFun.push((val) => { try { // 执行回调函数 let x = rejectCallback(val); x instanceof Mypromise ? x.then(resolve, reject) : reject(x); } catch (error) { reject(error); } }); }); } //静态方法 static all(promiseArr) { let result = []; //申明一个计数器 每一个promise返回就加一 let count = 0; return new Mypromise((resolve, reject) => { for (let i = 0; i < promiseArr.length; i++) { //这里用 Promise.resolve包装一下 避免不是Promise类型传进来 Promise.resolve(promiseArr[i]).then( (res) => { //这里不能间接push数组 因为要管制程序一一对应(感激评论区斧正) result[i] = res; count++; //只有全副的promise执行胜利之后才resolve进来 if (count === promiseArr.length) { resolve(result); } }, (err) => { reject(err); } ); } }); } //静态方法 static race(promiseArr) { return new Mypromise((resolve, reject) => { for (let i = 0; i < promiseArr.length; i++) { Promise.resolve(promiseArr[i]).then( (res) => { //promise数组只有有任何一个promise 状态变更 就能够返回 resolve(res); }, (err) => { reject(err); } ); } }); }}// 应用// let promise1 = new Mypromise((resolve, reject) => {// setTimeout(() => {// resolve(123);// }, 2000);// });// let promise2 = new Mypromise((resolve, reject) => {// setTimeout(() => {// resolve(1234);// }, 1000);// });// Mypromise.all([promise1,promise2]).then(res=>{// console.log(res);// })// Mypromise.race([promise1, promise2]).then(res => {// console.log(res);// });// promise1// .then(// res => {// console.log(res); //过两秒输入123// return new Mypromise((resolve, reject) => {// setTimeout(() => {// resolve("success");// }, 1000);// });// },// err => {// console.log(err);// }// )// .then(// res => {// console.log(res); //再过一秒输入success// },// err => {// console.log(err);// }// );
扩大思考:如何勾销 promise
Promise.race()办法能够用来竞争 Promise
能够借助这个个性 本人包装一个 空的 Promise 与要发动的 Promise 来实现
function wrap(pro) { let obj = {}; // 结构一个新的promise用来竞争 let p1 = new Promise((resolve, reject) => { obj.resolve = resolve; obj.reject = reject; }); obj.promise = Promise.race([p1, pro]); return obj;}let testPro = new Promise((resolve, reject) => { setTimeout(() => { resolve(123); }, 1000);});let wrapPro = wrap(testPro);wrapPro.promise.then((res) => { console.log(res);});wrapPro.resolve("被拦挡了");
为什么 0.1 + 0.2 != 0.3,请详述理由
因为 JS 采纳 IEEE 754 双精度版本(64位),并且只有采纳 IEEE 754 的语言都有该问题。
咱们都晓得计算机示意十进制是采纳二进制示意的,所以 0.1
在二进制示意为
// (0011) 示意循环0.1 = 2^-4 * 1.10011(0011)
那么如何失去这个二进制的呢,咱们能够来演算下
小数算二进制和整数不同。乘法计算时,只计算小数位,整数位用作每一位的二进制,并且失去的第一位为最高位。所以咱们得出 0.1 = 2^-4 * 1.10011(0011)
,那么 0.2
的演算也根本如上所示,只须要去掉第一步乘法,所以得出 0.2 = 2^-3 * 1.10011(0011)
。
回来持续说 IEEE 754 双精度。六十四位中符号位占一位,整数位占十一位,其余五十二位都为小数位。因为 0.1
和 0.2
都是有限循环的二进制了,所以在小数位开端处须要判断是否进位(就和十进制的四舍五入一样)。
所以 2^-4 * 1.10011...001
进位后就变成了 2^-4 * 1.10011(0011 * 12次)010
。那么把这两个二进制加起来会得出 2^-2 * 1.0011(0011 * 11次)0100
, 这个值算成十进制就是 0.30000000000000004
上面说一下原生解决办法,如下代码所示
parseFloat((0.1 + 0.2).toFixed(10))
首屏和白屏工夫如何计算
首屏工夫的计算,能够由 Native WebView 提供的相似 onload 的办法实现,在 ios 下对应的是 webViewDidFinishLoad,在 android 下对应的是onPageFinished事件。
白屏的定义有多种。能够认为“没有任何内容”是白屏,能够认为“网络或服务异样”是白屏,能够认为“数据加载中”是白屏,能够认为“图片加载不进去”是白屏。场景不同,白屏的计算形式就不雷同。
办法1:当页面的元素数小于x时,则认为页面白屏。比方“没有任何内容”,能够获取页面的DOM节点数,判断DOM节点数少于某个阈值X,则认为白屏。 办法2:当页面呈现业务定义的错误码时,则认为是白屏。比方“网络或服务异样”。 办法3:当页面呈现业务定义的特征值时,则认为是白屏。比方“数据加载中”。
继承
原型链继承
function Animal() { this.colors = ['black', 'white']}Animal.prototype.getColor = function() { return this.colors}function Dog() {}Dog.prototype = new Animal()let dog1 = new Dog()dog1.colors.push('brown')let dog2 = new Dog()console.log(dog2.colors) // ['black', 'white', 'brown']
原型链继承存在的问题:
- 问题1:原型中蕴含的援用类型属性将被所有实例共享;
- 问题2:子类在实例化的时候不能给父类构造函数传参;
借用构造函数实现继承
function Animal(name) { this.name = name this.getName = function() { return this.name }}function Dog(name) { Animal.call(this, name)}Dog.prototype = new Animal()
借用构造函数实现继承解决了原型链继承的 2 个问题:援用类型共享问题以及传参问题。然而因为办法必须定义在构造函数中,所以会导致每次创立子类实例都会创立一遍办法。
组合继承
组合继承联合了原型链和盗用构造函数,将两者的长处集中了起来。根本的思路是应用原型链继承原型上的属性和办法,而通过盗用构造函数继承实例属性。这样既能够把办法定义在原型上以实现重用,又能够让每个实例都有本人的属性。
function Animal(name) { this.name = name this.colors = ['black', 'white']}Animal.prototype.getName = function() { return this.name}function Dog(name, age) { Animal.call(this, name) this.age = age}Dog.prototype = new Animal()Dog.prototype.constructor = Doglet dog1 = new Dog('奶昔', 2)dog1.colors.push('brown')let dog2 = new Dog('哈赤', 1)console.log(dog2) // { name: "哈赤", colors: ["black", "white"], age: 1 }
寄生式组合继承
组合继承曾经绝对欠缺了,但还是存在问题,它的问题就是调用了 2 次父类构造函数,第一次是在 new Animal(),第二次是在 Animal.call() 这里。
所以解决方案就是不间接调用父类构造函数给子类原型赋值,而是通过创立空函数 F 获取父类原型的正本。
寄生式组合继承写法上和组合继承根本相似,区别是如下这里:
- Dog.prototype = new Animal()- Dog.prototype.constructor = Dog+ function F() {}+ F.prototype = Animal.prototype+ let f = new F()+ f.constructor = Dog+ Dog.prototype = f
略微封装下下面增加的代码后:
function object(o) { function F() {} F.prototype = o return new F()}function inheritPrototype(child, parent) { let prototype = object(parent.prototype) prototype.constructor = child child.prototype = prototype}inheritPrototype(Dog, Animal)
如果你厌弃下面的代码太多了,还能够基于组合继承的代码改成最简略的寄生式组合继承:
- Dog.prototype = new Animal()- Dog.prototype.constructor = Dog+ Dog.prototype = Object.create(Animal.prototype)+ Dog.prototype.constructor = Dog
class 实现继承
class Animal { constructor(name) { this.name = name } getName() { return this.name }}class Dog extends Animal { constructor(name, age) { super(name) this.age = age }}
原函数形参不定长(此时 fn.length
为0)
function curry(fn) { // 保留参数,除去第一个函数参数 let args = [].slice.call(arguments, 1); // 返回一个新函数 let curried = function () { // 新函数调用时会持续传参 let allArgs = [...args, ...arguments]; return curry(fn, ...allArgs); }; // 利用toString隐式转换的个性,当最初执行函数时,会隐式转换 curried.toString = function () { return fn(...args); }; return curried;}// 测试function add(...args) { return args.reduce((pre, cur) => pre + cur, 0);}console.log(add(1, 2, 3, 4));let addCurry = curry(add);console.log(addCurry(1)(2)(3) == 6); // trueconsole.log(addCurry(1, 2, 3)(4) == 10); // trueconsole.log(addCurry(2, 6)(1).toString()); // 9console.log(addCurry(2, 6)(1, 8)); // 打印 curried 函数
深浅拷贝
浅拷贝:只思考对象类型。
function shallowCopy(obj) { if (typeof obj !== 'object') return let newObj = obj instanceof Array ? [] : {} for (let key in obj) { if (obj.hasOwnProperty(key)) { newObj[key] = obj[key] } } return newObj}
简略版深拷贝:只思考一般对象属性,不思考内置对象和函数。
function deepClone(obj) { if (typeof obj !== 'object') return; var newObj = obj instanceof Array ? [] : {}; for (var key in obj) { if (obj.hasOwnProperty(key)) { newObj[key] = typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key]; } } return newObj;}
简单版深克隆:基于简略版的根底上,还思考了内置对象比方 Date、RegExp 等对象和函数以及解决了循环援用的问题。
const isObject = (target) => (typeof target === "object" || typeof target === "function") && target !== null;function deepClone(target, map = new WeakMap()) { if (map.get(target)) { return target; } // 获取以后值的构造函数:获取它的类型 let constructor = target.constructor; // 检测以后对象target是否与正则、日期格局对象匹配 if (/^(RegExp|Date)$/i.test(constructor.name)) { // 创立一个新的非凡对象(正则类/日期类)的实例 return new constructor(target); } if (isObject(target)) { map.set(target, true); // 为循环援用的对象做标记 const cloneTarget = Array.isArray(target) ? [] : {}; for (let prop in target) { if (target.hasOwnProperty(prop)) { cloneTarget[prop] = deepClone(target[prop], map); } } return cloneTarget; } else { return target; }}