从宏观的角度讨论网络应用怎么运行及其前端代码系统编写和优化

一个网络页面,网络应用的前端开发,首先明白前端代码在浏览器上运行,访问服务器提供的接口,接收服务器传来的数据,展示在页面上。

本文从较为宏观的角度来讨论前端开发中的一些课题。

首先,是网络基础HTTP协议,浏览器与服务器之间的通讯xhr,跨域问题

接着,浏览器的渲染,缓存,兼容,同时会涉及到部分服务器端

然后,性能优化,从页面开启到代码运行期间,如何减少消耗浏览器和服务器端的资源,让页面更快加载出来

另外,前端开发的模式,框架,模块,主要是关于vue框架

最后,是项目的管理,前后端联调,打包,上线部署

通过本文,可以了解到一个网络应用怎么运行,系统编写,如何优化。

网络协议

TCP

全双工通信

HTTP

HTTP服务过程:应答机制限制了全双工通信

在web浏览器地址输入:www.baidu.com,具体发生了什么?

  1. 对www.baidu.com进行DNS域名解析,得到对应的IP地址

    DNS域名解析采用递归查询的方式

    过程:

    1)找DNS缓存(浏览器1min,1000条——系统——hosts文件)

    2)缓存找不到,去找根域名服务器

    3)根域名又会去找下一级

    DNS优化:DNS缓存,DNS负载均衡

  2. 根据这个IP,找到对应的服务器,发起TCP的三次握手

    HTTP基于传输层TCP协议

    TCP:端到端的可靠的面相连接的协议,不用担心数据传输的问题,当发生错误时,会重传

    User-Agent(浏览器)以随机端口向服务器的WEB程序(Nginx等)的80端口 发送连接请求

    原始的http请求 经过TCP/IP 4层模型的层层封包,经过各种路由设备,局域网除外,到达服务器端后,

    进入网卡,再进入到内核的TCP/IP协议栈,识别连接请求,解封包,一层一层的剥开,

    还有可能要经过Netfilter防火墙(属于内核的模块)的过滤

    最终到达WEB程序,建立TCP/IP的连接

  3. 建立TCP连接后,发起HTTP请求

    HTTP请求报文:

    1)请求行:客户端(浏览器)的请求方式GET/POST等,请求的资源名称(URI),HTTP协议的版本号

    2)请求头:host及post,User-Agent客户端(浏览器)的一些信息

    3)消息体:GET参数在请求行中?后,POST的参数在消息中,且请求头中有Content-Length,服务器才能知道请求是否发送结束

    ps:

    URI Uniform Resource Identifier 统一资源标识符
    URL Uniform Resource Locator 统一资源定位符
    URN Uniform Resource Name 统一资源名称

    URL和URN都属于URI

    请求方法

    GET: 完整请求一个资源 (常用)
    HEAD: 仅请求响应首部
    POST:提交表单 (常用)
    PUT: (webdav) 上传文件(但是浏览器不支持该方法)
    DELETE:(webdav) 删除
    OPTIONS:返回请求的资源所支持的方法的方法
    TRACE: 追求一个资源请求中间所经过的代理(该方法不能由浏览器发出)

  4. 服务器响应HTTP请求,浏览器得到html代码

    HTTP响应:

    1)状态行:协议版本、状态码、状态码描述

    1xx:指示信息——表示请求已经接受,继续处理

    2xx:成功——表示请求已经被成功接收、理解、接受(200 OK)

    3xx:重定向——要完成请求必须进行更进一步的操作

    302(找别人),304(找缓存),307(找缓存)

    4xx:客户端错误——请求有语法错误或请求无法实现

    403(有这个资源,但没有访问权限),404(服务器没有这个资源)

    5xx:服务器端错误——服务器未能实现合法的请求(500 服务器有问题)

    2)响应头:服务器信息,客户端如何处理信息(如:Set-Cookie)

    3)消息体:服务器返回给客户端的数据

  5. 浏览器解析html代码,并请求html代码中的资源,js,css,图片等

    Keep-alive,建立一次HTTP连接,可以请求多个资源,浏览器多线程请求下载资源

  6. 浏览器对页面进行渲染呈现给用户(页面渲染详解重点)

    边解析边渲染,js解析是由浏览器的js解析引擎完成的,页面首次加载必reflow回流,repaint重绘,尽可能减少reflow和repaint

  7. 服务器关闭TCP连接

如果有Connection:keep-alive,TCP连接在发送后仍然保持打开状态,默认是发完就关闭

HTTP协议本身无状态,服务器无法判断用户身份(so 浏览器请求网站携带cookie)

WebSocket

应用层协议,H5新增的协议,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息

客户端会向服务器发送一个HTTP请求,包含一个Upgrade请求头来告知服务器:客户端想要建立一个WebSocket链接

建立在TCP协议之上,服务器端实现比较容易,且与HTTP有良好的兼容性,默认端口也是80和443,握手阶段采用HTTP协议,因此握手时不容易屏蔽,能通过各种HTTP代理服务器

  • 数据格式比较轻量,性能开销小,通信高效。

  • 可以发送文本,也可以发送二进制数据。

  • 没有同源限制,客户端可以与任意服务器通信。

  • 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。

    安全的WebSocket连接机制和HTTPS类似。

    首先,浏览器用wss://xxx创建WebSocket连接时,会先通过HTTPS创建安全的连接,然后,该HTTPS连接升级为WebSocket连接,底层通信走的仍然是安全的SSL/TLS协议

https://www.7moor.com/successcasenew 七陌 客服 可以试用
https://www.easemob.com/product/cs/online 环信客服 可以试用

前端:  环信客服注册账号  -》登录客服云-》点击右上角管理员模式->渠道管理-》选中网站之类

       把那段js复制到页面body里面>

聊天实现 : websocket\sockect-io\node-chat> 1 先npm i 2 node .\server.js

应用场景:聊天客服,在线咨询,即时通讯,有一个好用的封装的 socket.io

一般不采用ajax轮询,定时器每隔1s,发送ajax到后台,因为,WebSocket数据格式比较轻量,性能开销小,通信高效

sokect.io使用:

前端:

  1. 引入socket.io.js

  2. 调用var socket = io( )

  3. 发消息到后台socket.emit

  4. 接收消息socket.on,把消息显示到页面上

HTTP与WebSocket

HTTP单向:基于TCP,请求-应答机制限制了全双工通信

ajax轮询问题:实时性,频繁请求给服务器带来极大的压力

Comet本质也是轮询,在没有消息的情况下,服务器先拖一段时间,等到有消息了再回复

问题:

  1. 极大地浪费服务器资源

  2. 一个HTTP连接在长时间没有数据传输的情况下,链路上的任何一个网关都可能关闭这个连接,而网关是我们不可控的,这就要求Comet连接必须定期发一些ping数据表示连接“正常工作”

WebSocket双向:基于TCP

接下来,咱们通信就不使用HTTP协议了,直接互相发数据

Web缓存控制

数据库缓存
服务器缓存

代理服务器缓存

CDN缓存

浏览器缓存
http缓存

浏览器是一种客户端。

浏览器可能会把上一次的代码存起来,再次访问,没有去拿新代码,而是直接去拿的缓存

缓存命中率:从缓存中得到数据的请求数与所有请求数的比率,越高越好

过期内容:超过设置的有效时间,不能回复客户端的请求,必须重新向源服务器请求新的内容或验证缓存的内容是否仍然准备

验证:验证是否仍然有效,验证通过的话刷新过期时间

失效:把内容从缓存中移除,内容发生改变时,必须移除失效的内容

强缓存:不发请求到服务器,直接拿缓存

协商缓存:会发请求到服务器,服务器让去拿缓存

浏览器在加载资源时,根据资源的http header等,判断缓存配置是否为强缓存,其Expires(客户端本地时间)和Cache-Control(相对时间,优先级更高)是否生效,若直接使用缓存。

如果强缓存过期,浏览器会发送请求到服务器,服务器端依据资源的另外的http header等验证资源是否命中协商缓存

如果命中协商缓存,服务器会将请求返回,但不会返回资源的数据,而是告诉浏览器可以直接从缓存中加载资源,浏览器就又会从自己的缓存中加载这个资源。

后台设置头 协商缓存header

Last-Modified服务器返回的header最后修改时间 / If-Modified-Since(再次请求该资源带上最后修改时间)

如果命中,304,不返回资源,也不返回Last-Modified,比对服务器时间,不会有强缓存Expires的时间问题

Etag 校验码,保证每一个资源是唯一的,值的变更则说明资源状态已经被修改 / If-None-Match

ETag扩展说明

我们对ETag寄予厚望,希望它对于每一个url生成唯一的值,资源变化时ETag也发生变化。神秘的Etag是如何生成的呢?以Apache为例,ETag生成靠以下几种因子

  1. 文件的i-node编号,此i-node非彼iNode。是Linux/Unix用来识别文件的编号。是的,识别文件用的不是文件名。使用命令’ls –I’可以看到。
  2. 文件最后修改时间
  3. 文件大小
    生成Etag的时候,可以使用其中一种或几种因子,使用抗碰撞散列函数来生成。所以,理论上ETag也是会重复的,只是概率小到可以忽略。

HTTP1.1中Etag的出现主要是为了解决几个Last-Modified比较难解决的问题:

  1. Last-Modified标注的最后修改只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间

  2. 如果某些文件会被定期生成,当有时内容并没有任何变化,但Last-Modified却改变了,导致文件没法使用缓存

3.有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形

小结:

浏览器第一次请求和第二次请求

先强缓存(先Cache-Control,再Expires,200,from cache),再协商缓存(先Etag,再Last-Modified,304),直接从服务器加载资源数据(200)

大部分web服务器都默认开启协商缓存,协商缓存需要发请求给服务器,资源是否更新,服务器知道

含义是让浏览器不缓存当前页面。但是代理服务器不解析 HTML 内容,一般应用广泛的是用 HTTP 头信息控制缓存。

浏览器缓存行为与用户行为有关:

新开窗口有效有效
用户操作Expires/Cache-ControlLast-Modified/Etag
地址栏回车有效有效
页面链接跳转有效有效
前进、后退有效有效
F5 刷新无效有效
**Ctrl+F5 **刷新无效无效

