vue3 新特性概览

之前看过尤雨溪关于VUE3介绍的一个视频直播,下面总结了一下直播里面提到的内容。

Performance 性能提升

  • 模板编译的优化;
  • 优化了Slots操作,把函数传给子组件让子组件自己决定更新哪个节点;
  • 静态内容提取,直接渲染不需要转化;
  • 重点是proxy;全语言特性支持(支持对象、数组、集合、字典的增删) proxy的模式叫 lazy by default 就是只有数据被用到的时候才会被监听,使得实例的初始化快了1倍,内存减半。 同时尽最大的努力向下兼容2.0 ;

Tree shaking support

按需引入,移除无用代码使得runtime体积更小。比如只有你使用了v-mode的时候才会引入v-mode相关的代码。体积由原来的20k可以简化到10k。

Better Typescript support

  • 全部用TS重写,对TS支持更好;
  • 内部的模块清晰的解耦,降低了阅读源码的难度 插件化设计 比如v-for、v-if等 带位置信息的parser(source map) 包括原生的Class API和TSX ;

更好的多端渲染支持Custom Renderer API

比如从runtime-core里面去自定义需要的功能,比如修改dom相关的模块,对于使用vue去开发小程序之类的用户更加友好。

响应式数据监听 API —— 封装了一个观察者对象

首先引入vue下的observable对象 把需要监听的数据传入observable,比如: const state = observable({ count:0 }) 然后每次count发生改变的时候,就可以通过一个effect方法就监听count的变化

renderTriggered 轻松排查组件更新的原因

vue组件里面写一个renderTriggered方法,然后在里面写一个debugger。这样每次组件更新之后就会把具体的信息通过event参数返回出来。 例如修改了count的值1为2。导致了组件更新,那么event里面就会有一个对象,还会告诉你具体的哪一行代码触发了这次更新。 { key:count, oldValue:1, newValue:2 }

experimental Hooks API

希望能把Hooks 以一种取代mixins的逻辑复用的机制引入到vue中

experimental Time slicing Support

因为Js的单线程机制,如果一次代码块过大的话就会发生阻塞。例如我一次渲染200个dom需要200毫秒,那么这期间,用户的点击操作里面的回调函数就不会马上被执行,页面上就能感觉到明显的卡顿。time slicing的原理就是把JS代码块切成一帧一帧的形式,比如16毫秒执行一次,这样就可以在每次执行的期间来执行用户的异步操作了。

Fragment 支持DOM的多节点

Teleport 就相当于React的Portal允许把DOM渲染的其他节点 Suspense 包裹节点实现异步等待

Composition API

写法更简洁,数据和方法都是放入到setup函数里面,并返回给setup。 思路更清晰,相同业务的代码都放到同一个函数里面。可读性更好。

快速了解Webpack

本文的目的就是让读者对webpack快速的有一个整体的认识,了解其主要的功能。如果对其中某一项感兴趣,详细的使用方式建议查阅webpack官网文档。

首先了解一下gulp与webpack的区别

gulp:强调流程,通过配置一系列的task,定义task处理的事物(例如文件压缩合并、雪碧图、启动server、 版本控制等),然后定义执行顺序,来让gulp执行task,从而构建前端项目的流程。

webpack:是一个前端模块化方案,侧重模块打包,把开发中的所有资源(图片、js文件、css文件等)都看成模块,通过loader(加载器)和plugins(插件)对资源进行处理,打包成符合生产环境部署的前端资源。

相同:

可以文件合并与压缩(css)

不同点:

虽然都是前端自动化构建工具,但看他们的定位就知道不是对等的。
gulp严格上讲,模块化不是他强调的东西,他旨在规范前端开发流程。
webpack更是明显强调模块化开发,而那些文件压缩合并、预处理等功能,不过是他附带的功能。

webpack五个核心概念

  1. Entry – 如何,以哪个文件为起点开始打包,分析构建内部依赖图。
  2. Output – 把bundles输出到哪里,以及如何命名。
  3. Loader – webpack默认只能处理JS,如果需要处理其他必须html/css/img等资源就需要加载对应的Loader。相当于翻译官
  4. Plugins – 做一些功能更复杂的操作,比如压缩代码,定义环境变量,优化插件,语法检查,dll等
  5. Mode – development 本地调试,简单配置,能运行即可。production – 配置各种优化项,保证上线之后的性能

