TCP的三次握手和四次挥手

(1)三次握手

三次握手(Three-way Handshake)其实就是指建设一个TCP连贯时,须要客户端和服务器总共发送3个包。进行三次握手的次要作用就是为了确认单方的接管能力和发送能力是否失常、指定本人的初始化序列号为前面的可靠性传送做筹备。本质上其实就是连贯服务器指定端口,建设TCP连贯,并同步连贯单方的序列号和确认号,替换TCP窗口大小信息。

刚开始客户端处于 Closed 的状态,服务端处于 Listen 状态。

  • 第一次握手:客户端给服务端发一个 SYN 报文,并指明客户端的初始化序列号 ISN,此时客户端处于 SYN_SEND 状态。
首部的同步位SYN=1,初始序号seq=x,SYN=1的报文段不能携带数据,但要消耗掉一个序号。
  • 第二次握手:服务器收到客户端的 SYN 报文之后,会以本人的 SYN 报文作为应答,并且也是指定了本人的初始化序列号 ISN。同时会把客户端的 ISN + 1 作为ACK 的值,示意本人曾经收到了客户端的 SYN,此时服务器处于 SYN_REVD 的状态。
在确认报文段中SYN=1,ACK=1,确认号ack=x+1,初始序号seq=y
  • 第三次握手:客户端收到 SYN 报文之后,会发送一个 ACK 报文,当然,也是一样把服务器的 ISN + 1 作为 ACK 的值,示意曾经收到了服务端的 SYN 报文,此时客户端处于 ESTABLISHED 状态。服务器收到 ACK 报文之后,也处于 ESTABLISHED 状态,此时,单方已建设起了连贯。
确认报文段ACK=1,确认号ack=y+1,序号seq=x+1(初始为seq=x,第二个报文段所以要+1),ACK报文段能够携带数据,不携带数据则不耗费序号。

那为什么要三次握手呢?两次不行吗?

  • 为了确认单方的接管能力和发送能力都失常
  • 如果是用两次握手,则会呈现上面这种状况:
如客户端收回连贯申请,但因连贯申请报文失落而未收到确认,于是客户端再重传一次连贯申请。起初收到了确认,建设了连贯。数据传输结束后,就开释了连贯,客户端共收回了两个连贯申请报文段,其中第一个失落,第二个达到了服务端,然而第一个失落的报文段只是在某些网络结点长时间滞留了,延误到连贯开释当前的某个工夫才达到服务端,此时服务端误认为客户端又收回一次新的连贯申请,于是就向客户端收回确认报文段,批准建设连贯,不采纳三次握手,只有服务端收回确认,就建设新的连贯了,此时客户端疏忽服务端发来的确认,也不发送数据,则服务端统一期待客户端发送数据,浪费资源。

简略来说就是以下三步:

  • 第一次握手: 客户端向服务端发送连贯申请报文段。该报文段中蕴含本身的数据通讯初始序号。申请发送后,客户端便进入 SYN-SENT 状态。
  • 第二次握手: 服务端收到连贯申请报文段后,如果批准连贯,则会发送一个应答,该应答中也会蕴含本身的数据通讯初始序号,发送实现后便进入 SYN-RECEIVED 状态。
  • 第三次握手: 当客户端收到连贯批准的应答后,还要向服务端发送一个确认报文。客户端发完这个报文段后便进入 ESTABLISHED 状态,服务端收到这个应答后也进入 ESTABLISHED 状态,此时连贯建设胜利。

TCP 三次握手的建设连贯的过程就是互相确认初始序号的过程,通知对方,什么样序号的报文段可能被正确接管。 第三次握手的作用是客户端对服务器端的初始序号的确认。如果只应用两次握手,那么服务器就没有方法晓得本人的序号是否 已被确认。同时这样也是为了避免生效的申请报文段被服务器接管,而呈现谬误的状况。

(2)四次挥手

刚开始单方都处于 ESTABLISHED 状态,如果是客户端先发动敞开申请。四次挥手的过程如下:

  • 第一次挥手: 客户端会发送一个 FIN 报文,报文中会指定一个序列号。此时客户端处于 FIN_WAIT1 状态。