使用Ctrl+F5重新请求的就是没有缓存的页面,浏览器会在HTTP请求头中增加一些请求项(最重要的是在请求头中增加了两个请求项Pragma:no-cache和Cache-Control:no-cache)
通过它来告诉服务器我们要获取的是最新的数据,而不是缓存数据

ps:

cache-control的常见取值及其组合释义:
no-cache: 数据内容不能被缓存, 每次请求都重新访问服务器, 若有max-age, 则缓存期间不访问服务器.
no-store: 不仅不能缓存, 连暂存也不可以(即: 临时文件夹中不能暂存该资源).
private(默认): 只能在浏览器中缓存, 只有在第一次请求的时候才访问服务器, 若有max-age, 则缓存期间不访问服务器.
public: 可以被任何缓存区缓存, 如: 浏览器、服务器、代理服务器等.
max-age: 相对过期时间, 即以秒为单位的缓存时间.
no-cache, private: 打开新窗口时候重新访问服务器, 若设置max-age, 则缓存期间不访问服务器.
- private, 正数的max-age: 后退时候不会访问服务器.
- no-cache, 正数的max-age: 后退时会访问服务器.

indexDB
用户信息缓存
cookie

一小段的文本信息(key-value格式)

客户端浏览器会把cookie保存起来,当浏览器再请求该网站,浏览器把请求的网址连同Cookie一同提交给服务器

作用:存储数据,如:用户的登录状态

//设置名称为name,值为value的Cookie
function Setcookie (name, value){
		var expdate = new Date();   //初始化时间
		expdate.setTime(expdate.getTime() + 30 * 60 * 1000);   //时间
		document.cookie = name+"="+value+";expires="+expdate.toGMTString()+";path=/";
		 //即document.cookie= name+"="+value+";path=/";   
		 //时间可以不要expdate.toGMTString()
		 //但路径(path)必须要填写,因为JS的默认路径是当前页,如果不填,此cookie只在当前页面生效!
}

替代:token(jwt 鉴权模式下,前端拿到的加密串token),是否登录成功的标志,在之后的接口请求中携带token

jwt 鉴权模式

前后分离下,后端没有办法用session来存储你任意一个前端项目域名下的身份信息

通过一个鉴权接口将用户的身份,登录时间,请求端口,协议头…等等信息 组装成一个加密的串 返给前端请求, 前端拿到了这个串,就可以认为自己登录成功

操作:

为了方便,我们会一般在请求工具 axios(举例)的拦截器中**统一注入token**, 减少代码的重复

token 同时具有时效性,我们也需要在此时对token过期进行处理,一旦出现过期的请求码, 就需要进行 换取新token 或者重新登录的解决方案

拓展:

依据**有无加密串** 在前端对于某些页面的访问进行限制, 这个会用到我们的Vue-Router中的导航守卫.

通讯

浏览器与服务器的通讯

请求

XHR

XMLHttpRequest:Web应用与远程资源通信的基础

fetch

浏览器提供的api(原生XHR)

  1. 语法简洁,更加语义化
  2. 基于标准 Promise 实现,支持 async/await
  3. 更加底层,提供的API丰富(request, response)
  4. 脱离了XHR,是ES规范里新的实现方式

问题:

  1. fetch只对网络请求报错,对400,500都当做成功的请求,服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject。
  2. fetch默认不会带cookie,需要添加配置项: fetch(url, {credentials: ‘include’})
  3. fetch不支持abort,不支持超时控制,使用setTimeout及Promise.reject的实现的超时控制并不能阻止请求过程继续在后台运行,造成了流量的浪费
  4. fetch没有办法原生监测请求的进度,而XHR可以
fetch('http://example.com/movies.json')
  .then(function(res) {
    return res.json();
  })
  .then(function(myJson) {
    console.log(myJson);
  });
axios(优先)
  1. 社区封装的组件

  2. 基于Promise 用于浏览器和 nodejs 的 HTTP 客户端

本质上也是对原生XHR的封装,Promise的实现版本,符合最新的ES规范

  1. 客户端支持防止CSRF

  2. 提供了一些并发请求的接口(重要,方便了很多的操作)

  3. 拦截请求和响应

  4. 转换请求和响应数据

  5. 取消请求

  6. 自动转换JSON数据

// axios举例
axios.get('/user', {
    params: {
      ID: 12345
    }
  })
  .then(function (res) {
    console.log(res);
  })
  .catch(function (err) {
    console.log(err);
  });

小结:axios既提供了并发的封装,也没有fetch的各种问题,而且体积也较小,当之无愧现在最应该选用的请求的方式。

  1. axios的github仓库地址
  2. axios的api文档说明

浏览器内多个标签页间

借助服务器
WebSocket

使用websocket技术使多页签都监听服务器推送事件来获得其他页签发送的数据

详见网络协议WebSocket,没有同源限制

h5-SharedWorker

共享worker,可以多个标签页,iframe共同使用

可以被多个window共同使用,但必须保证这些标签页都是同源的 (相同的协议,主机和端口号)

webworker端(暂且这样称呼) 服务器上 的 worker.js代码(与index.html在同一目录) 就如下,只需注册一个onmessage监听信息的事件,客户端(即使用sharedWorker的标签页,监听message事件)发送message时就会触发

服务器上的worker.js代码(浏览器本身的安全机制)

// sharedWorker所要用到的js文件,不必打包到项目中,直接放到服务器即可
let data = '';
let onconnect = function (e) {
  let port = e.ports[0];
  port.onmessage = function (e) {
    if (e.data === 'get') {
      port.postMessage(data)
    } else {
      data = e.data
    }
  }
}

客户端(浏览器)的代码:

// 这段代码是必须的,打开页面后注册SharedWorker,显示指定worker.port.start()方法建立与worker间的连接
    if (typeof Worker === "undefined") {
      alert('当前浏览器不支持webworker')
    } else {
      let worker = new SharedWorker('worker.js')
      worker.port.addEventListener('message', (e) => {
        console.log('来自worker的数据:', e.data);
      }, false);  
  worker.port.start();
  window.worker = worker;
}
// 获取和发送消息都是调用postMessage方法,我这里约定的是传递'get'表示获取数据。

window.worker.port.postMessage('发送信息给worker')		//页面A发送“发送信息给worker”给worker
window.worker.port.postMessage('get')		//页面B

页面A发送数据给worker,然后打开页面B,调用window.worker.port.postMessage('get'),即可收到页面A发送给worker的数据。

传递的数据为**‘get’**时,就把变量data的值回传给客户端

其他情况,则把客户端传递过来的数据存储到data变量

借助浏览器

(缓存):本地存储方式

localStorage
  1. 在一个标签页里面使用 localStorage.setItem(key,value)添加( 修改、删除removeItem(key) )内容;

只要被添加,修改,删除,都会触发事件,下面监听事件即可进行页面信息通讯,不用setInterval

注意:Safari在无痕迹模式下设置localstorge值抛出QuotExceededError的异常

  1. 在另外一个标签页监听document对象的storage事件,在事件event对象属性中获取信息

    window.addEventListener(“storage”, function(event){ } )

event事件对象包含以下信息

  1. domain

  2. newValue常用

  3. oldValue

  4. key常用

cookie+setInterval
  1. 在A页面将需要传递的消息存储在cookie当中document.cookie =
  1. 在B页面设置setInterval,以一定的时间间隔去读取cookie的值

JSON解析

h5离线存储

manifest

把需要离线存储在本地的文件列在一个mainfest配置文件里,即使离线,用户也可以正常看见网页

查看 在 application ---- application cache里面可以看见

1 在需要离线缓存存储的页面 加上 manifest = “cache.manifest”

<!DOCTYPE HTML>
<html manifest = "cache.manifest">
...
</html>

2 在根目录 新建文件 cache.manifest 并写上对应代码

CACHE MANIFEST
#v0.11

CACHE:

js/app.js
css/style.css

NETWORK:
resourse/logo.png

FALLBACK:
/ /offline.html

CACHE:表示需要离线存储的资源列表,不需要写,资源页面有写1.manifest = "cache.manifest"即可

NETWORK:在线才能访问,但是优先级比CACHE低

FALLBACK:资源替换,如果访问根目录下任何一个资源失败了,那么就去访问offline.html

跨域

域名:协议 ip地址 端口 任何一个不一样 就跨域

同源策略带来跨域问题

解决跨域:

1 jsonp —使用script的src发送 只能get 请求,不安全、有缓存、大小限制

2 cors 后台(服务端)设置允许跨域 需要后台设置 允许跨域

​ 所有后台语言 都可以设置

Access-Control-Allow-Origin:*
Access-Control-Allow-Methods:"POST, GET, OPTIONS, DELETE"       

3 服务器代理

在开发环境(本地调试)期间,进行代理, 简单说, 在本地通过nodejs 启动一个微型服务,

请求我们的微型服务, 微型服务是服务端, 服务端**代我们去请求我们想要的跨域地址, 因为服务端是不受同源策略**的限制的

具体到开发中,打包工具webpack集成了代理的功能,可以采用配置webpack的方式进行解决

等项目上线时,还是需要另择代理 nginx

​ 前端 vue 框架 是可以自己设置 服务器代理的 proxy

vue在 vue.config.js 可以配置重写webpack

以下为webpack配置代理的配置

// vue.config.js
module.exports = {
  // 修改的配置
  devServer: {
      proxy: {
          '/api': {
              target: 'http://122.51.238.153',
              changeOrigin: true,
              pathRewrite: {
                '^/api': ''
              }
          }
      }
  }
}

target:接口域名;

changeOrigin: 如果设置为true,那么本地会虚拟一个服务端接收你的请求并代你发送该请求;

pathRewrite:如果接口中是没有api的,那就直接置空(如上)如果接口中有api,就需要写成{‘^/api’:‘’}

接口:强类型语言定义一个标准的入口然后供后续子孙继承使用

API:Application Program Interface,API就是操作系统留给应用程序的一个调用接口

上线了如果还有跨域 可以让后台设置 允许跨域

页面渲染

概念

标签,元素,节点node

渲染render

布局layout

回流,重排,重构:reflow

flush队列:冲洗,批处理

浏览器的运行机制

