Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[笔记] - 我的面试回顾(高频问题点) #34

Open
zhaofeihao opened this issue May 4, 2020 · 0 comments
Open

[笔记] - 我的面试回顾(高频问题点) #34

zhaofeihao opened this issue May 4, 2020 · 0 comments
Labels

Comments

@zhaofeihao
Copy link
Owner

[TOC]

JS

1. 用过的ES6新特性

分别能答出 是啥咋用解决了什么问题

是啥 咋用 解决什么问题/新增的特性
let、const ES6新增的用来声明变量的关键字 ① let 声明一个后续可改变的变量 ② const声明一个只读的常量 ① 为 JavaScript 新增了块级作用域,杜绝了变量在声明前可使用的方式,防止内层变量泄露为全局变量,如for循环的计数器 ② 使用let、const全局作用域声明的变量不会挂到window上③不能重复声明
箭头函数 函数表达式的新语法,创建一个函数 ① 使用“箭头”(=>)定义函数 ② 箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回 ①箭头函数没有自己的this,其this指向离他最近的非箭头函数的this ② 不可作为构造函数 ③无arguments对象,使用rest参数替代
class 语法 构造函数的语法糖 class关键字、constructor、原型方法是如何一一对应的 更符合面向对象的思想
ES6 模块化 ES6 在语言标准的层面上,实现了模块功能,可以取代 CommonJS 和 AMD 规范 ① export 可以导出变量、函数或类,与其对应的值是动态绑定关系 ② import语句会执行所加载的模块;import命令具有提升效果,会提升到整个模块的头部,首先执行 ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系。可以说一下前端模块化发展历史
Promise Promise 是异步编程的一种解决方案,它是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果 Promise对象是一个构造函数,用来生成Promise实例,构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。这两个函数由 JavaScript 引擎提供,不用自己部署。Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。 ①对象的状态不受外界影响,只有异步操作的结果,可以决定当前是哪一种状态 ② 一旦状态改变,就不会再变

2. Promise

Promise为我们解决了什么问题?

在传统的异步编程中,如果异步之间存在依赖关系,我们就需要通过层层嵌套回调来满足这种依赖,如果嵌套层数过多,可读性和可维护性都变得很差,产生所谓“回调地狱”,而Promise将回调嵌套改为链式调用,增加可读性和可维护性。

Promise的调用流程:

  • Promise的构造方法接收一个executor(),在new Promise()时就立刻执行这个executor回调
  • executor()内部的异步任务被放入宏/微任务队列,等待执行
  • then()被执行,收集成功/失败回调,放入成功/失败队列
  • executor()的异步任务被执行,触发resolve/reject,从成功/失败队列中取出回调依次执行

这是个「观察者模式」,这种 收集依赖 -> 触发通知 -> 取出依赖执行 的方式,被广泛运用于观察者模式的实现,在Promise里,执行顺序是then收集依赖 -> 异步触发resolve -> resolve执行依赖

1. BAT前端经典面试问题:史上最最最详细的手写Promise教程
2. 八段代码彻底掌握 Promise - 掘金

3. 事件循环(输出顺序)

JS引擎在执行JS脚本的时候是单线程的,脚本会顺序放入执行栈中依次解析并执行,如果遇到setTimeout这种延时任务,会交给Web APIs线程处理,web api 等到延时结束后把回调函数推进宏任务队列,遇到promise这种异步任务时,会把它推进微任务队列。宏微任务也有其执行规则,每次执行完一个宏任务,就要把微任务队列中的所有微任务取出来执行,微任务执行完毕,开始检查渲染。这就是JavaScript的异步执行机制,也叫事件循环。

宏任务包括:

  • I/O
  • setTimeout / setInterval / setImemediate
  • requestAnimationFrame

微任务包括:

  • promise / .then /.catch /.finally
  • mutationObserver
  • process.nextTick

3234e5fc6baa13e07ebec247561ccadb.png

图解搞懂JavaScript引擎Event Loop
从面试题看 JS 事件循环与 macro micro 任务队列(带async/await)
微任务、宏任务与Event-Loop
深入理解NodeJS事件循环机制

4. 对this的理解

this是动态变化的,谁调用它,就指向哪里,this有四种绑定规则:

  • 默认绑定(严格/非严格模式)严格是undefined;非严格独立函数调用时this指向window。
  • 隐式绑定,当函数引用有上下文对象时,隐式绑定会把函数中的this绑定到这个上下文对象。
  • 显示绑定,通过call()或者apply()方法,第一个参数是一个对象。
  • new绑定,如果有new关键字,this指向new出来的那个对象。
作为一个函数调用
    var name = "windowsName";
    function a() {
        var name = "Cherry";

        console.log(this.name);          // windowsName

        console.log("inner:" + this);    // inner: Window
    }
    a();
    console.log("outer:" + this)         // outer: Window
函数作为方法调用
    var name = "windowsName";
    var a = {
        name: "Cherry",
        fn : function () {
            console.log(this.name);      // Cherry
        }
    }
    a.fn();
使用构造函数调用函数
// 构造函数:
function myFunction(arg1, arg2) {
    this.firstName = arg1;
    this.lastName  = arg2;
    this.sayName = function(){
        console.log(this.lastName)
    }
}

// This    creates a new object
var a = new myFunction("Li","Cherry");
a.sayName();
作为函数方法调用函数
    var name = "windowsName";

    function fn() {
        var name = 'Cherry';
        innerFunction();
        function innerFunction() {
            console.log(this.name);      // windowsName
        }
    }

    fn()