devServer

自动编译,自动打开浏览器,自动刷新

性能优化

开发环境

优化打包构建速度 – HMR
优化代码调式 – sourceMap(压缩后的js文件可以映射到源文件,快速找到代码行)

生成环境

打包速度
代码运行的性能 – oneOf、缓存、tree shanking、代码分割、懒加载、PWA、多进程打包、external、dll。

HMR(hot module replacement) – 热模块替换

监听细微变化进行局部更新
devServer:{
hot:ture
}

oneOf

rules配置项下面把所有的loader都放到oneOf数组里面,性能更好

缓存

只编译代码改变了的模块,其他模块开启babel缓存
rules下的babel-loader配置cacheDirectory:true
文件资源缓存,根据chunk生成hash值
content hash:根据文件的内容生成hash值

tree shaking

去除那些没有使用的代码
前提:

  • 必须es6的模块化方案
  • 开启production环境
    package.json中配置
    sideEffects:[“*.css”] // 保证css不会被摇掉

code split – 代码分割

entry里面配置多个入口,单页面还是多页面。如果所有的文件都在一个bundle里面的话bundle体积就会非常大。所以要把bundle拆开多个小文件。

optimization:{
    splitChunks:{
    chunks:’all'
    }
}

懒加载

先进行代码分割,再把要加载的文件import的时候加上webpackChunkName 标识
预加载的标识为 webpackPrefetch:ture

import(/* webpackChunkName:’test’, webpackPrefetch:true */ ‘.test’).then(({mul}) = > {
    console.log(mul(4, 5))
})

PWA (Progressive web apps,渐进式 Web 应用)

  • Discoverable, 内容可以通过搜索引擎发现。
  • Installable, 可以出现在设备的主屏幕。
  • Linkable, 你可以简单地通过一个URL来分享它。
  • Network independent, 它可以在离线状态或者是在网速很差的情况下运行。
  • Progressive, 它在老版本的浏览器仍旧可以使用,在新版本的浏览器上可以使用全部功能。
  • Re-engageable, 无论何时有新的内容它都可以发送通知。
  • Responsive, 它在任何具有屏幕和浏览器的设备上可以正常使用——包括手机,平板电脑,笔记本,电视,冰箱,等。
  • Safe, 在你和应用之间的连接是安全的,可以阻止第三方访问你的敏感数据。
    workboxWepackPlugin

多进程打包

npm i thread-loader -D

在babel-loader外面套一个thread-loader
缺点:进程启动大概为600ms,进程开销比较大。
适用于工作消耗时间比较长的编译loader,比如编译js的babel-loader相对css就比较耗时。

externals

忽略一些包被打包进来,注意如果要使用这个库就要在html文件里面的script标签里面引用

externals:{
    jquery:’jQuery'
}

dll

和externals类似,可以将某些第三方依赖包统一打包到dll文件中。
把一些固有的库都先打包到dll文件里面。这样打包1次后面就不用每次都打包了。可以让第2次打包的过程变快。提升构建速度。

二分查找

题目:例如这样一个[1,3,4,5,7]有序的数组,如何快速找到某个值的下标?
这是一个经典的二分查找算法题。

解题思路:

  1. 先找到数组的中间下标值
  2. 然后判断目标值如果大于中间值,就在数组的右半边继续查找
  3. 如果目标值小于中间值,就在数组的左半边继续查找
  4. 然后不断的重复上面的步骤。

时间复杂度是O(logN) 因为遍历减半了就是n的一半=logN,如果遍历了全部就是N

let binarySeach = function (arr, target) {
    let s = 0 // 开始下标
    let e = arr.length - 1 //结束下标
    while (s <= e) { // 如果开始下标小于等于结束下标就标识区间内有值
        let mid = Math.floor((s + e) / 2) //当前数组的中间值下标
        let item = arr[mid] // 中间值
        if (item < target) { //如果中间的值小于目标值
            s = mid + 1 // 把开始下标移动到中间值的后面,在2叉树的右边继续搜索了
        } else if (item > target) { //如果中间值大于目标值
            e = mid - 1 // 把结束下标移动到中间值的前面一位,在2叉树的左边数组继续搜索
        } else { // 如果中间值等于目标值直接返回当前下标
            return mid
        }
    }
    return -1 // 找不到就返回-1
}
let res = binarySeach([1, 2, 3, 4, 5], 2)
console.log(res)

