2021 年面试总结

705 阅读36分钟

css

盒模型

  • 标准的盒模型: 元素的宽度 = margin-left + border-left + padding-left + width + padding-right + border-right + margin-right
  • 怪异盒模型 : 元素占据的宽度 = margin-left + width + margin-right height = 内容的 height + padding + border、
box-sizing: content-box // 标准盒模型
box-sizing: border-box // 怪异盒模型
box-sizing: padding-box // ⽕狐的私有模型,没⼈⽤

居中的一些方法

height: 200px; width:200px; margin:0 auto;
display: flex; justify-content: center; align-items: centerw、
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
position: absolute; top: 50%; left: 50%; margin-left: 盒子的 width/2; margin-top: 盒子的 height/2;
display:table; margin:0 auto; 等

实现一个盒子 高度是宽度的 一半

设置父元素的的 font-size, eg: 自身 width: 10em; 自身 height5em
width: 100px; height: 0; padding-top: 50%;
方式1 em的方式 .fu {
  font-size: 20px;
}
.son {
  height: 5em;
  width: 10em;
  border: 1px solid red;
}
方式2 使用css新特性计算的方式 :root {
  --my-width: 200px;
  --my-height: calc(var(--my-width) / 2);
}
div {
  background: yellow;
  width: var(--my-width);
  height: var(--my-height);
}
方式三 padding-top的方式 .fu {
  width: 100px;
}
.son {
  padding-top: 50%;
  background: red;
}

动画有哪些属性 并且属性值是什么

```css
animation-name: 动画名称(默认值为none)
animation-duration: 持续时间(默认值为0)
animation-timing-function: 时间函数(默认值为ease)
animation-delay: 延迟时间(默认值为0)
animation-iteration-count: 循环次数(默认值为1)
animation-direction: 动画方向(默认值为normal)
animation-play-state: 播放状态(默认值为running)
animation-fill-mode: 填充模式(默认值为none)
```

平时做动画时, 用的是什么属性

  • 使用的是 transform 使用 transform 性能更好 因为 transform 属性, 启用了浏览器的 GPU 加速,可以缩短平滑动画的绘制时间及不会触发重绘

画三角形

  • 使用 border
  • 使用伪类
  • 使用 clip-path
  • 使用 canvas
<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
      .fu {
        width: 0;
        height: 0;
        border-top: 50px solid black;
        border-right: 50px solid red;
        border-bottom: 50px solid green;
        border-left: 50px solid blue;
      }

      .fu1 {
        width: 0;
        height: 0;
        border-top: 50px solid black;
        border-right: 50px solid transparent;
        border-bottom: 50px solid transparent;
        border-left: 50px solid transparent;
      }

      /* 2、通过伪类的方式  右下三角*/
      .pseudo-ele {
        width: 20px;
        height: 20px;
        background: transparent;
        /* 必须有  超出直接干掉 */
        overflow: hidden;
      }

      .pseudo-ele:after {
        display: block;
        width: 150%;
        height: 150%;
        content: '';
        background: red;
        transform: rotateZ(45deg) translateX(10px);
      }

      .demo {
        clip-path: polygon(0 100%, 50% 0, 100% 100%);
      }
    </style>
  </head>

  <body>
    <div class="fu">
    </div>
    <br>
    <div class="fu1">
    </div>
    .
    <!--html-->
    <div class='pseudo-ele'></div>
    <!--  -->

    <div class="demo" style="width: 300px; height: 300px; margin: auto; background: red;">
    </div>

  </body>

</html>

js

浏览器渲染原理

  • 三个script标签 第一个设置把背景成红色、第二个把背景设置成黄色、第三个把背景设置成蓝色 。当前页面是否有闪的操作?

  • 当前最后显示的是第三个颜色,不会有闪的操作

  • 三个script标签 第一个设置把背景成红色、第二个把背景设置成黄色,同时执行10万次的for循环、第三个把背景设置成蓝色

    • 当前最后显示的也是第三个颜色,不会有闪的操作

图形用户界面(Graphical User Interface,简称 GUI,又称图形用户接口)是指采用图形方式显示的计算机操作用户界面。

原因 :GUI渲染线程与JS引擎线程互斥的,是由于JavaScript是可操纵DOM的,如果在修改这些元素属性同时渲染界面(即JavaScript线程和UI线程同时运行),那么渲染线程前后获得的元素数据就可能不一致。当JavaScript引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到引擎线程空闲时立即被执行。由于GUI渲染线程与JS执行线程是互斥的关系,当浏览器在执行JS程序的时候,GUI渲染线程会被保存在一个队列中,直到JS程序执行完成,才会接着执行。因此如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。

for of和for in的区别

  • for of无法循环遍历对象 只能遍历数组 和数组嵌对像

  • for of 输出的是值 for in输出的是下标 及key

  • for in 会遍历自定义原型上的属性 for of 不会

    
      var arr = ['nick', 'freddy', 'mike', 'james'];
          arr.name = "数组";
    
          for (var key in arr) {
            console.log(key + ': ' + arr[key]);
          }
          // 0: nick
          // 1: freddy
          // 2: mike
          // 3: james
          // name: 数组
          console.log('-----------分割线-----------');
          for (var item of arr) {
            console.log(item);
          }
        //   nick
        //  freddy
        //  mike
        //  james
    
    

数组中获取最大值的方式

var max = Math.max.apply(null, arr);

js 的类型

  • 简单类型: null、number、string、symbol、bigint、boolean、undefind
  • 复杂类型:Object

闭包

  • 定义加理解:函数内嵌套函数内部函数可以访问外部函数的变量形成了闭包(闭包 =『函数』和『函数体内可访问的变量总和』 )
  • 作用:隐藏变量

原型链

  • 原因是每个对象都有 __proto__ 属性,此属性指向该对象的构造函数的原型。
  • 对象可以通过__proto** 与上游的构造函数的原型对象连接起来,⽽上游的原型对象也有⼀个 **proto__ ,这样就形成 了原型链。
  • function Function()._proto__ == Function.prototype
  • Function.prototype.constructor== function Function()
  • Function.prototype.__proto** == Object.prototype 函数的__proto**也指向 Object 的原型

img

this 的指向问题

  • 谁调用的 this 就指向谁,箭头函数没有 this