参考

掘金 - this、apply、call、bind

5. 讲解闭包及其作用

什么是闭包?

简单讲,闭包就是指有权访问另一个函数作用域中的变量的函数。

如何产生一个闭包?

创建闭包最常见方式,就是在一个函数内部创建另一个函数并返回。
闭包的作用域链包含着它自己的作用域,以及包含它的函数的作用域和全局作用域。

闭包的注意事项:
通常,函数的作用域及其所有变量都会在函数执行结束后被销毁。但是,在创建了一个闭包以后,这个函数的作用域就会一直保存到闭包不存在为止。

闭包的应用:
设计私有的方法和变量。
函数柯里化

参考

闭包

6. 实现new

7. 实现 bind

8. 隐式类型转换

[] == ![] !? 浅析JS的类型系统和隐式类型转换
JavaScript 运算符规则与隐式类型转换详解

9. 浏览器事件相关

e.target和e.currentTarget的区别?

currentTarget始终是监听事件者,而target是事件的真正发出者。

load 与 DOMContentLoaded
DOMContentLoaded

当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架的完成加载。

意思是HTML下载、解析完毕之后就触发。

load

load 应该仅用于检测一个完全加载的页面 当一个资源及其依赖资源已完成加载时,将触发load事件。

意思是页面的html、css、js、图片等资源都已经加载完之后才会触发 load 事件。

JS中事件冒泡与捕获

10. 跨域

跨域的几种解决方式。
...

后端如何设置cors?

app.use('*',function (req, res, next) {
  res.header('Access-Control-Allow-Origin', '*'); //这个表示任意域名都可以访问,这样写不能携带cookie了。
//res.header('Access-Control-Allow-Origin', 'http://www.baidu.com'); //这样写,只有www.baidu.com 可以访问。
  res.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');
  res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');//设置方法
  if (req.method == 'OPTIONS') {
    res.send(200); // 意思是,在正常的请求之前,会发送一个验证,是否可以请求。
  }
  else {
    next();
  }
});

11. 对 async/await 的理解

首先,async函数返回的是⼀个Promise对象;

然后,async 函数内部 return 返回的值。会成为 then 方法回调函数的参数; 如果 async 函数内部抛出异常,则会导致返回的 Promise 对象状态变为 reject 状态。抛出的错误而会被 catch 方法回调函数接收到。

async 函数返回的 Promise 对象,必须等到内部所有的 await 命令的 Promise 对象执行完,才会发生状态改变.

正常情况下,await 命令后面跟着的是 Promise ,如果不是的话,也会被转换成一个 立即 resolvePromise.

理解 async/await

CSS

1. flex

首先,采用 Flex 布局的元素,称为 Flex 容器(flex container),简称"容器"。它的所有子元素自动成为容器成员,称为 Flex 项目(flex item),简称"项目"。

其次,容器默认存在两根轴:水平的主轴(main axis)和垂直的交叉轴(cross axis)。

最后,项目默认沿主轴排列。单个项目占据的主轴空间叫做main size,占据的交叉轴空间叫做cross size。

容器属性,指定容器内元素的排列、对齐方式

  • flex-direction
  • flex-wrap
  • flex-flow
  • justify-content
  • align-items
  • align-content

项目属性,指定元素本身的排列、对齐方式

  • order
  • flex-grow
  • flex-shrink
  • flex-basis
  • flex(该属性有两个快捷值:auto (1 1 auto) 和 none (0 0 auto))
  • align-self
参考

1. Flex 布局教程:语法篇
2. Flex 布局教程:实例篇

2. 浮动布局

float: left;  /* 表明元素必须浮动在其所在的块容器左侧。*/
float: right;  /*表明元素必须浮动在其所在的块容器右侧*/
float: none;  /*表明元素不进行浮动*/
float: inline-start;  /*表明元素必须浮动在其所在块容器的开始一侧,在ltr脚本中是左侧,在rtl脚本中是右侧。*/
float: inline-end;  /*表明元素必须浮动在其所在块容器的结束一侧,在ltr脚本中是右侧,在rtl脚本中是左侧。*/

当一个元素浮动之后,它会被移出正常的文档流,然后向左或者向右平移,一直平移直到碰到了所处的容器的边框,或者碰到另外一个浮动的元素。

也就是说,right 会挨着 left,而不是重叠。

<div class="left" style="float:left;background-color:#000;width:100px;height:110px"></div>
<div class="right" style="float:left;background-color:#ccc;width:110px;height:100px"></div>

image

这种情况与 position:absolute 正好相反

<div class="left" style="position:absolute;background-color:#000;width:100px;height:110px"></div>
<div class="right" style="position:absolute;background-color:#ccc;width:110px;height:100px"></div>

这时候,right 会覆盖在 left 上面。

但是,有时可能需要强制一个浮动元素移至任何浮动元素下方,那么使用clear: both;
image

参考

1. MDN - float

3. position

position是一种描述物体相对位置的艺术,它的核心是「参考坐标系」的选择