边解析(html,css文件)边渲染,js单线程允许,可能会修改DOM结构,js执行完前,后续所有的资源下载是没有必要的,js单线程会阻塞后续资源下载

  1. 构建DOM树/内容树parse(html):将标签转化成DOM node
  2. 构建渲染树construct(css,和html指令/b等):使每个node有自己的style,构建渲染树
  3. 布局(reflow,layout)渲染树:计算元素大小和位置
  4. 绘制渲染树(paint):使用UI层绘制节点

display:none和head节点,不会呈现,所以,渲染树不包含

重排:元素的几何属性(规模尺寸),布局,隐藏等改变

重绘:元素外观属性发生变化

重排一定会引发重绘,重绘不一定会引发重排,比如改颜色

性能优化

性能:从网站开始生成到代码开始运行,消耗的浏览器和服务器的资源

加载
静态资源过多过大

(html,css,js,图片等)

资源过多-图片

1)懒加载的方式减少首屏图片的加载量

监听滚动条事件,如果(滚动条距离浏览器顶部的高度 === 图片距离顶部的高度),那么就将 data-src 的值赋值到 src

A lazy image

<------ 滚动到特定位置的时候 ------>

A lazy image

2)小图片icon很多

A. 对于纯色系小图标可以使用 iconfont 来解决

  • 设置 font-family 的 CSS 属性

B. 对于一些彩色的小图片可以使用雪碧图(精灵图)

  • 把所有小图片拼接到一张大图片上

  • 并使用 background-position 的 CSS 属性来修改图片坐标

资源过大-压缩

1)css和js 用Webpack 来进行混淆和压缩

  • 混淆:将 JS 代码进行字符串加密(最大层度减少代码,比如将长变量名变成单个字母等等)
  • 压缩:去除注释空行以及 console.log 等调试代码

2)图片压缩

  • 可以通过自动化工具来压缩图片

    熊猫站:智能压缩 PNG 和 JPG 的一个网站

    通过减少颜色的数量以及不必要的数据来实现文件压缩,对图片进行等比例无损压缩

    安全考虑:熊猫站图片压缩工具已开源,可以使用 npm 安装开源包,就可以在我们本地进行图片压缩

  • 对图片进行转码 -> base64 格式

    使用 Webpack 的 url-loader 进行图片策略配置,将小图转换成 base64 格式

    base64 格式的图片 减少资源的数量,但会 增大原有图片的体积

  • 使用 WebP 格式

    WebP 比 PNG 文件少了 26% 的体积

3)通过开启 gzip 进行全部资源压缩

  • gzip: 是一种压缩文件格式,可以对任何文件进行压缩(类比于文件压缩)
  • 可以通过 nginx 服务器的配置项进行开启(演示 /usr/local/etc/nginx
请求量多

先通过工具来确定是哪些类型的资源请求过多

1)通过浏览器的 Network 可以确定首页加载的资源和请求量

  • requests:请求数量

  • resources:前端资源总大小

  • DOMContentLoaded: 浏览器已经完全加载了 HTML, 其他静态资源( JS, CSS, 图片等)并没有下载完毕(能看,不能用)

  • Load:浏览器已经加载了所有的静态资源(能用了)

2)通过 converge 来查看代码的使用状况

  • 只针对 JS 和 CSS

  • 可以看出哪些代码虽然加载了但是没有执行

  • 没有执行的代码可以考虑一下是否可以懒加载?

减少资源的请求量:

1)合并静态资源

  • 通过 nginx 服务器 (可用来做 CDN,用来处理静态资源)来做资源文件合并 combo将多个JavaScript、CSS文件合并成一个
  • 通过打包工具(Webpack)来做资源文件的物理打包(相对没有第一种灵活)

2)代码

a. 引入大型的第三方库,可以通过特定的 Babel 插件来进行按需加载

比如 组件库(antdelement-ui),函数库(lodash)等

b. 在路由层面也可以使用 React lazy 进行动态路由的加载,从而可以减少首页的 JS 和 CSS 的大小(只限于 SPA 应用

  • (React 16.6 以上版本才可以使用 React lazy)

先看看使用方式

// 1. 引入 react lazy, 并且使用 import 动态导入组件
import { lazy } from 'react'; // 静态导入


lazy(() => import('./Home')); // 动态导入

// 2. 引入 Suspense 组件,并使用 Suspense 将根组件包裹起来,并使用 fallback props 传入 loading 组件
import { Suspense } from 'react';

// 注意:使用 lazy 加载的组件,必须是 Suspense 子组件,或者孙组件
<Suspense fallback={<div>Loading...</div>}>
	<OtherComponent />
</Suspense>

动态导入(dynamic import):当代码运行 import 的时候,再导入组件

import("./math").then(math => {
  console.log(math.add(16, 26));
});

// 类似于 fetch,都是返回一个 Promise

fetch("./math").then(math => {
  console.log(math.add(16, 26));
});
  1. import(‘xxx’) 返回的是一个 Promise

  2. Webpack 只要遇到了 import(‘xxx’),就会把括号里引入的内容单独打一个包

为什么 React lazy 可以进行动态路由的加载?

小结:

React lazy 是使用了 dynamic import动态导入 的标准,webpack 只要遇到了 dynamic import, 就会把里面引入的内容单独打一个包。

由于 dynamic import 返回的是一个 Promise,所以可以使用 Promise 的状态来做渲染的流程控制

如果当前 Promise 是 pending 状态,那么就渲染 Loading 组件,如果 Promise 是 resolve 状态那么就渲染动态导入的组件

CDN 加速

CDN(内容分发网络)放静态资源的服务器(JS,CSS, 图片, 字体…)

扩展:日常企业项目中服务器按照功能区分:

  • 应用服务器:服务端语言运行的服务器(Java,NodeJS…)放淘宝应用的位置
  • 数据库服务器:放数据库的服务器
  • 存储服务器:放大型文件的服务器(例如各种网盘)
  • CDN服务器放静态资源的服务器(JS,CSS, 图片, 字体…)

很多地方都部署 CDN 服务器,如果用户需要下载静态资源,会自动选择最近的节点下载

扩展:

  • Http1.1 请求:对于同一个协议、域名、端口,浏览器允许同时打开最多 6个 TCP 连接(最多同时发送 6个请求)
  • Http2.0: 引入了多路复用的机制,可以最大化发送请求数量

同时由于 CDN 服务器的地址一般都跟主服务器的地址不同,所以可以破除浏览器对同一个域名发送请求的限制

Webpack打包

小 -> 使用 Webpack 进行混淆和压缩(压缩资源里),所有与 Webpack 优化相关的配置都是在 optimization 这个配置项里管理。

少 -> 使用 Webpack 进行物理打包(减少请求量里)

从 webpack 4 开始,会根据你选择的 mode 来执行不同的优化,不过所有的优化还是可以手动配置和重写。

development:不混淆,不压缩,不优化

production:混淆 + 压缩,自动内置优化

结论:只需要将 mode 改成 production 即可

小结:使用 Webpack 对代码进行混淆和压缩,并且可以使用 React lazy 进行拆包,结合路由进行按需加载

拆包后的文件,不可能同时加载的,所以就不会造成同一时间资源请求过多的请求

打包策略:(使用 Webpack 的 optimization.splitChunks)

把第三方包node_modules打一个包,公共的src代码打一个包,非公共的代码打一个包。

第三方包:改动频率 – 小

公共代码包:改动频率 – 中

非公共代码包:改动频率 – 高

缓存

打包策略结合网络缓存

第三方包,可以使用 强缓存 Cache-Control: max-age=31536000(缓存一年) 并配合协商缓存ETag` 使用(一旦文件名变动才会下载新的文件)

代码包,可以使用 Cache-Control: no-cache 并配合 ETag 使用,表示该资源已被缓存,但是每次都会发送请求询问资源是否更新

(h5的mainfest)

渲染
前端渲染

无论是浏览器中的 DOM 和 BOM,还是 NodeJS的Path,FS,NET,它们都是基于 JavaScript 引擎之上开发出来的

最终都要转换成js引擎能够处理的数据

在浏览器中最消耗性能的就是操作DOM–>减少DOM操作

优化:

  1. 浏览器:维护一个队列,到一定数量或一定时间间隔,进行批处理

  2. 我们:

    1) 不常用table布局页面,因为要多次计算才能确定节点的属性值(布局渲染树时减少计算节点的属性值)

    2)将需要多次重排的元素,position属性设置为absolute或fixed,脱离文档流(减少DOM操作,减少重排)

    3)可以采取分段渲染的方式,比如:一次只渲染一屏的数据最后使用 window.requestAnimationFrame 来逐帧渲染(懒加载的思路)

    4)需要创建多个DOM节点,使用DocumentFragment创建完后一次性的加入document(减少DOM操作,减少重排)

    举例如下:window.requestAnimationFrame+DocumentFragment(逐帧加载+一次性dom操作)

    网站动画的思路:

    // 插入十万条数据
    const total = 100000;
    let ul = document.querySelector('ul'); // 拿到 ul
    
    // 懒加载的思路 -- 分段渲染
    // 1. 一次渲染一屏的量
    const once = 20;
    // 2. 全部渲染完需要多少次,循环的时候要用
    const loopCount = total / once;
    // 3. 已经渲染了多少次
    let countHasRender = 0;
    
    function add() {
      // 创建虚拟节点,(使用 createDocumentFragment 不会触发渲染)
      const fragment = document.createDocumentFragment();
      // 循环 20 次
      for (let i = 0; i < once; i++) {
        const li = document.createElement('li');
        li.innerText = Math.floor(Math.random() * total);
        fragment.appendChild(li);
      }
      // 最后把虚拟节点 append 到 ul 上
      ul.appendChild(fragment);
      // 4. 已渲染的次数 + 1
      countHasRender += 1;
      loop();
    }
    
    // 最重要的部分来了
    function loop() {
      // 5. 如果还没渲染完,那么就使用 requestAnimationFrame 来继续渲染
      if (countHasRender < loopCount) {
        // requestAnimationFrame 叫做逐帧渲染
        // 类似于 setTimeout(add, 16);
        // 帧:一秒钟播放多少张图片,一秒钟播放的图片越多,动画就越流畅
        // 1000/60 = 16
        window.requestAnimationFrame(add);
      }
    }
    loop();
    

ps:

createElement创建节点

createDocumentFragment创建虚拟节点对象(集合多个节点)

appendChild(节点or节点对象):将节点加入document

document.getElementById(‘id名’):获取元素,后面可进行系列操作,比如加入新的节点等

window.requestAnimationFrame(add)逐帧渲染:递归

服务端渲染

应用场景:对 实时到达时间(页面访问时间)的绝对需求

服务端渲染这里 有一个成熟优秀的框架 nuxt.js , 正如next.js对于react,nuxt是vue服务端渲染的优秀解决方案

nuxt的出现可以让渲染内容完全服务端化,首屏渲染速度不够迅速的问题,解决seo不够友好

因为:

vue单页面应用渲染是从服务器获取所需js,在客户端将其解析生成html挂载于id为app的DOM元素上

  1. 由于资源请求量大,造成网站首屏加载缓慢
  2. 由于页面内容通过js插入,对于内容性网站来说,搜索引擎无法抓取网站内容,不利于SEO

而Nuxt.js预设了利用Vue.js开发服务端渲染的应用所需要的各种配置

可以将html在服务端渲染,合成完整的html文件再输出到浏览器

注意:服务端渲染比重大 对于服务器的访问处理能力 要求也会急剧增大

步骤
  1 脚手架 npx create-nuxt-app <项目名>
  2 yarn dev 启动开发
  
  上线 
  yarn build
  yarn start

nuxt与vue还有一些其他方面的区别:

  1. 路由
    nuxt按照 pages 文件夹的目录结构自动生成路由
    vue需在 src/router/index.js 手动配置路由

  2. 入口页面
    nuxt页面入口为 layouts/default.vue
    vue页面入口为 src/App.vue

  3. webpack配置
    nuxt内置webpack,允许根据服务端需求,在 nuxt.config.js 中的build属性自定义构建webpack的配置,覆盖默认配置
    vue关于webpack的配置存放在build文件夹下

  4. asyncData 里面发送ajax 这个东西跟生命周期这些都是平级的

要理解asyncData方法执行时,其实是在服务端完成的,这个数据是在服务端渲染好了的

unxtjs的ajax,你先别往你那个异步上去思考,其实这里面所有的ajax最后都会形成页面。你别想着,我一点按钮,调用一个方法,然后再ajax去加载数据。因为我们最后全部都会生成静态,所以任何的获取数据的操作,最后都会变成页面的跳转。

所以,官方给了一套写法,你必须按照这个去写,
并且这里的ajax会再页面渲染之前就执行。这个东西跟生命周期这些都是平级的。

1 cnpm install @nuxtjs/axios --save
2 .plugins目录新建axios.js

import * as axios from 'axios'

let options ={}

//需要全路径才能工作

if(process.server){

  options.baseURL=http://${process.env.HOST || 'localhost'}:${process.env.PORT || 3000}/api

}

export default axios.create(options)

3.Nuxt.config.js增加axios配置

modules:[

  '@nuxtjs/axios'

],

4 使用 asyncData 里面发送ajax 这个东西跟生命周期这些都是平级的 在页面渲染之前

export default {

  async asyncData({app}){

          let res =await app.$axios({

                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                method: 'get',
                url: `http://test.yms.cn/testjson.asp`,
                data: ''
    
          })
        // app.$axios
          console.log('res',res.data)
          return{
                testData:res.data.title
          }
    },
    created(){
      console.log('nuxt reg组件')
      
    }

}

限制接口访问频次

接口请求部分的逻辑我们可以封装成一个函数 fun。假设存在一个处理函数的函数better,接受fun,返回一个 betterFun。当 betterFun 被多次调用,fun 只在不要的时候执行。

const betterFun=better(fun);

debounce防抖

实现 debounce(setTimeout)

const debounce = (fn, delay: number) => {
  let inDebounce;
  return function(...args) {
    const context = this;
    clearTimeout(inDebounce);
    inDebounce = setTimeout(() => {
      fn.apply(context, args);
    }, delay);
  };
};

export default debounce;

debounce 单元测试

import debounce from "./debounce";
jest.useFakeTimers();

test("debounce", () => {
  const func = jest.fn();
  const debounced = debounce(func, 1000);
  debounced();
  debounced();

  jest.runAllTimers();
  expect(func).toHaveBeenCalledTimes(1);
});

test("debounce twice", () => {
  const func = jest.fn();
  const debounced = debounce(func, 1000);
  debounced();
  debounced();
  setTimeout(debounced, 20);
  setTimeout(debounced, 2000);
  jest.runAllTimers();
  expect(func).toHaveBeenCalledTimes(2);
});
throttle节流
  1. 实现 throttle
/**
*
* @param fn {Function}   实际要执行的函数
* @param delay {Number}  执行间隔,单位是毫秒(ms)
*
* @return {Function}     返回一个“节流”函数
*/
function throttle(fn, threshold) {
  // 记录上次执行的时间
  var last
  // 定时器
  var timer
  // 默认间隔为 250ms
  threshold || (threshold = 250)
  // 返回的函数,每过 threshold 毫秒就执行一次 fn 函数
  return function () {
    // 保存函数调用时的上下文和参数,传递给 fn
    var context = this
    var args = arguments
    var now = +new Date()
    // 如果距离上次执行 fn 函数的时间小于 threshold,那么就放弃
    // 执行 fn,并重新计时
    if (last && now < last + threshold) {
      clearTimeout(timer)
      // 保证在当前时间区间结束后,再执行一次 fn
      timer = setTimeout(function () {
        last = now
        fn.apply(context, args)
      }, threshold)
    // 在时间区间的最开始和到达指定间隔的时候执行一次 fn
    } else {
      last = now
      fn.apply(context, args)
    }
  }
}
  1. debounce.leading vs debounce.trailing

debounce.leading demo: https://codepen.io/dcorb/pen/GZWqNV

debounce.trailing demo: https://codepen.io/dcorb/pen/KVxGqN

function debounce(func, wait, immediate) {
	var timeout;
	return function() {
		var context = this, args = arguments;
		var later = function() {
			timeout = null;
			if (!immediate) func.apply(context, args);
		};
		var callNow = immediate && !timeout;
		clearTimeout(timeout);
		timeout = setTimeout(later, wait);
		if (callNow) func.apply(context, args);
	};
};

参考:

https://css-tricks.com/the-difference-between-throttling-and-debouncing/

http://hackll.com/2015/11/19/debounce-and-throttle/index.html

https://zhuanlan.zhihu.com/p/50367441

http://demo.nimius.net/debounce_throttle/

兼容

移动端兼容
  1. 判断安卓还是ios
//获取浏览器的userAgent,并转化为小写
var ua = navigator.userAgent.toLowerCase();

//判断是否是苹果手机,是则是true
var isIos = (ua.indexOf('iphone') != -1) || (ua.indexOf('ipad') != -1);
if(isIos){
   做苹果手机兼容
}else{
  做安卓
}

indexOf判断字符串,没有出现为-1

  1. 兼容场景

    安卓

    1)部分安卓手机点击图片会放大,如果要禁止放大

    设置css:

    img{
    pointer-events: none;
    }

    但是,这会使得img的点击事件失效,如果要给图片添加点击事件,要在外面再加一层包住

    1. 安卓不会自动播放视频

    autoplay没效果 需要手动触发一下

    window.addEventListener('touchstart', function(){
    		audio.play(); // 需要主动调用一下js 让视频播放
    }, false);
    

    iOS

    1)禁止iOS识别长串数字为电话

    2)非可点击元素如(label,span)监听点击事件,不会在IOS下触发

    css增加 cursor:pointer

    3)半透明的遮罩层改为全透明,解决“闪屏”

    在ios上,当点击一个链接或通过js绑定了点击事件的元素时,会出现一个半透明的背景,当手指离开屏幕,该灰色背景消失,出现“闪屏”

    html, body {
    -webkit-tap-highlight-color: rgba(0,0,0,0);
    }

    安卓和iOS

    1)禁止复制、选中文本

    设置CSS属性 -webkit-user-select:none

    2)上下拉动滚动条时卡顿、慢

    body {
    -webkit-overflow-scrolling: touch;
    overflow-scrolling: touch;
    }

    Android3+和iOS5+支持CSS3的新属性为overflow-scrolling

Web安全

XSS

Cross Site Script:跨站脚本攻击,注入恶意的客户端代码,js,html,flash

将一些隐私数据像 cookie、session 发送给攻击者,将受害者重定向到一个由攻击者控制的网站,在受害者的机器上进行一些恶意操作

  1. 反射型 Reflected XSS

发出请求时,XSS代码出现在url中,作为输入提交到服务器端,服务器端解析后响应,XSS代码随响应内容一起传回给浏览器,最后浏览器解析执行XSS代码

  1. 存储型存 Stored XSS

具有攻击性的脚本被保存到了服务器端(数据库,内存,文件系统)并且可以被普通用户完整的从服务的取得并执行,从而获得了在网络上传播的能力

  1. DOM型 DOM-based or local XSS

基于DOM或本地的 XSS 攻击

DOM漏洞:可以通过 DOM来动态修改页面内容,从客户端获取 DOM中的数据并在本地执行

例子:

  1. 拼接url 获取用户敏感数据(反射型)

  2. 后台存在漏洞拼接sql 盗取数据库信息

  3. 举例有这样一个网站,可以让你对某个文章输入评论(DOM)

防御措施

  1. 输入过滤: URL、查询关键字、POST数据等,仅接受指定长度范围内、采用适当格式、采用所预期的字符的内容提交,对其他的一律过滤。(客户端和服务器都要)

  2. 输出转义:

    例如: 往 HTML 标签之间插入不可信数据的时候,首先要做的就是对不可信数据进行 HTML Entity 编码

    html字符实体