setTimeout和setInterval的区别

先看一下他们两个的概念描述:

超时调用(setTimeout):在指定的毫秒数后调用函数或计算表达式。
setTimeout(func, 1000); // func执行的函数,1000毫秒

间歇调用(setInterval):按照指定的周期(以毫秒计)来调用函数或计算表达式
setInterval(func, 1000);

最大的区别:

超时调用会在前面的代码执行完毕之后,新建一个计时器若干秒后执行回调函数;

而间歇调用是先创建一个计时器,然后每隔一段时间就执行一次回调,不管回调函数里面的代码执行的快还是慢;

间歇调用的缺点就是因为计时器的时间固定写死了,如果回调函数里面的代码执行时间超过了计时器时间就会出问题,这个种情况在工作中很常见。
所以我平时很少用间歇调用。如果想实现一个间歇调用的效果可以用setTimeout模拟。因为setTimeout每次都是新建一个计时器执行后再新建一个计时器就可以解决这个问题,例如这样:

var interval = 200;
setTimeout(function () {
// 处理代码
setTimeout(arguments.callee, interval); //递归调用自身
}, interval)

闭包概念的深入理解

MDN上面对于闭包的定义:一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。

说的直白一点就是闭包不仅仅只是函数本身,也包括一切与它相连的变量的一个闭包体。ES6的let语法出现之前,JS语言是没有块级作用域的,只有全局作用域和函数作用域。而其他语言例如JAVA和PHP都有。所谓块级作用域,比如代码块for循环或if等等。

先看一下下面这段代码:

// 父级函数foo
function foo(x) {
    var z = 3 // 父级函数内部的变量z
    // 返回一个匿名子函数
    return function (y) {
        // 子函数内部可以访问的变量有自己作用域的参数y和父级函数内定义的变量z和父级函数的参数变量x,还有最外层的全局变量bar
        console.log(x + y + z)
    }
}
var bar = foo(2) //
bar(3)

我们来慢慢解释一下上面的代码:
首先我们定义一个函数foo,然后在里面定义一个变量z,然后返回一个匿名函数。这个匿名函数就是foo的子函数。
然后在最外面执行foo这个函数并把返回值,也就是里面的那个匿名子函数给到变量bar
最后执行bar这个函数,参数3就是y

先看一下匿名子函数里面能访问的变量一共有哪些。
因为是最内层,所以子函数内部可以访问的变量有自己作用域的参数y和父级函数内定义的变量z和父级函数的参数变量x,还有最外层的全局变量bar

像这种情况,赋值到bar变量的那个匿名的子函数就是一个闭包体。它的词法环境就包括了函数本身和外层的z变量和x参数变量。

接下来说一下JS的回收机制。
当一个函数执行完毕之后就会把它和它里面定义的变量一同销毁。比如上面的foo函数执行之后里面定义的z变量就会被销毁。
但是由于我们已经把闭包体赋值给了bar这个变量,所以就算执行foo(2),里面的变量z也不会被销毁。因为闭包体被变量bar引用到,除非把变量bar也销毁,z才会被释放。这就是闭包会引起内存泄漏的原因。

闭包的缺点就是会引起内存泄漏。那么闭包有什么用?
最常见的用途就是函数柯里化,请参考我的另一篇文章,专门介绍什么是函数柯里化。

什么是报文?

1.报文(message)
我们将位于应用层的信息分组称为报文。报文是网络中交换与传输的数据单元,也是网络传输的单元。报文包含了将要发送的完整的数据信息,其长短不需一致。报文在传输过程中会不断地封装成分组、包、帧来传输,封装的方式就是添加一些控制信息组成的首部,那些就是报文头。

2.报文段(segment)
通常是指起始点和目的地都是传输层的信息单元。

3.分组/包(packet)
分组是在网络中传输的二进制格式的单元,为了提供通信性能和可靠性,每个用户发送的数据会被分成多个更小的部分。在每个部分的前面加上一些必要的控制信息组成的首部,有时也会加上尾部,就构成了一个分组。它的起始和目的地是网络层。

4.数据报(datagram)
面向无连接的数据传输,其工作过程类似于报文交换。采用数据报方式传输时,被传输的分组称为数据报。通常是指起始点和目的地都使用无连接网络服务的的网络层的信息单元。