了解下 service worker

MDN 描述: Service workers 本质上充当 Web 应用程序、浏览器与网络(可用时)之间的代理服务器。这个 API 旨在创建有效的离线体验,它会拦截网络请求并根据网络是否可用采取来适当的动作、更新来自服务器的的资源。它还提供入口以推送通知和访问后台同步 API。

总结描述:一个服务器与浏览器之间的中间人角色,如果网站中注册了 service worker 那么它可以拦截当前网站所有的请求,进行判断(需要编写相应的判断程序),如果需要向服务器发起请求的就转给服务器,如果可以直接使用缓存的就直接返回缓存不再转给服务器。从而大大提高浏览体验。

字符串的一些 api

  • split()
  • indexOf()
  • lastIndexOf()
  • match()
  • replace() 替换,参数 1 是旧的,参数 2 是新的
  • startwith()
  • endwith()
  • slice()
  • substring()
  • substr()
  • includes()
  • trim()
  • trimLeft()
  • trimRight()
  • padStart()
  • padEnd()
  • charAt()
  • charCodeAt() 获取字符的 Unicode 编码
  • str.toUpperCase() / str.toLowerCase() 转大写 / 转小写 等等

数组的一些 api

  • splice()
  • indexOf()
  • lastIndexOf()
  • includes()
  • slice()
  • map()
  • forEach()
  • reduce()
  • reduceRight()
  • pop()
  • push()
  • shift()
  • unshift()
  • filter()
  • some()
  • every()
  • reverse()
  • concat()
  • find(),findIndex() 返回满足元素的第一个值/下标
  • sort() 等等

正则

Object

  • 深拷贝 JSON.parse(JSON.stringfy(obj)) ,递归
  • Object.assign() 对象的合并 ,第一层数据时是深拷贝,多层时第二层开始就是浅拷贝
  • Object.prototype.toString() 用来检查一个变量的类型, 精确,比 typeOf 更精确
  • Object 的存储方式,地址存储在栈里面,引用的对象存储在堆中

宏任务, 微任务

  • 宏任务。

    • script (可以理解为外层同步代码)
    • setTimeout/setInterval
    • UI rendering/UI事件
    • postMessage,MessageChannel
    • setImmediate,I/O(Node.js)
  • 微任务。

    • Promise
    • mutationObserve,
    • nextick
    • await

性能优化

  • DNS预解析、缓存(强缓存和协商缓存)

事件循环 Event loop

白屏时间, 首屏时间 计算

  • 白屏时间

    • 可使用 Performance API 时
    白屏时间 = firstPaint - performance.timing.navigationStart;
    
    • 不可使用 Performance API 时
    白屏时间 = firstPaint - pageStartTime;
    
    <head>
      <meta charset="UTF-8">
      <title>白屏</title>
      <script type="text/javascript">
        // 不兼容performance.timing 的浏览器,如IE8
        window.pageStartTime = Date.now();
      </script>
      <!-- 页面 CSS 资源 -->
      <link rel="stylesheet" href="common.css">
      <link rel="stylesheet" href="page.css">
      <script type="text/javascript">
        // 白屏时间结束点
        window.firstPaint = Date.now();
      </script>
    </head>
    
  • 首屏时间

    • 或者可以说是 加载最慢的图片的时间点 - performance.timing.navigationStart;

OSI 网络7层模型

  • 是什么

    • OSI (Open System Interconnect)模型全称为开放式通信系统互连参考模型,是国际标准化组织 ( ISO ) 提出的一个试图使各种计算机在世界范围内互连为网络的标准框架
  • OSI将计算机网络体系结构划分为七层

    • 应用层
    • 表示层
    • 会话层
    • 传输层
    • 网络层
    • 数据链路层
    • 物理层

http/ https

  • https是安全版的http,因为http协议的数据都是明⽂进⾏传输的,所以对于⼀些敏感信息的传输就很不安全,HTTPS就 是为了解决HTTP的不安全⽽⽣的。

http1.0、 http1.1、http2.0 区别

  • http1.0

    • 默认每次与服务器交互都需要重新建立一个TCP连接
    • 如果需要建立长连接需要设置一个非标准的 Connection字段 Connection: keep-alive
  • http1.0

    • 默认支持长连接 Connection: keep-alive ,即在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟
      • 在加载html文件的时候,文件中多个请求和响应就可以在一个连接中传输
    • 还允许客户端不用等待上一次请求结果返回,就可以发出下一次请求,但服务器端必须按照接收到客户端请求的先后顺序依次回送响应结果,以保证客户端能够区分出每次请求的响应内容,这样也显著地减少了整个下载过程所需要的时间
      • 添加了缓存策略 (强缓存/协商缓存)
      • 添加了其他的请求方式 put、delete、options、trace、connect
  • HTTP2.0

    • 多路复用

      • HTTP/2 复用TCP连接,在一个连接里,客户端和浏览器都可以「同时」发送多个请求或回应,而且不用按照顺序一一对应,这样就避免了”队头堵塞”
    • 二进制分帧

      帧是HTTP2通信中最小单位信息

      HTTP/2 采用二进制格式传输数据,而非 HTTP 1.x的文本格式,解析起来更高效

      将请求和响应数据分割为更小的帧,并且它们采用二进制编码

      HTTP2中,同域名下所有通信都在单个连接上完成,该连接可以承载任意数量的双向数据流

      每个数据流都以消息的形式发送,而消息又由一个或多个帧组成。多个帧之间可以乱序发送,根据帧首部的流标识可以重新组装,这也是多路复用同时发送数据的实现条件

    • 首部压缩

      HTTP/2在客户端和服务器端使用“首部表”来跟踪和存储之前发送的键值对,对于相同的数据,不再通过每次请求和响应发送

      首部表在HTTP/2的连接存续期内始终存在,由客户端和服务器共同渐进地更新

      • 第一次请求发送所有的头部字段,第二次请求则只需要发送差异数据,这样可以减少冗余数据,降低开销
    • 服务器推送

      HTTP2引入服务器推送,允许服务端推送资源给客户端

      服务器会顺便把一些客户端需要的资源一起推送到客户端,如在响应一个页面请求中,就可以带上其他页面的资源

      免得客户端再次创建连接发送请求到服务器端获取

      这种方式非常合适加载静态资源