即收回连贯开释报文段(FIN=1,序号seq=u),并进行再发送数据,被动敞开TCP连贯,进入FIN_WAIT1(终止期待1)状态,期待服务端的确认。
  • 第二次挥手:服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的序列号值 +1 作为 ACK 报文的序列号值,表明曾经收到客户端的报文了,此时服务端处于 CLOSE_WAIT 状态。
即服务端收到连贯开释报文段后即收回确认报文段(ACK=1,确认号ack=u+1,序号seq=v),服务端进入CLOSE_WAIT(敞开期待)状态,此时的TCP处于半敞开状态,客户端到服务端的连贯开释。客户端收到服务端的确认后,进入FIN_WAIT2(终止期待2)状态,期待服务端收回的连贯开释报文段。
  • 第三次挥手:如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号。此时服务端处于 LAST_ACK 的状态。
即服务端没有要向客户端收回的数据,服务端收回连贯开释报文段(FIN=1,ACK=1,序号seq=w,确认号ack=u+1),服务端进入LAST_ACK(最初确认)状态,期待客户端的确认。
  • 第四次挥手:客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答,且把服务端的序列号值 +1 作为本人 ACK 报文的序列号值,此时客户端处于 TIME_WAIT 状态。须要过一阵子以确保服务端收到本人的 ACK 报文之后才会进入 CLOSED 状态,服务端收到 ACK 报文之后,就处于敞开连贯了,处于 CLOSED 状态。
即客户端收到服务端的连贯开释报文段后,对此收回确认报文段(ACK=1,seq=u+1,ack=w+1),客户端进入TIME_WAIT(工夫期待)状态。此时TCP未开释掉,须要通过工夫期待计时器设置的工夫2MSL后,客户端才进入CLOSED状态。

那为什么须要四次挥手呢?

因为当服务端收到客户端的SYN连贯申请报文后,能够间接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。然而敞开连贯时,当服务端收到FIN报文时,很可能并不会立刻敞开SOCKET,所以只能先回复一个ACK报文,通知客户端,“你发的FIN报文我收到了”。只有等到我服务端所有的报文都发送完了,我能力发送FIN报文,因而不能一起发送,故须要四次挥手。

简略来说就是以下四步:

  • 第一次挥手: 若客户端认为数据发送实现,则它须要向服务端发送连贯开释申请。
  • 第二次挥手:服务端收到连贯开释申请后,会通知应用层要开释 TCP 链接。而后会发送 ACK 包,并进入 CLOSE_WAIT 状态,此时表明客户端到服务端的连贯曾经开释,不再接管客户端发的数据了。然而因为 TCP 连贯是双向的,所以服务端仍旧能够发送数据给客户端。
  • 第三次挥手:服务端如果此时还有没发完的数据会持续发送,结束后会向客户端发送连贯开释申请,而后服务端便进入 LAST-ACK 状态。
  • 第四次挥手: 客户端收到开释申请后,向服务端发送确认应答,此时客户端进入 TIME-WAIT 状态。该状态会继续 2MSL(最大段生存期,指报文段在网络中生存的工夫,超时会被摈弃) 工夫,若该时间段内没有服务端的重发申请的话,就进入 CLOSED 状态。当服务端收到确认应答后,也便进入 CLOSED 状态。

TCP 应用四次挥手的起因是因为 TCP 的连贯是全双工的,所以须要单方别离开释到对方的连贯,独自一方的连贯开释,只代 表不能再向对方发送数据,连贯处于的是半开释的状态。

最初一次挥手中,客户端会期待一段时间再敞开的起因,是为了避免发送给服务器的确认报文段失落或者出错,从而导致服务器 端不能失常敞开。

手写题:数组扁平化

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

什么是 XSS 攻打?

(1)概念

XSS 攻打指的是跨站脚本攻打,是一种代码注入攻打。攻击者通过在网站注入歹意脚本,使之在用户的浏览器上运行,从而盗取用户的信息如 cookie 等。

XSS 的实质是因为网站没有对恶意代码进行过滤,与失常的代码混合在一起了,浏览器没有方法分辨哪些脚本是可信的,从而导致了恶意代码的执行。