function htmlEncodeByRegExp  (str){  
         var s = "";
         if(str.length == 0) return "";
         s = str.replace(/&/g,"&amp;");
         s = s.replace(/</g,"&lt;");
         s = s.replace(/>/g,"&gt;");
         s = s.replace(/ /g,"&nbsp;");
         s = s.replace(/\'/g,"&#39;");
         s = s.replace(/\"/g,"&quot;");
         return s;  
 }
var tmpStr="<p>123</p>";   
var html=htmlEncodeByRegExp (tmpStr)
console.log(html) //&lt;p&gt;123&lt;/p&gt;
document.querySelector(".content").innerHTML=html; //<p>123</p>
  1. 使用 HttpOnly Cookie

    将重要的cookie标记为httponly

    当浏览器向Web服务器发起请求的时就会带上cookie字段,但是在js脚本中却不能访问这个cookie

ps:web开发框架如vue.js、react.js等都已经考虑了XSS攻击对html插值进行了更进一步的抽象、过滤和转义

使用他们大部分情况下可以避免XSS攻击

CSRF

Cross-site request forgery 跨站请求伪造

通过 XSS 或链接欺骗等途径,让用户在本机(即拥有身份 cookie 的浏览器端)发起用户所不知道的请求

比如以你的名义发送邮件、发消息,盗取你的账号,添加系统管理员,甚至于购买商品、虚拟货币转账等

CSRF攻击攻击原理及过程如下:

  1. 用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A;
  2. 在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求到网站A;
  3. 用户未退出网站A之前,在同一浏览器中,打开一个TAB页访问网站B
  4. 网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A
  5. 浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户C的Cookie信息以C的权限处理该请求,导致来自网站B的恶意代码被执行。

防范措施

  1. 验证 HTTP Referer 字段

    Referer 的值是由浏览器提供的,不可全信,低版本浏览器下 Referer 存在伪造风险。用户自己可以设置浏览器使其在发送请求时不再提供 Referer 时,网站将拒绝合法用户的访问

  2. 在请求地址中添加 token 并验证

    把 token 以参数的形式置于 HTTP 请求之中

    对所有请求都添加 token 比较困难。难以保证 token 本身的安全,依然会被利用获取到 token

  3. 在 HTTP 头中自定义属性并验证

    也是使用 token 并进行验证, 把它放到 HTTP 头中自定义的属性里

    通过 XMLHttpRequest 这个类,可以一次性给所有该类请求加上 csrftoken 这个 HTTP 头属性,并把 token 值放入其中 (方便)

    通过 XMLHttpRequest 请求的地址不会被记录到浏览器的地址栏,也不用担心 token 会透过 Referer 泄露到其他网站中去

    无法在非异步的请求上实施

小结:xss和csrf都是攻击正常访问网站的用户,而非服务器,服务器DDoS攻击

xss主要是获取账户信息,csrf请求伪造

网页验证码

目的
  1. 防止恶意频繁登录注册,判断人机

    智能选图,文字点选,滑动,输入验证码

  2. 验证用户的合法性

    邮箱验证,短信,第三方授权等

实践

前端主要是发送ajax

  1. 短信验证码

点击发送验证码,即提交手机号给后台,后台发短信,用户收到短信,将收到的验证码填入表单,再进行ajax提交,交给后台验证

  1. 滑动验证码

    购买后 前端只需要引入js文件 然后 按照文档 写上就行

    https://docs.geetest.com/sensebot/apirefer/api/web

开发方式

移动应用开发方式

Native APP

原生app:本地应用程序(Native开发语言),升级总通过应用商店升级

Web App

移动web:网页应用程序(Web开发语言),通过移动浏览器安装

手机浏览器——web代码

Hybrid App

混合app:混合应用程序,从应用商店安装,部分更新可不通过应用商店升级

原生容器——web代码

折中选择,开发技能通用,h5可用性和功能迅速改进

混合开发结构(框架):

1)移动终端web壳:使用操作系统的API来创建嵌入式HTML的渲染引擎

定义安卓应用程序与网页之间的接口,允许网页中的js调用安卓应用程序,提供基于web的应用程序的安卓api,将web嵌入到安卓应用程序中

2)前端交互js:基础功能和业务功能

3)前端适配器:不同的终端:pad,Android,iOS,wap

h5与原生app的交互

  1. app调用h5的代码

    原生app可以直接访问h5

    在h5中曝露一些全局对象(方法),在原生app内即可调用这些

    javascript

    window.sdk = {
    
    ``double = value => value * 2,
    
    ``triple = value => value * 3,
    
    };
    

    android:

    webview.evaluateJavascript(``'window.sdk.double(10)'``, ``new ValueCallback<String>() {
    
    ``@Override
    
    ``public void onReceiveValue(String s) {
    
    ``// 20
    
    ``}
    
    });
    

    ios:

    NSString *func = @``"window.sdk.double(10)"``;
    
    NSString *str = [webview stringByEvaluatingJavaScriptFromString:func]; ``// 20
    
  2. h5调用app的代码

    h5 不能直接访问宿主app

    这种调用常用有两种方式:

    1)由app向h5注入一个全局js对象,然后在h5直接访问这个对象

    可能存在安全隐患: Android WebView 使用漏洞

    android

    webview.addJavascriptInterface(``new Object() {
    
    ``@JavascriptInterface
    
    ``public int double(value) {
    
    ``return value * 2;
    
    ``}
    
    ``@JavascriptInterface
    
    ``public int triple(value) {
    
    ``return value * 3;
    
    ``}
    
    }, ``"appSdk"``);
    

    ios

    NSString *scripts = @``"window.appSdk = {double: value => value * 2, triple: value => value * 3}"``;
    
    [webview stringByEvaluatingJavaScriptFromString:scripts];
    

    javascript

    window.appSdk.double(10); // 20

    2)由h5发起一个自定义协议请求,app拦截这个请求后,再由app调用 h5 中的回调函数

  3. 由 app 自定义协议,比如 sdk://action?params

  4. 在 h5 定义好回调函数,比如

    window.bridge={getDouble:value=>{},getTriple:value=>{}}

  5. 由 h5 发起一个自定义协议请求,比如 location.href=‘sdk://double?value=10’

  6. app 拦截这个请求后,进行相应的操作,获取返回值

  7. 由 app 调用 h5 中的回调函数,比如 window.bridge.getDouble(20);

javascript

window.bridge = {

  getDouble: value => { // 20

  }, 

  getTriple: value => {
  // more  
  }
};
location.href =  'sdk://double?value=10' ;

android:

webview.setWebViewClient( new WebViewClient () {

@Override

public boolean houldOverrideUrlLoading( WebView view,String url) {

// 判断如果 url 是 sdk:// 打头的就拦截掉

// 然后从 url sdk://action?params 中取出 action 与params 

	Uri uri = Uri.parse(url);                                 

    if ( uri.getScheme().equals( "sdk" )) {
    // 比如 action = double, params = value=10
        webview.evaluateJavascript( 'window.bridge.getDouble(20)' );
            return true;
    }
      
  }

});

ios:

(BOOL)webview:( UIWebView *)webview shouldStartLoadWithRequest:( NSURLRequest *)request navigationType:( UIWebViewNavigationType )navigationType { 

// 判断如果 url 是 sdk:// 打头的就拦截掉

// 然后从 url sdk://action?params 中取出 action 与params

NSString *urlStr = request.URL.absoluteString;

if ([urlStr hasPrefix:@ "sdk://" ]) {

  // 比如 action = double, params = value=10

  NSString *func = @"window.bridge.getDouble(20)" ;

  [webview stringByEvaluatingJavaScriptFromString:func];

      return NO; 

  }

  return YES;

}

原生安卓ios操作

视频

扫码

h5调用原生app的代码

开发框架

vue框架

组件化开发

主组件App.Vue 子组件

vue-cli

安装见文件和官网

  1. node.js (npm) node -v npm -v 设置全局目录

命令提示行打入:

npm confifig set prefifix “D:\nodejs\node_global”

再打入:

npm confifig set cache “D:\nodejs\node_cache”

cnpm容易出现各种乱七八糟的bug

安装cnpm:npm install -g cnpm --registry=https://registry.npm.taobao.org

cnpm -v

若出现“XXX不是外部指令或者内部指令XXX”则需要添加环境变量:

我的电脑右键-系统-高级设置-环境变量D:\nodejs\node_global

D:\nodejs\node_global目录下有cnpm才行

  1. 安装脚手架vue-cli

    https://cli.vuejs.org/zh/guide/installation.html

可以看官网了,记得直接用cnpm

创建项目,记得进入新建好的项目目录,方便找

vue create hello-world
// 接下来按提示,npm要改为cnpm
// 打包:build

ps:node.js相当于提供后台测试

脚手架:node_modules 100多M,依赖项

public公共常用文件

src源码

如果要用scss或less要选manual选上pre-css

scoped当前组件,不会影响子组件

  1. vscode插件

Vetur 0.24.0

Vue 2 Snippets

vscode终端cnpm install

不允许执行脚本,设置如下:

set-ExecutionPolicy RemoteSigned

get-ExecutionPolicy

cnpm install

  1. 打包后放到Nginx

    修改nginx.conf的server,listen:80 location:root 文件名/dist

    启动nginx

界面
Element-UI

View UI

分为vue2.0+ 和vue3,详见官网

vue3如下:https://element-plus.gitee.io/#/zh-CN/component/installation

安装或cdn(js.css)直接引入

安装完成后,可以引入部分组件,或者之后可以结合webpack打包压缩去掉不需要的

见快速上手

全部引入:main.js文件 样式文件需要单独引入 .use(ElementPlus)

按需引入:

1)插件:babel-plugin-import

2)Vite:按需编译Vite是在推出Vue 3的时候开发的,目前仅支持Vue 3.x,这意味着与Vue 3不兼容的库也不能与Vite一起使用

main.js :

全局配置:默认弹窗的zIndex为2000

.use(ElementPlus,{ })

Nuxt.js 渲染

  1. 布局

栅栏24列

el-row :gutter行间距20

el-col::span=“16”, :offset=“4” 占16列,居中左4列,右4列

(如果居中的话,el-row 可以直接用type=‘flex’ justify=‘center’)

  1. 容器el-container(放在布局里列里el-col里)

el-aside左边边栏,也可以再放el-container

el-header头顶容器

el-main主容器:el-table-column 行

  1. 表单

element-UI有提供体验功能,不用再另外写

如果有比较复杂的验证需求,要重写

密码显示

应用场景:表单,菜单导航

搭建

vue属性
data数据流

vue数据流

vue React数据流向是单向

由父节点流向子节点,如果父节点的props发生了改变,那么React会递归遍历整个组件

子组件自己并没有权利修改这些数据,如果要修改,只能把修改这一个行为通过 event 的方式报告给父组件,由父组件本身决定改如何处理数据