缓存(强缓存/协商缓存)

强缓存 (响应头 ExpiresCache-Control )

  • Expires
    • Expires:Expires是http1.0提出的⼀个表示资源过期时间的header,它描述的是⼀个绝对时间,由服务器返回,
    • Expires 受限于本地时间,如果修改了本地时间,可能会造成缓存失效

Expires: Wed, 11 May 2018 07:20:00 GMT

  • Cache-Contro

    • Cache-Control 出现于 HTTP / 1.1,优先级⾼于 Expires ,表示的是相对时间

    Cache-Control: max-age=315360000

  • ⽬前主流的做法使⽤ Cache-Control 控制缓存,除了 max-age 控制过期时间外,还有⼀些不得不提

    • Cache-Control: public 可以被所有⽤户缓存,包括终端和CDN等中间代理服务器
    • Cache-Control: private 只能被终端浏览器缓存,不允许中继缓存服务器进⾏缓存
    • Cache-Control: no-cache ,先缓存本地,但是在命中缓存之后必须与服务器验证缓存的新鲜度才能使⽤
    • Cache-Control: no-store,不会产⽣任何缓存

在缓存有效期内命中缓存,浏览器会直接读取本地的缓存资源,当缓存过期之后会与服务器进⾏协商。

协商缓存

当第⼀次请求时服务器返回的响应头中没有Cache-Control和Expires或者Cache-Control和Expires过期抑或它的属性设置为no-cache时,那么浏览器第⼆次请求时就会与服务器进⾏协商。

如果缓存和服务端资源的最新版本是⼀致的,那么就⽆需再次下载该资源,服务端直接返回304 Not Modified 状态码, 如果服务器发现浏览器中的缓存已经是旧版本了,那么服务器就会把最新资源的完整内容返回给浏览器,状态码就是 200 Ok。

服务器判断缓存是否是过期的两个⽅法

  • Last-Modified/If-Modified-Since

    • 客户端⾸次请求资源时,服务器会把资源的最新修改时间 Last-Modified:Thu, 19 Feb 2019 08:20:55 GMT 通过响应部⾸发 送给客户端,当再次发送请求是,客户端将服务器返回的修改时间放在请求头 If-Modified-Since:Thu, 19 Feb 2019 08:20:55 GMT 发送给服务器,服务器再跟服务器上的对应资源进⾏⽐对,如果服务器的资源更新,那么返回最新的资源,此时状态码200,当服务器资源跟客户端的请求的部⾸时间⼀致,证明客户端的资源是最新的,返回304状态码,表示客户端直接⽤缓存即可。
  • ETag/If-None-Match

  • ETag的流程跟Last-Modified是类似的,区别就在于ETag是根据资源内容进⾏hash,⽣成⼀个信息摘要,只要资源内容 有变化,这个摘要就会发⽣巨变,通过这个摘要信息⽐对,即可确定客户端的缓存资源是否为最新,这⽐Last-Modified的精确度要更⾼。

![image-20210801234216312](/Users/lizhidan/Library/Application Support/typora-user-images/image-20210801234216312.png)

301 302 304 307

  • 301 永久重定向
  • 302/307 临时重定向
  • 304 自从上次请求后,请求的网页未修改过。服务器返回此响应时,不会返回网页内容。
    • 如果网页自请求者上次请求后再也没有更改过,您应将服务器配置为返回此响应(称为 If-Modified-Since HTTP 标头)。服务器可以告诉 Googlebot 自从上次抓取后网页没有变更,进而节省带宽和开销。

DNS 协议是什么?说说 DNS 完整的查询过程?

  • 定义: DNS(Domain Names System),域名系统,是互联网一项服务,是进行域名和与之相对应的 IP 地址进行转换的服务器

    • 简单来讲,DNS相当于一个翻译官,负责将域名翻译成ip地址
      • IP 地址:一长串能够唯一地标记网络上的计算机的数字
      • 域名:是由一串用点分隔的名字组成的 Internet 上某一台计算机或计算机组的名称,用于在数据传输时对计算机的定位标识
  • 域名是一个具有层次的结构,从上到下一次为根域名、顶级域名、二级域名、三级域名...

    • 例如www.xxx.comwww为三级域名、xxx为二级域名、com为顶级域名,系统为用户做了兼容,域名末尾的根域名.一般不需要输入
  • 域名缓存

    • 浏览器缓存:浏览器在获取网站域名的实际 IP 地址后会对其进行缓存,减少网络请求的损耗
    • 操作系统缓存:操作系统的缓存其实是用户自己配置的 hosts 文件
  • 查询过程

    • 首先搜索浏览器的 DNS 缓存,缓存中维护一张域名与 IP 地址的对应表

    • 若没有命中,则继续搜索操作系统的 DNS 缓存

    • 若仍然没有命中,则操作系统将域名发送至本地域名服务器,本地域名服务器采用递归查询自己的 DNS 缓存,查找成功则返回结果

      • 若本地域名服务器的 DNS 缓存没有命中,则本地域名服务器向上级域名服务器进行迭代查询

      • 首先本地域名服务器向根域名服务器发起请求,根域名服务器返回顶级域名服务器的地址给本地服务器

      • 本地域名服务器拿到这个顶级域名服务器的地址后,就向其发起请求,获取权限域名服务器的地址

      • 本地域名服务器根据权限域名服务器的地址向其发起请求,最终得到该域名对应的 IP 地址

    • 本地域名服务器将得到的 IP 地址返回给操作系统,同时自己将 IP 地址缓存起来

    • 操作系统将 IP 地址返回给浏览器,同时自己也将 IP 地址缓存起

    • 至此,浏览器就得到了域名对应的 IP 地址,并将 IP 地址缓存起

