# qianduan_mianshi **Repository Path**: js-class/qianduan_mianshi ## Basic Information - **Project Name**: qianduan_mianshi - **Description**: eeeeeeeeeeeeeeeeeeeeeerrrrrrrrr - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-12-13 - **Last Updated**: 2023-01-15 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # HTML9-3 * 如何理解`html`语义化? * `html`语义化标签更易读(针对人) 屏幕快照 2021-04-03 下午1.36.07 * 让搜索引擎更容易读懂(SEO)(针对机器) * 块状元素和内联元素? * `display:block/table;`有`div h1 h2 table ul ol p`等 * `display:inline/inline-block;`有`span img input button`等 # css部分 ### 1. 请问div1的offsetWidth是多大?(盒模型宽度计算) ```` html
this is div1
```` > 知识点: offsetWidth = (内容宽度+内边距+边框),无外边距 > 因此答案是122px > `console.log(document.getElementById('div1').offsetWidth);//122px` #### 补充问题:如果让offsetWidth等于100px,该如何做? * 解决方法:添加样式代码:box-sizing:border-box * 解释:指定宽度和高度(最小/最大属性)确定元素边框。 也就是说,对元素指定宽度和高度包括了 padding 和 border(把padding和border算到了100px以内) 。通过从已设定的宽度和高度分别减去边框和内边距才能得到内容的宽度和高度。 * 为元素指定的任何内边距和边框都将在已设定的宽度和高度内进行绘制。 通过从已设定的宽度和高度分别减去边框和内边距才能得到内容的宽度和高度。 ### 2. AAA和BBB之间的距离是多少?(margin纵向重叠问题) 知识点:相邻元素的margin-top和margin-bottom会重叠,空p标签被忽略 答案:15px >```` html > > > >

AAA

>

>

>

>

BBB

> >```` ### 3.margin的top,left,right,bottom设置负值,有何效果?(margin负值问题) 1. margin-top和margin-left为负值,元素向上,向左移动 2. margin-right负值,右侧元素左移,自身不受影响 3. margin-bottom负值,下方元素上移,自身不受影响 ### 4.什么是BFC?如何应用?(BFC的理解与应用) * 简介 * Block format context ,块级格式化上下文 * 一块独立渲染区域,内部元素的渲染不会影响边界以外的元素 > bfc元素表现的特性就是一个独立的盒子,内部的子元素再怎么折腾都不会影响外部的元素和父元素,
> 如果触发bfc,子盒子的margin和浮动不会对父盒子产生影响,所以可以用来清除浮动问题。 * 形成BFC的条件(清除浮动) * 父元素的float不是none * 父元素的position是absolute或者fixed * overflow 不是visible,例如为:hidden * display是flex或 inline-block等 * BFC常见应用 * 清除浮动 * 利用BFC避免margin重叠。 ```html ``` * 自适应两栏布局 ### 5.实现倒三角形 > 1. 写一个div盒子 > 2. div宽高设为0px > 3. 设置比较大的四个border宽度,用以撑起盒子的面积 > 4. 把不想看到的边用transparent属性隐藏起来,留下 的最后一个border呈现出来的就是倒三角形 Title
--- ## 3-5 --- ### 5.如何实现圣杯布局和双飞翼布局?(float布局) * 圣杯布局的目的 1. 三栏布局,中间一栏最先加载和渲染(内容最重要) * 两侧内容固定,中间内容随着宽度自适应 * 一般用于PC网页 ```html 圣杯布局
this is center
this is left
``` * 圣杯布局和双飞翼布局的技术总结 1. 使用float布局 2. 两侧使用margin负值,以便和中间内容横向重叠 3. 防止中间内容被两侧覆盖,一个用padding一个用margin 4. 圣杯布局是用外层盒子的padding进行留白,所以内层盒子要想出现在留白处除了使用margin-left(rigth)还要借助相对定位。
而双飞翼布局是利用中间盒子的margin进行留白,所以直接用margin-left(rigth)就可以出现在留白处了 ```html Document
this is center
this is left
this is right
``` ### 6.手写clearfix ```css 原理:通过CSS伪元素在容器的内部元素之后添加一个看不见的空格“/20”或点“.” ,并且设置clear属性清除浮动 .clearfix:after { content: ''; //和:before 及 :after 伪元素配合使用,来插入生成内容。 display: table;//设置成块状元素,或许display:block;也可以 clear: both;//在左右两侧均不允许浮动元素。 } .clearfix{ zoom:1;/*兼容IE低版本*/ } ``` ### 7.flex实现三个点的色子(flex布局) * flex实现一个三点的色子 * 要求熟练掌握属性: * `flex-direction` * `justify-content`:和主轴方向一致,即和`flex-direction`设置的方向一致 * `aligin-items`:交叉轴,相对于`justify-content` * `flex-wrap` * `align-self` ### 8. absolute和relative分别依据什么定位 * relative依据自身定位 * absolute依据最近一层的定位元素定位 * 定位元素: * `absolute relative fixed` * `body` > 先找`absolute relative fixed` 如果没有就直接参考body定位 ### 9. 居中对齐有哪些实现方式? * 水平居中 * inline元素:`text-align:center` * block元素: `给宽度+margin:auto`,若有父元素则相对于父元素水平居中,如果没有则相对于整个浏览器水平居中 * absolute元素:`left:50%+margin-left负值`(需要知道子元素的尺寸) * 垂直居中 * inline元素:`line-height的值等于height值` * absolute元素:`top:50% + margin-top负值` ``(需要知道子元素的尺寸)`` * absolute元素:`transform:translate(-50%,50%)`(不需要知道尺寸) * absolute元素:`position:absoulute,top,left,bottom,right=0+margin:auto`(不需要知道尺寸) --- ## 3-10 --- ### 10.图文样式 ##### line-height如何继承: ```html

这是一行文字

``` * `line-height: 50px;` line-height为确定数值,那么p标签的`line-height`直接继承`50px` * `line-height: 1.5;`line-height为比例数值,那么p标签的继承的`line-height`为`16*1.5=24px`(补充知识:`font-size=line-height=16`) * `line-height: 200%;`line-height为百分比的形式,那么p标签的继承的`line-height`为`40px`(父元素的`font-size)*200%=40px` ### 11.响应式 #### rem: * px,绝对长度单位,最常用 * em,相对长度单位,在 font-size 中使用是相对于父元素的字体大小,在其他属性中使用是相对于自身的字体(fontsize)大小,如 width * rem,相对长度单位,相对于根元素(html元素),常用于响应式布局 * 换算尺度由html设置,如:html的` font-size`为100px,那么子元素的font-size的1rem就是100px,0.16rem就是16px * 除了`font-size`以外任何使用长度单位的情况也都可以使用rem #### 响应式布局的常用方案: * media-query,根据不同的屏幕宽度设置根元素属性值,例如:`font-size` * 给不同尺寸的屏幕设置不同像素数值 ```html 响应式布局
this is div
``` * rem,基于根元素的相对单位 #### vw/vh * rem的弊端 * 弊端:阶梯性 * 网页视口尺寸 * window.screen.height //屏幕高度 * window.innerHeigth //网页视口高度 =100vh * document.body.clientHeigth //body高度 屏幕快照 2021-03-31 下午1.32.05 * vw/vh * 优点:比rem方式更加细腻,避免了阶梯性 * 1vh是网页视口高度的1/100 * 1vw是网页视口宽度的1/100 * vmax取两者最大值;vmin取两者最小值 ### 12.css面试总结 * 如何理解HTML语义化? * 让人更容易读懂(增加代码可读性) * 让搜索引擎更容易读懂(SEO) * 常见的块状元素和内联元素有哪些? * display:block/table;有div h1 h2 table ul ol p等 * display:inline/inline-block;有span img input button等 * 盒模型宽度如何计算? * offsetWidth = (内容宽度+内边距+边框),无外边距 * 让元素的width=offsetWidth:使用box-sizing:border-box; * margin 纵向重叠问题 * 相邻元素的margin-top和margin-bottom会发生重叠 * 空白内容的p标签也会重叠 * 元素margin负值问题 * margin-top和margin-left负值,元素向上,向左移动 * margin-rigth负值,右侧元素左移,自身不受影响 * margin-bottom负值,下方元素上移,自身不受影响 * BFC理解和应用 * 一块独立渲染区域,内部元素的渲染不会影响边界以外的元素 * 形成BFC的条件: * xxx见上文 * BFC的常见应用 * 清除浮动 * 圣杯布局和双飞翼布局的技术总结 * 使用float布局 * 两侧使用margin负值,以便和中间内容横向重叠 * 防止中间内容被两侧覆盖,一个用padding一个用margin * 手写clearfix * flex布局(画三个点的色子) * absolute和relative的定位 * relative依据自身定位 * absolute依据最近一层的元素定位 * 居中对齐的实现方式 * 水平居中 * 垂直居中 * line-height继承 * rem是什么 * 响应式布局的常用方案 # JS部分 ### 13.变量类型和计算 #### 题目: * typeof能判断哪些类型 * 何时使用===何时使用== * 值类型和引用类型的区别 * 手写深拷贝 #### 知识点: * 变量类型 * 值类型vs引用类型 ```javascript //值类型 let a = 100; let b = a; a = 200; console.log(b);//100 //常见的值类型 let a;//undefined const s = 'abc' const n = 100; const b = true; const s = Symbol('s'); // 引用类型 let a = {age:20}; let b = a; b.age = 21; console.log(a.age)//21 // 常见的引用类型 const obj = {x:100}; const arr = ['a','b','c']; const n = null//特殊引用类型,指针指向为空地址 //特殊引用类型,但不用于存储数据,所以没有"拷贝,复制函数"这一说 function fn(){} ``` --- ## 4-1 --- >接上文…… * typeof 运算符 * 识别所有值类型(太简单不演示了,注意特别的Symbol) * 返回数据类型的字符串表达 ```javascript const s = Symbol('s'); typeof s//'symbol' ``` * 识别函数 * 判断是否是引用类型(不可再细分) * 深拷贝 * 递归 * Object.assgin(目标对象,原始对象) * `JSON.stringfy(obj)`将对象转换成字符串,在利用`JSON.parse(string)`将字符串转换成对象,本质底层还是利用递归 ```javascript var obj1 = { age:18, arr:[0,1,2], person:{ name:'yang', sex:'nan' } }; var obj2= {}; function deepClone(obj1,obj2){ if (typeof obj1!=='object' || obj1 == null){ return obj1; }else{ for(var k in obj1){ if (obj1.hasOwnProperty(k)){ if (typeof obj1[k] == 'object'){ //obj2[k] 赋值为数组或者对象,用于在下一轮循环中接收原对象中的数组或者对象 obj2[k] = obj1[k].constructor==Array?[]:{}; // obj2[k] = Object.prototype.toString.call(obj1[k]) == "Array" ? [] : {}; deepClone(obj1[k],obj2[k]); } else{ obj2[k] = obj1[k]; } } } } } deepClone(obj1,obj2); console.log(obj2) var obj1 = {//原始对象 age: 18, arr: [1, 2, 3], }; var obj2 = {}; function copy(obj1, obj2) { for (var k in obj1) { if (typeof obj1[k] == 'object') { //obj2[k] 赋值为数组或者对象,用于在下一轮循环中接收原对象中的数组或者对象 obj2[k] = (obj1[k].constructor == Array) ? [] : {}; // obj2[k] = Object.prototype.toString.call(obj1[k]) == "Array" ? [] : {}; copy(obj1[k], obj2[k]) } else { obj2[k] = obj1[k]; } } return obj2; } var obj2 = copy(obj1, obj2); console.log(obj2); obj1.age = 199; obj1.arr[0] = 'zhangsan'; console.log(obj1, obj2) //注意:深拷贝同时复制了基本类型和引用类型的属性,所以任何一个对象的引用类型属性或者基本类型属性改变都不会相互影响 //利用JSON深拷贝,原理还是递归 //var obj2 = JSON.parse(JSON.stringify(obj1)); ``` ```javascript /** * 深拷贝 */ const obj1 = { age: 20, name: 'xxx', address: { city: 'beijing' }, arr: ['a', 'b', 'c'] } const obj2 = deepClone(obj1) obj2.address.city = 'shanghai' obj2.arr[0] = 'a1' console.log(obj1.address.city) console.log(obj1.arr[0]) /** * 深拷贝 * @param {Object} obj 要拷贝的对象 */ function deepClone(obj = {}) { if (typeof obj !== 'object' || obj == null) { // obj 是 null ,或者不是对象和数组,直接返回 return obj } // 初始化返回结果 let result if (obj instanceof Array) { result = [] } else { result = {} } for (let key in obj) { // 保证 key 不是原型的属性 if (obj.hasOwnProperty(key)) { // 递归调用!!! result[key] = deepClone(obj[key]) } } // 返回结果 return result } ``` ```JavaScript //浅拷贝对象 var obj1 = { age: 18, arr: [1, 2, 3] }; var obj2 = {}; function copy(obj1) { for (var k in obj1) { obj2[k] = obj1[k]; } return obj2; } var obj2 = copy(obj1); console.log(obj2 == obj1); obj1.age = 33; obj1.arr[0] = 'ooo'; console.log(obj1, obj2) //注意:浅拷贝只是复制了基本类型,所以其中一个对象的基本类型的属性改变是不会影响到另一个对象的, // 但是两个对象的引用类型属性值发生改变还是会互相影响的 // 利用assign()浅拷贝 // var obj2 = {}; //Object.assign(目标对象, 原始对象) // Object.assign(obj2, obj1); // obj1.arr[0] = 'ooo'; // console.log(obj1, obj2) ``` ```javascript //合并对象 var obj1 = { age: 19, name: 'yang', num: [1, 2, 3] } var obj2 = { sex:'nan' } var obj3 = Object.assign(obj2, obj1); console.log(obj3)//obj1和obj2的集合 ``` * 特殊的值`undefined`和`null` * `null`指空值(曾经赋过值,但是目前没有值),特殊关键字,不是标识符,不能当做变量使用和赋值 * `undefined`指没有值(从未赋过值),是一个标识符,可以当做变量使用 * `typeof undefined => 'undefined'` * `typeof null =>’object‘` * 特殊的数字`NaN`(不是数字的数字) * 理解为"无效数值","失败数值"或者"坏数值",指出数字类型中的错误情况即:"数学运算没有成功,这是失败后返回的结果" * `NaN`是一个特殊值,它和自身不相等,但是`NaN!=NaN//true` ```javascript var a = 2/"foo"; a == NaN //false a === NaN //false window.isNaN(a) //true,有bug不可取 Number.isNaN(a) //true ES6工具函数,准确 ``` * 假值(假值得布尔强制类型转换结果为false,但是假值得封装对象是真值) * `undefined` * `null` * `false` * `+0,-0和NaN` * `""` * 真值:假值列表以外的值都是真值 * 变量计算-类型转换 * 字符串拼接 * 当需要将非数字当做数字来使用,比如数学运算,遵循以下规则: * `true`转换为1 * `false`转换为0 * `undefined`转换为`NaN` * `null`转换为0 ```javascript const a = 100 + 10 //110 const b = 100 + '10' //'10010' const c = true + '10' //'true10' const d = undefined + 100 //NaN const e = 100 + null //100 ``` * `==`和`===`运算 * `==`是宽松相等:允许在相等比较中进行强制类型转换,而`===`不允许 * `==`两边的值转换规则(两边的值分别转换成什么类型的规则) * 数字和字符串:(简单说:字符串转换成数字) 1. 如果`Type(x)`是数字,`Type(y)`是字符串,则返回`x==ToNumber(y)`的结果 2. 如果`Type(x)`是字符串,`Type(y)`是数字,则返回`ToNumber(x)==y`的结果 * 其他类型和布尔类型:(简单说:布尔类型转成数字类型) 1. 如果`Type(x)`是布尔类型,则返回`ToNumber(x)==y`的结果 2. 如果`Type(y)`是布尔类型,则返回`x==ToNumber(y)`的结果 * null和undefined 1. 如果`x`为`null`,y为`undefined`,则结果为`true` 2. 如果`x`为`undefined`,`y`为`null`,则结果为`true` > 在`==`中`null`和`undefined`相等(与其自身也相等),除此之外其他值都不和它们相等 也就是说在`==`中`null`和`undefined`是一回事,可以相互隐式强制类型转换。 ```javascript 100=='100' //true 0 == '' //true 0 == false //true false == '' //true null == undefined //true NaN==NaN //false +0==-0 //true var a = '3.14'; var b = a - 0; b//3.14 ``` * if语句和逻辑运算 * `||`(或)和`&&`(与)或许叫`["选择运算符"]`更准确,因为它返回的是两个操作数中的一个 * 比较规则: * 先进行条件判断第一个值是true还是false,如果第一个值不是布尔值,那么就先将它进行强制类型转换,然后执行条件判断。 * 对于`||`来说,如果条件判断结果是true就返回第一个值,如果是false就返回第二个值 * `&&`正好相反,如果条件判断结果是true就返回第二个值,如果是false就返回第一个值 > 引述ES5规范11.11节: `&&` 和 `||`运算符的返回值不一定是布尔类型,而是两个操作数其中一个的值: 见书《你不知道的JS》中册74页 ### 14.原型和原型链 #### 题目: * 如何判断一个变量是不是数组? ```javascript var arr = [1,2,3]; function isArray(arr){ //instance of 回答的问题是:在arr整条【prototype】链中是否有Array.prototype指向的对象? // return arr instanceof Array?'数组':'' return arr.constructor == Array?'数组':'' }; console.log(isArray(arr)) ``` * 手写一个简易的jQuery,考虑插件和扩展性 ```html Title