而,vue 的 v-model双向数据 无论数据改变,或是用户操作,都能带来互相的变动,自动更新

双向数据绑定

原理其实就是MVVM的实现原理

通过Object.defineProperty 完成对于数据的劫持,

通过对于 data中数据 set的监听,

通过观察者模式, 完成对于节点的数据更新

重点:vue传值

1 父 子 传值 使用props接受

<!-- 父组件的template中,子组件的msg的值为父组件的message -->
        <Test :msg="message" />

// 子组件对父组件传的值的要求
props:{
  msg:{
    type:String,
    default:'默认值',
    validator:function(value){
      return value.length > 10
    }
  }
}

2 子 父 传值 父组件把回调函数传递给子组件,子组件通过 $emit触发回调函数,并把参数传给父组件

@e-zi标识,

<!-- 父组件的template中,父组件获取子组件的值 -->
        <Test @e-zi="getData" />
methods:{
	// 子组件传来的数据为形参
	getDate(msg,num){}
}


// 子组件发出数据
<button @click='send'> </button>
data:{
	msg:'',
	num:5
}
methods:{
  send(){
    this.$emit('e-zi',this.msg,this.num)
		// 把msg和num传给父组件
  }
}

3 兄弟传值 $bus 中转站

temp1把值传给temp2,发布订阅模式,temp1发布 e m i t , t e m p 2 订 阅 emit,temp2订阅 emittemp2on

两个组件要挂载在同一个el下,用同一个vm1

// temp1发布
methods:{
  send(){
    vm1.$emit('data-msg',this.msg)	//'data-msg自定义事件'
  }
}
// temp2订阅,接收值后,可进行操作
mounted(){
  vm1.$on('data-msg',msg=>{
    this.msg = msg
  })
}
// mounted组件生命周期

4 如果组件之间 关系很远 是很多组件都要用的值 vuex

vuex 就是一个全局状态数据管理 (中大型项目)

类似全局变量 哪个组件都可以使用

数据要被多个组件频繁用到,在项目中使用vuex

如:一个网站用户的昵称,账号,资料,像这种系统级别的信息

vuex小结:state存数据,getters处理数据(return),mutations同步数据(交互@method事件,同步函数),actions同步数据(请求数据,异步函数)

操作全局vuex的 state数据

正常情况 必须 dispatch (actions)—>actions去commit触发 mutations–》mutation里面才能修改state全局数据

​ actions—>mutations—>修改state

其他情况 你也可以跳过 actions 去 直接 commit mutations–》修改state全局数据

代码简写:mapState,mapMutations,mapActions详细可见api

mutations-type.js mutations-type的集合文件,方便管理

应用场景:购物车数据存在state,筛选数据getters,增加减少数量mutations

项目里:

一、main.js:注册store,下载使用脚手架直接就可以选vuex

import store from './store'
new Vue({
  	store,
  	render: h => h(App)
}).$mount('#app')			// 挂载

二、store:index.js:state存全局数据

import Vue from 'vue'
import Vuex from 'vuex'

// 1. 导入 use一下
Vue.use(Vuex)

// 2. 全局数据写在state
export default new Vuex.Store({
 state: {
    count:1 //这个count 就是全局的数据
 },
 mutations: {
 },
 actions: {
 },
 modules:{
 },
 getters
}
})

三、组件/页面上使用:放在computed计算属性里,template就可以直接当data里的变量用getCount

computed:{
  	getCount(){
      	return this.$store.state.count
    }
}
v-for="(item) in getCount" :key="item.index"
// getCount为对象,item为数组
  1. 优化组件/页面上使用:取state里多个数据,可以用mapState简写
import {mapState} from 'vuex'

computed:{
  	...mapState([
  			'count',
  			'totalNum'
  	])
}
  1. 优化组件/页面上使用:封装一个getters.js,可以对state数据先进行筛选处理

    1. 在store里的getters.js处理state数据
export default{
  	// 直接获取
  	goodsObj: state => {
      	return state.goods
    },
  	// 重点:getters传参,会产生方法,可以直接this.$store.getters.getById(3)来调用
  	// 通过id筛选,state参数,内部又嵌一个函数id也是参数
  	getById: (state)=>(id)=>{
      	return state.goods.filter((item)=>((item.id-0) === id))
        // item.id-0转化为num类型,传入的id参数直接是num类型即可
        // goods数据数组的filter方法:传入函数,该函数的参数为item单条数据
    }
}

​ 2) 在store里的index.js导入getters.js,多加个getters属性

import getters from './getters'

import Vue from 'vue'
import Vuex from 'vuex'

// 1. 导入 use一下
Vue.use(Vuex)

// 2. 全局数据写在state
export default new Vuex.Store({
  state: {
     count:1 //这个count 就是全局的数据
  },
  mutations: {
  },
  actions: {
  },
  modules:{
  },
  getters
}
})

​ 3) 在组件/页面上使用,和没有getters.js差不多,区别主要在不是点state而是getters

created(){
  	console.log(this.$store.getters.getById(3))
},

computed:{
  	goods(){
      	return this.$store.getters.goodsObj
    }
}

mutations:和getters类似

1)在mutations.js

export default{
  	// 加1操作
  	// 和getters的区别,是操作,不返回值
  	increment (state,index) {
      	state.goods[index].num++
    }

2)index.js导入mutations.js

3)在组件/页面上使用,this.$store.commit(‘increment’,index),直接传参数

也可传对象,payload

payload传参:

this.$store.commit({
		type:'increment',
  	index
})

很多mutations可以做一个mutations-type.js 集合文件,方便查询使用管理

export const ADD = 'ADD'

1)然后在mutations.js引入

import {ADD} from './mutations-type'

export default{
  	// 加1操作
  	// 和getters的区别,是操作,不返回值
  	[ADD] (state, payload) {
      	state.goods[payload.index].num++
    }
  1. 在组件/页面上使用,type:‘ADD’

另,mutations也有,mapmutations,如果需要传值,需要在template方法传入对象

比如:@click = “handleAdd({index})”

script如下:

import {mapMutations} from 'vuex'

methods:{
  	...mapMutations({
      	handleAdd:'ADD'
    })
}

actions.js:能用mutations的也能用actions,异步函数的话,只能用mutations

actions.js:

import {ADD} from './mutations-type'

export default{
  	// 加1操作
  	// 和mutations的区别,是ADD是commit的参数
  	// mutations——commit--actions
  	increment ({commit}, payload) {
      	commit(ADD,payload.index)
    }

组件/页面:

// mutations用的commit提交,actions用的dispatch派发
// components--dispatch--actions
this.$store.dispatch({
  	type: 'increment',
  	index
})

vuex模块化module管理

分析: 此题考查 当vuex维护的数据越来越复杂的时候, 模块化的解决方案

解析:使用单一的状态树,应用的所有状态都会**集中在一个比较大的对象上面,随着项目需求的不断增加,状态树也会变得越来越臃肿,增加了状态树维护的复杂度,而且代码变得沉长;因此我们需要modules(模块化)来为我们的状态树分隔**成不同的模块,每个模块拥有自己的state,getters,mutations,actions;而且允许每个module里面嵌套子module;如下:

store
├── index.js          # 我们组装模块并导出 store 的地方
├── actions.js        # 根级别的 action
├── mutations.js      # 根级别的 mutation
├── state.js          # 根级别的 state
└── modules
  ├── module1.js   # 模块1的state树
  └── module2.js   # 模块2的state树

上面的设计中, 每个vuex子模块都可以定义 state/mutations/actions

需要注意的是 我们原来使用**vuex辅助函数** mapMutations/mapActions 引入的是 全局的的mutations 和actions , 并且我们vuex子模块 也就是module1,module2 … 这些模块的aciton /mutation 也注册了全局,

也就是如果 module1 中定义了 loginMutation, module2中也定义了 loginMutation, 此时, mutation就冲突了

如果重名,就报错了…

如果不想冲突, 各个模块管理自己的action 和 mutation ,需要 给我们的子模块一个 属性 namespaced: true

那么 组件中怎么使用子模块的action 和 mutations

你写一遍 步骤基本是死的 熟悉就好了

// 你可以将模块的空间名称字符串作为第一个参数传递给上述函数,这样所有绑定都会自动将该模块作为上下文
 methods:{
     ...mapMutations('m1', ['loginMutation']),
     add(){
       console.log('add',this)
      //  this.$store.commit("m1/loginMutation")
      // 或者下面的  先mapMutations 相当于帮你写了commit
      // this.loginMutation()
     }
  }

     // 这句话的意思是 直接 解构出 全局 m1模块下的 loginMutation 
    // 把loginMutation 放到this上 并且帮你写好了 commit
    // 相当于帮你简化了代码
     ...mapMutations('m1', ['loginMutation']),
       //不是modules的直接写  ...mapMutations( ['loginMutaton]) 
       

此题具体考查 Vuex虽然是一个公共状态, 但是公共状态还可以切分成若干个子状态模块, 也就是moduels,

解决当我们的状态树过于庞大和复杂时的一种解决方案. 但是笔者认为, 一旦用了vuex, 几乎 就认定该项目是较为复杂的

重点:页面刷新了之后vuex中的数据消失怎么解决?

将vuex数据进行本地持久化

vuex数据位于内存, 页面的刷新重置会导致数据的**归零**

vuex数据本地持久化,用本次存储 sesstionStorage 或者 localStorage

state数据初始化 /更新时,进行读取和设置本地存储操作:如下

export default new Vuex.store({
state: {
   user: localStorge.getItem('user')  // 初始化时读取 本地存储
},
mutations: {
   updateUser (state, payload) {
       state.user = payload.user
       localStoregae.setItem('user',payload.user) // 数据更新时 设置本地存储
   }
}
})

计算属性和监听器都和data里的值有关系

计算属性

computed特点:缓存特性,当依赖的数据发生变化,关联的数据才会发生不变

用于计算,格式化数据

应用场景:购物车合计,贷款计算,工资计算等

computed,类似method,template里{{ }},method:function一定要有返回值**

先在js处理值,方便处理后的值直接展示到页面,不用在template里修改

重点:

computed和method的区别:缓存特性,当依赖的数据发生变化,关联的数据才会发生不变

而method不会缓存,每次都要计算

	<div id="app">
        <p>{{message}}</p>
        <p>{{reverseMsg}}</p>
    </div>
    <script>
        var vm = new Vue({
            el:'#app',
            data:{
                message:'hello'
            },
            // 计算属性

            // 简写get
            // computed:{
            //     reverseMsg: function(){
            //         return this.message.split('').reverse().join('')
            //     }
            // }

            // 原本
            computed:{
                reverseMsg: {
                    // 返回值可以用于template中{{reverseMsg}}
                    get: function(){
                        return this.message.split('').reverse().join('')
                    },
                    // 可以对得到的值val进行一些操作
                    set: function(val){
                        console.log(val)
                    }
                }
            }
        })
    </script>
监听器

watch特点:只要某个数据发生改变,就可以处理处理数据,派发事件,可同步或异步

用于 与 事件和交互相关的场景

应用场景:达到某个临界值的弹窗

监听对象

watch最基本的用法:

export default {
    data () {
        return {
            name: '张三'
        }
    },
    watch: {
        name (newValue, oldValue) {
	
        }
    }
}

有个原则监听谁,写谁的名字,然后是对应的执行函数, 第一个参数为最新的改变值,第二个值为上一次改变的值

除了监听 data,也可以监听**计算属性** 或者一个 函数的计算结果

在组件中监听Vuex的数据变化

state负责管理状态,监听Vuex的数据变化,其实就是监听Vuex中的state的变化

  1. 在组件中通过 组件的watch方法来做,组件可以将state数据映射到 组件的计算属性

    监听 映射的 计算属性

    // vuex中的state数据
      state: {
        count: 0
      },
         
    //  A组件中映射 state数据到计算属性
      computed: {
        ...mapState(['count'])
      }
    // A组件监听 count计算属性的变化
       watch: {
        count () {
          // 用本身的数据进行一下计数
          this.changeCount++
        }
      }
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wn98Ex6Y-1620218821073)(/Users/zhuyuanlan/Downloads/2021前端面试必刷跨域浏览器工作原理VueReact性能优化-学习资料/Vue/笔记/assets/image-20200217103409496.png)]

  2. vuex中store对象本身提供了**watch**函数 ,可以利用该函数进行监听

  • watch(fn: Function, callback: Function, options?: Object): Function