5.帧(frame)
帧是数据链路层的传输单元。它将上层传入的数据添加一个头部和尾部,组成了帧。它的起始点和目的点都是数据链路层。

6.数据单元(data unit)
指许多信息单元。常用的数据单元有服务数据单元(SDU)、协议数据单元(PDU)。
SDU是在同一机器上的两层之间传送信息。PDU是发送机器上每层的信息发送到接收机器上的相应层(同等层间交流用的)。

下面是网络模型中每一层对应的信息情况

应用层——报文(message)
传输层——数据段/报文段(segment) (注:TCP叫TCP报文段,UDP叫UDP数据报,也有人叫UDP段)
网络层——分组、数据包(packet)
链路层——帧(frame)
物理层——P-PDU(bit)

TCP和UDP的区别

UDP
TCP
是否连接
无连接
面向连接
是否可靠
不可靠传输,不使用流量控制和拥塞控制
可靠传输,使用流量控制和拥塞控制
连接对象个数
支持一对一,一对多,多对一和多对多交互通信
只能是一对一通信
传输方式
面向报文
面向字节流
首部开销
首部开销小,仅8字节
首部最小20字节,最大60字节
适用场景
适用于实时应用(IP电话、视频会议、直播等)
适用于要求可靠传输的应用,例如文件传输

快速理解WebSocket 协议

在了解WebSocket之前需要先了解socket是什么。
首先要知道,现在任何的网络协议都是基于socket协议。
socket的中文翻译叫“套接字”,然而这个中文翻译非常晦涩难懂,很难解释清楚。
其实socket直接翻译过来的含义是插座的意思。
我的个人理解就是2个人互相发送文件的时候,如果发1个文件就在插座上插1条线,再发另外1个文件就再插1条线。不管是发文件还是收文件,都是通过插座上面的线来传输,这个线就叫“线程”。而插座就是为了“创建”和“关闭”这些“线程”而设计的。

先看一下socket的流程图

我们发现socket的流程非常简单,主要分三步
1.创建
2.收发
3.关闭
程序中创建socket协议通常有两个参数,第一个为ip协议规范(ipv4/ipv6),第二个为协议类型(TCP/UPD)
例如PHP的socket_create方法,socket_create ( int $domain , int $type , int $protocol ) : resource
具体参数对应的含义请查看文档

接下来正式说明什么是WebSocket
定义:WebSocket是一种在单个TCP连接上进行全双工通信(同时双向发送数据)的协议。浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
http协议url地址是长这样http://example.com:/some/path
而websocket协议的地址是这样ws://example.com:80/some/path

在客户端使用WebSocket非常简单

var ws = new WebSocket("wss://echo.websocket.org");

ws.onopen = function(evt) { //连接成功后的回调
console.log("Connection open ...");
ws.send("Hello WebSockets!");
};

ws.onmessage = function(evt) { //接收数据
console.log( "Received Message: " + evt.data);
ws.close();
};