描述
static 默认值。没有定位,元素出现在正常的流中(忽略 top, bottom, left, right 或者 z-index 声明)。
fixed 生成绝对定位的元素,相对于浏览器窗口进行定位。元素的位置通过 "left", "top", "right" 以及 "bottom" 属性进行规定。
relative 生成相对定位的元素,相对于其正常位置进行定位。因此,"left:20" 会向元素的 LEFT 位置添加 20 像素。
absolute 生成绝对定位的元素,相对于 static 定位以外的第一个父元素进行定位。元素的位置通过 "left", "top", "right" 以及 "bottom" 属性进行规定。
inherit 规定应该从父元素继承 position 属性的值。
sticky 元素根据正常文档流进行定位,然后相对它的最近滚动祖先和最近块级祖先,包括table-related元素,基于top, right, bottom, 和 left的值进行偏移。偏移值不会影响任何其他元素的位置。
参考

1. MDN - position
2. 前端重构范式之 position

4. css各种布局

两栏布局
  1. 第一种方式 --- 浮动
.outer1 .left {
    width: 200px;
    float: left;
}
.outer1 .right {
    width: auto;
    margin-left: 200px;
}

<div class="outer outer1">
    <div class="left">1-left</div>
    <div class="right">1-right</div>
</div>
  1. flex
.outer2 {
   display: flex;
}
.outer2 .left {
   flex: 0 0 200px; /* flex-grow: 0;flex-shrink:0; flex-basis:200px; */
}
.outer2 .right {
   flex: auto;
}

<div class="outer outer2">
    <div class="left">2-left</div>
    <div class="right">2-right</div>
</div>
  1. 绝对定位
.outer3 {
   position: relative;
}
.outer3 .left {
   position: absolute;
   width: 200px;
}
.outer3 .right {
   margin-left: 200px;
}

<div class="outer outer3">
   <div class="left">3-left</div>
   <div class="right">3-right</div>
</div>
三栏布局
  1. 绝对定位
.outer1 {
   position: relative;
}
.outer1 .left {
   position: absolute;
   width: 100px;
}
.outer1 .middle {
   margin: 0 200px 0 100px;
}
.outer1 .right {
   position: absolute;
   width: 200px;
   top: 0;
   right: 0;
}
/*注意:左右分别使用绝对定位,中间设置外边距*/

<div class="outer outer1">
   <div class="left">1-left</div>
   <div class="middle">1-middle</div>
   <div class="right">1-right</div>
</div>
  1. flex
.outer2 {
   display: flex;
}
.outer2 .left {
   flex: 0 0 100px;
}
.outer2 .middle {
   flex: auto;
}
.outer2 .right {
   flex: 0 0 200px;
}

<div class="outer outer2">
   <div class="left">2-left</div>
   <div class="middle">2-middle</div>
   <div class="right">2-right</div>
</div>
  1. 浮动
.outer3 .left{
   float: left;
   width: 100px;
}
.outer3 .right {
   float: right;
   width: 200px;
}
.outer3 .middle {
   margin: 0 200px 0 100px;
}

<div class="outer outer3">
   <div class="left">3-left</div>
   <div class="right">3-right</div>
   <div class="middle">3-middle</div>
</div>
圣杯布局
<style>
*{
        box-sizing:content-box;/* 伸缩项目自动box-sizing:border-box,所以需调整为content-box */
        margin:0;
        padding:0;
    }

    body{
        display:flex;
        flex-direction:column;/* 头、中部、脚纵向显示 */
    }

    header,footer{
        flex:0 0 50px;/* 头、脚尺寸固定,不放大、不缩小 */
        background:#3f3f3f;
    }

    .main{
        display:flex;

        /* 
        flex:1 == 1 1 auto:剩余空间放大比例(flex-grow)  空间不足缩小比例(flex-shrink)    分配多余空间之前项目占据的主轴空间(flex-basis)
        flex:1指的是:中部区域自由伸缩
        auto指的是项目本来大小,因未给main设置高度,main高度由子元素最高者决定,若子元素高度为0,则main高度为0
        块级元素未主动设置高度或未被子元素撑起高度,浏览器默认为块级元素分配高度为0。
        */
        flex:1;
    }

   .content{
        background:red;
        height:1000px;

        /* 
        横向中间内容区自适应,即使未指定宽度,但会分配宽度 
        块级元素未主动设置宽度或未被子元素撑起宽度,浏览器默认为块级元素分配宽度为可使用的全部宽度,比如全屏宽。
        */
        flex:1;
   }
   .left,.right{
        height:800px; /*不设置高度,则自动撑满整个高度*/
        background:blue;
        flex:0 0 100px;/* 左右两列固定宽 */
   }

   .left{
        order:-1;/* 让left居于左侧 */
   }
</style>

<html>
<body>
    <header></header>
    <div class="main">
        <div class="content">中间栏要在放在文档流前面以优先渲染。</div>
        <div class="left"></div>
        <div class="right"></div>
    </div>
    <footer></footer>
</body>
</html>
水平垂直居中
  1. 定宽
.box {
    position: absolute;
    top: 50%;
    left: 50%;
    margin-left: -50px;
    margin-top: -25px;
}
  1. 不定宽
body {
    display: flex;
    justify-content: center;
    align-items: center;
}
  1. transform:tranlate

5. 伪类和伪元素的区别

伪类

伪类用于当已有元素处于的某个状态时,为其添加对应的样式,这个状态是根据用户行为而动态变化的。比如说,当用户悬停在指定的元素时,我们可以通过:hover 来描述这个元素的状态。虽然它和普通的 css 类相似,可以为已有的元素添加样式,但是它只有处于 dom 树无法描述的状态下才能为元素添加样式,所以将其称为伪类,伪类的操作对象是文档树中已有的元素。