址栏输入 URL 敲下回车后发生了什么?

  • URL解析

    • 首先判断你输入的是一个合法的URL 还是一个待搜索的关键词,并且根据你输入的内容进行对应操作
  • DNS 查询

  • TCP 连接 (三次握手)

    • 客户端向服务器发送一个SYN的包
    • 服务器接收后返回一个ACK+SYN的包
    • 客户端确认收到ACK+SYN包之后,再向服务器发送一个标有ACK数据包的数据
  • HTTP 请求

    • 请求行
    • 请求头
    • 请求主体
  • 响应请求

    • 状态行

    • 响应头

    • 响应正文

      在服务器响应之后,由于现在http默认开始长连接keep-alive,当页面关闭之后,tcp链接则会经过四次挥手完成断开

  • 页面渲染

    • 当浏览器接收到服务器响应的资源后,首先会对资源进行解析:

      • 查看响应头的信息,根据不同的指示做对应处理,比如重定向,存储cookie,解压gzip,缓存资源等等
      • 查看响应头的 Content-Type的值,根据不同的资源类型采用不同的解析方式

      关于页面的渲染过程如下:

      • 解析HTML,构建 DOM 树
      • 解析 CSS ,生成 CSS 规则树
      • 合并 DOM 树和 CSS 规则,生成 render 树
      • 布局 render 树( Layout / reflow ),负责各元素尺寸、位置的计算
      • 绘制 render 树( paint ),绘制页面像素信息
      • 浏览器会将各层的信息发送给 GPU,GPU 会将各层合成( composite ),显示在屏幕上

三次握手 / 四次挥手 为什么不能是两次握手, 或者多次握手。 为什么不能是三次挥手, 多次挥手 。

总结就是不能少,也不## 能多, 多的话,浪费带宽资源, 少的话, 发送方不能确定接收方是否收到信息

三次握手

  • 定义: 其实就是指建立一个TCP连接时,需要客户端和服务器总共发送3个包

  • 作用: 为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性传送做准备

    • 第一次握手:客户端给服务端发一个 SYN 报文,并指明客户端的初始化序列号 ISN(c),此时客户端处于 SYN_SENT 状态
    • 第二次握手:服务器收到客户端的 SYN 报文之后,会以自己的 SYN 报文作为应答,为了确认客户端的 SYN,将客户端的 ISN+1作为ACK的值,此时服务器处于 SYN_RCVD 的状态
    • 第三次握手:客户端收到 SYN 报文之后,会发送一个 ACK 报文,值为服务器的ISN+1。此时客户端处于 ESTABLISHED 状态。服务器收到 ACK 报文之后,也处于 ESTABLISHED 状态,此时,双方已建立起了连接
    • 最终结论 (通过三次握手,就能确定双方的接收和发送能力是正常的。之后就可以正常通信了)
      • 第一次握手:客户端发送网络包,服务端收到了 这样服务端就能得出结论:客户端的发送能力、服务端的接收能力是正常的。
      • 第二次握手:服务端发包,客户端收到了 这样客户端就能得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的。不过此时服务器并不能确认客户端的接收能力是否正常
      • 第三次握手:客户端发包,服务端收到了。这样服务端就能得出结论:客户端的接收、发送能力正常,服务器自己的发送、接收能力也正常

四次挥手

  • 定义:tcp终止一个连接,需要经过四次挥手

    • 第一次挥手:客户端发送一个 FIN 报文,报文中会指定一个序列号。此时客户端处于 FIN_WAIT1(等待) 状态,停止发送数据,等待服务端的确认
    • 第二次挥手:服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的序列号值 +1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT(等待关闭)状态
    • 第三次挥手:如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号。此时服务端处于 LAST_ACK(最后确认) 的状态
    • 第四次挥手:客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答,且把服务端的序列号值 +1 作为自己 ACK 报文的序列号值,此时客户端处于 TIME_WAIT状态。需要过一阵子以确保服务端收到自己的 ACK 报文之后才会进入 CLOSED 状态,服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态
  • 四次挥手的原因

    • 服务端在收到客户端断开连接Fin报文后,并不会立即关闭连接,而是先发送一个ACK包先告诉客户端收到关闭连接的请求,只有当服务器的所有报文发送完毕之后,才发送FIN报文断开连接,因此需要四次挥手

![image-20210801235307945](/Users/lizhidan/Library/Application Support/typora-user-images/image-20210801235307945.png)

https 的原理, 怎么交互的。 如何加密的

  • 过程⽐较复杂,我们得先理解两个概念

    • 对称加密:即通信的双⽅都使⽤同⼀个秘钥进⾏加解密,⽐如特务接头的暗号,就属于对称加密

      对称加密虽然很简单性能也好,但是⽆法解决⾸次把秘钥发给对⽅的问题,很容易被⿊客拦截秘钥。

    • ⾮对称加密:

      1. 私钥 + 公钥= 密钥对

      2. 即⽤私钥加密的数据,只有对应的公钥才能解密,⽤公钥加密的数据,只有对应的私钥才能解密

      3. 因为通信双⽅的⼿⾥都有⼀套⾃⼰的密钥对,通信之前双⽅会先把⾃⼰的公钥都先发给对⽅

      4. 然后对⽅再拿着这个公钥来加密数据响应给对⽅,等到到了对⽅那⾥,对⽅再⽤⾃⼰的私钥进⾏解密

    ⾮对称加密虽然安全性更⾼,但是带来的问题就是速度很慢,影响性能。

    解决⽅案

    那么结合两种加密⽅式,将对称加密的密钥使⽤⾮对称加密的公钥进⾏加密,然后发送出去,接收⽅使⽤私钥进⾏解密得到对称加密的密钥,然后双⽅可以使⽤对称加密来进⾏沟通。

    此时⼜带来⼀个问题,中间⼈问题

    如果此时在客户端和服务器之间存在⼀个中间⼈,这个中间⼈只需要把原本双⽅通信互发的公钥,换成⾃⼰的公钥,这样中间⼈就可以轻松解密通信双⽅所发送的所有数据。

    所以这个时候需要⼀个安全的第三⽅颁发证书(CA),证明身份的身份,防⽌被中间⼈攻击。

    证书中包括:签发者、证书⽤途、使⽤者公钥、使⽤者私钥、使⽤者的HASH算法、证书到期时间等

    但是问题来了,**如果中间⼈篡改了证书,那么身份证明是不是就⽆效了?**这个证明就⽩买了,这个时候需要⼀个新的技术,数字签名

    • 数字签名就是⽤CA⾃带的HASH算法对证书的内容进⾏HASH得到⼀个摘要,再⽤CA的私钥加密,最终组成数字签名。

    当别⼈把他的证书发过来的时候,我再⽤同样的Hash算法,再次⽣成消息摘要,然后⽤CA的公钥对数字签名解密,得到CA

    创建的消息摘要,两者⼀⽐,就知道中间有没有被⼈篡改了。

    这个时候就能最⼤程度保证通信的安全了。