攻击者能够通过这种攻击方式能够进行以下操作:

  • 获取页面的数据,如DOM、cookie、localStorage;
  • DOS攻打,发送正当申请,占用服务器资源,从而使用户无法访问服务器;
  • 毁坏页面构造;
  • 流量劫持(将链接指向某网站);

(2)攻打类型

XSS 能够分为存储型、反射型和 DOM 型:

  • 存储型指的是歹意脚本会存储在指标服务器上,当浏览器申请数据时,脚本从服务器传回并执行。
  • 反射型指的是攻击者诱导用户拜访一个带有恶意代码的 URL 后,服务器端接收数据后处理,而后把带有恶意代码的数据发送到浏览器端,浏览器端解析这段带有 XSS 代码的数据后当做脚本执行,最终实现 XSS 攻打。
  • DOM 型指的通过批改页面的 DOM 节点造成的 XSS。

1)存储型 XSS 的攻打步骤:

  1. 攻击者将恶意代码提交到⽬标⽹站的数据库中。
  2. ⽤户关上⽬标⽹站时,⽹站服务端将恶意代码从数据库取出,拼接在 HTML 中返回给浏览器。
  3. ⽤户浏览器接管到响应后解析执⾏,混在其中的恶意代码也被执⾏。
  4. 恶意代码窃取⽤户数据并发送到攻击者的⽹站,或者假冒⽤户的⾏为,调⽤⽬标⽹站接⼝执⾏攻击者指定的操作。

这种攻打常⻅于带有⽤户保留数据的⽹站性能,如论坛发帖、商品评论、⽤户私信等。

2)反射型 XSS 的攻打步骤:

  1. 攻击者结构出非凡的 URL,其中蕴含恶意代码。
  2. ⽤户关上带有恶意代码的 URL 时,⽹站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器。
  3. ⽤户浏览器接管到响应后解析执⾏,混在其中的恶意代码也被执⾏。
  4. 恶意代码窃取⽤户数据并发送到攻击者的⽹站,或者假冒⽤户的⾏为,调⽤⽬标⽹站接⼝执⾏攻击者指定的操作。

反射型 XSS 跟存储型 XSS 的区别是:存储型 XSS 的恶意代码存在数据库⾥,反射型 XSS 的恶意代码存在 URL ⾥。

反射型 XSS 破绽常⻅于通过 URL 传递参数的性能,如⽹站搜寻、跳转等。 因为须要⽤户被动关上歹意的 URL 能力⽣效,攻击者往往会联合多种⼿段诱导⽤户点击。

3)DOM 型 XSS 的攻打步骤:

  1. 攻击者结构出非凡的 URL,其中蕴含恶意代码。
  2. ⽤户关上带有恶意代码的 URL。
  3. ⽤户浏览器接管到响应后解析执⾏,前端 JavaScript 取出 URL 中的恶意代码并执⾏。
  4. 恶意代码窃取⽤户数据并发送到攻击者的⽹站,或者假冒⽤户的⾏为,调⽤⽬标⽹站接⼝执⾏攻击者指定的操作。

DOM 型 XSS 跟前两种 XSS 的区别:DOM 型 XSS 攻打中,取出和执⾏恶意代码由浏览器端实现,属于前端JavaScript ⾃身的安全漏洞,⽽其余两种 XSS 都属于服务端的安全漏洞。

函数柯里化

柯里化(currying) 指的是将一个多参数的函数拆分成一系列函数,每个拆分后的函数都只承受一个参数。

对于曾经柯里化后的函数来说,当接管的参数数量与原函数的形参数量雷同时,执行原函数; 当接管的参数数量小于原函数的形参数量时,返回一个函数用于接管残余的参数,直至接管的参数数量与形参数量统一,执行原函数。

代码输入后果

Promise.resolve(1)  .then(2)  .then(Promise.resolve(3))  .then(console.log)

输入后果如下:

1

看到这个题目,好多的then,实际上只须要记住一个准则:.then.catch 的参数冀望是函数,传入非函数则会产生值透传

第一个then和第二个then中传入的都不是函数,一个是数字,一个是对象,因而产生了透传,将resolve(1) 的值间接传到最初一个then里,间接打印出1。

函数柯里化

什么叫函数柯里化?其实就是将应用多个参数的函数转换成一系列应用一个参数的函数的技术。还不懂?来举个例子。