li:first-child {}

p:first-letter {}

a:hover {}
伪元素

伪元素用于创建一些不在文档树中的元素,并为其添加样式。比如说,我们可以通过:before(或::before,CSS3标准) 来在一个元素前增加一些文本,并为这些文本添加样式。虽然用户可以看到这些文本,但是这些文本实际上不在文档树中。

参考

Alloy-team - 总结伪类与伪元素

网络

1. 对缓存的了解

分为强缓存和协商缓存:
1)浏览器在加载资源时,先根据这个资源的一些http header(expries和cache-control)判断它是否命中强缓存,强缓存如果命中,浏览器直接从自己的缓存中读取资源,不会发请求到服务器。比如某个css文件,如果浏览器在加载它所在的网页时,这个css文件的缓存配置命中了强缓存,浏览器就直接从缓存中加载这个css,连请求都不会发送到网页所在服务器;

no-cache
在发布缓存副本之前,强制要求缓存把请求提交给原始服务器进行验证(协商缓存验证)。
no-store
缓存不应存储有关客户端请求或服务器响应的任何内容,即不使用任何缓存。

2)当强缓存没有命中的时候,浏览器一定会发送一个请求到服务器,通过服务器端依据资源的另外一些http header(last-modified和etag)验证这个资源是否命中协商缓存,如果协商缓存命中,服务器会将这个请求返回,但是不会返回这个资源的数据,而是告诉客户端可以直接从缓存中加载这个资源,于是浏览器就又会从自己的缓存中去加载这个资源;

3)强缓存与协商缓存的共同点是:如果命中,都是从客户端缓存中加载资源,而不是从服务器加载资源数据;区别是:强缓存不发请求到服务器,协商缓存会发请求到服务器。

4)当协商缓存也没有命中的时候,浏览器直接从服务器加载资源数据。
(精辟)流云诸葛 - 浏览器缓存知识小结及应用
彻底理解浏览器的缓存机制

2. HTTP 状态码

fc54219ba5a960e00d218cf633716197.png

3. 对 https 的了解

首先讲HTTP的缺点:

  • 通信使用明文加密,容易遭到窃听
  • 不验证对方身份,容易遭到伪装
  • 无法确保报文完整性,容易遭到篡改

https的出现就是为了解决这三个问题:

https = 加密 + 认证 + 完整性保护

  • 采用非对称加密的方式实现秘钥的交换,后续混合加密,实现了机密性
  • 采用摘要算法(MD5/SHA-2)确保报文完整性
  • 采用数字签名+第三方证书机构实现身份认证

掘金 - HTTP和HTTPS详解
掘金 - 看完这篇 HTTPS,和面试官扯皮就没问题了

4. 讲解http1.1 、http2.0、https

http1.0
  • 超文本传输协议,不仅可以传输文字,还可传输视频、图片、二进制文件
  • 增加POST和HEAD命令
  • 增加状态码、缓存等
http1.0存在问题

每个tcp连接只能发送一个请求,下一个请求发送需要重新建立tcp连接,性能很差

http1.1

针对http1.0的问题,http1.1做了很大的优化

长连接

http1.1默认connection:keep-alive,一个tcp连接可以持续传输请求。

管道机制

因为http1.1支持长连接,发送一个请求之后还可以发送第二个请求,如果第二个请求要等待第一个请求返回之后才发送,效率就很低,所以引入了管道机制,多个请求可以按顺序同时发送,响应按顺序返回

Content-Length

因为一个tcp可以同时发送多个请求,我们需要一个字段来区分多个请求,Content-length表示当前请求响应共有多少字段,超过这个值的响应就不是该请求的响应

分块传输

因为http1.1是按顺序发送请求和响应,如果一个顺序靠前的请求响应时间较长,会阻碍后面请求的响应,造成很大的延迟,这就是线头阻塞。故http1.1支持分块传输:Transfer-Encoding: chunked,每个响应头前都有一个16进制数字表示该块儿大小,如果为0表示本次分块数据传输完成

其他功能

新增PUT、PATCH、HEAD、 OPTIONS、DELETE请求方法

客户端请求头新增Host字段,有了Host字段,就可以将请求发往同一台服务器上的不同网站,为虚拟主机的兴起打下了基础。

http1.1存在问题

主要还是线头阻塞问题,为了避免这个问题,只有两种方法:一是减少请求数,二是同时多开持久连接。这导致了很多的网页优化技巧,比如合并脚本和样式表、将图片嵌入CSS代码、域名分片(domain sharding)等等。如果HTTP协议设计得更好一些,这些额外的工作是可以避免的。

http1.1和http1.0对比
  • 缓存处理:http1.0中只有if-Modified-Since、Expires,http1.1中增加了E-tag、if-None-match、等
  • 优化带宽和网络请求:如果我们的请求的只是一个资源的一部分,http1.0会全部返回整个资源,造成带宽的浪费,http1.1中增加了range字段,允许请求资源的某部分
  • 错误通知机制:新增了24个错误状态码
  • Host名增加:http1.0认为域名和主机是一一对应的,http1.1增加Host字段,支持多个域名对应一个机器,为虚拟主机的发展奠定了基础
  • 长连接:支持长连接和管道机制,一定程度上弥补了http1.0每次请求都要重建tcp连接的缺陷