ws.send('your message’); // 发送数据

ws.onclose = function(evt) { // 连接关闭后的回调
console.log("Connection closed.");
};

服务端使用WebSocket如果是NodeJS常用的就是
Socket.IO
如果用java就是Tomcat7.0以上
PHP用swoole

WebSocket协议的优点是什么?
最大的优点就是在实现即时通讯的方法上面更节省资源。
先说下面两种及时通讯方式的缺点
HTTP轮询:因为是单向通信,1次请求对应一次响应,然后关闭连接。如果服务端没有最新信息,那么就有大量的空响应回来,也就是无意义的请求,浪费资源。
Socket长连接:一旦建立连接就不会关闭,如果服务端没有最新数据,那么线程被占用,却没有收发数据,比轮询的方式还浪费资源。
WS协议:先用HTTP的方式握手建立连接,然后用socket的长连接方式保持通话,再用HTTP的方式挥手关闭连接。结合了上面两种方式的优点,是目前的最优手段。

快速理解TCP三次握手和四次挥手

三次握手:
例如我们打电话的时候先要确认双方身份。
假设客户端是儿子,服务端是妈妈
儿子:喂,妈妈在吗?
妈妈:我在,是儿子给我打电话吗?
儿子:对,是我啊,可以聊了。
如果双方确认不了身份就挂机不聊了。

TCP协议包每种标识对应的含义
* SYN(synchronous),建立联机。
* ACK(acknowledgement),确认。
* PSH(push),传输。
* FIN(finish),结束。
* RST(reset),重置。
* URG(urgent),紧急。

3次握手的过程:
1 client 发送 SYN seq=x 请求与服务器建立连接
2 server 发送 ACK=x+1,SYN seq=y 确认请求,请求与客户端建立连接
3 client 发送 ACK=y+1 确认请求

确认请求的方式就是把syn里面的x的值加1。

注意!!TCP协议每次传输数据包前面都会先再带一个ACK确认包,告诉对方我已经收到上次的数据包了;这也是TCP和UDP最大的区别。
TCP如果没有收到确认包,就会隔一段时间再发送一次数据,来保证每次传输的数据都是完整的。而UDP中没有,所以TCP稳定。
为什么要进行3次握手,因为客户端在没有收到响应的情况下会因为连接超时从而断开连接,服务端为了不浪费资源,发现客户端超时之后就会关闭连接了。

四次挥手:
三次握手是为了建立连接,而四次挥手是为了关闭连接,如果理解三次握手,那么四次挥手就非常好理解了,原理差不多。
先看两张图

1 客户端先发送关闭请求包FIN,进入等待状态1
2 服务端收到关闭且请求后,先发送确认收到的包ACK,进入关闭等待状态。这时候客户端先收到了确认包,进入等待状态2.
3 服务端也发送一个请求关闭的包FIN,进入最后确认状态
4 客户端收到服务端的关闭请求后发送最终确认包ACK给服务端,然后进入等待计时器,在计时器结束后,进入关闭状态,服务端收到最终确认包之后立即进入关闭状态。

为何要四次分手呢?

1 当客户端发出FIN报文段时,只是表示已经没有数据要发送了。但是,这个时候主客户端还是可以接受来自服务器的数据;
2 当服务器返回ACK报文段时,表示它已经知道客户端没有数据发送了,但是还是可以往客户端推送数据包;
3 当服务器也发送了FIN报文段时,就会告诉客户端,我也没有数据要发送了;
4 再双方都确认之后彼此就会愉快的中断这次TCP连接。
如果要正确的理解四次分手的原理,就需要了解四次分手过程中的状态变化。

FIN_WAIT_1: 这个状态要好好解释一下,其实FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报文。而这两种状态的区别是:FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET即进入到FIN_WAIT_1状态。而当对方回应ACK报文后,则进入到FIN_WAIT_2状态,当然在实际的正常情况下,无论对方何种情况下,都应该马上回应ACK报文,所以FIN_WAIT_1状态一般是比较难见到的,而FIN_WAIT_2状态还有时常常可以用netstat看到。(主动方)

FIN_WAIT_2:上面已经详细解释了这种状态,实际上FIN_WAIT_2状态下的SOCKET,表示半连接,也即有一方要求close连接,但另外还告诉对方,我暂时还有点数据需要传送给你(ACK信息),稍后再关闭连接。(主动方)

CLOSE_WAIT:这种状态的含义其实是表示在等待关闭。怎么理解呢?当对方close一个SOCKET后发送FIN报文给自己,你系统毫无疑问地会回应一个ACK报文给对方,此时则进入到CLOSE_WAIT状态。接下来呢,实际上你真正需要考虑的事情是察看你是否还有数据发送给对方,如果没有的话,那么你也就可以 close这个SOCKET,发送FIN报文给对方,也即关闭连接。所以你在CLOSE_WAIT状态下,需要完成的事情是等待你去关闭连接。(被动方)

LAST_ACK: 这个状态还是比较容易好理解的,它是被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,也即可以进入到CLOSED可用状态了。(被动方)

TIME_WAIT: 表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态了。如果FINWAIT1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。(主动方)

CLOSED: 表示连接中断。

快速理解HTTPS协议

先知道HTTP协议是明文发送,比如抓包工具里面可以看到账号密码;
HTTPS多了一层SSL协议,用来加密信息,原理如下:

1. 服务器先发送一个安全证书,告诉客户端与这个服务器通信是安全的
2. 请求信息前,服务器会生成2把钥匙和一个空箱子。
3. 然后把空箱子和其中1把钥匙发给客户端
4. 客户端把信息放入箱子,锁上再发给服务器,这样2边都有钥匙,就都可以访问箱子里面的内容了。

再传输的过程中,就算箱子被截取了,因为没有钥匙,就很难获取箱子里面的数据。以现在的黑客技术,破解箱子也是非常有难度的