一段文字 1

一段文字 2

一段文字 3

``` # 11-1 * 事件代理 > 1. “事件代理(事件委托)”即是把原本需要绑定在子元素的响应事件(click、keydown......)委托给父元素,让父元素担当事件监听的职务 > 2. 多用于子元素个数不确定或者量很大的情况。 > 3. 通过事件冒泡触发父元素绑定的事件,再根据`e.target.nodeName`或者`e.target.matches`获取触发事件的具体子元素并且判断是否是目标子元素,如果是则对子元素进行进一步处理 * 代码简洁 * 减少浏览器内存占用 * 但是,不要滥用 ```html ``` ## ajax #### 题目: * 手写一个简易的ajax ```javascript //new XMLHttpRequest() //open() //监听xhr.onReadyStatechange //xhr.readyState == 4; //xhr.state = 200; //xhr.responseText //xhr.send() ``` ```javascript //XMLHttpRequest 对象用于在后台与服务器交换数据。 //get请求 const xhr = new XMLHttpRequest(); xhr.open('GET','./data/test.json',true);//true 异步请求 xhr.send(null);//向服务器发送请求,不带参数 xhr.onreadystatechange = function () { if (xhr.readyState===4){ if(xhr.status === 200){ // console.log(JSON.parse(xhr.responseText)) alert(xhr.responseText) }else if(xhr.status === 404){ console.log('404 not found') } } } //post请求 const xhr = new XMLHttpRequest(); xhr.open('POST','./login',true); xhr.onreadystatechange = function () { if (xhr.readyState===4){ if(xhr.status === 200){ // console.log(JSON.parse(xhr.responseText)) alert(xhr.responseText) } } } const postData = { userName:'zhangsan', password:'xxx' } xhr.send(JSON.stringify(postData));//必须发送字符串类型 ``` ```javascript //promise形式ajax请求 function ajax(url) { const p = new Promise((resolve, reject) => { const xhr = new XMLHttpRequest() xhr.open('GET', url, true) xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if (xhr.status === 200) { resolve( JSON.parse(xhr.responseText) ) } else if (xhr.status === 404 || xhr.status === 500) { reject(new Error('404 not found')) } } } xhr.send(null) }) return p } const url = './data/test.json' ajax(url) .then(res => console.log(res)) .catch(err => console.error(err)) ``` * 跨域的常用实现方式 * JSONP * CORS #### 知识点: * XMLHttpRequest * 状态码 * xhr.readyState * 0 (未初始化)还没有调用send()方法 * 1(载入)已调用send()方法,正在发送请求 * 2(载入完成)send()方法执行完成,已经收到全部响应内容 * 3(交互)正在解析响应内容 * 4(完成)响应内容解析完成,可以在客户端调用(只需要这一种) * xhr.status(**) * 2xx -表示成功处理请求,如200 * 3xx 需要走重定向,浏览器直接跳转,如301(永久重定向),302(临时重定向),304(资源没改变,使用缓存资源) * 4xx 客户端请求错误,如404(找不到地址),403(客户端没有权限) * 5xx 服务器端错误 * 跨域:同源策略,跨域解决方案 * 什么是跨域(同源策略) * ajax请求时,浏览器要求当前网页和server必须同源(安全) * 同源:协议(http,https),域名(xxx.com),端口(80port),三者必须一致 * 前端:`http://a.com:8080/;`服务端:`https://b.com/api/xxx`(端口默认是80)不能跨域 * 所有跨域,都必须经过server端允许和配合 * 未经server端允许就实现跨域,说明浏览器有漏洞,危险信号 * JSONP * CORS(服务端支持) * 加载图片css,js可无视同源策略(浏览器不限制) * `` * ``可以用于统计打点,可以使用第三方统计服务 * `` #### 解答: # 12-2 #### JSONP * 访问http://imooc.com/,服务端一定返回一个html文件吗? * 服务器可以任意动态拼接数据返回,只要符合html格式要求 * 同理于` ``` ```javascript //jsonp文件 abc( { name: 'xxxgfhdfgh' } ) ``` image-20210411122522228 image-20210411122820309 ## 存储cookie ### 题目 * 描述cookie session localStorage sessionStorage区别 ### 知识点 * cookie * 本身用于浏览器和server通讯 * 被‘借用’到本地存储来 * 可用document.cookie=' '来修改 * 存储大小,最大存储4kb * http请求时要发送到服务端,增加请求数量 * 只能用document.cookie = ' ' 来修改,太过简陋 * localStorage和sessionStorage * HTML5专门为存储设计,最大可存5M * API简单易用setItem,getItem * 不会随着http请求被发送出去 * localStorage数据会永久存储,除非代码或手动删除 * sessionStorage数据值存在于当前会话,浏览器关闭则清空 * 一般用localStorage会更多一些 image-20210411130421542 * cookie 1. cookie 是存储在浏览器的小段文本 2. 存储的是状态信息 3. 发请求时会被携带到服务器 * session 1. 是存储在服务器的用户数据 2. 浏览器第一次向服务器发起请求时,服务器会为当前会话创建一个session,并且把对应的 session-id 写入 cookie 中,用来标识 session 3. 此后,每次用户的请求都会携带一个包含了 session-id 的 cookie,服务器解析出了 session-id,便能定位到用户的用户信息。 # 13-2 ### http * 前端工程师开发界面 * 需要调用后端的接口,提交/获取数据---http协议 * 要求事先掌握好ajax ### http1.1解决的问题 * http1.0Tcp链接不可复用,http1.1实现长链接, * `尝试用管线化`(批量的发送http请求)解决`对头阻塞`(A请求依赖B响应) ### http2.0解决的问题 * 头部压缩(客户端和服务端都维护一个存储了头部信息的字典,http请求的时候只要携带和上一次不同的头部信息即可,其他的可以通过字典补充) * 解决对头阻塞问题(多路复用) #### 题目 * http常见的状态码有哪些 * 状态码分类 * 1xx服务器收到请求 * 2xx请求成功,如200 * 3xx重定向,如302 * 4xx客户端错误,如404(请求的地址服务端找不到) * 5xx服务端错误,如500 * 常见状态码 * 200成功 * 301永久重定向(配合location(新的地址),浏览器自动处理):浏览器自己记住新地址,下次直接访问,使用情况:网站域名到期了或者想换域名了,老的域名就可以返回301状态码然后返回一个location 等于新的域名。以后浏览器不会再访问老的域名 * 302临时重定向(配合location,浏览器自动处理):浏览器每次都会访问老地址,例如百度搜索引擎中访问其他网站,都是第一时间访问百度地址,然后再跳转到目标地址 * 304 资源未被修改:服务端告诉你,之前请求的资源在本地还有效,不需要重新请求了 * 404 资源未找到:请求的地址服务端找不到 * 403 没有权限 * 500 服务器错误 * 504 网关超时:能访问到服务器,但是服务器内部在做处理,比如做跳转或者连接其他服务器(比如数据库)的访问时造成超时 * 关于协议和规范 * 就是一个约定 * 要求大家都跟着执行 * 不要违反规范,例如IE浏览器 * http常见的header有哪些 * 什么是RestfulAPI * REST全称是Representational State Transfer,中文意思是表述(编者注:通常译为表征)性状态转移 * API 设计规范,用于 Web 数据接口的设计 * 描述下http的缓存机制(重要) #### http methods * 传统的methods * get获取服务器的数据 * post向服务器提交数据 * 简单的网页功能,就是这两个操作 * 现在的methods * get获取数据 * post新建数据 * patch/put更新数据 * delete删除数据 * Restful API * 一种新的API(前端后端交互的api)设计方法(早已推广使用) * 传统API设计理念:把每个url当做一个功能 (*) * Restful API设计理念 :把每个url当做一个唯一的资源(*)。如何设计成一个资源? * 尽量不用url参数 * 传统的API设计:/api/list?pageIndex=2(很像一个函数+一个参数的样子) * Restful API 设计: /api/list/2(没有参数,就是一个资源标识,list的第二页) * 用method表示操作类型(传统的API设计,当做功能设计)做一个博客列表 * post请求 /api/create-blog * post请求 : /api/update-blog?id=100 * get请求 : /api/get-blog?id=100 * 用method表示操作类型(Restful API设计) * post 请求: /api/blog * patch 请求: /api/blog/100 * get 请求: /api/blog/100 #### http headers * 常见的Request Headers * Accept 浏览器可以接受的数据格式 * Accept-Encoding 浏览器可接受的压缩算法,如gzip * Accept-Language浏览器可以接受的语言,如zh-CN * Connections:keep-alive 一次TCP连接重复使用(客户端和服务端建立连接之后就会重复的使用这个链接,不会断开,把资源一次性请求完成) * cookie:每次同域请求资源是浏览器自己携带cookie * Host 请求的域名是什么 * User-Agent:浏览器信息(简称UA),例如:分析一个网站多少人用了Chrome浏览器,多少人用了Safari,IE浏览器,和不同的操作系统信息,都是通过服务端收到的UA来分析 * Content-type 发送数据的格式(post请求中),如applications/json * 常见的Response Headers * Content-type 服务端返回数据的格式,如applications/json * Content-length 返回数据的大小,多少字节 * Content-Encoding 返回数据的压缩算法,如gzip * Set-Cookie 服务端修改cookie #### 自定义header * 用于简单的前端验证 image-20210412153125889 #### 缓存相关的headers * Cache-Control Expires * Last-Modified If-Modified-Since * Etag If-None-Match ​ # 14-5 ### http 缓存 * 什么是缓存 * 把没必要重新从服务端获取的资源暂存一份,节省请求服务器浪费的时间 * 为什么需要缓存 * 通过缓存可以减少网络请求的数量和体积,让整个页面加载和渲染的速度更快一些 * 哪些资源可以被缓存? ---- 静态资源(js css img) ### http缓存策略 #### 1. 强制缓存 image-20210413123609485 image-20210413124726451 * Cache-Control * max-age:设置最大缓存过期时间 * no-cache:不用本地缓存,正常的向服务端请求,服务端进行处理(服务端可能有缓存措施),比如html资源,不想做缓存 * no-store:不用本地缓存,也不用服务端的一些缓存措施(服务端“协商缓存”),更彻底 * private:只能允许最终用户作为缓存 * public:允许中间用户或者代理作为缓存 * 在Response Headers 中( 由服务端决定哪个资源可以缓存,服务端添加) * 控制强制缓存的逻辑 * 例如:Cache-Control:max-age = 31536000(单位是秒):让某个资源(js文件)在客户端缓存一年的时间 image-20210413121454603 image-20210413125053576 * Expires * 也是在Response Headers中 * 也是控制缓存过期 * 已经被Cache-Control代替 #### 2. 协商缓存(对比缓存) * 服务端缓存策略(判断一个请求资源是否可以使用本地缓存) * 服务器判断客户端资源,是否和服务端资源一样,一样就使用本地缓存 * 如果一致则返回304,否则返回200和最新的资源 image-20210413131748627 * 资源标识 * 在Response Headers中,有两种 * Last-Modified资源的最后修改时间 * Etag资源的唯一标识(一个字符串,类似人类的指纹) 屏幕快照 2021-04-13 下午4.09.04 屏幕快照 2021-04-13 下午4.23.39 屏幕快照 2021-04-13 下午5.29.42 屏幕快照 2021-04-13 下午5.32.23 * 优先使用Etag * Last-Modified只能精确到秒级 * 如果资源被重复生成,而内容不变,则Etag更精确 image-20210602214631475 ### 三种刷新操作(刷新方式对缓存影响) * 正常操作:地址栏输入url,跳转链接,前进后退等 * 强制缓存失效,协商缓存有效 * 手动刷新:F5,点击刷新按钮,右击菜单刷新 * 强制缓存失效,协商缓存有效 * 强制刷新:ctrl+F5 * 强制缓存失效,协商缓存失效 #### http面试题 - 总结 # 14-10 #### 开发环境 * git * 调试工具 * 抓包 * webpack babel * Linux 常用命令 #### git * 常用的代码版本管理工具 * 大型项目需要多人协作开发,必须熟用git * 如果不知道或者之前不用git,不会通过面试 * git服务端常见的有github,coding.net等 * 大公司会搭建自己的内网git服务 * 常用命令 * git add 添加代码到暂存区 * Git checkout xxx 切换分支 * git commit -m 'xxx' 提交代码到版本库 * git push origin master 提交分支代码 * git pull origin master 拉去分支代码 * git branch 创建分支 * git branch -v 查看分支 * git checkout -b xxx /git checkout xxx 切换分支 * git merge xxx 合并分支 ```javascript git checkout -b xxx :新建并且切换分支 (本分支和之前的分支代码相同) The current branch bxg has no upstream branch当前分支不在远程仓库里 git push origin bxg 把本地分支推送到远程仓库,远程仓库也有bxg分支了 git checkout 分支名 切换分支 git branch -v 查看当前分支 合并分支 1.通过git pull 拿到最新的被合并的分支代码(这里是bxg) 2.切换回要合并到的分支(这里是master) 3.git merge bxg 把bxg分支合并到master(要合并的分支)分支上 git reset --hard 版本号 回退历史版本 ``` # 15-6 ### 抓包 * 移动端h5页,查看网络请求,需要用工具抓包 * Windows一般用fiddler * Mac OS 一般用charles * 手机和电脑连同一个局域网 * 将手机代理到电脑上 * 手机浏览网页,即可抓包 * 查看网络请求 * 网址代理 * https ## webpack和babel * ES6模块化,浏览器暂不支持 * ES6语法,浏览器并不完全支持 * 压缩代码,整合代码,以让网页加载更快 # 15-11 linux命令: * ls * ls-a 平铺形式查看 * ll 列表形式查看 * clear * rm -rf 删除文件夹内容(-rf强制删除所有内容) * cd * mv 修改文件名 * mv 移动文件 * cp 拷贝文件 * touch 创建文件 * vi:新建文件(vim编辑器) * i或a 开始输入 * ESC 退出输入模式 * ESC -> :w 保存 * ESC -> :q 退出 * ESC -> :q! 强制退出(不保存) * 查看文件 vi 文件名 * cat 查看文件 * head 查看文件(前几行) * tail 打印最末尾几行 * grep 'babel' package.json 在.json文件中查找关键字babel * vimtutor 学习vim命令 #### 运行环境 * 运行环境即浏览器(server 端有node.js) * 下载网页代码,渲染出页面,期间会执行若干js * 要保证代码在浏览器中:稳定且高效 * 网页加载过程 * 性能优化 * 安全 #### 网页加载过程 ##### 题目 * 从输入url到渲染出页面的整个过程 * 下载资源:各个资源类型,下载过程 * 渲染页面:结合html,css javascript图片等 * window.onload和DOMContentLoaded的区别 * window.onload资源全部加载完成才能执行,包括图片 * DOMContentLoaded DOM渲染完成即可,图片可能尚未下载 ##### 知识点 * 加载资源的形式 * html 代码 * 媒体文件,如图片,视频等 * Javascript css * 加载资源的过程 * DNS(Domain Name Syste)解析:域名->IP地址 (把域名转解析成ip地址) * 不用域名直接作为ip地址的原因: 1. 域名更好记 2. ip地址在不同区域内不一样(做不同区域的均衡或者代理),访问域名的时候域名解析的服务会根据地域解析不同的域名,提升访问速度 3. 使用域名就必须使用域名解析服务,手机或电脑访问一个域名的时候,真正访问的还是ip地址,域名只是方便使用和记忆的符号 * 浏览器根据ip地址向服务器发起http请求 * 服务器处理http请求,并返回给浏览器 * 渲染页面的过程 * 根据html代码生成DOM Tree * html 文本代码通过浏览器解析成树结构 * 根据css生成CSSOM(css对象模型) * css 文本代码通过浏览器解析成css结构化对象 * DOM Tree 和cssom整合形成Render Tree(渲染树) * 根据Render Tree渲染页面 * 遇到` ``` * watch如何深度监听? * 默认浅监听,针对引用类型可以深度监听(引用类型对象内部发生任何变化都可以触发watch函数) * watch监听引用类型,拿不到oldVal ```html ``` > ```json > computed和watch之间的区别: > 1.computed能完成的功能,watch都可以完成。 > 2.watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作。 > 两个重要的小原则: > 1.所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象。 > 2.所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数, > 这样this的指向才是vm 或 组件实例对象。 > ``` #### class和style * 使用动态属性 ```javascript