http2.0/SPDY

http2.0是基于SPDY的,两者没有太大的区别,主要是header压缩方式以及http2.0支持明文传输。

对比http1.1,主要区别有以下几个

多路复用

http2.0一个tcp连接支持多个stream流同时传输
546da8c3ae827b1ff00cc658d15f33e2.png

请求优先级

多路复用可能会带来一个新的问题,多个请求并发可能导致关键请求被阻塞,故http2.0支持request设置优先级

header压缩

header中的一些信息是重复的,采用合适的压缩算法减少请求大小

服务端推送

正常文档流请求style.css后服务端会主动推送script.js到客户端,服务端请求的时候直接从缓存读取

浏览器

1. 浏览器本地存储

浏览器的本地存储主要分为Cookie、localStorage、sessionStorage 和 IndexedDB。

cookie

Cookie 本质上就是浏览器里面存储的一个很小的文本文件,向同一个域名下发送请求,都会携带相同的 Cookie,服务器拿到 Cookie 进行解析,便能拿到客户端的状态。

cookie 就是用来做状态存储的,但是也有缺陷:

  • 容量缺陷
  • 性能缺陷
  • 安全缺陷
localStorage

localStorage有一点跟Cookie一样,在同一个域名下,会存储相同的一段localStorage。

相对Cookie的区别:

  • 容量。localStorage 的容量上限为5M
  • 只存在客户端,默认不参与与服务端的通信
  • 接口封装,有get set 函数

应用场景:

  • 利用localStorage的较大容量和持久特性,可以利用localStorage存储一些内容稳定的资源,比如官网的logo,存储Base64格式的图片资源,因此利用localStorage
sessionStorage

与localStorage基本相同。

但sessionStorage和localStorage有一个本质的区别,那就是前者只是会话级别的存储,并不是持久化存储。会话结束,也就是页面关闭,这部分sessionStorage就不复存在了。

应用场景:

  • 可以用它对表单信息进行维护,将表单信息存储在里面,可以保证页面即使刷新也不会让之前的表单信息丢失。
  • 可以用它存储本次浏览记录。如果关闭页面后不需要这些记录,用sessionStorage就再合适不过了。事实上微博就采取了这样的存储方式。
indexedDB

IndexedDB是运行在浏览器中的非关系型数据库, 本质上是数据库,理论上容量是没有上限的。

重要特性:

  • 支持事务,存储二进制数据
  • 键值对存储
  • 异步操作
  • 受同源策略限制,即无法访问跨域的数据库
参考

浏览器灵魂之问 - 能不能说一说浏览器的本地存储?各自优劣如何?

2. 对前端路由的了解

何为前端路由?

简单的说,就是在保证只有一个 HTML 页面,且与用户交互时不刷新和跳转页面的同时,为 SPA 中的每个视图展示形式匹配一个特殊的 url。在刷新、前进、后退和SEO时均通过这个特殊的 url 来实现。

为实现这一目标,我们需要做到以下 2 点:

  • 改变 url 且不让浏览器向服务器发送请求。
  • 可以监听到 url 的变化

接下来要介绍的 hash 模式和 history 模式,就是实现了上面的功能.

hash 模式

这里的 hash 就是指 url 后的 # 号以及后面的字符。比如说 "www.baidu.com/#hashhash" ,其中 "#hashhash" 就是我们期望的 hash 值。
由于 hash 值的变化不会导致浏览器向服务器发送请求,而且 hash 的改变会触发 hashchange 事件,浏览器的前进后退也能对其进行控制,所以在 H5 的 history 模式出现之前,基本都是使用 hash 模式来实现前端路由。

history 模式

在 HTML5 之前,浏览器就已经有了 history 对象。但在早期的 history 中只能用于多页面的跳转:

history.go(-1);       // 后退一页
history.go(2);        // 前进两页
history.forward();     // 前进一页
history.back();      // 后退一页

在 HTML5 的规范中,history 新增了以下几个 API:

history.pushState();         // 添加新的状态到历史状态栈
history.replaceState();      // 用新的状态代替当前状态
history.state                // 返回当前状态对象
参考

「前端进阶」彻底弄懂前端路由

3. 前端安全(xss、csrf)

  • sql 注入
    要理解sql注入的场景,它的原理是什么,当前的数据库的解决方案是什么?

  • xss 攻击
    分为 DOM xss、反射型xss、存储型 xss

    常见的攻击场景,什么类型的网站容易被xss攻击,整个流程的原理是什么?
    恶意攻击者往Web页面里插入恶意Script代码,当用户浏览该页之时,其他用户看到这个包括恶意脚本的页面并执行,获取用户的cookie等敏感信息,从而达到恶意攻击用户的目的。
      预防措施:防止下发界面显示html标签,把</>等符号转义 。

  • csrf 攻击
    其实就是一个钓鱼网站,要理解为什么会收到攻击,应该采取什么策略进行防御。
    CSRF通过伪装来自受信任用户的请求来利用受信任的网站。用户C首先登录受信任网站A,并在本地生成Cookie。在不登出A的情况下,访问危险网站B。B要求访问站点A,浏览器会自动带上用户C的cookie,并且A站点也分辨不清好坏,B就可以冒充受信任用户C来进行操作。
      预防措施:请求中加入随机数,让钓鱼网站无法正常伪造请求。

  • cookie 安全
    要理解为什么用token,优势等

  • 密码安全
    主要是用户登陆,用户数据提交,加密,存入数据库的一整个流程

  • https
    https解决了什么问题