function add(a, b, c) {    return a + b + c}add(1, 2, 3)let addCurry = curry(add)addCurry(1)(2)(3)

当初就是要实现 curry 这个函数,使函数从一次调用传入多个参数变成屡次调用每次传一个参数。

function curry(fn) {    let judge = (...args) => {        if (args.length == fn.length) return fn(...args)        return (...arg) => judge(...args, ...arg)    }    return judge}

你在工作终于到那些问题,解决办法是什么

常常遇到的问题就是Cannot read property ‘prototype’ of undefined解决办法通过浏览器报错提醒代码定位问题,解决问题Vue我的项目中遇到视图不更新,办法不执行,埋点不触发等问题个别解决方案查看浏览器报错,查看代码运行到那个阶段未之行完结,浏览源码以及相干文档等而后举进去最近开发的我的项目中遇到的算是两个比拟大的问题。

冒泡排序--工夫复杂度 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]));

数组去重

第一种: 通过ES6新个性Set()例如: var arr = [1, 2, 3, 1, 2]; var newArr= [...new Set(arr)]
第二种:封装函数利用 {} 和【】function uniqueEasy(arr) {    if(!arr instanceof Array) {        throw Error('以后传入的不是数组')    }    let list = []    let obj = {}    arr.forEach(item => {        if(!obj[item]) {            list.push(item)            obj[item] = true        }    })    return list}

当然还有其余的办法,但自己我的项目中个别应用以上两种根本满足

参考:前端进阶面试题具体解答

陈说输出URL回车后的过程

1.读取缓存:         搜寻本身的 DNS 缓存。(如果 DNS 缓存中找到IP 地址就跳过了接下来查找 IP 地址步骤,间接拜访该 IP 地址。)2.DNS 解析:将域名解析成 IP 地址3.TCP 连贯:TCP 三次握手,繁难形容三次握手           客户端:服务端你在么?            服务端:客户端我在,你要连贯我么?            客户端:是的服务端,我要链接。            连贯买通,能够开始申请来4.发送 HTTP 申请5.服务器解决申请并返回 HTTP 报文6.浏览器解析渲染页面7.断开连接:TCP 四次挥手对于第六步浏览器解析渲染页面又能够聊聊如果返回的是html页面依据 HTML 解析出 DOM 树依据 CSS 解析生成 CSS 规定树联合 DOM 树和 CSS 规定树,生成渲染树依据渲染树计算每一个节点的信息依据计算好的信息绘制页面

僵尸过程和孤儿过程是什么?

  • 孤儿过程:父过程退出了,而它的一个或多个过程还在运行,那这些子过程都会成为孤儿过程。孤儿过程将被init过程(过程号为1)所收养,并由init过程对它们实现状态收集工作。
  • 僵尸过程:子过程比父过程先完结,而父过程又没有开释子过程占用的资源,那么子过程的过程描述符依然保留在零碎中,这种过程称之为僵死过程。

CDN的作用

CDN个别会用来托管Web资源(包含文本、图片和脚本等),可供下载的资源(媒体文件、软件、文档等),应用程序(门户网站等)。应用CDN来减速这些资源的拜访。

(1)在性能方面,引入CDN的作用在于:

  • 用户收到的内容来自最近的数据中心,提早更低,内容加载更快
  • 局部资源申请调配给了CDN,缩小了服务器的负载

(2)在平安方面,CDN有助于进攻DDoS、MITM等网络攻击:

  • 针对DDoS:通过监控剖析异样流量,限度其申请频率
  • 针对MITM:从源服务器到 CDN 节点到 ISP(Internet Service Provider),全链路 HTTPS 通信

除此之外,CDN作为一种根底的云服务,同样具备资源托管、按需扩大(可能应答流量顶峰)等方面的劣势。

原函数形参定长(此时 fn.length 是个不变的常数)