动态属性 id

data() { return { dynamicId: `id-${Date.now()}` } } ``` ```html ``` * 使用驼峰式写法 #### 条件渲染 * `v-if`和`v-else`的用法,可使用变量,也可以使用===表达式 * `v-if`和`v-show`的区别? * `v-if`为`false`的元素不出出现在`dom`节点中不会被渲染,而`v-show`为`false`的元素会进行`dom`渲染,但是会被设置为`display:none;`隐藏。 * `v-if`和`v-show`的使用场景 * 如果一次性的选择或者更新不频繁就使用v-if(目的:不要把过多的元素暴露在浏览器里),如果更新很频繁就使用`v-show`(性能好些,不会像`v-if`一样会频繁的加载和销毁`dom`节点) # 3-3 #### 循环(列表)渲染 * 如何遍历对象? -------也可以用v-for * key的重要性。key不能乱写(如`random`或者`index`),一般要用业务id * `v-for`和`v-if`不能一起使用(官方不建议) * `v-for`比`v-if`计算优先级高,渲染模板的时候会先用`v-for`进行循环,循环之后再用`v-if`判断,那么每次循环都要进行一次判断,影响性能 * `v-for`比`v-if`优先级高,所以使用的话,每次v-for都会执行v-if,造成不必要的计算,影响性能 ```html ``` #### 事件 * event参数,自定义参数 ```html ``` * 事件修饰符,按键修饰符 image-20210411012725216 image-20210411012818349 * 【观察】事件被绑定在哪里? * event对象是原生的 * 事件被挂载到当前元素,和 DOM 事件一样 #### 表单 * `v-model` * 常见表单项`textarea checkbox radio select` * 修饰符`lazy number trim` ```javascript ``` #### Vue 组件使用 * props 和$emit(父子组件) * props:父组件向子组件传递数据 * 在组件中接收props参数的时候,如果传过来的参数是`Object`或者`Array`类型,则defalut这里应该用一个函数返回,否则会报错:`Invalid default value for prop “slides”: Props with type Object/Array must use a factory function to return the default value.` ![在这里插入图片描述](https://tva1.sinaimg.cn/large/008i3skNly1gv4w7ju7xaj60cz089gm802.jpg) * 再举两个例子: ![在这里插入图片描述](https://tva1.sinaimg.cn/large/008i3skNly1gv4w9151mwj307e09xaah.jpg) * $emit:子组件触发父组件的的事件(方法)(@xxx) ```html * 组件间通讯-自定义事件(和全局事件总线类似) * 兄弟组件使用 * `vm.$emit('addTitle',title)` * `vm.$off('addTitle',this.addHandleTitle)`:移除监听事件 * event是全局vue实例,因为自定义组件要用全局的,而 this 只是当前组件的组件实例,只有vue的实例才能让兄弟组件之间产生联系,`$emit`派发和`$on`监听方法都绑定在同一vue实例对象上。而且组件有可能会接下来被销毁。所以,一定要有一个全局的、第三方的、永远不会被销毁的自定义事件对象。 > 给谁绑定(`$emit`)的事件就要找谁监听(`$on`)该事件 * ```javascript //组件A methods:{ event.$emit('xxx',data); } //组件B mounted(){ event.$on('xxx',this.fn) } ``` * 组件生命周期 * el:提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标。可以是 CSS 选择器,也可以是一个 HTMLElement 实例。 在实例挂载之后,元素可以用 `vm.$el` 访问。 * $el:获取Vue实例关联的DOM元素; * created和mounted的区别 * created:把vue实例初始化完成,只是存在js内存模型中的一个变量而已,并没有开始渲染(理解:没有经历挂载和模板编译阶段) * mounted:组件在网页上真正的渲染完成 * beforeDestory * 解除绑定(销毁$emit自定义事件,addEventLister监听事件,window事件) * 清除定时器 vue生命周期 `event listerners指的是自定义事件` 如果是非自定义事件,例如原生的监听事件或者全局事件总线要手动解除监听 数据代理:通过一个对象代理对另一个对象中属性的操作:读/写,其原理也是利用Object.defineProperty() beforeCreate:此时是有vm的 beforeMount中也可以用于整理参数,在此函数中vm可以访问data中的数据和method中方法 ```js beforeMount() { // console.log(this.$route.query) //整理参数,发送请求,服务器返回查询的数据 Object.assign(this.searchParams,this.$route.query,this.$route.params) }, ``` ![20200815191941397](/Users/yangxianqiang/Desktop/20200815191941397.png) * 挂载阶段 * beforeCreated * created * beforeMounted * Mounted * 更新阶段 * beforeUpdate * updated * 销毁阶段 * beforeDestroy * Destroyed image-20210507181819748 ![image-20210602131850367](https://tva1.sinaimg.cn/large/008i3skNly1gucuhqavijj608403pglj02.jpg) 父组件先created,子组件后created 子组件先mounted,父组件后mounted ![image-20210507182258846](https://tva1.sinaimg.cn/large/008i3skNly1gucuhwhilbj603u0293yc02.jpg) 父组件先beforeUpdate 子组件先updated ![image-20210507182522815](https://tva1.sinaimg.cn/large/008i3skNly1gucui0onmxj6050024mx002.jpg) destroy image-20210507183205439 ​ # 3-9 #### Vue高级特性 > 不是每个都很常用,但用到的时候必须要知道 > > 考察候选人对Vue的掌握是否全面,且有深度 > > 考察做过的项目是否有深度和复杂度(至少能用到高级特性) * 自定义v-model ```html //父组件 ``` ```html ``` ```html ``` * $nextTick * Vue是异步渲染 * data改变之后,dom不会立刻渲染 * $nextTick会在dom渲染之后被触发(显示页面),以获取最新dom节点 * 页面渲染时会将 data 的修改做整合,多次 data 修改只会渲染一次 * 在下次dom更新循环结束之后执行延迟回调,在修改数据之后立即使用这个方法,获取更新后的dom > 使用场景: > > swiper轮播图实现: > > 要把new Swiper() 放在this.$nextTcik(()=>{})的调函数中执行,如果直接把new Swiper()放在mouted中执行,不会实现Swiper效果,因为请求数据时需要花费时间的(图片)。放在mounted就意味着数据还没有没有请求回来就执行new Swiper了。既然数据(图片)没有请求回来,页面呈现的就不是最新的dom,所以必然失败。this.$nextTick()是在dom下次更新之后执行的回调函数,所以,把new Swiper放在回调函数里就意味着数据已经请求回来的,页面上已经是最新的dom了,此时就成功实现了轮播图 ```html ``` ![image-20220930141134595](../../../Library/Application Support/typora-user-images/image-20220930141134595.png) > vue中nextTick的作用与原理 > > nextTick是为了可以获取更新后的dom,由于Vue Dom的更新是异步执行的,即修改数据时,视图不会立即更新,而是会监听数据变化,并缓存在同一时间循环中。等同一数据循环中的所有数据变化完成之后,再同一进行视图更新。为了确保得到更新的dom,所以设置了nextTick就是在下次dom更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的dom,在下次dom更新循环结束之后执行延迟回调。nextTick主要使用宏任务和微任务。根据执行环境分别采用promise,MutationObserver,setImmediate,如果以上都不行则采用setTimeout定义一个异步方法,多次调用nextTick会将方法存入队列中,通过这个异步方法清空当前队列 > > eg:写一个对话框,过程中务必要监听scroll事件,要获取实时的scrollHeight(总之就是肯定要过去最新关于对话框高度的东西,这时候就要用this.$nextTick了,因为对话框中的聊天内容也可以看多是列表,就是获取列表的最新高度,如果获取的不是最新的高度,就不能完整的展示聊天内容,比如某次明明已经发送消息了,但是不会展示在窗口中,就是因为窗口的高度获取的有问题。 * slot * 基本使用 * 父组件向子组件插入内容 ```html ``` ```html ``` * 作用域插槽 * 父组件获取子组件data数据 ```html ``` ```html ``` * 具名插槽 * 子组件在slot中获取父组件数据 image-20210509170448016 * 动态,异步组件 * 动态组件(xxx) * ` `用法 ```html return{ newsData:{ 1:{ type:'text' }, 2:{ type:'text' }, 3:{ type:'image' } } } ``` * 需要根据数据,动态渲染的场景。即组件类型不确定 * 例如把组件名字放在一个对象中定义,程序执行过程中,通过v-for获取每一个组件名 image-20210509170802618 * 异步组件: * 利用import()函数引入组件路径 ```javascript export default { components: { FormDemo: () => import('../BaseUse/FormDemo'), }, ``` * 按需加载,异步加载大组件(什么时候用什么时候加载) * keep-alive * 缓存组件(就不会销毁组件了) * 和`v-show`的区别:keep-alive里面是对组件对象的渲染,`v-show`是通过css属性值`display:none` * 频繁切换,不需要重复渲染 * 经常出现“Vue常见性能优化”这个面试题里面 * 用法:用keep-alive把相关组件标签包围起来 * ```html ``` * mixin * 多个组件有相同的逻辑,抽离出来 * mixin并不是完美的解决方案,会有一些问题 * 不同的mixin.js文件命名容易冲突 * 变量来源不明确,不利于阅读 * mixin和组件可能出现多对多的关系,复杂度高 * 混入规则 * 数据 data 数据对象在混入时,会进行合并,发生冲突时,保留组件的数据 * 值为对象 methods computed等 在混入时,同名的methods会合并成为一个对象 如果对象的键名发生冲突,则保留组件对象的键值对 * 生命周期钩子函数 同名的钩子函数会被合并为一个数组,依次都会被调用,但是混入对象的钩子函数先被调用 * Vue3提出的CompositionAPI只在解决这些问题 ```html ``` ```javascript //mixin.js export default { data() { return { city: '北京' } }, methods: { showName() { // eslint-disable-next-line console.log(this.name) } }, mounted() { // eslint-disable-next-line console.log('mixin mounted', this.name) } } ``` * v-model的实现原理 ```js ``` # 3-14 ### vuex使用 * 面试考点不多(因为熟悉Vue之后,vuex没有难度) * 但是基本概念,基本使用和API必须掌握 * 可能会考察state的数据结构设计 #### Vue 基本概念 * state * getter * action * mutation * dispatch * commit * mapState * mapGetters * mapActions * mapMutations ```js ...mapActions(['increment']) this.increment();
this.$store.dispatch('increment') --------------------------------------------------- ...mapMutations(['INCREMENT']), this.INCREMENT();
this.$store.commit('INCREMENT') --------------------------------------------------- this.$store.state.count; ...mapState(['count']), ``` image-20210510224026561 ### Vue-router * 面试考点不多 * 路由模式(hash,h5 history) * hash模式(默认),如http://abc.com/#/user/10 * H5 history模式,如http://abc.com/user/20 * 后者需要server端支持,因此无特殊需求可选择前者 * 路由配置(动态路由,懒加载) image-20210510224744804 image-20210510224818552 v-show和v-if的区别? 为何v-for中要用key? * key要用业务中的属性 # 3-19 ### vue原理 * 面试为何会考察原理? * 之前然知其所以然 * 了解原理,才能应用的更好 * 大厂造轮子(有钱有资源,业务定制,技术KPI) * 面试中如何考察?以何种方式 * 考察重点,而不是考察细节。掌握2/8原则 * 和使用相关的原理,例如vdom,模板渲染 * 整体流程是否全面?热门技术是否有深度? * Vue原理包括哪些 #### 考察范围 * 组件化 * 响应式 * vdom和diff * 模板编译 * 渲染过程 * 前端路由 #### 回顾面试题 * v-show 和 v-for的区别 * 为何v-for中要使用key * 描述Vue组件生命周期(有父子组件的情况) * Vue组件如何通讯 * 描述组件渲染和更新的过程 * 双向数据绑定v-model的实现原理 #### 组件化基础 * “很久以前”就有组件化 * asp,jsp,php已经有组件化了 * node.js也有类似的组件化 * 数据驱动视图(MVVM,setState) * 传统组件,只是静态渲染(在后端拼接数据渲染完成),如果更新还要依赖于操作dom * 数据驱动视图-Vue MVVM * 不再自己操作dom * 直接修改数据就可以,Vue会根据数据重新渲染视图,(关注数据和业务逻辑就可以) image-20220523202123394 * M:model(data) * model数据发生改变,ViewModel会通过监听事件或者指令重新渲染视图(data中的数据改变(true,false),通过指令`v-if`,`v-show`来展示或者隐藏元素) * V:View(视图) * View通过dom事件操作可以修改model数据(同click事件让data中的数据加减) * VM:ViewModel(监听事件和指令),dom监听和数据绑定 * 比较抽象的一个层级 * Vue提供的一个能力(视图和model互相影响的能力),是一个连接层;例如包含:click事件,methods里面的方法----触发点击事件,调用methods里面的函数,修改view * ViewModel关联Model和View ![image-20210512104000224](https://tva1.sinaimg.cn/large/008i3skNly1gtirkm29pvj61tm0tewif02.jpg) * 数据驱动视图-React setState #### Vue响应式 * 组件data的数据一旦变化,立刻触发视图的更新 * 实现数据驱动的第一步 * 考察Vue原理的第一题 * 核心API-Object.defineProperty * 监听对象,监听数组(给对象的属性赋新值时,Object.defineProperty内部的set方法中会更新视图) * 监听复杂对象时要深度监听(利用递归) * 几个缺点 * 深度监听,需要递归到底,一次性计算量大(Vue3.0可以分多次递归,提升性能) * 无法监听新增/删除属性(Vue.set Vue.delete) * 无法原生监听数组,需要特殊处理 * 要通过`Object.create()`重新定义数组原型:用`Object.create()`创建一个新的关联原生数组原型的对象 * 然后再给这个对象通过`forEach()`重新定义数组方法`[push,pop,shift,unshit]` * 用这些数组方法分别调用`call()`方法执行真正的原型上的数组方法,在调用call()方法之前执行更新视图的函数 * 把要监听数组通过隐式原型属性`__proto__`关联到之前创建的对象上,以此对象作为要监听的数组的原型 ```javascript //监听对象 const data = {}; var name = 'zhangsan'; Object.defineProperty(data,'name',{ get:function () { console.log('get'); return name; }, set:function (newVal) { console.log('set'); name = newVal } }); console.log(data.name) data.name = 'lisi' ``` * 如何显示响应式 * `针对对象` > ```json > 1.创建observe函数,传入data对象 > 2.在observe中判断data是否是object 和是否是null 如果是则return > 3.在observe中通过for in 遍历出data中key,value,并且把它们和data对象传入defineReactive函数中 > 4.在defineReactive中先调用observe函数且传入value进行递归,即如果value是对象则再次进行for in遍历 > 5.在defineReactive函数中写Object.defineProperty(),传入data,key,和带有get,set方法的对象 > 6.get方法返回value,在set方法中如果newValue和当前的value不同,则给当前的value赋值为newValue > 7.执行更新视图函数updateView() > ``` * `针对数组` >```json >针对数组 >1.通过Object.create(Array.prototype)创建新的对象arrProto(在此对象上定义方法不会影响原始的数组原型) >2.在observe函数中判断通过Array.isArray()判断data是否是数组,如果是则同隐式原型__proto__关联到arrProto上 >3.在arrProto上通过forEach()函数分别给它定义数组的方法(push,pop,shift,unshift) > 1).先调用更新视图函数dateUpdate() > 2).Array.prototype[方法名].call(this,arguments); > >``` ```javascript // 触发更新视图 function updateView() { console.log('视图更新') } // 重新定义数组原型 const oldArrayProperty = Array.prototype // 创建新对象,原型指向 oldArrayProperty ,再扩展新的方法不会影响原型 const arrProto = Object.create(oldArrayProperty); ['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => { arrProto[methodName] = function () { updateView() // 触发视图更新 oldArrayProperty[methodName].call(this, ...arguments) // Array.prototype.push.call(this, ...arguments) } }) // 重新定义属性,监听起来 function defineReactive(target, key, value) { // 深度监听 observer(value) // 核心 API Object.defineProperty(target, key, { get() { return value }, set(newValue) { if (value !==newValue ) { // 深度监听 observer(newValue) // 设置新值 // 注意,value 一直在闭包中,此处设置完之后,再 get 时也是会获取最新的值 value = newValue // 触发更新视图 updateView() } } }) } // 监听对象属性 function observer(target) { if (typeof target !== 'object' || target === null) { // 不是对象或数组 return target } // 污染全局的 Array 原型 // Array.prototype.push = function () { // updateView() // ... // } if (Array.isArray(target)) { target.__proto__ = arrProto } // 重新定义各个属性(for in 也可以遍历数组) for (let key in target) { defineReactive(target, key, target[key]) } } // 准备数据 const data = { name: 'zhangsan', age: 20, // info: { // address: '北京' // 需要深度监听 // }, // nums: [10, 20, 30] } // 监听数据 observer(data) // 测试 // data.name = 'lisi' // data.age = 21 // // console.log('age', data.age) // data.x = '100' // 新增属性,监听不到 —— 所以有 Vue.set // delete data.name // 删除属性,监听不到 —— 所有已 Vue.delete // data.info.address = '上海' // 深度监听 // data.nums.push(4) // 监听数组 ``` * Vue.set(),vm.$set(),this.$delete() * 数据劫持:给普通的数据对象添加`getter,setter`能力,用于监视数据变化,然后重新解析模板(原理:就是利用`Object.definedProperty()`) * Vue监视数据的原理: 1. vue会监视data中所有层次的数据。 2. 如何监测对象中的数据? 通过setter实现监视,且要在new Vue时就传入要监测的数据。 (1).对象中后追加的属性,Vue默认不做响应式处理 (2).如需给后添加的属性做响应式,请使用如下API: ` Vue.set(target,propertyName/index,value) `或 ` vm.$set(target,propertyName/index,value)` (3).注意对象不能是 Vue 实例,或者 Vue 实例的根数据对象。 3. 如何监测数组中的数据? Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括: 通过包裹数组更新元素的方法实现,本质就是做了两件事: (1).调用原生对应的方法对数组进行更新。 (2).重新解析模板,进而更新页面。 4. 在Vue修改数组中的某个元素一定要用如下方法: 1. 使用这些`API:push()、pop()、shift()、unshift()、splice()、sort()、reverse() (filter()、concat() 和 slice()`。它们不会变更原始数组,而总是返回一个新数组。当使用非变更方法时,可以用新数组替换旧数组,所以归根接地就没有修改原数组,只是替换而已 2. Vue.set() 或 vm.$set() 3. `数组里的每个元素不是通过getter,setter实现监视的,所以想要通过数组的索引改变数组不能实现` **特别注意:Vue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象 添加属性!!!(只能给data对象里面的某个属性添加值,而不能直接给data添加)** * Object.defineProperty的一些缺点(Vue3.0 启用Proxy) ```javascript Object.defineProperty(obj, prop, descriptor) obj 要定义属性的对象。 prop 要定义或修改的属性的名称(key)或 Symbol 。 descriptor 要定义或修改的属性描述符。(get,set函数) ``` * Proxy有兼容性问题 * Proxy兼容性不好,且无法polyfill * Vue2.x还会存在一段时间,所以都得学 * Vue3.0相关知识下章讲 # 4-6 ### 虚拟DOM(Virtual DOM) 和diff * vdom是实现vue和React的重要基石 * diff算法是vdom中最核心,最关键的部分 * vdom是一个热门话题,也是面试中的热门问题 * dom操作非常耗费性能 * 以前用jQuery,可以自行控制dom操作的时机,手动调整 * vue和React是数据驱动视图,如何有效控制dom操作? ### 解决方案-vdom * 有了一定的复杂度,想减少计算次数比较难 * 能不能把计算,更多的转移为js计算?因为js执行速度很快 * vdom-用JS模拟dom结构,计算出最小的变更,操作dom ### 用JS模拟dom结构 * html是xml语言的特殊版本 * 所有的xml语言都可以用json/js对象的方式表示 image-20210515001323310 ### 通过snabbdom学习vdom * 简介强大的vdom库,易学易用 * Vue参考它实现的vdom和diff * https://github.com/snabbdom/snabbdom * Vue3.0重写了vdom的代码,优化了性能 * 但vdom的基本理念不变,面试考点也不变 * React vdom具体实现和Vue也不同,但不妨碍统一学习 ### snabbdom重点总结(虚拟dom库) * h函数:在snabbdom中通过commonjs引入 * h 函数:按照自己的参数设计传入`sel(标签) ,data(属性,样式,事件),children(子元素)`,返回vnode * vnode:就是vdom(用JS描述dom结构) * patch函数:补丁 * patch(container,vnode):把vdom渲染到指定节点上 * patch(vnode,newVnode):更新vdom * patch(newVnode,null) :销毁当前dom元素 * h函数->vnode->patch * h函数生成vnode,然后通过patch方法添加节点或者更新节点 * render函数就是对这个生成/更新vnode过程的一个封装函数 ### vdom总结 * 用js模拟dom结构(vnode) * 新旧vnode对比,得出最小的更新范围,最后更新dom(通过diff算法实现) * **patch()函数更新:尽量的把所有计算放在js中进行,对比计算之后,找出最需要更新的dom操作,进行更新,不需要更新的就不会更新** * 在数据驱动视图模式下,有效控制dom操作 ### vdom问题回答重点 1. 用js模拟dom结构(vnode) 2. vdom的基本操作--h函数->vnode->patch 3. 意义是什么?得出最小更新范围进行更新,通过diff算法减小时间复杂度,节约性能 ```javascript const snabbdom = window.snabbdom // 定义 patch const patch = snabbdom.init([ snabbdom_class, snabbdom_props, snabbdom_style, snabbdom_eventlisteners ]) // 定义 h const h = snabbdom.h //创建容器 const container = document.getElementById('container') // 生成 vnode const vnode = h('ul#list', {}, [ h('li.item', {}, 'Item 1'), h('li.item', {}, 'Item 2') ]) //更新dom patch(container, vnode) document.getElementById('btn-change').addEventListener('click', () => { // 生成 newVnode const newVnode = h('ul#list', {}, [ h('li.item', {}, 'Item 1'), h('li.item', {}, 'Item B'), h('li.item', {}, 'Item 3') ]) patch(vnode, newVnode) // vnode = newVnode // patch 之后,应该用新的覆盖现有的 vnode ,否则每次 change 都是新旧对比 }) ``` ```javascript ``` ### diff算法 * diff算法是vdom中最核心,最关键的部分 * diff算法能再日常使用vue React中体现出来(如 key) * diff算法是前端热门话题,面试“宠儿” * diff即对比,是一个广泛的概念,如`linux diff`命令,`git diff`等 * 两个js对象也可以做diff,如`https//github.com/cujojs/jiff` * 两棵树做diff,如这里的`vdom diff` image-20210515113042799 ### 树diff的时间复杂度o(n^3)(不可用的复杂度) * 第一,遍历tree1; * 第二,遍历tree2 * 第三,排序 * 1000个节点,要计算1亿次,算法不可用 ### 优化时间复杂度到o(n) * 只比较同一层级,不跨级比较 * tag不相同,则直接删除掉重建,不再深度比较(基于第一点,在同一层级。大部分场景都可以适用) > 一种猜测:虽然下边的子元素可能也相同,但是不再在进行比较,就是直接给你删除重建 * tag和key,两者都相同,则认为是相同节点,不再深度比较(只比较本节点,下层节点都会被认为是相同的不会再进行比较了) * 只是tag相同不足以判断两个节点就是相同节点,如果错误的判断为两个节点相同,则会做无用的dom更新 > 当页面的数据发生变化时,Diff算法只会比较同一层级的节点: > > 1. **如果节点类型不同,直接干掉前面的节点,再创建并插入新的节点,不会再比较这个节点以后的子节点了。** > 2. **如果节点类型相同,则会重新设置该节点的属性,从而实现节点的更新。** image-20210515113132377 image-20210515113152818 ### snabbdom-源码解读 * h函数返回vnode的源码流程: * `h(sel,data,children)`函数`data(属性,样式,事件)`,返回`vnode(sel,data,children,text)`, (text:文本元素),vnode函数最终返回的是对象`{sel,data,children,text,elm,key}` elm:newVnode对应的添加的dom.元素 ,children 和text不能共存,要么子元素是多个dom节点--数组,要么是文本 * patch()源码流程 * var patch = snabbdom.init() * init()返回一个`patch(VNode | Element,VNode)`,如果第一个参数不是vnode,而是一个dom元素,就通过`emptyNodeAt`创建一个空的vnode并且关联到dom元素. * 在`emptyNodeAt`函数中通过`sameVnode()`判断是否是相同的vnode,sameVnode方法内部判断两个vnode是否相同,判断两的依据是两个vnode的key 和sel是否都相同,如果都相同就执行patchVnode(),如果两个vnode不相同,直接删除重建 * patchVnode()中执行prepatch hook(类似于生命周期的钩子) * 设置vnode.elem # 4-11 ### diff算法总结 * patchVnode * addVnodes removeVnodes * updateChildren(key的重要性) ### vdom和diff-总结 * 细节不重要,updateChildren的过程不重要,不要深究 * vdom核心概念很重要:h,vnode,patch,diff,key * vdom存在的价值更加重要:基于数据驱动视图,要频繁控制dom操作,并且要提升性能 ### 模板编译 * 模板是vue开发中最常用的部分,即与使用相关的原理 * 它不是html,有指令,插值,js表达式,到底是什么? * 面试不会直接问,但是会通过“组件渲染和更新过程”考察 * 前置知识:js的with语法 * 改变{}内自由变量的查找规则,当做obj属性来查找 * 如果找不到匹配的obj属性,就会报错 * with要慎用,它打破了作用域规则,可读性变差 * image-20210518005752363 * vue template complier 将模板编译为render函数 * render() 对h()->vnode->patch()生成新的vnode的封装 * 执行render函数生成vnode # 4-15 ### 编译模板 * 模板不是html,有指令,插值,js表达式,能实现判断,循环 * html是标签语言,只有js才能实现判断,循环(图灵完备:顺序执行,判断执行,循环执行) * 因此,模板一定是转换为某种js代码,即模板编译 * 模板编译为render函数,执行render函数返回vnode * 基于vnode在执行patch和diff(后面讲) * 使用webpack vue-loader ,会在开发环境下编译模板 image-20210525025349284 ### vue组件中使用render代替template image-20210608145340001 * 讲完模板编译,再讲render,就比较好理解了 * 在有些复杂情况中,不能用template,可以考虑用render * React 一直都用render(没有模板),和这里一样 总结 * with语法 * 模板到render函数,再到vnode,再到渲染和更新 * render() 对h()->vnode->patch()生成新的vnode的封装 * vue组件可以用render代替template ### 组件渲染/更新过程 * 一个组件渲染到页面,修改data触发更新(数据驱动视图) * 其背后原理是什么,需要掌握哪些要点? * 考察对流程了解的全面程度 * 初次渲染过程 * 解析模板为render函数(或在开发环境已完成,vue-loader) * 触发响应式,监听data属性getter setter * 执行render函数,生成vnode,patch(elem,vnode) * image-20210525031156479 * 更新过程 * 修改data,触发setter(此前在getter中已被监听) * 重新执行render函数,生成newVnode * patch(vnode,newVnode) image-20210525032921716 * 异步渲染 * 回顾$nextTick * 汇总data的修改,一次性更新视图 * 我写的获取dom(想要最新的)的代码的位置是在vue渲染之前,所以我正常情况下我们获取的数据时dom更新前的数据。如果加上this.$nextTick的话,那么回调函数会在vue更新结束之后执行,那么就是获取的最新的dom相关数据了 * 减少dom操作次数,提高性能 ### 回顾知识 * 响应式:监听data属性getter setter (包括数据) * 模板编译:模板到render函数,在到vnode * vdom:patch(elem,vnode)和patch(vnode,newVnode) ### 总结1 * 渲染和响应式的关系 * 渲染和模板编译的关系 * 渲染和vdom的关系 ### 总结2 ### 前端路由原理 * 稍微复杂一点的SPA(single-page application),都需要路由 * Vue-router 也是vue全家桶的标配之一 * 属于“和日常使用相关的原理”,面试常考 * 回顾vue-router的路由模式 * hash * H5 history image-20210525033922311 #### hash的特点 * hash变化会触发网页跳转,即浏览器的前进,后退 * hash变化不会刷新页面,SPA必须的特点 * hash永远不会提交到server端(前端自生自灭) ```html

hash test

``` #### H5 history * 需要后端支持,以node后端为例,无论访问什么样的路由,最终都返回主文件index.html(否则报页面找不到错误),然后再通过前端界面使用pushstate()的方式触发路由的切换 * 用url规范的路由,但跳转时**不刷新页面** * `history.pushState`(更新路由,但是浏览器不会更新) * **`history.pushState()`** 方法向当前浏览器会话的历史堆栈中添加一个状态,接受三个参数 * state:一个与指定网址相关的状态对象,popstate事件触发时,该对象会传入回调函数。如果不需要这个对象,此处可以填null。可用它来传一些数据 * title:新页面的标题,但是所有浏览器目前都忽略这个值,因此这里可以填null。 * url:新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个网址。 ```html

history API test

``` * `Window.onpopstate` * 监听浏览器前进,后退,进一步可以通过`location.pathname`查看当前路由 * `window.onpopstate`是`popstate`事件在window对象上的事件处理程序. 每当处于激活状态的历史记录条目发生变化时,`popstate`事件就会在对应`window`对象上触发. 如果当前处于激活状态的历史记录条目是由`history.pushState()`方法创建,或者由`history.replaceState()`方法修改过的, 则`popstate`事件对象的`state`属性包含了这个历史记录条目的`state`对象的一个拷贝. **注意:**调用`history.pushState()`或者`history.replaceState()`不会触发popstate事件. `popstate`事件只会在浏览器某些行为下触发, 比如点击后退、前进按钮(或者在JavaScript中调用`history.back()、history.forward()、history.go()`方法),此外,a 标签的锚点也会触发该事件. image-20210525035107422 image-20210525035139646 ```html

history API test

``` ### 总结 * Hash:通过window.onhashchange监听hash的改变 * H5 history - history.pushState(更新路由) 和 window.onpopstate(监听浏览器前进后退) * H5 history 需要后端支持,以node后端为例,无论访问什么样的路由,最终都返回index.html(否则报页面找不到错误),然后再通过前端界面使用pushstate的方式触发路由的切换 ### 两者选择 * to B(后台管理系统)的系统推荐用hash,简单易用,不需要服务端配合,对url规范不敏感 * to C的系统,可以考虑选择H5 history,但需要服务端支持 * 越简单越好 ### Vue原理-总结 * 组件化 * 响应式 * vdom和diff * 模板编译 * 渲染过程 * 前端路由 ### Vue面试真题演练 * 自己觉得是面试重点 * 网上收集整理的面试题 * 热门技术和知识点 #### v-show和v-if的区别 * v-show通过CSS display 控制显示和隐藏 * v-if 组件真正的渲染和销毁,而不是显示和隐藏 * 频繁切换显示状态用v-show,否则用v-if #### 为什么在v-for中用key * 必须使用key,且不能是index和random * diff算法中要通过tag和key来判断是否是sameNode * 减少渲染次数,提升渲染性能 #### 描述Vue组件声明周期(父子组件) * 单组件声明周期图 * 父子组件生命周期关系 ![20200815191941397](/Users/yangxianqiang/Desktop/20200815191941397.png) #### Vue组件如何通讯(常见) * 父子组件props和this.$emit * 自定义事件event.$on event.$off event.$emit(event:第三方提供的vue实例) * vuex image-20210510224026561 #### 描述组件渲染和更新的过程 image-20210525032921716 #### 双向数据绑定v-model的实现原理 * input元素的value= this.name * 绑定input事件this.name = $event.target.value * data更新触发re-render #### 对MVVM的理解 #### computed有何特点 * 缓存,data不变不会重新计算 * 提高性能 #### 为何组件data必须是一个函数? image-20210525043909144 * .vue文件编译后是一个class,在项目中每个不同的地方使用这个class时相当于对这个class进行实例化,实例化的时候会执行这个data。 * 如果data不是函数,那每一个组件实例的数据都相同了,即数据共享了。那么不同实例的数据就会相互影响 * 如果data是函数,那么实例化class时就会执行函数,而数据就在闭包中,不同组件不会相互影响数据 > 概述: > > 1.vue文件编译成class类,不同组件中引入class类,并且实例化。实例化的时候会执行data函数 > > 2.data不是函数,那么不同组件就可以数据共享,互相污染数据 > > 3.data是函数,执行data函数,而数据就相当于在闭包中,不同组件不会互相污染数据 #### ajax请求应该放在哪个生命周期钩子 * mounted * JS是单线程的,ajax异步获取数据 * 放在mounted之前没有用,只会让逻辑更加混乱,只要是页面没有渲染完ajax就在异步的判定过程中,不会提前获取数据 #### 如何将组件所有props传递给子组件? * $props * `` * 细节知识点,优先级不高 #### 如何自己实现v-model image-20210525045216402 #### 多个组件有相同的逻辑,如何抽离? * mixin * 以及mixin的缺点 * 不同的mixin.js文件命名容易冲突 * 变量来源不明确,不利于阅读 * mixin和组件可能出现多对多的关系,复杂度高 #### 何时要使用异步组件 * 利用import()函数引入组件路径 * 按需加载,异步加载大组件(什么时候用什么时候加载) * 路由异步加载 ```javascript export default { components: { FormDemo: () => import('../BaseUse/FormDemo'), }, ``` #### 何时使用keep-alive? * 缓存组件,不需要重复渲染 * 如多各静态tab页的切换 * 优化性能 #### 何时使用beforeDestory * 解绑自定义事件event.$off * 清除定时器 * 销毁子组件 * 解绑自定义的dom事件,如window scroll(addEventListener(' ',()=>{}))等 #### 什么是作用域插槽 image-20210525045948730 #### Vuex中action和mutation 有何区别 * action中处理异步,mutation不可以 * mutation做原子操作(只做一个操作) * action可以整合多个mutation的集合 #### Vue-router常用的路由模式 * hash默认 * H5 history(需要服务端支持) * 两者比较 #### 如何配置Vue-router异步加载 * 通过import函数来引入组件路径 image-20210525050400255 image-20210525050555462 #### 监听data变化的核心api是什么? * Object.defineProperty * 以及深度监听,监听数组 * 有何缺点 #### Vue如何监听数组变化 * Object.defineProperty不能监听数组变化 * 重新定义,重写push pop 等方法,实现监听 * Proxy可以原生支持监听数组变化 #### 请描述响应式原理 * 监听data变化 * 组件渲染和更新的流程 #### diff算法的时间复杂度 * O(n) * 在O(n^3)基础上做了些调整 * 只比较同一层级 * 如果tag不相同就直接销毁重建 * 通过tag和key来判断是不是同一个组件,如果是同一个组件就不在重复对比 * 通过以上把时间复杂度从n(o^3)降到n > 当页面的数据发生变化时,Diff算法只会比较同一层级的节点: > > 1. **如果节点类型不同,直接干掉前面的节点,再创建并插入新的节点,不会再比较这个节点以后的子节点了。** > 2. **如果节点类型相同,则会重新设置该节点的属性,从而实现节点的更新。** image-20210525051502182 image-20210525051726122 ### Vue常见的性能优化方式 * 合理使用v-show 和 v-if * 合理使用computed(有缓存) * 使用v-for时加key,以及避免和v-if同时使用 * 自定义事件,dom事件及时销毁 * 合理使用异步组件(components中用import函数导入组件路径) * 合理使用路由懒加载(在路由中用import函数导入组件路径) * 合理使用keep-alive * data层级不要太深(监听时递归的次数多) * 使用vue-loader在开发环境做模板编译 * webpack层面的优化(后边讲) * 前端通用的性能优化,如图片懒加载 * 使用SSR # 5-6 ### 面试题1/4: 1. Vue3比Vue2有什么优势? 2. 描述Vue3生命周期? 3. 如何看待CompositionAPI和Options API? ### 面试题2/4: 1. 如何理解`ref` `oRef `和`toRefs`? 2. Vue3升级了哪些重要的功能? 3. Componsition API如何实现代码逻辑复用?x 4. Vue3 如何实现响应式? 5. watch和watchEffect的区别是什么? 6. setup中如何获取组件实例? 7. Vue3为何比Vue2快? 8. Vite是什么? 9. Composition API和React Hooks的对比 #### Vue3比Vue2有什么优势? * 性能更好 * 体积更小 * 更好的ts支持 * 更好的代码组织 * 更好的逻辑抽离 * 更多新功能 #### 描述Vue3生命周期? * beforeDestroy改为beforeUnmount * destroyed改为unmouted * 其他沿用Vue2的生命周期 #### CompositionAPI和Options API的对比? * Composition API带来了什么? * 更好的代码组织 * 更好的逻辑复用(有一道专门的面试题) * 更好的类型推导 image-20210527005812733 * Composition API 和 Options API如何选择? * 不建议共用,会引起混乱 * 小型项目,业务逻辑简单,用Options API * 中大型项目,逻辑复杂,用Composition API * 别误解Composition API * Composition属于高阶技巧,不是基础必会 * Composition API是为解决复杂业务逻辑而设计 * Composition API就像Hooks在React中的地位 #### 如何理解ref toRef 和 toRefs * 是什么 * 最佳使用方式 * 进阶,深入理解 ##### ref * 生成值类型的响应式数据 * 可用于模板和reactive * 通过. value修改值 ```html ``` ```html //模板中的使用 ``` # 6-6 ##### toRef * 针对一个响应式对象(reactive封装)的prop * 创建一个ref,具有响应式 * 两者保持引用关系 * 普通对象实现响应式用reactive,一个响应式对象里其中一个属性要想实现响应式用toRef ```html ``` ##### toRefs * 将响应式对象(reactive封装)转换为普通对象 * 对象的每个prop都是对应的ref * 两者保持引用关系 ```html ``` image-20210530230517255 #### 最佳使用方式 * 用reactive作对象的响应式实现方式,用ref作值类型的响应式(实现响应式的两种方式) * setup中返回`toRefs(state)`,或者`toRef(state,'xxx')` * ref的变量命名都用xxxRef * 合成函数返回响应式对象时,使用toRefs #### 进阶,深入理解 * 为何需要ref ? * 如果没有ref,返回值的类型会丢失响应式 * 如在setup,computed,合成函数,都有可能返回值类型 * computed返回的是类似于ref的对象,也有.value * 所以,Vue如不定义ref,用户将自造ref,反而混乱 * 为何需要.value ? * ref是一个对象(不会丢失响应式),value存储值 * 通过.value属性的get和set实现响应式 * 用于模板,reactive时,不需要.value,其他情况都需要 * 为何需要toRef toRefs ? * 初衷:不丢失响应式的情况下,把对象数据**分解/扩散**(解构) * 前提:针对的是响应式对象(对象用reactive封装,值类型用ref封装),并非普通对象 * 注意:不**创造**响应式,而是**延续**响应式 # 6-11 ### Vue3升级了哪些重要的功能 * createApp image-20210602023544202 * emits属性 image-20210602023739171 ```html //父组件 ``` ```html //子组件 ``` * 生命周期 * 多事件 image-20210602025832096 * Fragment image-20210602030357592 * 移除.sync image-20210602030321136 ```html ``` ```html ``` * 异步组件的写法 * 导入defineAsyncComponent image-20210602030622410 * 移除fitler image-20210602030742546 * Teleport * Teleport 提供了一种干净的方法,允许我们控制在 DOM 中指定父节点下呈现 HTML,而不必求助于全局状态或将其拆分为两个组件。 image-20210602031031822 ```html ``` * Suspense image-20210602031642167 * Composition API * reactive * ref * readonly * ```html ``` * watch和watchEffect * 两者都可以监听data属性变化 * watch需要明确监听那个属性 * watchEffect会根据其中的属性,自动监听其变化 ```html ``` * setup(setup中如何获取组件实例) * 在setup和其他Composition API中没有this * 可通过getCurrentInstance 获取当前实例 * 若使用Options API可照常使用this ```html ``` * 生命周期钩子函数 ### Composition API实现逻辑复用 * 抽离逻辑代码到一个函数 * 函数命名约定为useXxxx格式(React Hooks也是) * 在setup中引用useXxxx函数 ```html ``` ```javascript //useMousePosition.js文件 import {ref, onMounted, onUnmounted} from 'vue' function useMousePosition() { const x = ref(0) const y = ref(0) function update(e) { x.value = e.pageX y.value = e.pageY } onMounted(()=>{ console.log('useMousePositoin mounted') window.addEventListener('mousemove',update) }) onUnmounted(()=>{ console.log('useMousePositoin unMounted') window.removeEventListener('mousemove',update) }) return { x, y } } export default useMousePosition ``` ### Vue3 如何实现响应式 * 回顾Vue2.x 的Object.defineProperty * 学习Proxy语法 * Vue3如何用Proxy实现响应式 #### Proxy实现响应式 * 回顾Object.defineProperty缺点 * 深度监听需要一次性递归 * 无法监听新增属性/删除属性(Vue.set Vue.delete) * 无法原生监听数组,需要特殊处理 * Proxy实现响应式 * 基本应用 * Reflect * 和Proxy能力一一对应 * 规范化,标准化,函数式 * 代替掉Object上的工具函数 * 实现响应式 * 深度监听,性能更好 * 可监听 新增/删除属性 * 可监听数组变化 * 两者对比 # 6-16 ### Vue3为什么比Vue2快? * Proxy响应式效率更高 * PatchFlag(静态标记) * 编译模板时,动态节点做标记 * 标记,分为不同的类型,如9 TEXT PROPS ,1 TEXT ,2 CLASS , 8 PROPS id image-20210604215756803 * diff算法时,可以区分静态节点(不需要进行比较了),以及不同类型的动态节点(插值,动态属性) image-20210604144101741 * hoisStatic * 模板被编译成render函数 * 在render函数中将静态节点的定义,提升到父作用域,缓存起来 * 多个(非常多)相邻的静态节点,会被合并起来只定义一次 * 典型的拿空间换时间的优化策略 image-20210604214322258 编译优化的一种方式: image-20210604215056406 * cacheHandler * 缓存事件(缓存函数) image-20210604220241674 * SSR优化 * 静态节点直接输出,绕过vdom * 动态节点,还是需要动态渲染 * image-20210604220950327 * tree-shaking * 模板编译时,根据不同情况(模板中使用不同的动态属性,指令),render函数中引入不同的API执行 image-20210604221524666 ### vite是什么? * 是一个前端打包工具,Vue作者发起的项目 * 借助Vue的影响力,发展较快,和webpack竞争 * 优势:开发环境下无需打包,启动快 * 开发环境使用ES6 Module,无需打包--非常快(http-server -p 8881 启动) ```javascript //add.js文件 import print from './print.js' export default function add(a,b) { print('print','add') return a+ b } ``` ```javascript //print.js 文件 export default function (a,b) { console.log(a,b) } ``` 基本使用: ```html ``` ```html ``` 外链: ```javascript import {add,multi} from './math.js' console.log('add res1',add(10,20)) console.log('multi res',multi(10,20)) ``` ```html ``` 远程引入: ```html ``` * 生产环境使用rollup,并不会快很多 ### Composition API和React Hooks 对比1/2 ### 面试题 * Vue3和Vue2有什么优势? * 描述Vue3生命周期 * 如何看待Composition API 和 Options API? * 如何理解ref toRef 和 toRefs * Vue3升级了哪些重要的功能 * Vue3如何实现代码逻辑复用 * Vue3如何实现响应式? * watch和watchEffect的区别是什么 * setup中如何获取组件实例(vue3中没有this) * Vue3为什么比vue快? * vite是什么? * Composition API和React Hooks 对比 # 10-2 (进度后:10-11) ## 回顾之前的webpack面试题 * 前端代码为何要进行构建打包? * module chunk bundle 分别什么意思?有什么区别? * loader和plugin的区别 * webpack如何实现懒加载? * webpack常见性能优化 * babel-runtime和babel-polyfill的区别 ## 关于webpack5 * webpack5主要是内部效率的优化 * 对比webpack4,没有太多使用上的改动 * 你可以直接使用webpack5来学习课程 # 10-10 ## webpack基本配置 * 拆分配置和merge * 启动本地服务 * 处理es6 * 处理样式 * 处理图片 * 模块化 ## webpack高级配置 * 基本配置只能做demo,不能做线上项目 * 面试考察基本配置,只是为了快速判断你是否用过webpack * 一下高级配置,也是通过面试的必要条件 * 多入口 ```javascript entry:{ //多入口:有一个入口,最终输出就有一个bundle index: './src/js/index.js', test:'./src/js/test.js' }, ``` * 抽离css文件 ```javascript new MiniCssExtractPlugin({ //样式通过link标签引入,不会出现闪屏 //js和css文件分开,解析速度加快 //对输出css文件的重命名 filename:'css/[name].[contentHash:8].css' }) use:[ //创建style标签,将样式放入 // 'style-loader', //这个loader取代style-loader 作用:提取js中的css成单独文件 MiniCssExtractPlugin.loader, //将css文件整合到js文件中 'css-loader' ] ``` * 抽离公共代码 ```javascript //1。可以将node_modules(第三方)中代码单独打包一个chunk最终输出 //2.自动分析多入口chunk中,有没有公共的文件,如果有会打包成单独的一个chunk optimization: { splitChunks: { chunks: "all", /** * initial 入口chunk,对于异步导入的文件不处理 * async 异步chunk,只对异步导入的文件处理 * all 全部chunk */ //缓存分组 cacheGroups: { //第三方模块 vendor:{ name:'vendor',//chunk名称 priority: 1,//权限更高,优先抽离(有时模块既是第三方的也是公共的),重要 test:/node_modules/,//命中第三方模块位置 minSize: 0,//大小限制 minChunks: 1//最小复用过几次 }, //公共模块 common:{ name:'common',//chunk名称 priority: 0,//优先级 minSize: 0,//公共模块大小限制 minChunks: 2//公共模块最少复用过几次 } } } }, ``` * 懒加载(用import引入文件,返回promise) ```javascript index.js入口文件 console.log('index.js文件被加载了'); //引入动态数据 懒加载(利用代码分割):用到相关文件的时候再进行加载,而不是一开始就加载 setTimeout(()=>{ //回顾vue 异步组件 //定义新的chunk import('./test').then(res=>{ console.log('加载test文件'); }) },1500); ``` * 处理JSX(@babel/preset-env) * 处理Vue(vue-loader) ## module chunk bundle 的区别 * module-各个源码文件,webpack中一切皆模块 * chunk-多模块合并成的代码文件块,它内部有很多依赖(引用其他文件)。 entry,import()(懒加载), splitChunk(代码分割) 都可以定义chunk * bundle-最终的输出文件(不一定是一个文件) image-20210626194552032
# 10-11 ## webpack 性能优化 * 大厂必考&社区热议 * 优化打包构建速度-开发体验和效率 * 优化babel-loader > ```markdown > cacheDirectory:true > -->让第二次打包构建速度更快,es6代码没有修改的文件不会重新编译 > ``` image-20210629214248077 * IgnorePlugin(避免引入无用模块) * import momont from 'moment' * 默认会引入所有语言js代码,代码过大 * 如何只引入中文? * 直接不引入,代码中没有 ```js plugins: [ //plugins设置 //html-webpack-plugin //功能:默认创建一个空的html,自动引入打包输出的所有资源(js/css) //需求:需要有结构的html文件 new HtmlWebpackPlugin({ //复制 ./src/index.html 文件 , 并自动引入打包输出的所有资源(js/css) template: "./src/index.html" }), //忽略moment下的/locale目录 new Webpack.IgnorePlugin(/^\.\/locale$/, /moment$/) ], ``` ```js //index.js文件中手动引入中文文件包 import moment from 'moment' import 'moment/locale/zh-cn' //手动引入中文语言包 moment.locale('zh-cn');//设置中文 console.log('locale',moment.locale()); console.log('date',moment().format('ll'));//当前日期 ``` * noParse(避免重复打包) * 引入,但不打包 image-20210629221934203 * happyPack * js单线程,开启多进程打包 * 提高构建速度(特别是多核cpu) * ParallelUglifyPlugin多进程压缩js * webpack内置Uglify工具压缩js * js是单线程,开启多进程压缩更快 * 和happyPack同理 > 如果项目比较大,打包比较慢情况下,开启多进程能提高速度 > > 如果项目比较小,打包比较快情况下,开启多进程会降低速度(进程开销(进程的开启,销毁和进程之间的通讯)) * 自动刷新 image-20210629231546533 * 热更新 * 自动刷新:整个网页全部刷新,速度较慢,状态会丢失 * 热更新:新代码生效,网页不会刷新,状态不丢失 * ```text HMR:hot module replacement 热模块替换/模块热替换 作用:一个模块发生变化,只会重新打包这一个模块(而不是打包所有) 极大提升构建速度(例如只修改了样式文件,那么只会更新样式文件,js文件或者其他资源不会更新) 样式文件:可以使用HMR功能:因为syle-loader内部默认实现了HMR~(在开发环境中使用style-loader,在生产环境中要提取成单独文件, 开发环境借助style-loader使打包速度更快些) js文件:默认没有HMR功能 --》需要修改js代码,添加支持HMR功能的代码 注意:HMR功能对js的处理,只能处理非入口js文件的其他文件 html文件:默认没有HMR功能,同时会导致问题:html文件不能热更新了(本来就不需要做HMR功能,因为整个项目就一个html文件) 解决:修改entry入口,将html文件引入 ``` ```javascript devServer: { contentBase: resolve(__dirname, 'build'), compress: true, port: 3000, open: true, //开启HMR功能 //当修改了webpack配置,重新配置要想生效,必须重新启动webpack服务 hot:true }, ``` ```JAVA index.js文件中 入口文件 if (module.hot){ //全局中查找module,一旦module.hot 为true,说明开启了HMR功能。 -->让HMR功能代码生效 module.hot.accept('./print.js',function () { //方法会监听print.js文件的变化,一旦发生变化,其他模块不会重新打包构建 //会执行后面的回调函数 print(); }) } ``` * DllPlugin * 前端框架如vue React ,体积大,构建慢 * 比较稳定,不常升级版本 * 同一个版本只构建一次即可,不用每次都重新构建 * webpack已经内置DllPlugin * DllPlugin-打包dll文件 * DllReferencePlugin-使用dll文件 ```javascript webpack.dll.js文件 plugins: [ //打包生成一个manifest.json-->提供和jquery映射 new webpack.DllPlugin({ name:'[name]_[hash]',//映射库的暴露的内容名称 path:resolve(__dirname,'dll/manifest.json')//输出文件路径 }) ] ``` ```javascript webpack.config.js文件 //告诉webpack哪些库不参与打包,同时使用时的名称也得变 new webpack.DllReferencePlugin({ manifest: resolve(__dirname,'dll/manifest.json') }), //将某个文件打包输出出去,并在html中自动引入该文件 new AddAssetHtmlWebpackPlugin({ filepath:resolve(__dirname,'dll/jquery.js') }) ``` * 优化产出代码-产品性能 # 10-20 ## webpack 优化构建速度(可用于生产环境) * 优化babel-loader * IgnorePlugin * noParse * happyPack * ParalleUglifyPlugin ## webpack 优化构建速度(不可用于生产环境) * 自动更新 * 热更新 * DllPlugin ## webpack 性能优化-产出代码 * 打包出来的代码体积更小 * IgnorePlugin(避免引入无用模块) * Tree-Shaking * 合理分包,不重复加载 * HMR:hot module replacement 热模块替换/模块热替换, 作用:一个模块发生变化,只会重新打包这一个模块(而不是打包所有) * 懒加载 import函数引入要使用加载的文件 * 速度更快,内存使用更小 * 小图片base64编码(减少请求次数) * bundle加hash(contentHash:根据文件内容计算哈希,文件内容不变,文件名字就不会变,文件名字不变就可以命中强制缓存) * 懒加载:通过import语法引入要加载的文件 * 提取公共代码:提取公共代码和第三方代码,切割代码(避免重复打包模块) * IngorePlugin * 使用CDN加速 * 使用production * 自动开启代码压缩 * Vue React等会自动删掉调试代码(如开发环境的warning) * 自动启动Tree-Shaking * ``` tree shaking:打包时去除无用代码(未被引用/使用的代码) 前提:1.必须使用es6模块化 2.开启production环境 ``` * 使用Scope Hosting * 多个函数合并成一个函数 * 使代码体积更小 * 创建函数作用域更少 * 代码可读性更好 image-20210702191612252 ## ES6 Module和Commonjs的区别 * ES6 Modue 静态引入,编译时引入 * Commonjs 是动态引用,执行时引入 * 只有ES6 Module 才能静态分析,实现Tree-Shaking image-20210705145357505 # 10-25 ## babel * 前端开发环境必备工具 * 同webpack,需要了解基本的配置和使用 * 面试考察概率不高,但要求必会 * 环境搭建&基本配置 * 环境搭建 * .babelrc配置 * presets和plugins * babel-polyfill * babel-runtime ## babel-polyfill是什么 * 什么是Ployfill * core-js和regenerator(支持*处理异步的语法) * core-js库:它集成了所有es6,es7,promise,symbol等新语法的补丁 * babel-polyfill是core-js和regenerator的集合,可以满足所有es6以上所有新语法 * Babel7.4之后弃用babel-polyfill * 但是并不影响面试考察 * babel只检查语法(例如箭头函数转成普通es5函数,不会理会类似es6以上的Api:Promise.resolve()等新语法),不处理模块化,引入babel-polyfill后webpack处理模块化 ### babel-polyfill按需引入 * 文件较大 * 只有一部分功能,无需全部引入 * 配置按需引入 ## babel-poly的问题? * 污染全局环境 * 为了保证兼容性要写出类似的代码(污染全局环境) ```javascript window.Promise = funciton(){} Array.prototype.includes = function(){} ``` * 如果做一个独立的web系统,则无碍 * 如果做一个第三方的lib,则会有问题 ## babel-runtime解决babel-poly的问题(会重新命名Promise和includes,就不会污染全局环境) ## webpack考点总结和复习 * 基本配置 * 拆分配置和merge * 启动本地服务 * 处理es6 * 处理样式 * 处理图片 * 高级配置 * 多入口 * 抽离css文件 * 抽离公共代码 * 懒加载 * 处理JSX * 处理vue * 优化打包效率 * 优化babel-loader * ^^ * 优化产出代码 * 构建流程概述 * babel ## 真题演练 ### 前端为什么要进行打包和构建? #### 代码方面 * 体积更小(Tree-Shaking,压缩,合并),加载更快 * 编译高级语言或语法(TS,ES6+,模块化,scss) * 兼容性和错误检查(Polyfill,postcss,eslint) #### 这个部门流程效率的优化方面 * 统一,高效的开发环境 * 统一的构建流程产出标准 * 集成公司构建规范(提测,上线等) ### module,chunk,bundle的区别 ### loader和plugin的区别 * loader是模块转换器,如less->css * plugin扩展插件,如HtmlWebpackPlugin ### 常见的loader和plugin有哪些 ### babel和webpack的区别 * babel-JS新语法编译工具,不关心模块化 * webpack-打包构建工具,是多个loader plugin的集合,可以结合babel处理一些问题 ### 如何产出一个lib * 参考webpack.dll.js * output.library image-20210705161335756 ### Babel-polyfill和babel-runtime的区别 * babel-polyfill会污染全局 * babel-runtime不会污染全局 * 产出第三方lib要用babel-runtime ### webpack如何实现懒加载 * import() * 结合 vue react 异步组件 * 结合Vue-router React-router异步路由 ### 为何Proxy不能被Polyfill? * 如class可以用function模拟 * 如Promise可以用callback来模拟 * 但是Proxy的功能用Object.defineProperty无法模拟 image-20210717055812006 image-20210717055850220 # B站webpack ## 第一部分: ### 第一题:谈谈你对webpack的理解? webpack是一个打包模块化js的工具,在webpack里一切文件皆模块,通过loader转换文件,通过plugin注入钩子,最后输出由多个模块组合成的文件,webpack专注构建模块化项目。WebPack可以看做是模块的打包机器:它做的事情是,分析你的项目结构,找到js模块以及其它的一些浏览器不能直接运行的拓展语言,例如:Scss,TS等,并将其打包为合适的格式以供浏览器使用。 ### 第二题:说说webpack与grunt、gulp的不同? ​ 三者都是前端构建工具,grunt和gulp在早期比较流行,现在webpack相对来说比较主流,不过一些轻量化的任务还是会用gulp来处理,比如单独打包CSS文件等。 ​ grunt和gulp是基于任务和流(Task、Stream)的。类似jQuery,找到一个(或一类)文件,对其做一系列链式操作,更新流上的数据, 整条链式操作构成了一个任务,多个任务就构成了整个web的构建流程。 ​ webpack是基于入口的。webpack会自动地递归解析入口所需要加载的所有资源文件,然后用不同的Loader来处理不同的文件,用Plugin来扩展webpack功能。 ​ 所以,从构建思路来说,gulp和grunt需要开发者将整个前端构建过程拆分成多个`Task`,并合理控制所有`Task`的调用关系;webpack需要开发者找到入口,并需要清楚对于不同的资源应该使用什么Loader做何种解析和加工 ​ 对于知识背景来说,gulp更像后端开发者的思路,需要对于整个流程了如指掌 webpack更倾向于前端开发者的思路 ### 第三题:什么是bundle,什么是chunk,什么是module? ​ bundle:是由webpack打包出来的文件 ​ chunk:代码块,一个chunk由多个模块组合而成,用于代码的合并和分割 ​ module:是开发中的单个模块,在webpack的世界,一切皆模块,一个模块对应一个文件,webpack会从配置的entry中递归开始找出所有依赖的模块 ### 第四题:什么是Loader?什么是Plugin? ​ 1)Loaders是用来告诉webpack如何转化处理某一类型的文件,并且引入到打包出的文件中 ​ 2)Plugin是用来自定义webpack打包过程的方式,一个插件是含有apply方法的一个对象,通过这个方法可以参与到整个webpack打包的各个流程(生命周期)。 ### 第五题:有哪些常见的Loader?他们是解决什么问题的? * file-loader:一般用于打包其他资源(用exclude排除某些文件资源)把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件 * url-loader:处理css文件中引入的图片,能设置某个大小范围(8-12)以下的图片以base64格式的方式处理,优点:不必单独发送http请求,可以减少服务器请求次数。缺点:图片体积会更大(文件请求速度更慢,因为用base64表示二进制,比原来二进制表示多了1/3倍的字节) * 对于比较小的图片,使用base64编码,不必单独发送http请求图片,可以减少一次图片的网络请求;那么对于比较大的图片,使用base64就不适合了,因为用base64表示二进制,比原来二进制表示多了1/3倍的字节,因此设置一个合理的limit是非常有必要的 * source-map-loader:生成一个.map的映射文件,一种提供源代码到构建后代码映射的技术,方便调试,(如果构建后代码错了,可以通过映射关系追踪源代码错误) * image-loader:加载并且压缩图片文件 * babel-loader:把 ES6 转换成 ES5 * css-loader:加载 CSS到js文件中,支持模块化、压缩、文件导入等特性 * style-loader:把 CSS 代码注入到 JavaScript 中,在js文件中生成style标签,通过 DOM 操作去加载 CSS。 * eslint-loader:通过 ESLint按照相应规范(例如:airbnb) 检查 JavaScript 代码 ## 第二部分 ### 第一题:有哪些常见的Plugin?他们是解决什么问题的? * HtmlWebpackPlugin:默认创建一个空的html,自动引入打包输出的所有资源(js/css) * MiniCssExtractPlugin:提取css生成单独文件 * OptimizeCssAssetsWebpackPlugin:压缩css文件 ​ define-plugin:定义环境变量 ​ commons-chunk-plugin:提取公共代码 ​ uglifyjs-webpack-plugin:通过UglifyES压缩ES6代码 ### 第二题: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的构建流程是什么? ​ Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程: ​ 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数; ​ 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译; ​ 确定入口:根据配置中的 entry 找出所有的入口文件; ​ 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理; ​ 完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系; ​ 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会; ​ 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。 ​ 在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果。 ### 第四题:描述一下编写loader或plugin的思路? ​ Loader像一个"翻译官"把读到的源文件内容转义成新的文件内容,并且每个Loader通过链式操作,将源文件一步步翻译成想要的样子。 ​ 编写Loader时要遵循单一原则,每个Loader只做一种"转义"工作。 每个Loader的拿到的是源文件内容(source),可以通过返回值的方式将处理后的内容输出,也可以调用this.callback()方法,将内容返回给webpack。 还可以通过 this.async()生成一个callback函数,再用这个callback将处理后的内容输出出去。 此外webpack还为开发者准备了开发loader的工具函数集——loader-utils。 ​ 相对于Loader而言,Plugin的编写就灵活了许多。 webpack在运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。 ## 第三部分 ### 第一题:如何利用webpack来优化前端性能? ​ 用webpack优化前端性能是指优化webpack的输出结果,让打包的最终结果在浏览器运行快速高效。 ​ 压缩代码。删除多余的代码、注释、简化代码的写法等等方式。可以利用webpack的UglifyJsPlugin和ParallelUglifyPlugin来压缩JS文件, 利用cssnano(css-loader?minimize)来压缩css ​ 利用CDN加速。在构建过程中,将引用的静态资源路径修改为CDN上对应的路径。可以利用webpack对于output参数和各loader的publicPath参数来修改资源路径 ​ 删除死代码(Tree Shaking)。将代码中永远不会走到的片段删除掉。可以通过在启动webpack时追加参数--optimize-minimize来实现 ​ 提取公共代码和第三方代码。 ### 第二题:如何提高webpack的构建速度? ​ 多入口情况下,使用CommonsChunkPlugin来提取公共代码 ​ 通过externals配置来提取常用库 ​ 利用DllPlugin和DllReferencePlugin预编译资源模块 通过DllPlugin来对那些我们引用但是绝对不会修改的npm包来进行预编译,再通过DllReferencePlugin将预编译的模块加载进来。 ​ 使用Happypack 实现多线程加速编译 ​ 使用webpack-uglify-parallel来提升uglifyPlugin的压缩速度。 原理上webpack-uglify-parallel采用了多核并行压缩来提升压缩速度 ​ 使用Tree-shaking和Scope Hoisting来剔除多余代码 ### 第三题:怎么配置单页应用?怎么配置多页应用? ​ 单页应用可以理解为webpack的标准模式,直接在entry中指定单页应用的入口即可,这里不再赘述 ​ 多页应用的话,可以使用webpack的 AutoWebPlugin来完成简单自动化的构建,但是前提是项目的目录结构必须遵守他预设的规范。 多页应用中要注意的是: ​ 每个页面都有公共的代码,可以将这些代码抽离出来,避免重复的加载。比如,每个页面都引用了同一套css样式表 ​ 随着业务的不断扩展,页面可能会不断的追加,所以一定要让入口的配置足够灵活,避免每次添加新页面还需要修改构建配置 ### 第四题:如何在vue项目中实现按需加载? ​ Vue UI组件库的按需加载 为了快速开发前端项目,经常会引入现成的UI组件库如ElementUI、iView等,但是他们的体积和他们所提供的功能一样,是很庞大的。 而通常情况下,我们仅仅需要少量的几个组件就足够了,但是我们却将庞大的组件库打包到我们的源码中,造成了不必要的开销。 ​ 不过很多组件库已经提供了现成的解决方案,如Element出品的babel-plugin-component和AntDesign出品的babel-plugin-import 安装以上插件后,在.babelrc配置中或babel-loader的参数中进行设置,即可实现组件按需加载了。 # 11-2 ## 组件和状态设计 * 框架(Vue React)的使用(和高级特性)是必要条件 * 能独立负责项目?还是需要别人带着?--考察设计能力 * 面试必考(二,三面),场景题 ## 考察重点 * 数据驱动视图 * 状态:数据结构设计(React-state,Vue-=data) * 视图:组件结构和拆分 ## 回顾面试题 * React设计todoList(组件结构,redux state 数据结构) * Vue设计购物车(组件结构,vuex state数据结构) ### 如何讲解 * todolist * 购物车 ### React实现Todo List * state数据结构设计 * * 组件设计(拆分,组合)和组件通讯 * 代码演示 ### vue实现购物车 * data数据结构设计 * 组件设计和组件通讯 * 代码演示 image-20210705163549396 ## axios ### 1. 基本使用 * axios调用的返回值是Promise实例。 * 成功的值叫response,失败的值叫error。 * axios成功的值是一个axios封装的response对象,服务器返回的真正数据在response.data中 * 携带query参数时,编写的配置项叫做params * 携带params参数时,就需要自己手动拼在url中 ```js //完整版 axios({ url:'http://localhost:5000/persons', //请求地址 method:'GET',//请求方式 }).then( response => {console.log('请求成功了',response.data);}, error => {console.log('请求失败了',error);} ) //精简版 axios.get('http://localhost:5000/persons').then( response => {console.log('请求成功了',response.data);}, error => {console.log('请求失败了',error);} ) } ``` ### 2. 常用配置项 ```js //给axios配置默认属性 axios.defaults.timeout = 2000 axios.defaults.headers = {school:'atguigu'} axios.defaults.baseURL = 'http://localhost:5000' ``` ### 3. axios.create方法 * 根据指定配置创建一个新的axios, 也就是每个新axios都有自己的配置 * 新axios只是没有取消请求和批量发请求的方法, 其它所有语法都是一致的 * 为什么要设计这个语法? * 需求: 项目中有部分接口需要的配置与另一部分接口需要的配置不太一样 ```js //可以配置自己独有的配置项,它就是axios的一个实例,几乎和正常axios一样 const axios2 = axios.create({ timeout:3000, //headers:{name:'tom'}, baseURL:'https://api.apiopen.top' }) ``` ### 4. 拦截器 ```js //请求拦截器 axios.interceptors.request.use((config)=>{ console.log('请求拦截器1执行了'); if(Date.now() % 2 === 0){ config.headers.token = 'atguigu' } return config }) //响应拦截器 axios.interceptors.response.use( response => { console.log('响应拦截器成功的回调执行了',response); if(Date.now() % 2 === 0) return response.data else return '时间戳不是偶数,不能给你数据' }, error => { console.log('响应拦截器失败的回调执行了'); alert(error); return new Promise(()=>{})//返回一pendding状态的promise,中断了promise链,接受返回值的地方永远不会走error,若有错误则在响应拦截器里统一处理 } ) btn.onclick = async()=>{ const result = await axios.get('http://localhost:5000/persons21') console.log(result); } ``` #### axios请求拦截器 1. 是什么? * 在真正发请求前执行的一个回调函数 2. 作用? * 对所有的请求做统一的处理:追加请求头(token)、追加参数、界面loading提示等等 #### axios响应拦截器 1. 是什么? * 得到响应之后执行的一组回调函数 2. 作用: * 若请求成功,对成功的数据进行处理 * 若请求失败,对失败进行统一的操作,这样正式获取数据的地方就可以不用写处理错误相关的代码了。 #### axios通常的封装过程(一)(完整流程) 1. 通过axios.create()方法返回一个axios实例对象命名为request,并且配置baseURL和timeout,一般再配合调用拦截器,最后再export出去 ```js //封装axios import axios from 'axios' //引入进度条 import nprogress from 'nprogress' import 'nprogress/nprogress.css' console.log(nprogress) //1.利用axios对象的方法create , 去创建一个axios实例 //2. request就是axios,只不过稍微配置一下 const requests = axios.create({ //配置对象 baseURL: "/api", //代表请求超时时间 timeout: 5000, }) //请求拦截器,在发送请求之前,请求拦截器可以检测到,可以在请求发出去之前做一些事情 requests.interceptors.request.use((config) => { //config:配置对象,对象里面有一个属性很重要,headers请求头 //进入调开始动 nprogress.start(); return config }) //响应拦截器 requests.interceptors.response.use((res) => { //成功的回调函数,服务器响应数据回来以后,响应拦截器可以检测到,可以做一些事情 nprogress.done() return res.data }, (error) => { return Promise.reject(new Error('fail')) }) //对外暴露 export default requests ``` 2. 创建一个.js文件引入`requests`,通过requests对象以对象中key,value的形式配置本方法需要的具体的url地址以及方法类型,把配置后的requests对象在方法中通过return的方式返回,然后再本文件中通过export const 把封装好的方法暴露出去 ```js //当前这个模块,api进行统一管理 import requests from "@/api/request"; import mockRequests from './mockAjax' //三级联动的接口 // /api/product/getBaseCategoryList get 无参数 export const reqCategoryList = () => requests({url: '/product/getBaseCategoryList', method: 'get'}) //获取banner(Home首页轮播图接口) export const reqGetBannerList = () => mockRequests.get('/banner') //获取floor数据 export const reqFloorList = () => mockRequests.get('/floor') //获取搜索模块数据,params至少是个空对象 export const reqGetSearchInfo=(params)=>requests({url:'/list',method:'post',data:params}) ``` 3. 在页面获取js文件中引入并使用 ```js import {reqCategoryList, reqGetBannerList, reqFloorList} from "@/api"; const actions = { async categoryList({commit}) { let result = await reqCategoryList() if (result.code === 200) { commit("CATEGORYLIST", result.data) } } } ``` #### axios通常的封装过程(二) 1. 引入axios ```js import axios from 'axios'; import qs from 'qs';// qs为第三方库 ``` 2. axios全局配置项 ```js switch(process.env.NODE_ENV){ //可以在根目录的 package.json 配置 NODE_ENV case 'production': axios.defaults.baseURL = "生产环境地址"; break; case 'test' axios.defaults.baseURL ="测试环境地址"; default; axios.defaults.baseURL ="开发环境地址"; } /* 设置超时时间 设置是否允许跨域携带身份凭证 */ axios.defaults.timeout = 12000; axios.defailts.withCredentials = true; /* 设置请求传输数据的格式 只支持POST请求,根据实际要求决定设置不设置 */ //以'x-www-form-urlencoded'的形式发送post请求 axios.defaults.headers['Content-Type'] = 'application/x-www-form-urlencoded'; //把对象的形式转化为'name=zhangsan&sex=man'的形式,qs.parse(data) 对象<=>序列化形式 //axios.defaults.headers['Content-Type'] = 'application/json'; //JSON.stringify(), JSON.parse() , js对象<=>json对象 axios.defaults.transformRequest = data => {qs.stringify(data)}; //请求前改变请求数据的格式 ``` 3. 请求拦截器 **客户端发送请求**=>`[请求拦截器]`=>服务器 ```js axios.interceptors.request.use( config => {//请求成功时执行 //获取本地存储中的token let token = localStorage.getItem('token'); token && (config.headers.Authorization = token); return config; }, error => {//请求失败时执行 return Primise.reject(error) } ) ``` 4. 响应拦截器 服务器返回信息=>`[响应拦截器]`=>服务器 ```js axios.interceptors.response.use( response=>{ return response.data; },error => {//拦截失败 return Primise.reject(error); let {response } = error; if(response) { switch (response.status) { case 401://权限问题,提示未登录或无权限等; break; case 403://服务器拒绝执行 (token过期) break; case 404://找不到页面 break; } } else { //服务器连结果都没有返回 if(!window.navigator.onLine) { //断网处理:跳转断网页面/提示网络错误等等 return; } return Promise.reject(error) } } ) ``` #### axios通常的封装过程(三) 1. **封装的目的** 此次进行简单的封装,所以暂时没有考虑**取消重复请求、重复发送请求、请求缓存**等情况!这里主要实现以下目的: 1. 实现请求拦截 2. 实现响应拦截 3. 常见错误信息处理 4. 请求头设置 5. api 集中式管理 2. **初始化 axios 实例** 虽然 axios 可以调用 get、post 等方法发起请求,但是我们为了更好的全局控制所有请求的相关配置,所以我们使用 axios.create()创建实例的方法来进行相关配置,这也是封装 axios 的精髓所在。 示例代码: ```js // 创建 axios 请求实例 const serviceAxios = axios.create({ baseURL: "", // 基础请求地址 timeout: 10000, // 请求超时设置 withCredentials: false, // 跨域请求是否需要携带 cookie }); 作者:小猪课堂 链接:https://juejin.cn/post/7094165971874611230 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 ``` 通过 create 方法我们得到了一个 axios 的实例,该实例上有很多方法,比如拦截器等等。我们创建实例的时候可以配置一些基础设置,比如基础请求地址,请求超时等等。 3. **设置请求拦截** 我们在发送请求的时候可能需要携带一些信息在请求头上,比如 token 等,所以说我们就需要将请求拦截下来,处理一些我们的业务逻辑。 示例代码: ```js // 创建请求拦截 serviceAxios.interceptors.request.use( (config) => { // 如果开启 token 认证 if (serverConfig.useTokenAuthorization) { config.headers["Authorization"] = localStorage.getItem("token"); // 请求头携带 token } // 设置请求头 if(!config.headers["content-type"]) { // 如果没有设置请求头 if(config.method === 'post') { config.headers["content-type"] = "application/x-www-form-urlencoded"; // post 请求 config.data = qs.stringify(config.data); // 序列化,比如表单数据 } else { config.headers["content-type"] = "application/json"; // 默认类型 } } console.log("请求配置", config); return config; }, (error) => { Promise.reject(error); } ); ``` 我们通过调用 axios 的实例方法来进行请求拦截。 4. 设置响应拦截 axios 请求的返回结果里面包含了很多东西,我们的业务层面通常只需要后端返回的数据即可,所以我们需要设置相应拦截,在响应结果返回给业务层之前做一些操作 示例代码: ```js // 创建响应拦截 serviceAxios.interceptors.response.use( (res) => { let data = res.data; // 处理自己的业务逻辑,比如判断 token 是否过期等等 // 代码块 return data; }, (error) => { let message = ""; if (error && error.response) { switch (error.response.status) { case 302: message = "接口重定向了!"; break; case 400: message = "参数不正确!"; break; case 401: message = "您未登录,或者登录已经超时,请先登录!"; break; case 403: message = "您没有权限操作!"; break; case 404: message = `请求地址出错: ${error.response.config.url}`; break; case 408: message = "请求超时!"; break; case 409: message = "系统已存在相同数据!"; break; case 500: message = "服务器内部错误!"; break; case 501: message = "服务未实现!"; break; case 502: message = "网关错误!"; break; case 503: message = "服务不可用!"; break; case 504: message = "服务暂时无法访问,请稍后再试!"; break; case 505: message = "HTTP 版本不受支持!"; break; default: message = "异常问题,请联系管理员!"; break; } } return Promise.reject(message); } ); ``` 5. 完整示例 上面直接简单介绍了拦截等相关代码,接下来我们在实际项目中来演练一下,以 Vue 项目为例。 在 src 下面新建 http 文件夹,用来存储关于 axios 请求的一些文件,然后在 http 文件夹下新建 index.js 文件,用于封装我们的 axios,然后在新建 config 文件夹,主要用来创建配置文件,最后新建一个 api 文件夹,用于集中管理我们的接口。 文件目录: ![image-20221101234100689](https://tva1.sinaimg.cn/large/008vxvgGgy1h7q20rjqgtj306v03w746.jpg) ​ index.js 代码: ```js import axios from "axios"; import serverConfig from "./config"; import qs from "qs"; // 创建 axios 请求实例 const serviceAxios = axios.create({ baseURL: serverConfig.baseURL, // 基础请求地址 timeout: 10000, // 请求超时设置 withCredentials: false, // 跨域请求是否需要携带 cookie }); // 创建请求拦截 serviceAxios.interceptors.request.use( (config) => { // 如果开启 token 认证 if (serverConfig.useTokenAuthorization) { config.headers["Authorization"] = localStorage.getItem("token"); // 请求头携带 token } // 设置请求头 if(!config.headers["content-type"]) { // 如果没有设置请求头 if(config.method === 'post') { config.headers["content-type"] = "application/x-www-form-urlencoded"; // post 请求 config.data = qs.stringify(config.data); // 序列化,比如表单数据 } else { config.headers["content-type"] = "application/json"; // 默认类型 } } console.log("请求配置", config); return config; }, (error) => { Promise.reject(error); } ); // 创建响应拦截 serviceAxios.interceptors.response.use( (res) => { let data = res.data; // 处理自己的业务逻辑,比如判断 token 是否过期等等 // 代码块 return data; }, (error) => { let message = ""; if (error && error.response) { switch (error.response.status) { case 302: message = "接口重定向了!"; break; case 400: message = "参数不正确!"; break; case 401: message = "您未登录,或者登录已经超时,请先登录!"; break; case 403: message = "您没有权限操作!"; break; case 404: message = `请求地址出错: ${error.response.config.url}`; break; case 408: message = "请求超时!"; break; case 409: message = "系统已存在相同数据!"; break; case 500: message = "服务器内部错误!"; break; case 501: message = "服务未实现!"; break; case 502: message = "网关错误!"; break; case 503: message = "服务不可用!"; break; case 504: message = "服务暂时无法访问,请稍后再试!"; break; case 505: message = "HTTP 版本不受支持!"; break; default: message = "异常问题,请联系管理员!"; break; } } return Promise.reject(message); } ); export default serviceAxios; ``` **config/index.js 代码:** ```js const serverConfig = { baseURL: "https://smallpig.site", // 请求基础地址,可根据环境自定义 useTokenAuthorization: true, // 是否开启 token 认证 }; export default serverConfig; ``` **api/user.js 接口调用示例代码:** ```js import serviceAxios from "../index"; export const getUserInfo = (params) => { return serviceAxios({ url: "/api/website/queryMenuWebsite", method: "post", params, }); }; export const login = (data) => { return serviceAxios({ url: "/api/user/login", method: "post", data, }); }; ``` **注意:get 请求需要传 params,post 请求需要传 data。** **Vue 文件中调用示例:** ```js import { login } from "@/http/api/user" async loginAsync() { let params = { email: "123", password: "12321" } let data = await login(params); console.log(data); } ``` 6. 总结 我们这里只做了最最最基础的 axios 封装,但是可扩展性较高。相较于其它文章的过度封装,这里的封装形式其实可以满足大部分应用场景了。 - 请求拦截里面针对 token 进行处理 - 响应拦截里面判断 token 是否过期等等 - 在 config/index.js 里面动态更改 baseURL - 在请求拦截里面根据业务场景修改请求头 - 在拦截里面设置全局请求进度条等等 ## 性能优化 * 有的时候要向服务器传递空值参数(清空参数,category1Id),则要把属性值置为undefined,而不是空字符串,这样这个属性就不会随着参数对象传递到后台,从而减少占用的网络资源 * 合理的利用v-if,v-show * 利用路由懒加载 * 利用路由器生命周期钩子 * v-once * 如果进入页面后,展示的信息不会再做更改,可以使用v-once,使用了v-once的元素/组件及其所有的子节点,只会渲染一次,后面的渲染都会被当作静态内容跳过,可以优化性能。 ```js

输入框的值:{{ msg }}

输入框的值:{{ msg }}

``` * @click.self.once = "clickBtn" * once修饰符,使事件只能触发一次 * Keep-alive * 防抖:前面的所有的触发事件都不能成功的执行回调函数,最后一次触发事件的回调函数在规定的时间之后才会执行,把连续快速的触发事件和连续快速的执行回调函数,变为(连续快速的触发事件)但是只会执行一次回调函数 * 前边不管触发多少次,我最后就执行一次 * 节流:在规定的时间间隔范围内多次触发事件不会重复执行回调函数,只有大于这个时间间隔触发的事件才会执行一次回调函数,把频繁触发事件和频繁执行回调函数,变为频繁的触发事件但是少量执行回调函数 * 按照自己的节奏,到规定的时间间隔之后就要执行一次 * 压缩代码 * 使用雪碧图 * 使用更快的网络cdn * 懒加载(图片懒加载,上滑加载更多) * 插件,库,按需引入 * 根据具体场景,用编程式导航替代声明式导航 * 三级联动:如果使用声明式导航router-link,可以实现路由跳转与传递参数。但是要注意的是,出现卡顿现象。 * router-link:是一个组件,当服务器的数据返回之后,循环出很多router-link组件(返回的每个数据都会包裹一个router-link标签),这样返回数据的一瞬间会非常耗内存。 * 向后端传递空值为空字符串的属性的时候,用undefined代替空串,这样的话,其属性压根就不会出现在网络请求中,可以节省网络资源,带宽。 * 合理的利用事件代理/委派 * 项目中和后台有数据交互的公用组件,每次在其他组件中引入的时候都会进行一次组件销毁和创建(也就意味着每次都要发送请求),这样就会浪费内存资源。解决办法:其获取数据的操作放在app.vue文件中的mouted钩子函数中进行(`this.$store.dispatch('xxx')`),当然这是要基于把获取的数据放入store中管理的情况下。因为app.vue中的mounted只会执行一次,也就是只会发一次请求。可以节省资源 * 合理使用computed(有缓存) * 使用v-for时加key,以及避免和v-if同时使用 * 自定义事件,dom事件及时销毁 * 原生的事件监听在路由离开之前要解绑,`addEventListener()`,因为原生的事件监听vue组件销毁时不能自动解绑 * 合理使用异步组件(components中用import函数导入组件路径) * data层级不要太深(监听时递归的次数多) * webpack层面的优化(后边讲) * 前端通用的性能优化,如图片懒加载 ### 路由守卫项目中应用思路 ```js //配置路由守卫 router.beforeEach((to, from, next) => { //如果访问的是登录页,则放行 if (to.path === '/login') { return next(); } //如果用户未登录,则跳转到/login const userInfo = JSON.parse(sessionStorage.getItem('userInfo'));//获取用户信息,包括token if (!userInfo) { return next('/login'); } //如果用于已登录,则放行 next(); }) ``` ### 电商后台管理系统权限管理: * 超级管理员可以添加角色,删除角色,可以给角色分配权限 ### vue中name属性的作用 1. 组件自身调用,递归组件 当在组件中需要调用自身的时候,可以通过name属性来使用 2. 使用vue-tools工具时的组件名称 当使用调式工具时,组件的名称是通过name属性来设置的 3. 移除keep-alive状态下组件自动缓存功能 我们知道,在组件外使用了keep-alive导致我们第二次进入的时候页面不会重新请求ajax,即mounted() 钩子函数只会执行一次 解决的办法一个增加activated()函数,每次进入新页面的时候再获取一次数据(此处可以了解一下keep-alive状态下的activated()函数和deactivated()函数) 另外一个办法就是keep-alive中增加 exclude=“name”,移除选中页面的缓存 ```vue ``` 1. ajax请求方式有哪些 2. get post 请求方式的区别 3. require 和 from的区别 4. vue生命周期 5. vue2 vue3的区别 6. href和src属性的区别 7. 序列化格式和json格式的区别 8. 什么是虚拟dom 9. export default 和 export的区别 ```js //吸顶功能 mounted(){ window.addEventListener('scroll',this.initHeight) }, methods:{ initHeight(){ let scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop; this.isFixed = scrollTop > 152? true:false; } }, /** * 1. mounted里监听scroll滚动事件 * 2. 定义回调函数,在函数中通过document.body.scrollTop等api获取scrollTop * 3. 如果scrollTop大于预设的值,例如100px.则给目标节点添加写有相对窗口有定位样式(fixed)的类名 */ ``` ### 工作中项目问题 #### 三级联动: 1. 如果使用声明式导航router-link,可以实现路由的跳转与传递参数。但是要注意,会出现卡顿现象。 2. router-link是一个组件,当服务器的数据返回之后,循环出很多的router-link组件【创建组件实例,虚拟dom->真实dom】1000+,创建组件实例的时候,一瞬间创建1000+很耗内存,因此出现了卡顿现象 3. 通过该自定义属性确定点击的是否是目标标签,通过`event.target.dataset`可以获取自定义属性和属性值 > 解决办法: 利用编程式导航和事件代理进行优化 #### 编程式路由跳转到当前路由(参数不变),多次执行会抛出NavigationDuplicated的警告错误? 1. 路由跳转的两种形式:声明式导航,编程式导航 2. 声明式导航没有这类问题,因为vue-router已经处理好了 3. "vue-router":"3.5.3":引入了promise 4. push()方法执行后返回promise 5. promise中要求分别传入成功和失败的回调函数,所以解决方法就是给push方法传入两个函数 ```js this.$router.push({name:'search',params:{keyword:this.keyword},query:{k:this.keyword.toUpperCase()}},()=>{},()=>{}) ``` ```js this.$router.push() //返回一个promise对象 //push方法中类似有这种代码 function push(){ return new Promise((resolve,reject)=>{ }) } ``` ### this.$nextTick() 在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。 > swiper轮播图实现: > > 要把new Swiper() 放在this.$nextTcik(()=>{})的调函数中执行,如果直接把new Swiper()放在mouted中执行,不会实现Swiper效果,因为请求数据时需要花费时间的(图片)。放在mounted就意味着数据还没有没有请求回来就执行new Swiper了。既然数据(图片)没有请求回来,页面呈现的就不是最新的dom,所以必然失败。this.$nextTick()是在dom下次更新之后执行的回调函数,所以,把new Swiper放在回调函数里就意味着数据已经请求回来的,页面上已经是最新的dom了,此时就成功实现了轮播图(之前用setTimeout()也可以,但是太low了) #### 数据延迟 1. app.vue中通过获取购物车的商品数量和用户名,通过vuex,最后在首页中的data中取出渲染, 2. 刷新浏览器的时候页面是不会显示用户名和商品数量,但是令人费解的是刷新浏览器一定执行app.vue中的获取用户名和购物车数量的的方法,而且通过vueTools工具可以看到vuex中确实已经有了数据,然而最终结果却并没有渲染出来。 3. 经思查找资料的解释是,app.vue文件中的要获取接口数据需要时间(异步网络请求),相对来说,首页只是单纯渲染(同步),执行速度更快,所以还没等数据请求回来就已经渲染完了(渲染了个寂寞)。 `个人理解:归根接地是同步,异步和虚拟dom的问题,异步分为网络请求和定时任务,vue的页面渲染底层实际上是执行虚拟dom,虚拟dom就是js的同步任务。那么异步的网络请求要花费时间,所以同步代码先执行,所以渲染的动作优先于网络请求` > 解决办法是,在页面取值的时候把值放在计算属性里取,计算属性监听的对象一旦发生变化就会自动触发函数,自动返回新数据,进而可以把后边请求回来的数据进行渲染 #### 退出再登录后不显示用户信息和性能优化问题 ##### 退出再登录后不显示用户购物车商品数量的信息 1. 购物车的数量和用户名是在app.vue中请求的 2. 因为vue是单页面应用程序,所以当用户退出再登录的时候页面不会刷新,也就不会执行app.vue文件,数据也不会重新获取 3. 所以首页页面不会展示购物车数量 > 解决办法:在首页中mounted钩子中重新写入请求数据的方法,这样一旦进入首页组件会自动执行该方法重新获取数据以渲染页面 ##### 性能优化问题 1. 此时app.vue和首页中都有请求用户信息的方法,一旦用户进行刷新操作就会进行两次请求后端数据,造成资源浪费。 > 解决办法:在登录页面登录成功跳转页面到首页时通过params传入参数{from:login},在首页判断,如果当前路由中有数据{from:login}则可以判别是由登录页面跳转而来,那么就执行向后端发起数据请求的方法,否则不执行。 2. 用户未登录的情况下刷新浏览器,则会触发app.vue文件中的请求用户名和购物车商品数量的函数。然而用户未登录时获取这两个数据是没有必要的,这样就会造成资源浪费 > 解决办法:登录成功后服务端会返回userId,我们就可以根据用户userId判断用户是否登录,从而判断是否要请求用户名和购物车商品数量的数据(`this.$cookie.get('userId')`)。那么基于此就要把userId设置成用户登录时可以获取,用户退出时不可以获取,具体操作就是: > > 1 . 当用户登录成功后把服务端返回userId通过api放到cookie里,并且设置expires为“session”级别。 > > 2 . 当用户退出登录时把userId在cookie中的expires设置成-1 > > 这样当用户关闭浏览器和退出是cookie中的userId就会消失了。 3. TypeNav组件是非路由组件,在其他各个组件中会被复用,问题是,每次跳转到复用它的组件的时候都会执行一次TypeNav中的mounted函数,而TypeNav中的mounted函数中派发了一个actions || 获取商品分类的三级列表的数据。也就是或每次切换页面都会想服务端发起一次请求,这样会造成性能浪费。 > 解决办法:把函数中派发了一个actions || 获取商品分类的三级列表的数据的代码放到App.vue文件中mounted函数中执行,把数据存储到仓库中。因为App.vue是根组件,只会执行一次。这样以后其他组件再需要数据就直接到仓库中拿就可以了