参考

1. 前端安全知识
2. 前端安全系列(二):如何防止CSRF攻击?

4. cookie

把cookie聊清楚

5. 结合跨域问题理解简单请求和非简单请求

CORS 简单请求+预检请求(彻底理解跨域)

框架及工具

1. 对 React 的理解

记住一个公式:UI = f(state)

React 的声明式编程,抛弃了直接操作DOM的方式,只关注数据变动,引入virtual-dom,使得DOM操作由框架完成,提升了可读性可维护性。遵循从高阶组件到低阶组件的单向数据流。

2. hooks、typescript

hooks 解决了什么问题
  • 想要复用一个有状态的组件太麻烦了
    react组件冗长且难以复用。尤其是那些写成class的组件,它们本身包含了状态(state),所以复用这类组件就变得很麻烦。
    官方推荐解决方案:render props 和 高阶组件
    但是这里又会出现嵌套层级太深的问题。
  • 生命周期钩子函数里的逻辑太乱了
    也会产生很多重复代码
  • 需要时刻关注组件的this指向
    比如说要手动绑定this
    写函数式组件的时候,随着组件越来越复杂,可能又要改成成有状态的组件
hooks的用法和注意事项

最简单的React Hooks入门级教程

参考

30分钟精通React Hooks

3. React 生命周期

react v16.3 之前的生命周期:
1833e217417d195e41de27815b891119.png

react 16.3 新引入了两个钩子:
ac3dcd8c287dae442028d0b168cfe174.png

componentDidMount 中通常都做些什么操作?

  • 接口请求
  • 依赖DOM的操作

4. constructor 中的 super(props)的作用

  • ES6 中,在子类的 constructor 中必须先调用 super 才能引用 this;
  • super(props)的目的:在constructor中可以使用this.props;
  • 根本原因是constructor会覆盖父类的constructor,导致你父类构造函数没执行,所以手动执行下。

如果要从另一个角度看的话:

假设在es5要实现继承,首先定义一个父类:

//父类
function sup(name) {
    this.name = name;
}
//定义父类原型上的方法
sup.prototype.printName = function (){
    console.log(this.name);
}

再定义他sup的子类,继承sup的属性和方法:

function sub(name, age){
    sup.call(this,name);    //调用call方法,继承sup超类属性
    this.age = age;
}    

sub.prototype = new sup();   //把子类sub的原型对象指向父类的实例化对象,这样即可以继承父类sup原型对象上的属性和方法
sub.prototype.constructor = sub;    //这时会有个问题子类的constructor属性会指向sup,手动把constructor属性指向子类sub
//这时就可以在父类的基础上添加属性和方法了
sub.prototype.printAge = function (){
    console.log(this.age);
}

这时调用父类生成一个实例化对象:

    let jack = new sub('feihao', 18);
    jack.printName() ;   //输出 : feihao
    jack.printAge();    //输出 : 18

这就是es5中实现继承的方法。
而在es6中实现继承:

  class sup {
        constructor(name) {
            this.name = name
        }
    
        printName() {
            console.log(this.name)
        }
    }

class sub extends sup{
    constructor(name,age) {
        super(name) // super代表的事父类的构造函数
        this.age = age
    }

    printAge() {
        console.log(this.age)
    }
}

let jack = new sub('feihao', 18)
    jack.printName()    //输出 : feihao
    jack.printAge()    //输出 : 18
参考

1. 为什么我们要写 super(props) ?

5. React fiber

背景:
react在进行组件渲染时,从setState开始到渲染完成整个过程是「一气呵成」的。如果需要渲染的组件比较复杂,js执行会占据主线程时间较长,会导致页面响应速度变差,使得react在动画、手势等应用中效果比较差。

原因:
在setState后,react会立即开始reconciliation过程,从父节点(Virtual DOM)开始遍历,以找出不同。将所有的Virtual DOM遍历完成后,reconciler才能给出当前需要修改真实DOM的信息,并传递给renderer,进行渲染,然后屏幕上才会显示此次更新内容。对于特别庞大的vDOM树来说,reconciliation过程会很长,在这期间,主线程是被js占用的,因此任何交互、布局、渲染都会停止,给用户的感觉就是页面被卡住了。

解决:
fiber的机制是每次只做一个很小的任务,做完后能够“喘口气儿”,回到主线程看下有没有什么更高优先级的任务需要处理,如果有则先处理更高优先级的任务,没有则继续执行。
对于如何区别优先级,React 有自己的一套逻辑。比如对于动画这种实时性很高的东西,也就是 16 ms 必须渲染一次保证不卡顿的情况下,React 会每 16 ms(以内) 暂停一下更新,返回来继续渲染动画。

参考

1. fiber 精简版总结
2. fiber 详细总结

6. React render

render 函数并不做实际的渲染动作,它只是返回一个JSX描述的结构,最终由React来操作渲染过程。(render函数并不往DOM树上渲染或者装载内容,它只是返回一个JSX表示的对象,然后由React库来根据返回对象决定如何渲染。而React库肯定是要把所有组件返回的结果综合起来,才能知道该如何产生对应的DOM修改。)

render函数还可以返回 字符串 / 数组

7. React 8 种条件渲染方式