响应式地监听 fn 的返回值,当值改变时调用回调函数callback

fn 接收 store 的 state 作为第一个参数,其 getter 作为第二个参数。最后接收一个可选的对象参数表示 Vue 的 vm.$watch 方法的参数。

  created () {		// vuex中store对象提供的函数 watch()
    this.$store.watch((state, getter) => {
      return state.count		// fn的参数state和getter,监听fn的返回值
    }, () => {
      this.changeCount++		// 回调函数
    })
  }

深度监听对象

  1. 字符串嵌套方式
export default {
    data () {
        return {
           a: {
               b: {
                   c :'张三'
               }
           }
        }
    },
    watch: {
        "a.b.c": function (newValue, oldValue) {

        }
    }
}
  1. 启用深度监听方式

    export default {
        data () {
            return {
               a: {
                   b: {
                       c :'张三'
                   }
               }
            }
        },
        watch: {
            a: {
                deep: true // deep 为true  意味着开启了深度监听 a对象里面任何数据变化都会触发handler函数,
                handler(){
                   // handler是一个固定写法
                }
            }
        }
    }
    
method(方法,插件)

自定义方法 this.$loading()

不局限于vue提供的api

// vm外面
function Loading(){
	
}
Vue.prototype.$loading = Loading
// Loading即可在vm里以this.$loading()来调用

// vm里面
methods: {
		showLoading( ){
				const hide = this.$loading()
		}
}

封装成插件

  1. 定义插件

下面是对象install的用法,也可以直接函数

​ const loadingPlugin = {

​ // install会自动传入形参wm=Vue

​ install:function(vm){ }

​ }

  1. 使用插件

    Vue.use(插件名)

    Vue.use(loadingPlugin)

应用场景

展示加载中的窗口(子组件实例化),并且2秒后,自动消失(销毁,不用单例模式)

封装loadingPlugin插件,使用this.$loading(msg)方法

<style>
    #loading-wrapper{
        position: absolute;
        top: 0;
        left: 0;
        display: flex;
        justify-content: center;
        align-items: center;
        width: 100%;
        height: 100%;
        background: rgba(0, 0, 0, .7);
        color: #ffffff;
    }
</style>
<body>
    <div id="root">
        <button @click="showLoading">显示loading</button>
    </div>
    <script>
        
        // 封装为loadingPlugin插件,下面是对象install的用法,也可以直接函数
        const loadingPlugin = {
            // install会自动传入形参wm=Vue
            install:function(vm){
                // 定义子组件
                const LoadingComponent = vm.extend({
                    template:'<div id ="loading-wrapper">{{msg}}</div>',
                    props:{
                        msg:{
                            type:String,
                            default:'loading...'
                        }
                    }
                })

                // loading方法,返回一个函数
                // 展示加载中的页面(子组件)
                    // 使用子组件,以前是注册后直接写在template上,再在父组件里传值即可(props)
                    // 因为要执行loading方法后,才会 生成 这个组件,所以用以下的方法
                    // 优缺点:
                    // 第一种可以再结合display:none,已经产生了,只是没有渲染而已
                		//(实现较为简单,如果页面内容不多的时候可以使用)
                    // 第二种更加节省资源:要用的时候才生成,这里可以再结合 单例模式 ,只生成一次,不过,因为需求为2秒后自动消失,自动销毁,所以不用单例模式
                
                // 用第二种方法:
                
                // 弹出loading窗口(功能),返回一个函数
                function loading(msg){
                    
                    const div = document.createElement('div')
                    div.setAttribute('id','loading-wrapper')
                    document.body.append(div)
                    
                    // 实例化之前定义的组件,并传入msg的值
                    new LoadingComponent({
                        props:{
                            msg:{
                                type:String,
                                default: msg    // loading方法传入
                            }
                        }
                    }).$mount('#loading-wrapper')   // vm.$mount将组件挂载到el上,会替换 #loading-wrapper

                    // 返回函数:移除loading窗口
                    return ()=>{
                        document.body.removeChild(document.getElementById('loading-wrapper'))
                    }
                }

                // 将loading方法作为Vue的方法,方便 在vm里以this.$loading()来调用
                vm.prototype.$loading = loading
            }
        }

        // 使用插件
        Vue.use(loadingPlugin)

        // 主组件
        var vm = new Vue({
            el:'#root',
            methods:{
                // 展示加载中的窗口,并且2秒后,自动消失
                showLoading(){
                    const hide = this.$loading('加载中......')
                    setTimeout(()=>{
                        hide()
                    },2000)
                }
            }
        })
        
    </script>
vue组件

组件是一个 HTML结构 + 数据逻辑(js) + 样式(css)的 操作单元

继承自Vue对象, Vue对象中的所有的属性和方法,组件可自动继承

template,script,style

自带组件

keep-alive缓存

组件缓存

vue自带组件

Vue的组件是有销毁机制的,比如条件渲染, 路由跳转 时 组件都会经历销毁

用keep-alive 包裹想要缓存的组件实例, 这个时候, 组件创建之后,就不会再进行 销毁, 组件数据和状态得以保存

没有了销毁,也就失去了重生的环节, 我们失去了 原有的钩子函数, 所以keep-alive包裹的组件 都获取了另外

两个事件 --如果缓存组件需要重新获取数据

唤醒 activated 重新唤醒休眠组件实例时 执行

休眠 deactivated 组件实例进入休眠状态时执行

例子:对 组件容器 router-view 这个组件进行的缓存, 一般的策略是在路由的元信息 meta对象中设置是否缓存的标记, 然后根据标记决定是否进行缓存

  <div id="app">
    <keep-alive>
      <!-- 里面是当需要缓存时 -->
      <router-view  v-if="$route.meta.isAlive" />
    </keep-alive>
     <!-- 外面是不需要缓存时 -->
    <router-view  v-if="!$route.meta.isAlive" />
  </div>

被缓存的组件中如果还有子组件, 那么子组件也会一并拥有 激活和唤醒事件,并且这些事件会在同时执行

自定义组件

不封装:

开发VueComponent:

  1. 构造函数
  2. 对象

注册:Vue.component(组件名,VueComponent)

使用:<组件名 :msg=“message” />

msg为子组件的变量,message为主组件的变量,父传子props

		<div id="root">
        <!-- 子组件的msg的值为父组件的message -->
        <Test :msg="message" />
    </div>
    <script>
        // 定义并注册子组件Test

        // 1. 构造函数:子组件component继承自Vue
        const component = Vue.extend({
            template:'<div>{{msg}}</div>',
            // 父传子值,用props
            props:{
                msg:{
                    type:String
                }
            }
        })
        // 给子组件命名‘Test',并注册为全局组件,才能在这个页面使用
        Vue.component('Test',component)

        // 2.对象
        // 根据源码,不仅可以传入构造函数component,也可以直接传入对象,可以改为如下:
        Vue.component('Test',{
            template:'<div>{{msg}}</div>',
            // 父传子值,用props
            props:{
                msg:{
                    type:String
                }
            }
        })

        
        // 父组件,主组件
        var vm = new Vue({
            el:'#root',
            data(){
                return{
                    message:'ok'
                }
            }
        })
        
    </script>
组件封装

vue组件开发到使用:

1 新建vue组件 2 Vue.component注册组件 3 在其他组件使用 标签名

参数: 可以传入数据 使用props接受 比如 数组 定时器时间等

开发vue组件:

明确组件的业务需求

1)体验特征,静态的html结构

2)处理什么数据:数据存储在data属性或 vue传值(props父传子,vuex状态共享等)

3)完成什么交互,组件的生命周期的钩子函数mounted 和 事件驱动methods

常用的组件属性 => data/ methods/filters/ components/watch/created/mounted/beforeDestroy/computed/props

常用组件指令: v-if / v-on@ / v-bind: / v-model /v-text/v-once

ps:

data的存在形式:

Vue实例化时,data是对象

开发组件时,data是函数data( ) { return { } }

组件实例化的时候, 会直接将data数据作用在视图上,data数据进行共享

为了组件内部的数据是相互独立的,且互不响应,所以 采用 return {}