// 写法1-不保留参数,递归部分函数function curry(fn) {    let judge = (...args) => {        // 递归完结条件        if(args.length === fn.length) return fn(...args);        return (...arg) => judge(...args, ...arg);    }    return judge;}// 写法2-保留参数,递归整体函数function curry(fn) {    // 保留参数,除去第一个函数参数    let presentArgs = [].slice.call(arguments, 1);    // 返回一个新函数    return function(){        // 新函数调用时会持续传参        let allArgs = [...presentArgs, ...arguments];        // 递归完结条件        if(allArgs.length === fn.length) {            // 如果参数够了,就执行原函数            return fn(,,,allArgs);        }        // 否则持续柯里化        else return curry(fn, ...allArgs);    }}// 测试function add(a, b, c, d) {  return a + b + c + d;}console.log(add(1, 2, 3, 4));let addCurry = curry(add);// 以下后果都返回 10console.log(addCurry(1)(2)(3)(4));  console.log(addCurry(1)(2, 3, 4));console.log(addCurry(1, 2)(3)(4));console.log(addCurry(1, 2)(3, 4));console.log(addCurry(1, 2, 3)(4));console.log(addCurry(1, 2, 3, 4));

代码输入后果

Promise.resolve(1)  .then(res => {    console.log(res);    return 2;  })  .catch(err => {    return 3;  })  .then(res => {    console.log(res);  });

输入后果如下:

1   2

Promise是能够链式调用的,因为每次调用 .then 或者 .catch 都会返回一个新的 promise,从而实现了链式调用, 它并不像个别工作的链式调用一样return this。

下面的输入后果之所以顺次打印出1和2,是因为resolve(1)之后走的是第一个then办法,并没有进catch里,所以第二个then中的res失去的实际上是第一个then的返回值。并且return 2会被包装成resolve(2),被最初的then打印输出2。

compose

题目形容:实现一个 compose 函数

// 用法如下:function fn1(x) {  return x + 1;}function fn2(x) {  return x + 2;}function fn3(x) {  return x + 3;}function fn4(x) {  return x + 4;}const a = compose(fn1, fn2, fn3, fn4);console.log(a(1)); // 1+4+3+2+1=11

实现代码如下:

function compose(...fn) {  if (!fn.length) return (v) => v;  if (fn.length === 1) return fn[0];  return fn.reduce(    (pre, cur) =>      (...args) =>        pre(cur(...args))  );}

手写题:实现柯里化

事后设置一些参数

柯里化是什么:是指这样一个函数,它接管函数 A,并且能返回一个新的函数,这个新的函数可能处理函数 A 的残余参数

function createCurry(func, args) {  var argity = func.length;  var args = args || [];  return function () {    var _args = [].slice.apply(arguments);    args.push(..._args);    if (args.length < argity) {      return createCurry.call(this, func, args);    }    return func.apply(this, args);  }}

说一下常见的HTTP状态码?说一下状态码是302和304是什么意思?你在我的项目中呈现过么?你是怎么解决的?

    <!-- 状态码:由3位数字组成,第一个数字定义了响应的类别 -->    <!-- 1xx:批示音讯,示意申请已接管,持续解决 -->    <!-- 2xx:胜利,示意申请已被胜利接管,解决 -->    <!-- 200 OK:客户端申请胜利         204 No Content:无内容。服务器胜利解决,但未返回内容。个别用在只是客户端向服务器发送信息,而服务器不必向客户端返回什么信息的状况。不会刷新页面。         206 Partial Content:服务器曾经实现了局部GET申请(客户端进行了范畴申请)。响应报文中蕴含Content-Range指定范畴的实体内容 -->    <!-- 3xx 重定向 -->    <!-- 301 Moved Permanently:永恒重定向,示意申请的资源曾经永恒的搬到了其余地位。         302 Found:长期重定向,示意申请的资源长期搬到了其余地位         303 See Other:长期重定向,应应用GET定向获取申请资源。303性能与302一样,区别只是303明确客户端应该应用GET拜访         307 Temporary Redirect:长期重定向,和302有着雷同含意。POST不会变成GET         304 Not Modified:示意客户端发送附带条件的申请(GET办法申请报文中的IF…)时,条件不满足。返回304时,不蕴含任何响应主体。尽管304被划分在3XX,但和重定向一毛钱关系都没有 -->    <!-- 4xx:客户端谬误 -->    <!-- 400 Bad Request:客户端申请有语法错误,服务器无奈了解。         401 Unauthorized:申请未经受权,这个状态代码必须和WWW-Authenticate报头域一起应用。         403 Forbidden:服务器收到申请,然而回绝提供服务         404 Not Found:申请资源不存在。比方,输出了谬误的url         415 Unsupported media type:不反对的媒体类型 -->    <!-- 5xx:服务器端谬误,服务器未能实现非法的申请。 -->    <!-- 500 Internal Server Error:服务器产生不可预期的谬误。         503 Server Unavailable:服务器以后不能解决客户端的申请,一段时间后可能恢复正常, -->

事件触发的过程是怎么的

事件触发有三个阶段:

  • window 往事件触发处流传,遇到注册的捕捉事件会触发
  • 流传到事件触发处时触发注册的事件
  • 从事件触发处往 window 流传,遇到注册的冒泡事件会触发

事件触发一般来说会依照下面的程序进行,然而也有特例,如果给一个 body 中的子节点同时注册冒泡和捕捉事件,事件触发会依照注册的程序执行。

// 以下会先打印冒泡而后是捕捉node.addEventListener(  'click',  event => {    console.log('冒泡')  },  false)node.addEventListener(  'click',  event => {    console.log('捕捉 ')  },  true)

通常应用 addEventListener 注册事件,该函数的第三个参数能够是布尔值,也能够是对象。对于布尔值 useCapture 参数来说,该参数默认值为 falseuseCapture 决定了注册的事件是捕捉事件还是冒泡事件。对于对象参数来说,能够应用以下几个属性:

  • capture:布尔值,和 useCapture 作用一样
  • once:布尔值,值为 true 示意该回调只会调用一次,调用后会移除监听
  • passive:布尔值,示意永远不会调用 preventDefault

一般来说,如果只心愿事件只触发在指标上,这时候能够应用 stopPropagation 来阻止事件的进一步流传。通常认为 stopPropagation 是用来阻止事件冒泡的,其实该函数也能够阻止捕捉事件。

stopImmediatePropagation 同样也能实现阻止事件,然而还能阻止该事件指标执行别的注册事件。

node.addEventListener(  'click',  event => {    event.stopImmediatePropagation()    console.log('冒泡')  },  false)// 点击 node 只会执行下面的函数,该函数不会执行node.addEventListener(  'click',  event => {    console.log('捕捉 ')  },  true)

如何⽤webpack来优化前端性能?

⽤webpack优化前端性能是指优化webpack的输入后果,让打包的最终后果在浏览器运⾏疾速⾼效。

  • 压缩代码:删除多余的代码、正文、简化代码的写法等等⽅式。能够利⽤webpack的 UglifyJsPlugin 和 ParallelUglifyPlugin 来压缩JS⽂件, 利⽤ cssnano (css-loader?minimize)来压缩css
  • 利⽤CDN减速: 在构建过程中,将引⽤的动态资源门路批改为CDN上对应的门路。能够利⽤webpack对于 output 参数和各loader的 publicPath 参数来批改资源门路
  • Tree Shaking: 将代码中永远不会⾛到的⽚段删除掉。能够通过在启动webpack时追加参数 --optimize-minimize 来实现
  • Code Splitting: 将代码按路由维度或者组件分块(chunk),这样做到按需加载,同时能够充沛利⽤浏览器缓存
  • 提取公共第三⽅库: SplitChunksPlugin插件来进⾏公共模块抽取,利⽤浏览器缓存能够⻓期缓存这些⽆需频繁变动的公共代码

深拷贝(思考到复制 Symbol 类型)

题目形容:手写 new 操作符实现

实现代码如下:

function isObject(val) {  return typeof val === "object" && val !== null;}function deepClone(obj, hash = new WeakMap()) {  if (!isObject(obj)) return obj;  if (hash.has(obj)) {    return hash.get(obj);  }  let target = Array.isArray(obj) ? [] : {};  hash.set(obj, target);  Reflect.ownKeys(obj).forEach((item) => {    if (isObject(obj[item])) {      target[item] = deepClone(obj[item], hash);    } else {      target[item] = obj[item];    }  });  return target;}// var obj1 = {// a:1,// b:{a:2}// };// var obj2 = deepClone(obj1);// console.log(obj1);