场景:组件内有两种状态,对应渲染不同的元素

  • render 函数内直接 if...else
  • 某些条件下需要隐藏组件时可返回 null阻止渲染
  • 使用一个函数来返回要渲染的元素
  • 三元运算符
  • 短路运算符
  • 使用自执行函数
  • 拆分成子组件
  • 写一个 if 组件(类似Vue的 v-if)
  • 利用高阶组件
参考

React的8种条件渲染方法 - 内有示例代码

8. React 中如何减少 render 次数

  • shouldComponentUpdate 和 PureComponent
  • 对组件进行合理拆分
  • 涉及频繁setState的地方可进行防抖/节流
参考

1.性能!!让你的 React 组件跑得再快一点 - 政采云前端团队
2. React优化:竭尽全力的减少render渲染

9. React setState

setState 三种用法:

  • setState({key: value})
  • this.setState((prevState) => ({ count: prevState.count + 1 }))
  • setState(partialState, callback)

setState 同步/异步 如何表现:

  • setState 只在合成事件和钩子函数中是“异步”的,在原生事件和 setTimeout 中都是同步的。
  • setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。
  • setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setState , setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新。
参考

你真的理解setState吗?

10. 虚拟DOM的优缺点

答题思路:
传统DOM的劣势 --> react diff --> vdom的出现解决什么问题 --> vdom的优势

传统前端编程方式是命令式的,直接操作DOM,代码可读性差可维护性低;

react的声明式编程,抛弃了直接操作DOM,只关注数据变动,DOM操作由框架完成,提升了可读性可维护性;

最初react在更新的过程中会刷新整个页面,后来引入的diff过程,对比数据变动前后的DOM结构,但DOM结构diff起来太复杂,由此引出了vdom;

VDOM 和 Diff 算法的出现是为了解决由命令式编程转变为声明式编程、数据驱动后所带来的性能问题的。换句话说,直接操作 DOM 的性能并不会低于虚拟 DOM 和 Diff 算法,甚至还会优于。

这么说的原因是因为 Diff 算法的比较过程,比较是为了找出不同从而有的放矢地更新页面。但是比较也是要消耗性能的。而直接操作 DOM 就是有的放矢,我们知道该更新什么不该更新什么,所以不需要有比较的过程。所以直接操作 DOM 效率可能更高。

React 厉害的地方并不是说它比 DOM 快,而是说不管你数据怎么变化,我都可以以最小的代价来进行更新 DOM。 方法就是我在内存里面用新的数据刷新一个虚拟 DOM 树,然后新旧 VDOM 进行比较,找出差异,再更新到 DOM 树上。

虚拟DOM的作用:

  • Virtual DOM 在牺牲(牺牲很关键)部分性能的前提下,增加了可维护性,这也是很多框架的通性。
  • 实现了对 DOM 的集中化操作,在数据改变时先对虚拟 DOM 进行修改,再反映到真实的 DOM 中,用最小的代价来更新 DOM,提升效率(要想想是跟哪个阶段相比提升了效率,别只记住了这一条)
  • 打开了函数式 UI 编程的大门
  • 可以渲染到 DOM 以外的端,使得框架跨平台,比如 ReactNative,React VR 等
  • 可以更好地实现 SSR、同构渲染等。这条其实是跟上面一条差不多的
  • 组件的高度抽象化

虚拟DOM的缺点:

  • 首次渲染大量 DOM 时,由于多了一层虚拟 DOM 的计算,会比 innerHTML 插入慢。

  • 虚拟 DOM 需要在内存中维护一份 DOM 的副本(跟上面一条其实也差不多,上面一条是从速度上,这条是从空间上)。

  • 如果虚拟 DOM 大量更改,这是合适的。但是单一频繁地更新的话,虚拟 DOM 将会花费更多的时间处理计算的工作。所以,如果你有一个 DOM 节点相对较少页面,用虚拟 DOM,它实际上有可能会更慢。但对于大多数单页面应用,这应该都会更快。

参考

把这篇文章看完 - 从 React 历史的长河里聊虚拟 DOM 及其价值
Virtual Dom 的优势在哪里?

11. webpack 拆包

对于开发项目中不经常会变更的静态依赖文件。类似于我们的elementUi、vue全家桶等等。因为很少会变更,所以我们不希望这些依赖要被集成到每一次的构建逻辑中去。 这样做的好处是每次更改我本地代码的文件的时候,webpack只需要打包我项目本身的文件代码,而不会再去编译第三方库。以后只要我们不升级第三方包的时候,那么webpack就不会对这些库去打包,这样可以快速的提高打包的速度。

这里我们使用webpack内置的DllPlugin DllReferencePlugin进行抽离.

具体步骤:
①. 在与webpack配置文件同级目录下新建webpack.dll.config.js
②. 在package.json中配置dll命令
③. 接下来在我们的webpack.config.js中增加配置代码
④. 执行 npm run dll
⑤. 会发现生成了我们需要的集合第三方代码的vendor.dll.js 我们需要在html文件中手动引入这个js文件
⑥. 这样如果我们没有更新第三方依赖包,就不必npm run dll。直接执行npm run dev npm run build的时候会发现我们的打包速度明显有所提升。因为我们已经通过dllPlugin将第三方依赖包抽离出来了。

参考

2.5.1.5 抽离第三方模块

React diff 策略

  • Web UI 中 DOM 节点跨层级的移动操作特别少,可以忽略不计。
  • 拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构。
  • 对于同一层级的一组子节点,它们可以通过唯一 key 进行区分。