保证每个组件实例的唯一性

页面跳转

用路由控制vue-router

单页面和多页面

单页面即所有的模块统统置于一个html文件之上

切换模块时,不会重新对html文件和资源进行再次请求

服务器不会对我们**换页面**的动作 产生任何反应,

所以我们感觉不到任何的刷新动作,速度和体验很畅快

网址通过锚点传参#,然后vue-router来处理

多页面应用 即多个html页面 共同的使用

一个页面即一个模块,但是不排除 多个单页应用混合到一起的组合情况

多页面切换一定会造成 页面资源的重新加载, 会造成很数据的**重置**

网址会变化

vue-router路由

单页面

引入js文件,也可用cdn

  1. 一个html,用来展示不同路由页面,不同路由:使用不同的组件

应用:

vue 单页项目涉及到多角色用户权限问题,不同的角色用户拥有不同的功能权限, 不同的功能权限对应的不同的页面

一开始 有一些 默认的路由

登录后 跳转到新的路由 比如你是总经理 后台会返回给前端 总经理能看见的 路由页面地址 数组

<div id="app">
  <router-link to='/app'>跳转默认首页</router-link>
  <router-link to='/admin'>跳转管理首页</router-link>
 <!-- router-link类似a标签,路由拿到的组件渲染下方: -->
  <router-view></router-view>
</div>
<script>
  const app = {
    template:'<h1>默认首页<h1>'
  }
  const admin = {
    template:'<h1>管理者首页<h1>'
  }
  // 把组件交给路由
  let route = new VueRoute({
    routes:[
      {path:'/app',component:app},		//走的是锚点,#/app
      {path:'/admin',component:admin}
    ]
  })
  // 把路由挂载到el里
  new Vue({
    el: '#app',
    router:route
  })
</script>

传参

vue-router 传值 可以通过 地址传值

最简单的就是url传值, url传值又两种, params 和 query参数传值

  1. params传值 是指的动态路由传值———— :参数

可以把后面的/改为-以便区分/user-:id-show-:aid

{  path: '/user/:id' }  // 定义一个路由参数
<router-link to="/user"></router-link>  // 传值
this.$route.params.id   // script里取值
{{ $route.params.id }}			// template里取值

可以传入正则表达式限制路由的参数

{  path: '/user/:id(\\d{2})' }  // 定义一个路由参数,\\d{2}传入两位数才有效
{  path: '/user/:id?' }  //不管有没有输入id都在这里路由

router-link可以简写

{  path: '/user/:id', component:admin, name:'name'}  // 定义一个路由参数
<router-link to="{name:'admin', params:{id:v.id}}"></router-link>  // 传值
this.$route.params.id   // script里取值
{{ $route.params.id }}			// template里取值

可用watch监听‘$route’ 的变化

取参数用 r o u t e , 跳 转 的 时 候 用 route,跳转的时候用 routerouter.push(url)

  1. query传值,指通过?后面的拼接参数传值———— ?参数
{  path: '/user?id' }  // 定义一个路由参数

<router-link to="{/user?id=123}"></router-link>  // 传值
this.$route.query.id   // 取值

与uniapp,uniapp页面的跳转接收参数

一个项目分成很多 小vue项目 你去其实也可以直接创建两个项目

1 **新建多个页面 每个页面是一个单独的小vue类型 **

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-za2JciDc-1620218821077)(/Users/zhuyuanlan/Downloads/2021前端面试必刷跨域浏览器工作原理VueReact性能优化-学习资料/Vue/笔记/assets/3.png)]

2 配置 多入口页面在vue.config.js里写上这些 重点是入口选择对应页面的main.js

//vue.config.js
module.exports = {
  pages: {
    index: {
      // page 的入口
      entry: "src/views/index/main.js",
      // 模板来源
      template: "public/index.html",
      // 在 dist/index.html 的输出
      filename: "index.html",
      // 当使用 title 选项时,
      // template 中的 title 标签需要是 <title><%= htmlWebpackPlugin.options.title %></title>
      title: "Index Page"
    },
    ui: {
      // page 的入口
      entry: "src/views/ui/main.js",
      // 模板来源
      template: "public/ui.html",
      // 在 dist/ui.html 的输出
      filename: "ui.html",
      // 当使用 title 选项时,
      // template 中的 title 标签需要是 <title><%= htmlWebpackPlugin.options.title %></title>
      title: "ui Page"
    }
  }
};

3 public 写上不同的渲染的 html

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zkJFal8X-1620218821079)(/Users/zhuyuanlan/Downloads/2021前端面试必刷跨域浏览器工作原理VueReact性能优化-学习资料/Vue/笔记/assets/4.png)]

4 main.js 不同的入口 对应上自己的 根组件和 页面元素

5 通过a标签跳转

<div id="app">
    ui页面啊啊啊
    <a href="home.html">去home页面</a>
  </div>

vue 单页项目涉及到多角色用户权限问题,不同的角色用户拥有不同的功能权限, 不同的功能权限对应的不同的页面

一开始 有一些 默认的路由

登录后 比如你是总经理 后台会返回给前端 总经理能看见的 路由页面地址 数组

前端在router.beforeEach 路由导航守卫里面 拿到返回的地址 使用 router.addRouter 动态加上 这个项目路由就好了

routes= 后台返回的 符合条件的 路由数据 类似我们自己写的那个path 等等
this.$router.addRoutes(routes)

   例子

 router.beforeEach((to, from, next) => {

    //判断user信息是否已经获取
        if (token) {
        //根据用户的角色类型来生成对应的新路由
            const newRouter = [{path:"/xxx" ...} ..]
            //将新路由添加到路由中
       router.addRoutes(newRouter)
        //为了正确渲染导航,将对应的新的路由添加到vuex中
                渲染对应的侧边栏
    }
})

网络通信

axios

uniapp框架

使用Vue.js

云打包app

编辑器 上面的 发行 就可以 但是先看看 manifest.json 这个是配置app 安卓和ios的

发行 云打包 之后 可以在 发行 查看 打包状态

如果打包成功 你就去打开它的下载地址 下载 apk文件就行 安卓的安装包叫apk

在手机 安卓apk 就是 app了

如果要到商店去 就打包的时候发行 这个需要公司的资质的

有一些大家 真的不清楚的 你可以问问 uni app 客服

云打包遇到的一些问题:

  1. HTML5+Runtime

manifest.json——源码视图——app-plus

"app-plus" : {
	"compatible": {  
		"ignoreVersion": true //true表示忽略版本检查提示框,HBuilderX1.9.0及以上版本支持  
	}
}
  1. 打包时未添加videoplayer模块

    manifest.json——模块配置——VideoPlayer(视频播放)

react框架
  • React:用于构建用户界面的 JavaScript 库(负责组件的 UI 界面渲染的库)
  • Redux:JavaScript 状态容器(负责管理数据的工具)

Redux 的出现其实就是解决了复杂应用的状态管理问题,可以跨层级任意传递数据

常用模块

axios

lodash

nodejs 快速搭建服务器

代码管理

git分支

前后端联调

  1. 项目是前后端分离的项目;后端负责提供数据接口,前端负责页面

  2. 一般接口完成之后,后端会给一份接口文档,包含接口地址,请求类型,请求的参数,可能的数据返回

  3. 部分后端同学可能不写文档,会进行口头沟通。口头沟通的时候需要记录必要的信息。

  4. 联调的时候最好是工作时间(免得晚上,别人下班了),因为可能涉及到沟通。

  5. 最好开发之前想好你需要哪些数据,然后和后端提前沟通一下。避免一下你需要的数据,后端没有开发,导致项目工期延误。

项目打包

  1. es2015+ 转 es5

  2. Js/css/图片 文件合并

  3. ts 转 js

  4. 模板编译 jsx 转 js

  5. less/sass 转 css

  6. css

  7. 代码压缩

  8. 针对不同环境生成不同的代码

上线部署

npm run build生成代码,打包后把build下的html,css放到根路径

如果你是放到某个文件夹 不是直接放到根路径
那么 一般你需要yarn bulid打包的 配置路径

如果是linux服务器 可以使用 ssh 或者 ftp 去上传 html css js 到服务器
如果是windows服务器 可以使用 远程桌面连接 或者 ftp 上传到 服务器

开发 和build

问题

vue react如果 history模式下面 刷新 404 了 就需要服务器配置路由重写 指向index.html才可以

apache 服务器 配置 .htaccess 文件 设置重写

nginx 也需要配置 服务器

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我了解了你的问题。首先,交通流模型通常使用微观和宏观两种方法进行建模和仿真,而红绿灯控制算法也有多种实现方式。在这里,我将为你提供一种基于微观交通流模型的红绿灯控制算法,并使用MATLAB进行模拟和优化。 1. 建立交通流模型 首先,我们需要建立一个基于微观交通流模型的仿真环境。这里,我们可以使用SUMO(Simulation of Urban Mobility)软件进行建模和仿真。SUMO是一个开源的交通仿真软件,可以用于建立城市交通网络、车辆、行人和公共交通等模型,并进行仿真和优化。你可以在SUMO官网上下载和安装该软件,并使用SUMO提供的GUI界面进行建模和仿真。 2. 设计红绿灯控制算法 在建立好交通流模型后,我们需要设计一个有效的红绿灯控制算法,以实现交通流的优化。这里,我们可以使用基于车辆密度和等待时间的红绿灯控制算法。具体来说,我们可以通过检测交叉口的车辆密度和等待时间,来确定红绿灯的开启和关闭时间。当车辆密度较高或等待时间较长时,我们可以延长红灯时间,以减少车辆拥堵和等待时间;当车辆密度较低或等待时间较短时,我们可以缩短红灯时间,以提高交通效率和通过率。 3. 编写MATLAB代码进行模拟和优化 最后,我们可以使用MATLAB编写代码,对红绿灯控制算法进行模拟和优化。具体来说,我们可以通过调用SUMO提供的API接口,读取和修改交通流模型中的车辆和交通信号灯状态,并实现红绿灯控制算法。同时,我们也可以使用MATLAB提供的优化算法,对红绿灯控制算法进行参数调优和性能优化,以实现最佳的交通流控制效果。 综上所述,以上就是基于交通流模型和MATLAB的红绿灯控制算法的设计和实现方法。希望对你有所帮助!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值