为什么有了HTTP为什么还要HTTPS

  • https是安全版的http,因为http协议的数据都是明⽂进⾏传输的,所以对于⼀些敏感信息的传输就很不安全,HTTPS就

重传机制 ?

  • promise 中catch的时候根据后台返回的数据code进行重新调用
  • 或者使用 axios 中的拦截器 拦截 response 根据code 进行重传

跨域是什么

  • Dom 的同源策略 :禁止不同的dom进行操作。这里主要场景是iframe跨域的情况,不同域名的iframe是限制互相访问的。

  • XmlHttpRequest同源策略:禁止使用XHR对象向不同源的服务器地址发起HTTP请求。只要协议、域名、端口有任何一个不同,都被当作是不同的域,之间的请求就是跨域操作

    注:协议、域名、端口有任何一个不同,都视为不同的域

跨域怎么解决

  • jsonp 利⽤
  • 缺点
    • 只⽀持get请求(因为
    • 有安全性问题,容易遭受xss攻击
    • 需要服务端配合jsonp进⾏⼀定程度的改造
  • cors是⽬前主流的跨域解决⽅案,跨域资源共享(CORS) 是⼀种机制,它使⽤额外的 HTTP 头来告诉浏览器 让运⾏在⼀ 个 origin (domain) 上的Web应⽤被准许访问来⾃不同源服务器上的指定的资源。当⼀个资源从与该资源本身所在的服 务器不同的域、协议或端⼝请求⼀个资源时,资源会发起⼀个跨域 HTTP 请求。
  • 最⽅便的跨域⽅案Nginx
  • cookie 跨域怎么解决?

    • 设置window.domian
    • 服务端返回path

    xss csxf 定义

    XSS (Cross Site Scripting) 跨站脚本攻击

    • XSS,跨站脚本攻击,允许攻击者将恶意代码植入到提供给其它用户使用的页面中

    • XSS涉及到三方,即攻击者、客户端与Web应用

    • XSS的攻击目标是为了盗取存储在客户端的cookie或者其他网站用于识别客户端身份的敏感信息。一旦获取到合法用户的信息后,攻击者甚至可以假冒合法用户与网站进行交互

      ">

    • 定义

      • 存储型
      • 反射型
      • DOM 型 XSS的预防
    • 通过前面介绍,看到XSS攻击的两大要素:

      • 攻击者提交而恶意代码
      • 浏览器执行恶意代码

    CSRF(Cross-site request forgery)跨站请求伪造

    • 特点

      • 攻击一般发起在第三方网站,而不是被攻击的网站。被攻击的网站无法防止攻击发生

      • 攻击利用受害者在被攻击网站的登录凭证,冒充受害者提交操作;而不是直接窃取数据

      • 整个过程攻击者并不能获取到受害者的登录凭证,仅仅是“冒用”

      • 跨站请求可以用各种方式:图片URL、超链接、CORS、Form提交等等。部分请求方式可以直接嵌入在第三方论坛、文章中,难以进行追踪

    • CSRF的预防

      • CSRF通常从第三方网站发起,被攻击的网站无法防止攻击发生,只能通过增强自己网站针对CSRF的防护能力来提升安全性

      防止csrf常用方案如下:

      • 阻止不明外域的访问

        • 同源检测
        • Samesite Cookie
      • 提交时要求附加本域才能获取的信息

        • CSRF Token
        • 双重Cookie验证

    防止网页被iframe被网页嵌套

    <meta http-equiv="X-Frame-Options" content="SAMEORIGIN">
    

    或者更详细:

    <meta http-equiv="X-Frame-Options" content="SAMEORIGIN / DENY "> 
    
    指定具体网站:
    <meta http-equiv="X-Frame-Options" content="ALLOW-FROM http://xxx.xxx.com">
    
    • X-Frame-Options 有三个值:

      • DENY:表示该页面不允许在 frame 中展示,即便是在相同域名的页面中嵌套也不允许。

      • SAMEORIGIN:表示该页面可以在相同域名页面的 frame 中展示。

      • ALLOW-FROM uri表示该页面可以在指定来源的 frame 中展示。

    cmd amd umd commonjs

    • CommonJS,每个JS文件独立地存储它模块的内容(就像一个被括起来的闭包一样)。在这种作用域中,我们通过 module.exports 语句来导出对象为模块,再通过 require 语句来引入
    function myModule() {
     this.hello = function() {
       return 'hello!';
     }
    }
    module.exports = myModule;  
    
    • AMD 提倡依赖前置,在定义模块的时候就要声明其依赖的模块
    require([module], callback);
    
    • CMD规范是国内SeaJS的推广过程中产生的提倡就近依赖(按需加载),在用到某个模块的时候再去require
       define(function (require, exports, module) {
         var one = require('./one')
         one.do()
       // 就近依赖,按需加载
         var  two = require('./two')
         two.do() 
       })
    
    • UMD 是 AMD和CommonJS的结合,跨平台的解决方案,UMD先判断是否支持Node.js的模块(exports)是否存在,存在则使用Node.js模块模式。在判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块
    (function (window, factory) {
        if (typeof exports === 'object') {
            module.exports = factory();
        } else if (typeof define === 'function' && define.amd) {
            define(factory);
        } else {
            window.eventUtil = factory();
        }
    })(this, function () {
        //module ...
    });
    

    es6数组

    Promise

    vue 的源码 router 的实现 vuex 的实现 diff 算法 vue3 相比较于 vue2 的改动 这里观察你是否对技术的一个关注度

    axios

    怎么实现axios中断

    • 需求分析

      在项目中经常有一些场景会连续发送多个请求,而异步会导致最后得到的结果不是我们想要的,并且对性能也有非常大的影响。

      场景:每输入一个字符都要发送一次请求,但输入过快的时候其实前面的请求并没有必要真的发送出去,这时候就需要在发送新请求的时候直接取消上一次请求。

    • Axios 提供了一个 CancelToken的函数,这是一个构造函数,该函数的作用就是用来取消接口请求的。

        let CancelToken = axios.CancelToken
        let self = this
        axios.get('http://jsonplaceholder.typicode.com/comments', {
          cancelToken: new CancelToken(function executor(c) {
            self.cancel = c
            console.log(c)
            // 这个参数 c 就是CancelToken构造函数里面自带的取消请求的函数,这   里把该函数当参数用
          })
        }).then(res => {
          this.items = res.data
        }).catch(err => {
          console.log(err)
        })
      

    Vue

    Vue-Router的实现原理

    vuex的实现原理?

    this.$nextTick

    说说你对keep-alive的理解是什么?怎么缓存当前的组件?缓存后怎么更新?及原理

    Vue实例挂载的过程中发生了什么?

    • new Vue的时候调用会调用_init方法
    • 定义 setset、get 、deletedelete、watch 等方法
    • 定义 onon、off、emitemit、off等事件
    • 定义 _update、forceUpdateforceUpdate、destroy生命周期
    • 调用$mount进行页面的挂载
    • 挂载的时候主要是通过mountComponent方法
    • 定义updateComponent更新函数
    • 执行render生成虚拟DOM
    • 通过 _update 将虚拟DOM生成真实DOM结构,并且渲染到页面中

    Vue3.0里为什么要用 Proxy API 替代 defineProperty API ?

    Vue3.0 性能提升主要是通过哪几方面体现的?

    • diff算法优化
    • 静态提升
    • 事件监听缓存
    • SSR优化
    • 源码体积 (移出一些不常用的API,Tree-shanking 如 ref、reavtived、computed等,仅仅在用到的时候才打包,没用到的模块都被干掉,打包的整体体积变小)
    • 响应式系统 (proxy)
      • vue2中采用 defineProperty来劫持整个对象,然后进行深度遍历所有属性,给每个属性添加getter和setter,实现响应式
    • vue3采用proxy重写了响应式系统,因为proxy可以对整个对象进行监听,所以不需要深度遍历
      • 可以监听动态属性的添加
      • 可以监听到数组的索引和数组length属性
      • 可以监听删除属性

    Vue3.0的设计目标是什么?做了哪些优化?

    Vue3.0 所采用的 Composition Api 与 Vue2.x 使用的 Options Api 有什么不同?

    webpack

    说说Webpack的热更新是如何做到的?原理是什么?

    有哪些常⻅的Loader?

    • file-loader:把⽂件输出到⼀个⽂件夹中,在代码中通过相对 URL 去引⽤输出的⽂件
    • url-loader:和 file-loader 类似,但是能在⽂件很⼩的情况下以 base64 的⽅式把⽂件内容注⼊到代码中去
    • source-map-loader:加载额外的 Source Map ⽂件,以⽅便断点调试
    • image-loader:加载并且压缩图⽚⽂件
    • babel-loader:把 ES6 转换成 ES5
    • css-loader:加载 CSS,⽀持模块化、压缩、⽂件导⼊等特性
    • style-loader:把 CSS 代码注⼊到 JavaScript 中,通过 DOM 操作去加载 CSS。
    • eslint-loader:通过 ESLint 检查 JavaScript 代码

    有哪些常⻅的Plugin?

    • define-plugin: 定义一些环境变量
    • html-webpack-plugin:简化html文件创建
    • uglifyjs-webpack-plugin:通uglifyjs压缩ES6代码
    • webpack-parallel-uglify-plugin:多核压缩,提高压缩速度
    • webpack-bundle-analyzer: 可视化webpack输出⽂件的体积
    • mini-css-extract-plugin: CSS提取到单独的⽂件中,⽀持按需加载

    webpack的构建流程是什么?

    Webpack 的运⾏流程是⼀个串⾏的过程,从启动到结束会依次执⾏以下流程:

    1. 初始化参数:从配置⽂件和 Shell 语句中读取与合并参数,得出最终的参数;
    2. 开始编译:⽤上⼀步得到的参数初始化 Compiler 对象,加载所有配置的插件,执⾏对象的 run ⽅法开始执⾏编 译;
    3. 确定⼊⼝:根据配置中的 entry 找出所有的⼊⼝⽂件;
    4. 编译模块:从⼊⼝⽂件出发,调⽤所有配置的 Loader 对模块进⾏翻译,再找出该模块依赖的模块,再递归本步骤 直到所有⼊⼝依赖的⽂件都经过了本步骤的处理;
    5. 完成模块编译:在经过第4步使⽤ Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的 依赖关系;
    6. 输出资源:根据⼊⼝和模块之间的依赖关系,组装成⼀个个包含多个模块的 Chunk,再把每个 Chunk 转换成⼀个 单独的⽂件加⼊到输出列表,这步是可以修改输出内容的最后机会;
    7. 输出完成:在确定好输出内容后,根据配置确定输出的路径和⽂件名,把⽂件内容写⼊到⽂件系统。 在以上过程中,Webpack 会在特定的时间点⼴播出特定的事件,插件在监听到感兴趣的事件后会执⾏特定的逻辑,并 且插件可以调⽤ Webpack 提供的 API 改变 Webpack 的运⾏结果。

    在以上过程中,Webpack 会在特定的时间点⼴播出特定的事件,插件在监听到感兴趣的事件后会执⾏特定的逻辑,并 且插件可以调⽤ Webpack 提供的 API 改变 Webpack 的运⾏结果。

    如何⽤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插件来进⾏公共模块抽取,利⽤浏览器缓存可以⻓期缓存这些⽆需频繁变动的 公共代码

    如何提⾼webpack的打包速度?

    • happypack: 利⽤进程并⾏编译loader,利⽤缓存来使得 rebuild 更快,遗憾的是作者表示已经不会继续开发此项⽬,类 似的替代者是thread-loader
    • 外部扩展(externals): 将不怎么需要更新的第三⽅库脱离webpack打包,不被打⼊bundle中,从⽽减少打包时间,⽐ 如jQuery⽤script标签引⼊
    • dll: 采⽤webpack的 DllPlugin 和 DllReferencePlugin 引⼊dll,让⼀些基本不会改动的代码先打包成静态资源,避免 反复编译浪费时间
    • 利⽤缓存: webpack.cache 、babel-loader.cacheDirectory、 HappyPack.cache 都可以利⽤缓存提⾼rebuild效率
    • 缩⼩⽂件搜索范围: ⽐如babel-loader插件,如果你的⽂件仅存在于src中,那么可以 include: path.resolve(__dirname, 'src') ,当然绝⼤多数情况下这种操作的提升有限,除⾮不⼩⼼build了node_modules⽂件

    如何提⾼webpack的构建速度?

    1. 多⼊⼝情况下,使⽤ CommonsChunkPlugin 来提取公共代码
    2. 通过 externals 配置来提取常⽤库
    3. 利⽤ DllPlugin 和 DllReferencePlugin 预编译资源模块 通过 DllPlugin 来对那些我们引⽤但是绝对不会修改的npm 包来进⾏预编译,再通过 DllReferencePlugin 将预编译的模块加载进来。
    4. 使⽤ Happypack 实现多线程加速编译
    5. 使⽤ webpack-uglify-parallel 来提升 uglifyPlugin 的压缩速度。 原理上 webpack-uglify-parallel 采⽤了多核并⾏ 压缩来提升压缩速度
    6. 使⽤ Tree-shaking 和 Scope Hoisting 来剔除多余代码

    分别介绍bundle,chunk,module是什么

    • bundle: 是由webpack打包出来的⽂件
    • chunk:代码块,⼀个chunk由多个模块组合⽽成,⽤于代码的合并和分割
    • module:是开发中的单个模块,在webpack的世界,⼀切皆模块,⼀个模块对应⼀个⽂件,webpack会从配置的 entry中递归开始找出所有依赖的模块

    Loader和Plugin的不同?

    不同的作⽤

    • Loader直译为"加载器"。Webpack将⼀切⽂件视为模块,但是webpack原⽣是只能解析js⽂件,如果想将其他⽂件 也打包的话,就会⽤到 loader 。 所以Loader的作⽤是让webpack拥有了加载和解析⾮JavaScript⽂件的能⼒。
    • Plugin直译为"插件"。Plugin可以扩展webpack的功能,让webpack具有更多的灵活性。 在 Webpack 运⾏的⽣命 周期中会⼴播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。

    不同的⽤法:

    • Loader在 module.rules 中配置,也就是说他作为模块的解析规则⽽存在。 类型为数组,每⼀项都是⼀ 个 Object ,⾥⾯描述了对于什么类型的⽂件( test ),使⽤什么加载( loader )和使⽤的参数( options )
    • Plugin在 plugins 中单独配置。 类型为数组,每⼀项是⼀个 plugin 的实例,参数都通过构造函数传⼊。

    稍微看下 webpack 源码, 能看到编写的 plugin 怎么工作的

    实操

    实现promise.all

    const promiselist = [p1, p2, p3]
    Promise.all = function(promiseList) {
        return new Promise((resolve, reject) = {
            const newList= []
            promiseList.forEach(p = {
                p.then(res = {
                    newList.push(res)
                    if (newList.length == promiseList.length) {
                        resolve(newList)
                    }
                }, (err) = {
                    // 这里也可以将错误的promise加入数组, 最终返回我
                })
            })
        })
    }
    //  promise 的allset   跟上面的一样, 就是把catch里面的err 也加入到数组中, 然后返回
    

    实现一个数组的去重

    
          // 数组的方式
          var arr4 = [
            { name: 'a', id: 1 },
            { name: 'a', id: 2 },
            { name: 'b', id: 3 },
            { name: 'c', id: 4 },
            { name: 'c', id: 6 },
            { name: 'b', id: 6 },
            { name: 'd', id: 7 }];
          // array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
          // (function(必选初始值或计算结束返回值, 必选当前元素, 可选索引, 可选原数组),可选函数初始值)
          var obj = {};
          function deWeightFour() {
            arr4 = arr4.reduce(function (a, b) {
              obj[b.name] ? '' : obj[b.name] = true && a.push(b);
              return a;
            }, [])
            return arr4;
          }
          var newArr4 = deWeightFour();
          console.log('%c%s', 'color:red;', '方法一:es5,newArr4', newArr4);
    
    
    
          // map的方式
          var arr3 = [{ name: 'a', id: 1 }, { name: 'a', id: 2 }, { name: 'b', id: 3 }, { name: 'c', id: 4 },
          { name: 'c', id: 6 }, { name: 'b', id: 6 }, { name: 'd', id: 7 }];
          let deWeightThree = () => {
            let name = 'name';
            let map = new Map();
            for (let item of arr3) {
              if (!map.has(item.name)) {
                map.set(item.name, item);
              }
            }
            return [...map.values()];
          }
          let newArr3 = deWeightThree();
          console.log('%c%s', 'color:red;', '方法二:es6,newArr3', newArr3);
    
    

    实现一个 loader/plugin

    模拟new

    new操作符做了这些事:

    • 它创建了⼀个全新的对象
    • 它会被执⾏[[Prototype]](也就是proto)链接
    • 它使this指向新创建的对象
    • 通过new创建的每个对象将最终被[[Prototype]]链接到这个函数的prototype对象上
    • 如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调⽤将返回该对象引⽤
          function objectFactory() {
            const obj = new Object(); // 创建一个对象
            const Constructor = [].shift.call(arguments);
            obj.__proto__ = Constructor.prototype;  // 把原来函数的prototype 指向新的函数
            const ret = Constructor.apply(obj, arguments); // 改变this的指向
            return typeof ret === "object" ? ret : obj; // 返回一个新的对象
          }
    

    实现instanceOf

          // 模拟 instanceof 
          function instance_of(L, R) { //L 表示左表达式,R 表示右表达式 
            var O = R.prototype; // 取 R 的显示原型 
            L = L.__proto__; // 取 L 的隐式原型 
            while (true) {
              if (L === null) return false;
              if (O === L) // 这⾥重点:当 O 严格等于 L 时,返回 true 
                return true; L = L.__proto__;
            }
          }
    

    柯里化

    • 把接受多个参数的函数转换成接受一个单一参数的函数
    // 非函数柯里化
    var add = function (x,y) {
        return x+y;
    }
    add(3,4) //7
    
    // 函数柯里化
    var add2 = function (x) {
        //**返回函数**
        return function (y) {
            return x+y;
        }
    }
    add2(3)(4) //7
    

    手写一个ajax请求

    const request = new XMLHttpRequest() //创建
    request.onreadystatechange = function(e){  // 接收及主体逻辑
        if(request.readyState === 4){ // 整个请求过程完毕
            if(request.status >= 200 && request.status <= 300){
                console.log(request.responseText) // 服务端返回的结果
            }else if(request.status >=400){
                console.log("错误信息:" + request.status)
            }
        }
    }
    request.open('POST','http://xxxx') // 建立连接
    request.send() // 发送 
    

    实现一个深拷贝

    function deepClone(obj, hash = new WeakMap()) {
      if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
      if (obj instanceof Date) return new Date(obj);
      if (obj instanceof RegExp) return new RegExp(obj);
      // 可能是对象或者普通的值  如果是函数的话是不需要深拷贝
      if (typeof obj !== "object") return obj;
      // 是对象的话就要进行深拷贝
      if (hash.get(obj)) return hash.get(obj);
      let cloneObj = new obj.constructor();
      // 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
      hash.set(obj, cloneObj);
      for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
          // 实现一个递归拷贝
          cloneObj[key] = deepClone(obj[key], hash);
        }
      }
      return cloneObj;
    }
    

    实现一个 防抖 节流

    • **防抖 **

      • 定义: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时
    • 举例理解: 我们用手指一直按住一个弹簧,它将不会马上弹起直到你松手为止

    • 使用场景:

      • 按钮重复点击
      • 输入框连续输入
      • 判断scroll是否滑到底部
    // 防抖
        debounce(func, wait) {
          let timeout;
          return function () {
            let context = this; // 保存this指向
            let args = arguments; // 拿到event对象
            clearTimeout(timeout);
            timeout = setTimeout(function () {
              func.apply(context, args);
            }, wait);
          };
        },
    
    • 节流

    • 基本概念: 在规定的时间范围内相同的操作触发多次只执行一次

      • DOM拖拽

      • Canvas画笔

      • 窗口resize

        // 节流
            throttled(fn, time) {
              let _arguments = arguments;
              let canRun = true;
              return function () {
                if (!canRun) return;
                canRun = false;
                setTimeout(() => {
                  fn.call(this, _arguments);
                  canRun = true;
                }, time);
              };
            },
      

    实现 call apply bind

    // 实现call
    Function.prototype.call = function(obj, ...reg) {
        if (!obj) {
            this = null
        }
        obj.fn = this
        const result = obj.fn(...reg)
        delete obj.fn
        return result
    }
    // 实现apply
    Function.prototype.apply = function(obj, ...reg) {
        if (!obj) {
            this = null
        }
        obj.fn = this
        const result = obj.fn([...reg])
        delete obj.fn
        return result
    }
    // 实现bind 借助上面的call 或者apply
    Function.prototype.bind = function(obj, ...reg) {
        if (!obj) {
            this = null
        }
        obj.fn = this
        return function(...parmas) {
            const result = obj.fn.call(...[...reg, ...parmas])
            delete obj.fn
            return result
        }
    }
    

    实现发布订阅

    class Subscribe {
        // 注意这里是赋值语句, 是实例上的属性
        eventHandlerMap = {}
        on(eventName, callback) {
            if (this.eventHandlerMap[eventName]) {
                this.eventHandlerMap[eventName].push(callback)
            } else {
                this.eventHandlerMap[eventName] = [callback]
            }
        }
        off(eventName, callback) {
            if (this.eventHandlerMap[eventName]) {
                this.eventHandlerMap[eventName] = this.eventHandlerMap[eventName].filter(f = f !== callback)
            } else {
                return false
            }
        }
        emit(eventName, params) {
            if (this.eventHandlerMap[eventName]) {
                this.eventHandlerMap[eventName].forEach(f = {
                    f && f(params)
                })
            } else {
                return false
            }
        }
    }
    

    es5 的继承,(手写) 直接写(原型 + 寄生) 的继承方式

    • 原型继承(相当于浅拷贝)
    • 寄生式(封装继承过程的函数,该函数在内部以某种形式来做增强对象,最后返回对象
    • 组合式继承(寄生组合式继承, 即通过借用构造函数来继承属性, 在原型上添加共用的方法, 通过寄生式实现继承.
    // 原型继承
    function object(o) {
      var G = function () {};
      G.prototype = o;
      return new G();
    }
    
    var obj = {
      name: 'ghostwu',
      age: 22,
      show: function () {
        return this.name + ',' + this.age;
      },
    };
    var obj2 = object(obj);
    console.log(obj2.name, obj.age, obj.show());
    
    // 寄生式继承
    function object(o) {
      var G = function () {};
      G.prototype = o;
      return new G();
    }
    
    function CreateObj(srcObj) {
      var dstObj = object(srcObj);
      dstObj.sayName = function () {
        return this.userName;
      };
      return dstObj;
    }
    
    var obj = {
      userName: 'ghostwu',
    };
    var obj2 = CreateObj(obj);
    console.log(obj2.sayName()); //ghostwu
    
    // 寄生组合式
    function Person(name, age, sex) {
      this.name = name;
      this.age = age;
      this.sex = sex;
    }
    Person.prototype.sayHi = function () {
      console.log('阿涅哈斯诶呦');
    };
    function Student(name, age, sex, score) {
      //借用构造函数:属性值重复的问题
      Person.call(this, name, age, sex);
      this.score = score;
    }
    //改变原型指向----继承
    Student.prototype = new Person(); //不传值
    Student.prototype.eat = function () {
      console.log('吃东西');
    };
    var stu = new Student('小黑', 20, '男', '100分');
    console.log(stu.name, stu.age, stu.sex, stu.score);
    stu.sayHi();
    stu.eat();
    var stu2 = new Student('小黑黑', 200, '男人', '1010分');
    console.log(stu2.name, stu2.age, stu2.sex, stu2.score);
    stu2.sayHi();
    stu2.eat();
    

    实现一个 JSON.parse

    var json = '{"name":"cxk", "age":25}';
    var obj = eval("(" + json + ")");// eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码。
    console.log(obj)