tree diff

基于策略一,React 对树的算法进行了简洁明了的优化,即对树进行分层比较,两棵树只会对同一层次的节点进行比较。
即同一个父节点下的所有子节点。当发现节点已经不存在,则该节点及其子节点会被完全删除掉,不会用于进一步的比较。这样只需要对树进行一次遍历,便能完成整个 DOM 树的比较。

component diff

React 是基于组件构建应用的,对于组件间的比较所采取的策略也是简洁高效。

  • 如果是同一类型的组件,按照原策略继续比较 virtual DOM tree。
  • 如果不是,则将该组件判断为 dirty component,从而替换整个组件下的所有子节点。
  • 对于同一类型的组件,有可能其 Virtual DOM 没有任何变化,如果能够确切的知道这点那可以节省大量的 diff 运算时间,因此 React 允许用户通过 shouldComponentUpdate() 来判断该组件是否需要进行 diff。
element diff(不带key)
  • element相同,原地复用
  • element 不同,删旧建新
element diff(带key)

image
带key diff的过程看这里

参考

react diff 策略

webpack tree shaking 存在的问题

Tree-Shaking能帮助我们删除掉没有使用的代码,将会大大缩减打包后的代码量。

这里有个坑。tree-shaking的主要作用是用来清除代码中无用的部分。目前在webpack4 我们设置mode为production的时候已经自动开启了tree-shaking。 但是要想使其生效,生成的代码必须是ES6模块。不能使用其它类型的模块如CommonJS之流。 如果使用Babel的话,这里有一个小问题,因为Babel的预案(preset)默认会将任何模块类型都转译成CommonJS类型,这样会导致tree-shaking失效。修正这个问题也很简单,在.babelrc文件或在webpack.config.js文件中设置modules: false就好了

参考

1. 你的Tree-Shaking并没什么卵用

webpack 占位符

  • [ id ] – 返回块ID。
  • [ path ] – 返回文件路径。
  • [ name ] – 返回文件名称。
  • [ ext ] – 返回扩展名。
  • [ hash ] – 返回构建Hash。如果构建任何部分发生更改,则也会发生更改。
  • [ chunkhash ] – 返回条目块特定的hash。在配置中定义的每个entry都会收到自己的hash。如果entry的任何部分发生变化,那么hash也会发生改变。[chunkhash]比[hash]定义更细。
  • [ contenthash ] – 返回特定于内容的hash。[contenthash]只适用于ExtractTextPlugin,是可用的、最具体的选项。

Webpack入坑秘籍(18)

其他

1. 组件封装的要点

9. 如何设计一个 dialog 组件
① 确定UI

三种类型:alert、confirm、modal
样式是否支持自定义

② 确定API
  • 对外暴露的props属性配置
  • 调用方式
  • 确认/取消的回调函数
③ 具体实现
  • 遮罩
  • 头部、内容和底部的布局
参考

React造轮系列:对话框组件 - Dialog 思路

2. 项目中的难点

3. 工作收获

4. 项目经历

5. 职业规划

6. 还有什么要问的吗

7. 前端性能优化

  • 网络相关
    • 合适的缓存策略
    • 开启 HTTP 2.0
    • DNS预解析
    • 网页的预加载和预渲染
  • 渲染过程
    • 懒执行
    • 懒加载
    • 利用好本地存储
    • 减少回流、重绘
  • 文件优化
    • 图片优化
    • 细节优化
      • css和js引入的位置
      • 服务端启用GZIP压缩
      • 静态资源启用CDN
      • 计算量比较大的操作可交给webworker去做
  • 其他
    • 使用webpack优化项目
      • 对于 Webpack4,打包项目使用 production 模式,这样会自动开启代码压缩
      • 使用 ES6 模块来开启 tree shaking,这个技术可以移除没有使用的代码
      • 按照路由拆分代码,实现按需加载
      • 配置缓存
      • DLL
      • 缩小打包作用域
      • 压缩代码
      • 多进程多实例构建
    • 监控
      • http方面
        后端log日志,流入kafka,可以准确监控接口异常
      • 前端异常监控
        window.onerror js异常监控
        try catch 捕获
        异步异常的捕获,unhandledrejection
        前端的performance api
        4023c32d1d2795af5b222c7a00fbecdf.jpeg
        Chrome devtool performance 工具
      • 前端 sdk 埋点
        统计UV PV 转化率

8. 前端异常捕获与上报

前端异常捕获与上报
前端异常监控、上报及js压缩代码定位

8.1 前端首屏加载时间和白屏时间的概念

Web 性能优化-首屏和白屏时间

9. 对前端工程化的理解

前端的规模越来越大,已经上升到了工程学的问题,如何提高前端工程师的开发效率变得非常重要。这就是前端工程化所要解决的问题。前端工程化是根据业务特点,将前端开发流程规范化,标准化,它包括了开发流程,技术选型,代码规范,构建发布等,用于提升前端工程师的开发效率和代码质量。

b8a523e03a3ee9902051d34fa23d76fc.png

一个符合前端工程化要求的方案应该包含以下要素:

  • 开发规范
  • 模块化开发
  • 组件化开发
  • 组件仓库
  • 性能优化
  • 部署
  • 开发流程
  • 开发工具

我对前端工程化的理解

10. 单点登录和oauth2

前端关于单点登录的知识

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant