前端面试大全及答案
前端开发掌握的知识点内容摘要:
HTML&CSS:浏览器内核、渲染原理、依赖管理、兼容性、CSS语法、层次关系,常用属性、布局、选择器、权重、CSS盒模型、Hack、CSS预处理器、CSS3动画
JavaScript: 数据类型、运算、对象、Function、继承、闭包、作用域、事件、Prototype、RegExp、JSON、Ajax、DOM、BOM、内存泄漏、跨域、异步请求、模板引擎、模块化、Flux、同构、算法、ES6、ES7、ES8特性、Nodejs、HTTP
框架和类库: ajax、jQuery、Bootstrap、axios、Vue、Vuex、React、Element-ui、Layui、Webpack,Antd,Antd pro,Umi
CSS部分:
1、css 实现图片自适应宽高
一般做法如下
1 <style> 2 div{width: 200px; height: 200px} 3 div img{width: 100%; height: 100%} 4 </style> 5 <div> 6 <img src="xxxx.png" /> 7 </div>
但是,如果外层元素的宽高和图片不一致时,会导致图片被拉伸或收缩,巨丑。所以可以让图片的部分填充整个外层元素
1 <style> 2 div{ 3 width: 200px; 4 height: 200px; 5 background: url('xxxx.png') no-repeat; 6 background-size: cover; 7 background-position: center center; 8 } 9 </style> 10 <div></div>
如果图片大小不一致时想上传同样大小的头像怎么办?
2.背景图片居中
1 background-position:100px 70px ; /*宽的一半,高的一半*/
1 background-position:50% 50% ;
1 background-position:center center;
2、讲 flex,手写出 flex 常用的属性,并且讲出作用
flex-direction: row/row-reverse/column/column-reverse 决定主轴的方向(即项目的排列方向)
flex-wrap: wrap/nowrap/wrap-reverse 决定项目排列方式
flex-basis: number|auto|initial|inherit; 规定弹性项目的初始长度。
flex-grow: number|initial|inherit;规定在相同的容器中,项目相对于其余弹性项目的增长量。
flex-shrink: number|initial|inherit;规定在相同的容器中,项目相对于其余弹性项目的收缩量。
flex-flow <flex-direction>|<flex-wrap> 前两者简写形式,默认flex-flow:row nowrap;
justify-content: flex-start/flex-end/center/space-between/space-around 决定项目在主轴的对齐方式
* space-between:两端对齐,项目之间的间隔都相等。
* space-around:每个项目两侧的间隔相等。所以,项目之间的间隔比项目与边框的间隔大一倍。
align-items: flex-start/flex-end/center/baseline/stretch 定义项目在交叉轴上如何对齐
* baseline: 项目的第一行文字的基线对齐。
* stretch(默认值):如果项目未设置高度或设为auto,将占满整个容器的高度。
align-content: flex-start/flex-end/center/space-between/space-around/stretch 定义多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。
3、BFC 是什么
①何为BFC:BFC(Block Formatting Context)格式化上下文,是Web页面中盒模型布局的CSS渲染模式,指一个独立的渲染区域或者说是一个隔离的独立容器。
②形成BFC的条件:
1、浮动元素,float 除 none 以外的值;
2、定位元素,position(absolute,fixed);
3、display 为以下其中之一的值 inline-block,table-cell,table-caption;
4、overflow 除了 visible 以外的值(hidden,auto,scroll);
③BFC的特性
1.内部的Box会在垂直方向上一个接一个的放置。
2.垂直方向上的距离由margin决定
3.bfc的区域不会与float的元素区域重叠。
4.计算bfc的高度时,浮动元素也参与计算
5.bfc就是页面上的一个独立容器,容器里面的子元素不会影响外面元素。
详见https://www.cnblogs.com/chen-cong/p/7862832.html
4、meta标签
5、请简述css盒子模型
一个css盒子从外到内可以分成四个部分:margin(外边距),border(边框),padding(内边距),content(内容)
默认情况下,盒子的width和height属性只是设置content(内容)的宽和高
盒子真正的宽应该是:内容宽度+左右填充+左右边距+左右边框
盒子真正的高应该是:内容高度+上下填充+上下边距+上下边框
6、标准盒模型和怪异盒模型
Content-box 标准盒模型 width不包括padding和border
Border-box 怪异盒模型width包括padding和border
7、清除浮动的方式
高度塌陷:当所有的子元素浮动的时候,且父元素没有设置高度,这时候父元素就会产生高度塌陷。
清除浮动方式1:给父元素单独定义高度 优点:快速简单,代码少 缺点:无法进行响应式布局
清除浮动方式2:父级定义overflow:hidden;zoom:1(针对ie6的兼容) 优点:简单快速、代码少,兼容性较高 缺点:超出部分被隐藏,布局时要注意
清除浮动方式3:父级定义overflow:auto 优点:简单,代码少,兼容性好
清除浮动方式4:父级定义overflow:hidden;zoom:1(针对ie6的兼容) 优点:简单快速、代码少,兼容性较高 缺点:超出部分被隐藏,布局时要注意
清除浮动方式5:万能清除法:
给塌陷的元素添加伪对象
.father:after{
Content:“随便写”;
Clear:both;
display:block;
Height:0;
Overflow:hidden;
Visibility:hidden
} 优点:写法固定,兼容性高 缺点:代码多
8、定位属性
Position有四个属性值:relative absolute fixed static
Relative相对定位 不脱离文档流,相对于自身定位
Absolute 绝对定位,脱离文档流 相对于父级定位
Fixed 固定定位,脱离文档流,相对于浏览器窗口定位
Static 默认值,元素出现在正常的流中
9,水平垂直居中
- 子元素相对于父元素绝对定位,子元素top,left设置50%,子元素margin-top和margin-left减去各自宽高的一半
- 子元素相对于父元素绝对定位,子元素上下左右全为0,然后设置子元素margin:auto
- 子元素相对于父元素绝对定位,子元素top,left值为50%,transform:translate(-50%,-50%)
- 子元素相对定位, 子元素top,left值为50%,transform:translate(-50%,-50%)
- 父元素设置display:table-cell vertical-align:middle,子元素设置margin:auto
- 父元素设置弹性盒子:display:flex; justfy-content:center ;align-item:center; justfy-content:cent
10、竖直垂直居中
1).设置子元素和父元素的行高一样
2).子元素设置为行内块,再加vertical-align:middle
3).已知父元素高度,子元素相对定位,通过transform:translateY(-50%)
4).不知道父元素高度,子绝父相,子元素top:50%,transform:translateY(-50%)
5).创建一个隐藏节点,让隐藏节点的height为剩余高度的一半
6).给父元素display:table,子元素display:table-cell,vertical-align:middle
7).给父元素添加伪元素
8).弹性盒,父元素display:flex,子元素align-self:center
11、Px,rem,em、rpx的区别
em 相对长度单位,相对于当前对象内文本的字体尺寸,em的值并不是固定的,em会继承父级元素的字体大小(参考物是父元素的font-size),em中所有的字体都是相对于父元素的大小决定的
rem 相对于html根元素的font-size
1em=1rem=16px 在body中加入font-size:62.5% 这样直接就是原来的px数值除以10加上em就可以
rpx其实是微信对于rem的一种应用的规定,或者说一种设计的方案,官方上规定屏幕宽度为20rem,规定屏幕宽为750rpx。所以在微信小程序中1rem=750/20rpx。
javascript
1.Js基本数据类型有哪些
字符串String 数值Number 布尔boolean null undefined Symbol (new in ES6) 保存在栈内存中
2、js的引用类型
统称为 Object 类型。细分的话,有:Object 类型、Array 类型、Date 类型、RegExp 类型、Function 类型 等 指针在栈内存中,内容保存在堆内存中
3、javascript中在判断条件中为false的几种值:
Number类型:0 NaN
String类型:""
Boolean类型:false
Undefined类型:undefined
Null类型:null
4、js中的隐式转换
-
转换为boolean类型:数据在 【逻辑判断】 和 【逻辑运算】 之中会隐式转换为boolean类型
注意:
1、注意字面量形式、包装器方式,new 方式的区别,new操作符创建的隐式转换为Boolean都是true
2、连续使用两个非操作符(!!)可以将一个数强制转换为boolean类型,这在开发之中比较实用。
-
转换为number类型
-
转换为string类型
5、ES6,ES7,ES8,ES9,ES10,ES11,ES12,ES13的新特性?
ES6:
- let和const
- 类(class):把它看成是es5中构造函数的语法糖,它简化了构造函数的写法, 类的共有属性放到 constructor 里面
- 模块化(ES Module):
// 模块 A 导出一个方法export const sub = (a, b) = a + b;
// 模块 B 导入使用import { sub } from ./A;console.log(sub(1, 2)); // 3
// 创建一个A组件,在B组件引用
- 箭头(Arrow)函数
- 函数参数默认值
- 模板字符串
- 解构赋值
- 延展操作符 ...
- 对象属性简写
- Promise:
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。
Promise.resolve().then(() = { console.log(2); });console.log(1); // 先打印 1 ,再打印 2
// ES6 规定,Promise 对象是一个构造函数,用来生成 Promise 实例。// Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 和 reject。
1 const promise = new Promise(function(resolve, reject) { 2 3 4 // ... some code 5 6 7 if (/* 异步操作成功 */){ 8 9 10 resolve(value); 11 12 13 } else { 14 15 16 reject(error); 17 18 19 }});// Promise 实例生成以后,可以用 then 方法分别指定 resolved 状态和 rejected 状态的回调函数。promise.then(function(value) { 20 21 22 // success}, function(error) { 23 24 25 // failure});
Promise对象有以下两个特点:
(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。
Promise也有一些缺点:
首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
ES7:
- Array.prototype.includes()
- [1].includes(1); // true
- 指数操作符 **
- 2**10; // 1024
ES8:
1.async/await: 异步终极解决方案
1 async getData(){ 2 3 const res = await api.getTableData(); // await 异步任务 4 5 // do something }
2.Object.values()
Object.values({a: 1, b: 2, c: 3}); // [1, 2, 3]
3.Object.entries()
Object.entries({a: 1, b: 2, c: 3}); // [[a, 1], [b, 2], [c, 3]]
4.字符串填充:
String padding:String.prototype.padStart、String.prototype.padEnd
1 // padStart'hello'.padStart(10); // ' hello' 2 3 // padEnd'hello'.padEnd(10) // 'hello ' 4 5 'hello'.padEnd(10,'222') // 'hello22222'
5.函数参数列表结尾允许逗号
6.Object.getOwnPropertyDescriptors(): 获取一个对象的所有自身属性的描述符,如果没有任何自身属性,则返回空对象
7.SharedArrayBuffer 对象:用来表示一个通用的,固定长度的原始二进制数据缓冲区
8.原子对象:Atomics 对象:提供了一组静态方法用来对 SharedArrayBuffer 对象进行原子操作
ES9:
1、异步迭代:await可以和for...of循环一起使用,以串行的方式运行异步操作
2、Promise.finally():逻辑只可以放在一个地方,这有点像以前jQuery ajax的complete
1 Promise.resolve().then().catch(e = e).finally();
3、Rest/Spread 属性:允许我们将一个剩余参数表示为一个数组
4、正则表达式命名捕获组:允许命名捕获组使用符号?<name>
5、正则表达式反向断言(lookbehind)
6、正则表达式dotAll模式:正则表达式中点.匹配除回车外的任何单字符,标记s改变这种行为,允许行终止符的出现
7、正则表达式 Unicode 转义: Unicode 属性转义形式为\p{...}和\P{...}
ES10:
1、Array.flat()和Array.flatMap():数组展平
平坦的():把数组里的数组打开
[1, 2, [3, 4]].flat(Infinity); // [1, 2, 3, 4]
平面图()
[1, 2, 3, 4].flatMap(a = [a**2]); // [1, 4, 9, 16]
2、String.trimStart()和String.trimEnd():去掉开头结尾空格文本
3、String.prototype.matchAll:为所有匹配的匹配对象返回一个迭代器
const raw_arr = test1 test2 test3.matchAll((/t(e)(st(\d))/g));const arr = [...raw_arr];
4、Symbol.prototype.description:只读属性,回 Symbol 对象的可选描述的字符串
5、Object.fromEntries():返回一个给定对象自身可枚举属性的键值对数组
6、可选 Catch
7、JSON Superset 超集
8、JSON.stringify() 加强格式转化
9、Array.prototype.sort() 更加稳定
10、Function.prototype.toString() 重新修订
ES11:
1、动态 import ():按需导入
2、空值合并运算符:表达式在 ?? 的左侧 运算符求值为undefined或null,返回其右侧
3、可选链接:?.用户检测不确定的中间节点
4、BigInt:新基本数据类型,表示任意精度的整数
5、globalThis:浏览器:window、worker:self、node:global
5、Promise.allSettled:返回一个在所有给定的promise已被决议或被拒绝后决议的promise,并带有一个对象数组,每个对象表示对应的promise结果
6、for-in 结构:用于规范for-in语句的遍历顺序
ES12:
1、String.prototype.replaceAll :有了这个 API,替换字符不用写正则了
2、Promise.any() :返回第一个 fullfilled 的 promise ,若全部 reject,则返回一个带有失败原因的 AggregateError。
3、新增逻辑赋值操作符: ??=、&&=、 ||=
4、WeakRefs:使用弱引用对象,该弱引用不会阻止 GC,并且可以在 GC 前使用 WeakRef.prototype.deref ( ) 解除该引用。
5、下划线 (_) 分隔符:使用 _ 分隔数字字面量以方便阅读
6、Intl.ListFormat :用来处理和多语言相关的对象格式化操作
7、Intl.DateTimeFormat API 中的 dateStyle 和 timeStyle 的配置项:用来处理多语言下的时间日期格式化的函数
ES13:
1、声明类的字段:类字段可以在类的顶层被定义和初始化
2、私有方法&字段:用#前缀来定义类的私有方法和字段
3、类的静态公共方法和字段:增加了静态公共字段、静态私有方法和静态私有字段的特性
4、ECMScript 类静态初始化块:在类声明/定义期间评估静态初始化代码块,可以访问类的私有字段
5、检测私有字段:可以使用in操作符,如果指定的属性/字段在指定的对象/类中,则返回真,并且也能判断私有字段
6、正则匹配索引:该提案提供了一个新的/dflag,以获得关于输入字符串中每个匹配的开始和索引位置结束的额外信息
7、在所有内置的可索引数据上新增.at()方法
8、Object.hasOwn(object, property):使用 Object.hasOwn 替代 Object.prototype.hasOwnProperty.call
9、Error Cause:为了便捷的传递导致错误的原因
6、数组(对象)的最新用法(最新)
(⼀)使⽤ Array 构造函数:
var arr1 = new Array(); //创建⼀个空数组
var arr2 = new Array(20); // 创建⼀个包含20项的数组
var arr3 = new Array(“lily”,“lucy”,“Tom”); // 创建⼀个包含3个字符串的数组
(二)join() 方法也可将所有数组元素结合为一个字符串。
(三)concat() :将参数添加到原数组中。这个⽅法会先创建当前数组⼀个副本,然后将接收到的参数添加到这个副本的末尾,最后返回新构建的数组。在 没有给 concat()⽅法传递参数的情况下,它只是复制当前数组并返回副本。
var arr = [1,3,5,7];
var arrCopy = arr.concat(9,[11,13]);
console.log(arrCopy); //[1, 3, 5, 7, 9, 11, 13]
(四)forEach():对数组进⾏遍历循环,对数组中的每⼀项运⾏给定函数。这个⽅法没有返回值。参数都是function类型,默认有传参,参数分别为:遍历的数组内容;第对应的数组索引,数组本⾝。
(五)map():指“映射”,对数组中的每⼀项运⾏给定函数,返回每次函数调⽤的结果组成的数组。
(六)filter():“过滤”功能,数组中的每⼀项运⾏给定函数,返回满⾜过滤条件组成的数组。
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var arr2 = arr.filter(function(x, index) {
return index % 3 === 0 || x >= 8;
});
console.log(arr2); //[1, 4, 7, 8, 9, 10]
(七)every():判断数组中每⼀项都是否满⾜条件,只有所有项都满⾜条件,才会返回true。
(八)some():判断数组中是否存在满⾜条件的项,只要有⼀项满⾜条件,就会返回true。
(九)reduce()和 reduceRight() 数组扁平化的方法
这两个⽅法都会实现迭代数组的所有项,然后构建⼀个最终返回的值。reduce()⽅法从数组的第⼀项开始,逐个遍历到最后。⽽reduceRight()则从数组的最后⼀项开始,向前遍历到第⼀项。
这两个⽅法都接收两个参数:⼀个在每⼀项上调⽤的函数和(可选的)作为归并基础的初始值。
传给 reduce()和 reduceRight()的函数接收 4 个参数:前⼀个值、当前值、项的索引和数组对象。这个函数返回的任何值都会作为第⼀个参数⾃动传给下⼀项。第⼀次迭代发⽣在数组的第⼆项上,因此第⼀个参数是数组的第⼀项,第⼆个参数就是数组的第⼆项。
1 var values = [1,2,3,4,5]; 2 var sum = values.reduceRight(function(prev, cur, index, array){ 3 return prev + cur; 4 },10); 5 console.log(sum); //25
7、数组扁平化方法:
方法一:递归
思路:对数组进行遍历,然后判断每一项是否是数组,如果该项不是数组直接放入新数组,如果是数组就再次调用该函数,当数组遍历完成,返回新数组
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | let arrFlat = [1, 2, [ 'sun' , [ 18, 'happy' ], 'haha' ], 21 ] let flatArr = [] function flatArrFnc(arr) { for ( let i = 0; i<arr.length; i++) { // 判断某一项是否是数组 Array.isArray(arr[i]) ? flatArrFnc(arr[i]) :flatArr.push(arr[i]) } return flatArr } flatArrFnc(arrFlat) console.log(flatArr); // [1, 2, 'sun', 18, 'happy', 'haha', 21] |
方法二: 利用数组的reduce方法 reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。
1 let arrFlat = [1, 2, ['sun', [ 2 18, 'happy' 3 ], 'haha'], 21 4 ] 5 function flatArrFnc(arr) { 6 return arr.reduce((total, item) => { 7 return Array.isArray(item) ? total.concat(flatArrFnc(item)) : total.concat(item) 8 }, []) 9 } 10 console.log(flatArrFnc(arrFlat)); 11 // [1, 2, 'sun', 18, 'happy', 'haha', 21]
方法三:利用es6的flat方法
flat(n)将每一项的数组偏平化,n默认是1,表示扁平化深度,Infinity无限次
1 let arrFlat = [1, 2, ['sun', [ 2 18, 'happy' 3 ], 'haha'], 21 4 ] 5 console.log(arrFlat.flat(Infinity));
6 // [1, 2, 'sun', 18, 'happy', 'haha', 21]
方法四:利用扩展运算符
1 let arrFlat = [1, 2, ['sun', [ 2 18, 'happy' 3 ], 'haha'], 21 4 ] 5 function flatArrFnc(arr) { 6 while(arr.some(item => Array.isArray(item))) { 7 arr = [].concat(...arr) 8 } 9 return arr 10 } 11 console.log(flatArrFnc(arrFlat)); 12 // [1, 2, 'sun', 18, 'happy', 'haha', 21]
8,js对象方法(最详细)
一、Object.assign(目标对象,源对象) 浅复制、合并、拷贝、
继承属性和不可枚举的属性是不能被拷贝的,将所有源对象的所有属性复制到目标对象上,并且返回目标对象,不能复制不可枚举属性
var o=Object.assign({},obj,obj1);
var o ={...obj,...obj1}//复制,重新变成1个新对象
二、Object.keys(o) 获取o对象的所有key组成的数组
三、Object.values(o) 获取o对象的所有value组成的数组
四、Object.entries(o)
返回一个给定对象自身可枚举属性的键值对数组
五、Object.is() 判断两个值是否相同。
六、Object.fromEntries 把array转换为object
Object.entries的反向操作,把array转换为object 。
该方法可以把键值对列表转化为一个对象,入参可以是一个Map、Set、Array类型,或者其他实现了可迭代协议的可迭代对象。
七、delete 删除对象的属性
八、 Object.defineProperty(对象,属性名,属性描述对象); 定义属性,也可以定义Symbol
九、 Object.defineProperties()定义属性,可定义多个,默认为false,可定义Symbol
十、Object.getOwnPropertyNames(obj) 获取对象的所有属性名,返回属性名组成的数组,Symbol不能获取
十一、Object.getOwnPropertySymbols(obj) 获取对象的所有Symbol属性名,返回属性名组成的数组
十二、Object.getOwnPropertyDescriptor(obj,"d"); 获取属性的描述对象
十三、Reflect.ownKeys(obj); 获取到对象所有属性和方法名,包括Symbol属性
十四、Reflect.defineProperty 定义属性,默认为false,可定义Symbol,与Object.defineProperty相同
十五、 Reflect.deleteProperty(obj,"c"); 删除
十六、 Object.freeze(obj);冻结 不能修改属性,不能删除属性,不能添加属性
十七、 Object.isFrozen(obj) 判断对象是否被冻结
十八、 Object.seal(obj); 可以修改,不能删除,不能添加属性
十九、 Object.isSealed(obj) 判断是否使用seal
二十、 Object.preventExtensions(obj); 可以修改,可以删除,但是不能添加新属性,不能扩展
二十一、Object.isExtensible(obj) 判断是否可以扩展
二十二、Object.getOwnPropertyDescriptors(obj) 获取对象中所有属性的描述对象列表对象
二十三、Object.getPrototypeOf(obj) 获取obj对象的原型链
二十四、Object.setPrototypeOf(o1,o) 设置对象的原型链
将o1的原型链设置为o
二十五、 判断对象的对象属性是否包含这个属性名
console.log(o1.hasOwnProperty("b"))
console.log(Object.hasOwn(o1,"b"))
9、ajax请求的五个步骤
1.创建一个XMLHttpRequest异步对象
2.设置请求方式和请求地址
3.接着,用send发送请求
4.监听状态变化
5.最后,接收返回的数据
10、闭包
闭包就是能够读取其他函数内部变量的函数,说白了闭包就是个函数,只不过是处于其他函数内部而已。所以说,闭包可以简单理解成“定义在一个函数内部的函数“。
特点:
1.访问函数内部的变量
2.参数和变量不会被垃圾回收机制回收。
缺点:
闭包在处理速度和内存消耗方面对脚本性能具有负面影响。
容易造成内存泄露
应用场景:
在闭包作用下,定义事件函数的时候,每次循环的i值都封闭起来,这样在函数执行时,会查找定义时的作用域链,这个作用域链里的i值是在每次循环中都被保留的,因此点击不同的li会alert出不同编号。
1.setTimeout
原生的setTimeout传递的第一个函数不能带参数,通过闭包可以实现传参效果。
1 function f1(a) {
2 function f2() {
3 console.log(a);
4 }
5 return f2;
6 }
7 var fun = f1(1);
8 setTimeout(fun,1000);//一秒之后打印出1
2.回调
定义行为,然后把它关联到某个用户事件上(点击或者按键)。代码通常会作为一个回调(事件触发时调用的函数)绑定到事件。
比如下面这段代码:
1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <title>测试</title>
6 </head>
7 <body>
8 <a href="#" id="size-12">12</a>
9 <a href="#" id="size-20">20</a>
10 <a href="#" id="size-30">30</a>
11
12 <script type="text/javascript">
13 function changeSize(size){
14 return function(){
15 document.body.style.fontSize = size + 'px';
16 };
17 }
18
19 var size12 = changeSize(12);
20 var size14 = changeSize(20);
21 var size16 = changeSize(30);
22
23 document.getElementById('size-12').onclick = size12;
24 document.getElementById('size-20').onclick = size14;
25 document.getElementById('size-30').onclick = size16;
26
27 </script>
28 </body>
29 </html>
当点击数字时,字体也会变成相应的大小。
3.函数防抖
在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
实现的关键就在于setTimeOut
这个函数,由于还需要一个变量来保存计时,考虑维护全局纯净,可以借助闭包来实现。
如下代码所示:
1 /*
2 * fn [function] 需要防抖的函数
3 * delay [number] 毫秒,防抖期限值
4 */
5 function debounce(fn,delay){
6 let timer = null
7 //借助闭包
8 return function() {
9 if(timer){
10 clearTimeout(timer) //进入该分支语句,说明当前正在一个计时过程中,并且又触发了相同事件。所以要取消当前的计时,重新开始计时
11 timer = setTimeOut(fn,delay)
12 }else{
13 timer = setTimeOut(fn,delay) // 进入该分支说明当前并没有在计时,那么就开始一个计时
14 }
15 }
16 }
4.封装私有变量
如下面代码:用js创建一个计数器
1 function f1() {
2 var sum = 0;
3 var obj = {
4 inc:function () {
5 sum++;
6 return sum;
7 }
8 };
9 return obj;
10 }
11 let result = f1();
12 console.log(result.inc());//1
13 console.log(result.inc());//2
14 console.log(result.inc());//3
在返回的对象中,实现了一个闭包,该闭包携带了局部变量x,并且,从外部代码根本无法访问到变量x。
解决闭包的内存泄漏
1、在退出函数之前,将不使用的局部变量全部删除。可以使变量赋值为null;(示例如下)
2、避免变量的循环赋值和引用。 (示例如上)
3、利用Jquery释放自身指定的所有事件处理程序。
11、事件委托(事件冒泡机制)
称事件代理,是js中很常用的绑定事件的技巧,事件委托就是把原本需要绑定在子元素的响应事件委托给父元素,让父元素担当事件监听的职务,事件委托的原理是DOM元素的事件冒泡
优点:1.可以大量节省内存占用,减少事件注册。比如ul上代理所有li的click事件就很不错。
2.可以实现当新增子对象时,无需再对其进行事件绑定,对于动态内容部分尤为合适
缺点:事件代理的常用应用应该仅限于上述需求,如果把所有事件都用事件代理,可能会出现事件误判。即本不该被触发的事件被绑定上了事件。
13、cookie、session、token区别
1.token是 服务经过计算发给客户端的,服务不保存,每次客户端来请求,经过解密等计算来验证是否是自己下发的
2.session是服务器保存,发给客户端,客户端每次访问都带着,直接和服务的session比对
3.cookie是保存在客户端(浏览器)上的一些基本信息,服务不保存,每次请求时客户端带上cookie,里面有一些账户密码,浏览记录什么的
session的定义
和token一样,也是有效期
用一个session的变量,把用户数据存放在服务器端,保存到服务端的用户数据,就称为session
用户数据会进行加密,加密的过程session框架会进行完成,加密后的数据会自动放在响应头里cookie
cookie定义
cookie存放在浏览器端
cookie是和域名,ip绑定在一起,首次登陆后浏览器存放cookie,下次登陆会带上cookie值而不需要重新登陆
session 和 token区别
1. token是开发定义的格式,session是基于框架内的格式
2. token值不需要占用内存,session值是需要存在服务端(不进行存储无法校验)
3. token是可以跨平台(比如在电脑端取到token值拿到手机登陆是可以使用)
4.session不可以跨平台,因为session生成的cookie是和域名 ip绑定在一起,换个平台就失效了
session和cookie区别
- 同一个用户的信息存在服务端的称为:session
- 存储在客户端的称为:cookie
- session和cookie也是同时搭配使用的
14、cookie localStorage sessionStorage的区别与联系
1、划分域名。各域名下的存储空间由各业务组统一规划使用
2、跨页面传数据:考虑单页应用、优先采用 url 传数据
3、最后的兜底方案:清掉别人的存储
4、这里使用http-server来创建两个不同源的本地服务,使用postMessage跨域读写数据
14.什么是面向对象请简述
面向对象是一种思想,是基于面向过程而言的,就是说面向对象是将功能等通过对象来实现,将功能封装进对象之中,让对象去实现具体的细节;这种思想是将数据作为第一位,这是对数据一种优化,操作起来更加的方便,简化了过程。
Js本身是没有class类型的,但是每个函数都有一个prototype属性,prototype指向一个对象,当函数作为构造函数时,prototype就起到类似于class的作用
面向对象有三个特点:封装(隐藏对象的属性和实现细节,对外提供公共访问方式),继承(提高代码复用性,继承是多态的前提),多态(是父类或接口定义的引用变量可以指向子类或具体实现类的实例对象)
15、普通函数,箭头函数,构造函数区别
1、写法不同,箭头函数使用箭头定义,普通函数中没有
2、箭头函数都是匿名函数,普通函数可以有匿名函数,也可以有具体名函数,但是箭头函数都是匿名函数。
3、在普通函数中,this总是指向调用它的对象,如果用作构造函数,this指向创建的对象实例。箭头函数中没有this,声明时捕获其所在上下文的this供自己使用。所以箭头函数结合call(),apply()方法调用一个函数时,只传入一个参数对this没有影响。
5、箭头函数不绑定arguments,取而代之用rest参数…解决
6、箭头函数不可做Generator函数
16、原型、原型链
在JavaScript中,每当定义一个函数数据类型(普通函数、类)时候,都会天生自带一个prototype
属性,这个属性指向函数的原型对象,并且这个属性是一个对象数据类型的值。原型对象就相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象,我们可以将对象中共有的内容,统一设置到原型对象中。
prototype
......)也天生自带一个属性__proto__
,属性值是当前实例所属类的原型(prototype
)。原型对象中有一个属性constructor
, 它指向函数对象。1 function Person() {}
2 var person = new Person()
3 console.log(person.__proto__ === Person.prototype)//true
4 console.log(Person.prototype.constructor===Person)//true
5 //顺便学习一个ES5的方法,可以获得对象的原型
6 console.log(Object.getPrototypeOf(person) === Person.prototype) // true
在JavaScript中万物都是对象,对象和对象之间也有关系,并不是孤立存在的。对象之间的继承关系,在JavaScript中是通过prototype对象指向父类对象,直到指向Object对象为止,这样就形成了一个原型指向的链条,专业术语称之为原型链。
当我们访问对象的一个属性或方法时,它会先在对象自身中寻找,如果有则直接使用,如果没有则会去原型对象中寻找,如果找到则直接使用。如果没有则去原型的原型中寻找,直到找到Object对象的原型,Object对象的原型没有原型,如果在Object原型中依然没有找到,则返回undefined。
我们可以使用对象的hasOwnProperty()
来检查对象自身中是否含有该属性;使用in
检查对象中是否含有某个属性时,如果对象中没有但是原型中有,也会返回true
person实例中没有a这个属性,从 person 对象中找不到 a 属性就会从 person 的原型也就是 person.__proto__
,也就是 Person.prototype中查找,很幸运地得到a的值为123。那假如 person.__proto__
中也没有该属性,又该如何查找?
当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层Object为止。Object是JS中所有对象数据类型的基类(最顶层的类)在Object.prototype上没有__proto__
这个属性。
Object.getPrototypeOf:实际上其实是获取当前对象的__proto__
比如:
1 let a = {};
2 console.log(a.__proto__ == Object.prototype) //true
3 console.log(Object.getPrototypeOf(a) == a.__proto__); //true
4 console.log(Object.getPrototypeOf(a) == Object.prototype); //true
由于a.__proto__ == Object.prototype,所以Object.getPrototypeOf(a)是等于 Object.prototype
三、isPrototypeOf
isPrototypeOf: B.isPrototypeOf(b) 实际上是判断B是不是在a对象的原型链上,如果是,返回true,否则,返回false
这个方法结合实例会方便理解:
1 let B = function() {};
2 let b = new B();
3 console.log(B.prototype.isPrototypeOf(b)); //true
4 console.log(Object.prototype.isPrototypeOf(b)); //true
以b对象为开始,进行它的原型链分析:
b.__proto__ == B.prototype
B.prototype.__proto__ == Object.prototype
Object.prototype.__protot == null
所以:B.prototype.isPrototypeOf(b)、Object.prototype.isPrototypeOf(b)为true
之前看到一篇文章中有一个实例:Object.prototype.isPrototypeOf(B) 返回的为true,他理解的是由于B.__proto__指向Object.prototype,这个说话是错误的,正确原因应该是:
现在我们来构建B的原型链:
B.__proto__ == Function.prototype
Function.prototype.__proto__ == Object.prototype
Object.prototype.__proto__ == null
所以:Object.prototype.isPrototypeOf(B) 为true
17、ES6语法比如 promise、class 等等
promise详见https://www.cnblogs.com/Blod/p/15801202.html
class详见https://www.cnblogs.com/Blod/p/15801399.html
18、浅拷贝和深拷贝的问题
深拷贝和浅拷贝是只针对Object和Array这样的复杂类型的
也就是说a和b指向了同一块内存,所以修改其中任意的值,另一个值都会随之变化,这就是浅拷贝
浅拷贝, ”Object.assign() 方法用于将所有可枚举的属性的值从一个或多个源对象复制到目标对象。它将返回目标对象,注意:当object只有一层的时候,是深拷贝
深拷贝,JSON.parse()和JSON.stringify()给了我们一个基本的解决办法。但是函数不能被正确处理;手写递归方法;函数库lodash
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
19、call、apply、bind三者的用法和区别
call、apply与bind都用于改变 this 绑定
call、apply 在改变 this 指向的同时还会执行函数,一次性的。不同的是 call方法传递函数调用形参是以散列形式,而 apply 方法的形参是一个数组。在传参的情况下,call的性能要高于 apply,因为 apply 在执行时还要多一步解析数组。
bind 在改变 this 后是返回一个全新的绑定函数,即返回一个新的函数,不直接执行函数。并且此后 this 的指向无法在通过 call、apply、bind 改变。
20、js中的作用域和作用域链
作用域:是在运行时代码中的某些特定部分中变量,函数和对象的可访问性。换句话说,作用域决定了代码区块中变量和其他资源的可见性。作用域就是一个独立的地盘,让变量不会外泄、暴露出去。也就是说作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。
ES6 之前 JavaScript 没有块级作用域,只有全局作用域和函数作用域。ES6 的到来,为我们提供了‘块级作用域’,可通过新增命令 let 和 const 来体现。
全局作用域:
- 最外层函数 和在最外层函数外面定义的变量拥有全局作用域
- 所有末定义直接赋值的变量自动声明为拥有全局作用域
- 所有 window 对象的属性拥有全局作用域
全局作用域有个弊端:如果我们写了很多行 JS 代码,变量定义都没有用函数包括,那么它们就全部都在全局作用域中。这样就会 污染全局命名空间, 容易引起命名冲突。
函数作用域,是指声明在函数内部的变量,和全局作用域相反,局部作用域一般只在固定的代码片段内可访问到
作用域是分层的,内层作用域可以访问外层作用域的变量,反之则不行。
块级作用域可通过新增命令 let 和 const 声明,所声明的变量在指定块的作用域外无法被访问。块级作用域在如下情况被创建:
1、在一个函数内部
2、在一个代码块(由一对花括号包裹)内部
块级作用域有以下几个特点:
1、声明变量不会提升到代码块顶部
2、禁止重复声明
3、循环中的绑定块作用域的妙用;:for 循环的块级作用域,可以把声明的计数器变量限制在循环内
作用域链:当在 Javascript 中使⽤⼀个变量的时候,⾸先 Javascript 引擎会尝试在当前作⽤域下去寻找该变 量,如果没找到,再到它的上层作⽤域寻找,以此类推直到找到该变量或是已经到了全局作⽤域,如果在全局作⽤域⾥仍然找不到该变量,它就会在全局范围内隐式声明该变量(⾮严格模式下)或是直接 报错
Vue:
1、vue双向绑定原理
Vue 是一套构建用户界面的渐进式自底向上增量开发的 MVVM 框架,vue 的核心只关注视图层;
vue的数据双向绑定主要通过Object.defineProperty()方法来进行数据劫持以及发布者-订阅模式来实现的,vue实例化的时候会去遍历所有的属性,给这些属性添加get和set方法进行数据劫持;
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>forvue</title> 6 </head> 7 <body> 8 <input type="text" id="textInput"> 9 输入:<span id="textSpan"></span> 10 <script> 11 var obj = {}, 12 textInput = document.querySelector('#textInput'), 13 textSpan = document.querySelector('#textSpan'); 14 15 Object.defineProperty(obj, 'foo', { 16 set: function (newValue) { 17 textInput.value = newValue; 18 textSpan.innerHTML = newValue; 19 } 20 }); 21 22 textInput.addEventListener('keyup', function (e) { 23 obj.foo = e.target.value; 24 }); 25 26 </script> 27 </body> 28 </html>
使用Object.defineProperty()来定义属性的set函数,属性被赋值的时候,修改Input的value值以及span中的innerHTML;然后监听input的keyup事件,修改对象的属性值,即可实现这样的一个简单的数据双向绑定。
2、虚拟dom和真实dom
avaScript引擎不能直接操作真实DOM树。为了给JavaScript提供操作DOM树的能力,浏览器在全局对象window上为JavaScript封装了一个document对象,然后在该对象上提供了大量的DOM操作接口。操作真实DOM的代价往往是比较大的(这其中还涉及C++与JavaScript数据结构的转换问题)。再加上修改DOM经常导致页面重绘,所以一般来说,DOM操作越多,网页的性能就越差。当然了,虚拟DOM并不是解决DOM操作性能问题的唯一解决方案,Vue的响应式系统也是一种重要的解决方案。从某种程度上来说,Vue依靠响应式系统可以实现“精确定点更新”,即直接定位到哪个DOM节点需要更新,而不需要经过虚拟DOM的比对,不过“精确定点更新”的内存代价偏大,因此目前Vue采用了响应式系统和虚拟DOM结合的方式
虚拟DOM设计的核心就是用高效的js操作,来减少低性能的DOM操作,以此来提升网页性能。
3、 vue 的响应式原理
在生成vue实例时,为对传入的data进行遍历,使用Object.defineProperty
把这些属性转为getter/setter
.
Object.defineProperty
是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。
每个vue实例都有一个watcher实例,它会在实例渲染时记录这些属性,并在setter触发时重新渲染 由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。尽管如此我们还是有一些办法来回避这些限制并保证它们的响应性。
#对于对象
Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data
对象上存在才能让 Vue 将它转换为响应式的。例如:
1 var vm = new Vue({ 2 data:{ 3 a:1 4 } 5 }) 6 7 // `vm.a` 是响应式的 8 9 vm.b = 2 10 // `vm.b` 是非响应式的
对于已经创建的实例,Vue 不允许动态添加根级别的响应式 property。但是,可以使用 Vue.set(object, propertyName, value)
方法向嵌套对象添加响应式 property。例如,对于:
Vue.set(vm.someObject, 'b', 2)
您还可以使用 vm.$set
实例方法,这也是全局 Vue.set
方法的别名:
this.$set(this.someObject,'b',2)
有时你可能需要为已有对象赋值多个新 property,比如使用 Object.assign()
或 _.extend()
。但是,这样添加到对象上的新 property 不会触发更新。在这种情况下,你应该用原对象与要混合进去的对象的 property 一起创建一个新的对象。
1 // 代替 `Object.assign(this.someObject, { a: 1, b: 2 })` 2 this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
#对于数组
Vue 不能检测以下数组的变动:
- 当你利用索引直接设置一个数组项时,例如:
vm.items[indexOfItem] = newValue
- 当你修改数组的长度时,例如:
vm.items.length = newLength
- 当你利用索引直接设置一个数组项时,例如:
举个例子:
1 var vm = new Vue({ 2 data: { 3 items: ['a', 'b', 'c'] 4 } 5 }) 6 vm.items[1] = 'x' // 不是响应性的 7 vm.items.length = 2 // 不是响应性的
为了解决第一类问题,以下两种方式都可以实现和 vm.items[indexOfItem] = newValue
相同的效果,同时也将在响应式系统内触发状态更新
// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
你也可以使用 vm.$set
实例方法,该方法是全局方法 Vue.set
的一个别名: m.$set(vm.items, indexOfItem, newValue)
为了解决第二类问题,你可以使用 splice
: vm.items.splice(newLength)
2.声明响应式属性:由于 Vue 不允许动态添加根级响应式属性,所以你必须在初始化实例前声明所有根级响应式属性,哪怕只是一个空值。
如果你未在 data 选项中声明 message,Vue 将警告你渲染函数正在试图访问不存在的属性。
1 var vm = new Vue({ 2 data: { 3 // 声明 message 为一个空值字符串 4 message: '' 5 }, 6 template: '<div>{{ message }}</div>' 7 }) 8 // 之后设置 `message` 9 vm.message = 'Hello!'
3.异步更新队列
vue更新dom时是异步执行的
数据变化、更新是在主线程中同步执行的;在侦听到数据变化时,watcher将数据变更存储到异步队列中,当本次数据变化,即主线成任务执行完毕,异步队列中的任务才会被执行(已去重)。
如果你在js中更新数据后立即去操作DOM,这时候DOM还未更新;vue提供了nextTick接口来处理这样的情况,它的参数是一个回调函数,会在本次DOM更新完成后被调用。
使用方法:
1.在组件内使用 vm.$nextTick() 实例方法特别方便,因为它不需要全局 Vue,并且回调函数中的 this 将自动绑定到当前的 Vue 实例上:
1 Vue.component('example', { 2 template: '<span>{{ message }}</span>', 3 data: function () { 4 return { 5 message: '未更新' 6 } 7 }, 8 methods: { 9 updateMessage: function () { 10 this.message = '已更新' 11 console.log(this.$el.textContent) // => '未更新' 12 this.$nextTick(function () { 13 console.log(this.$el.textContent) // => '已更新' 14 }) 15 } 16 } 17 })
2.因为 $nextTick()
返回一个 Promise
对象,所以你可以使用新的 ES6 async/await 语法完成相同的事情:
1 methods: { 2 updateMessage: async function () { 3 this.message = '已更新' 4 console.log(this.$el.textContent) // => '未更新' 5 await this.$nextTick() 6 console.log(this.$el.textContent) // => '已更新' 7 } 8 }
4、vue原理
Vue是一套构建用户界面的渐进式的自底向上增量开发的MVVM框架,核心是关注视图层,vue的核心是为了解决数据的绑定问题,为了开发大型单页面应用和组件化,所以vue的核心思想是数据驱动和组件化,这也说一下MVVM思想,MVVM思想是模型视图vm是v和m连接的桥梁,当模型层数据修改时,VM层会检测到,并通知视图层进行相应修改
5、vue-router
(1)vue-router路由钩子是什么,执行顺序是什么
钩子函数种类有:全局守卫、路由守卫、组件守卫
全局前置/钩子:beforeEach、beforeResolve、afterEach
路由独享的守卫:beforeEnter
组件内的守卫:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave
完整的导航解析流程:
导航被触发。
在失活的组件里调用 beforeRouteLeave 守卫。
调用全局的 beforeEach 守卫。
在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
在路由配置里调用 beforeEnter。
解析异步路由组件。
在被激活的组件里调用 beforeRouteEnter。
调用全局的 beforeResolve 守卫 (2.5+)。
导航被确认。
调用全局的 afterEach 钩子。
触发 DOM 更新。
调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。
前置钩子:
1 //单独设置每个路由的属性: 2 meta: { may: true } 3 router.beforeEach((to, from, next) => { 4 if (to.matched.some(item => item.meta.may)) { 5 let id = window.localStorage.getItem("id") 6 if (id) { 7 next() 8 } else { 9 next({ name: "login" }) 10 } 11 } else { 12 next() 13 } 14 }) 15 注意:next 方法必须要调用,否则钩子函数无法 resolved
后置钩子:
1 router.afterEach((to,from) => { 2 if(to.meta && to.meta.title){ 3 document.title = to.meta.title 4 }else{ 5 document.title = "666" 6 } 7 })
单独路由独享钩子
1 { 2 path: '/home', 3 name: 'home', 4 component: Home, 5 beforeEnter(to, from, next) { 6 if (window.localStorage.getItem("id")) { 7 next() 8 } else { 9 next({ name: "login" }) 10 } 11 } 12 }
组件内的钩子:
1 beforeRouteEnter(to, from, next) { 2 // do someting 3 // 在渲染该组件的对应路由被 confirm 前调用 4 }, 5 beforeRouteUpdate(to, from, next) { 6 // do someting 7 // 在当前路由改变,但是依然渲染该组件是调用 8 }, 9 beforeRouteLeave(to, from ,next) { 10 // do someting 11 // 导航离开该组件的对应路由时被调用 12 }
全局解析守卫 router.beforeResolve 注册一个全局守卫,和 router.beforeEach 类似 可以在src目录下新建一个permission.js文件
1 import router from './router' 2 import store from './store' 3 import { Message } from 'element-ui' 4 import NProgress from 'nprogress' 5 import 'nprogress/nprogress.css' 6 import { getToken } from '@/utils/auth' 7 8 NProgress.configure({ showSpinner: false }) 9 10 const whiteList = ['/login', '/auth-redirect', '/bind', '/register'] 11 12 router.beforeEach((to, from, next) => { 13 NProgress.start() 14 if (getToken()) { 15 /* has token*/ 16 if (to.path === '/login') { 17 next({ path: '/' }) 18 NProgress.done() 19 } else { 20 if (store.getters.roles.length === 0) { 21 // 判断当前用户是否已拉取完user_info信息 22 store.dispatch('GetInfo').then(res => { 23 // 拉取user_info 24 const roles = res.roles 25 store.dispatch('GenerateRoutes', { roles }).then(accessRoutes => { 26 // 测试 默认静态页面 27 // store.dispatch('permission/generateRoutes', { roles }).then(accessRoutes => { 28 // 根据roles权限生成可访问的路由表 29 router.addRoutes(accessRoutes) // 动态添加可访问路由表 30 next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 31 }) 32 }) 33 .catch(err => { 34 store.dispatch('FedLogOut').then(() => { 35 Message.error(err) 36 next({ path: '/' }) 37 }) 38 }) 39 } else { 40 next() 41 // 没有动态改变权限的需求可直接next() 删除下方权限判断 ↓ 42 // if (hasPermission(store.getters.roles, to.meta.roles)) { 43 // next() 44 // } else { 45 // next({ path: '/401', replace: true, query: { noGoBack: true }}) 46 // } 47 // 可删 ↑ 48 } 49 } 50 } else { 51 // 没有token 52 if (whiteList.indexOf(to.path) !== -1) { 53 // 在免登录白名单,直接进入 54 next() 55 } else { 56 next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页 57 NProgress.done() 58 } 59 } 60 }) 61 62 router.afterEach(() => { 63 NProgress.done() 64 })
(2)怎么定义 vue-router 的动态路由? 怎么获取传过来的值?
可以通过query ,param两种方式,区别:query通过url传参,刷新页面参数还在,params刷新页面参数不在了。
param的类型: 通过$route.params.参数名 获取你所传递的值
- 配置路由格式:/router/:id
- 传递的方式:在path后面跟上对应的值
- 传递后形成的路径:/router/123
1 <!-- 动态路由-params --> 2 3 //在APP.vue中 4 <router-link :to="'/user/'+userId" replace>用户</router-link> 5 6 //在index.js 7 { 8 path: '/user/:userid', 9 component: User, 10 },
跳转方法:
1 // 方法1: 2 <router-link :to="{ name: 'users', params: { uname: wade }}">按钮</router-link> 3 // 方法2: 4 this.$router.push({name:'users',params:{uname:wade}}) 5 // 方法3: 6 this.$router.push('/user/' + wade)
query的类型:通过$route.query 获取你所传递的值
- 配置路由格式:/router,也就是普通配置
- 传递的方式:对象中使用query的key作为传递方式
- 传递后形成的路径:/route?id=123
1 <!--动态路由-query --> 2 //01-直接在router-link 标签上以对象的形式 3 <router-link :to="{path:'/profile',query:{name:'why',age:28,height:188}}">档案</router-link> 4 /* 5 02-或者写成按钮以点击事件形式 6 <button @click='profileClick'>我的</button> 7 */ 8 9 //点击事件 10 profileClick(){ 11 this.$router.push({ 12 path: "/profile", 13 query: { 14 name: "kobi", 15 age: "28", 16 height: 198 17 } 18 }); 19 }
跳转方法:
1 // 方法1: 2 <router-link :to="{ name: 'users', query: { uname: james }}">按钮</router-link> 3 // 方法2: 4 this.$router.push({ name: 'users', query:{ uname:james }}) 5 // 方法3: 6 <router-link :to="{ path: '/user', query: { uname:james }}">按钮</router-link> 7 // 方法4: 8 this.$router.push({ path: '/user', query:{ uname:james }}) 9 // 方法5: 10 this.$router.push('/user?uname=' + jsmes)
(3)route和router的区别:
①. router是VueRouter的一个对象,通过Vue.use(VueRouter)和VueRouter构造函数得到一个router的实例对象,这个对象中是一个全局的对象,包含了 所有的路由包含了许多关键的对象和属性。例如history对象
$router.push({path:’/path’}); 本质是向history栈中添加一个路由,在我们看来是 切换路由,但本质是在添加一个history记录
$router.replace({path:’/path’}); 替换路由,没有历史记录
②. route是一个跳转的路由对象,每一个路由都会有一个route对象,是一个局部的对象,可以获取对应的name,path,params,query等
$route.path
字符串,等于当前路由对象的路径,会被解析为绝对路径,如 “/index/” 。
$route.params
对象,包含路由中的动态片段和全匹配片段的键值对
$route.query
对象,包含路由中查询参数的键值对。例如,对于 /index?id=1 ,会得到 $route.query.id == 1。
③params传参合query传参的区别
params参数在地址栏中不会显示,query会显示
例如:
1 this.router.push({path:'/customList',params:{id:6}}) 2 this.router.push({path:'/customList',query:{id:6}}) 3 //params: http://localhost:8080/#/customList 4 //query:http://localhost:8080/#/customList?id=6
网页刷新后params参数会不存在
$route.router
路由规则所属的路由器(以及其所属的组件)。
$route.matched
数组,包含当前匹配的路径中所包含的所有片段所对应的配置参数对象。
$route.name
当前路径的名字,如果没有使用具名路径,则名字为空。
$route.path, $route.params, $route.name, $route.query这几个属性很容易理解,主要用于接收路由传递的参数
通过改变 URL,在不重新请求页面的情况下,更新页面视图。
更新视图但不重新请求页面,两种方式:
1.Hash --- 利用 URL 中的hash("#");原理:hash通过监听浏览器的onhashchange()事件变化,查找对应的路由规则 hash 能兼容到IE8
2.利用 History interface 在HTML5中新增的方法。原理:利用H5的 history中新增的两个API pushState() 和 replaceState() 和一个事件onpopstate监听URL变化history模式,history 只能兼容到 IE10;但浏览器不会刷新页面
因此改变hash值不会重新加载页面,基本都是使用 hash 来实现前端路由的。
Vue 中,它是通过 mode 这一参数控制路由的实现模式:
1 const router=new VueRouter({ 2 mode:'history', 3 routes:[...] 4 })
创建 VueRouter 的实例对象时,mode 以构造参数的形式传入
6、对 vuex 的理解
它是一个公共状态管理库,它在业务非常复杂的时候才会使用
核心属性为:state,getter,mutation,action,module
-
state:存储数据,存储状态;在根实例中注册了store 后,用
this.$store.state
来访问;对应vue里面的data;存放数据方式为响应式,vue组件从store中读取数据,如数据发生变化,组件也会对应的更新。 -
getters:可以认为是 store 的计算属性,相当于 vue中的 computed,依赖于 state里面的值。它的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
-
mutations:用于修改状态,store里面的数仅能通过
mutations
里面的方法改变,但是必须是同步的。更改 vuex 的 store 中的状态的唯一方法是提交 mutation,也就是$store.commit。 -
actions:包含任意异步操作,用它处理完后再触发mutations来改变状态。
-
module:将 store 分割成模块,每个模块都具有state、mutation、action、getter、甚至是嵌套子模块。
当组件进行数据修改的时候我们需要调用Dispatch来触发Actions里面的方法。
actions里面的每个方法中都会有一个commit方法,当方法执行的时候会通过commit来触发mutations里面的方法进行数据的修改。
mutations里面的每个函数都会有一个state参数,这样就可以在mutations里面进行state的数据修改,当数据修改完毕后,会传导给页面。页面的数据也会发生改变。
7、Vue常用的修饰符有哪些
修饰符:.lazy 改变后触发,光标离开input输入框的时候值才会改变
.number 将输出字符串转为number类型
.trim 自动过滤用户输入的首尾空格
事件修饰符:.stop 阻止点击事件冒泡,相当于原生js中的event.stopPropagation()
.prevent 防止执行预设的行为,相当于原生js中event.preventDefault()
.capture 添加事件侦听器时使用事件捕获模式,就是谁有该事件修饰符,就先触发谁
.self 只会触发自己范围内的事件,不包括子元素
.once 只执行一次
键盘修饰符:.enter 回车键 .tab 制表键 .esc返回键 .space 空格键 .up向上键 .down 向下键 .left向左建 .right向右键
系统修饰符:.ctrl .alt .shift .meta
8、computed和watch区别
1、computed支持缓存,只有符合:1.存在依赖型数据 2.依赖型数据发生改变这两个条件,computed才会重新计算。;而watch不支持缓存,数据变,直接会触发相应的操作。
2、computed不支持异步 ,当computed内有异步操作时无效,无法监听数据的变化;而watch支持异步。
3、computed属性值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data中声明过或者父组件传递的props中的数据通过计算得到的值;而watch监听的函数接收两个参数,第一个参数是最新的值,第二个参数是输入之前的值。
4、如果一个属性是由其它属性计算而来的,这个属性依赖其它属性,是一个多对一或者一对一,一般用computed;鴯-个属性发生变化时,需要执行对应的操作; 一对多一般用watch。
5、如果computed属性属性值是函数,那么默认会走get方法;函数的返回值就是属性的属性值;在computed中的,属性都有-个get和一 个set方法,当数据变化时,调用set方法。而watch监听数据必须是data中声明过或者父组件传递过来的props中的数据,当数据变化时,触发其它操作,函数有两个参数。
9、通信方式
1、正向:父组件把要传递的数据绑定在属性上,发送,子组件通过props接收。注:组件中的数据有三种形式:data props computed
2.逆向:子组件向父组件传值(通过事件的形式),子组件通过this.$emit(自定义事件名,要发送的数据),通过$emit触发事件传递数据,父组件中绑定对应的事件,通过$on监听对应的事件 接收子组件传递的数据
3、如果想实现兄弟组件之间进行通信,在项目规模不大的情况下,完全可以使用中央事件总线EventBus
的方式。如果你的项目规模是大中型的,那我们会使用vuex状态管理
10、插槽
分类:默认插槽;具名插槽;作用域插槽
当我们的组件中 我们只需要插入一个 html 标签的时候, 就使用默认插槽就可以了,
如果有多个, 我们就要给第一个 插槽取一个名字, 来决定到底插入哪一个插槽
当我们的插槽中要使用组件中的数据的时候, 就可能会用到作用域插槽
①默认插槽的用法:
使用时:
②具名插槽, 也就是说我们在组件中定一个 多个 slot , 为了分清到底作用到哪一个上面, 给插槽取一个名字来区分:
使用时:
这里说一下, vue 2.6 版本有一个新的插槽的写法, 其中要使用到 template 标签, , 我们知道 template 只是一个包裹标签, 它不会渲染到真实页面上, 新的slot 的写法就是使用到了它, 没有它还不行, 如下图
③作用域插槽:
我们来看一下, 上面的两种 插槽 展示的数据, 都是放在 插槽的使用者 About 组件的 data中的,但是我们有时候, 使用者是不管这些数据的, 数据中从 catetory 组件中自已获取的,使用者 About 只需要来管理 插槽中的内容的展现形式,这时就要使用作用域插槽了
上图中, 可以看到 作用域插槽中 向使用者传递了两个数据,那个使用者是怎么接收使用的呢
11、key的意义
1、key的作用是为了高效更新虚拟DOM,原理是在源码patch.js的patchVnode过程中,会触发updateChildren()方法中可以通过key精准判断节点是否是同一节点,从而避免频繁更新不同元素,使得整个patch过程更加高效,减少DOM操作量,提高性能。
- 另外,若不设置key还可能在列表更新时引发一些隐蔽的bug
- vue中在使用相同标签名元素的过渡切换时,也会使用到key属性,其目的也是为了让vue可以区分它们,否则vue只会替换其内部属性而不会触发过渡效果。
12、vue2和vue3的不同(最详细)
一,响应式区别:
vue2的响应式是基于数据劫持(object.defineProperty),当前这个方法基于主流IE9以上的浏览器都可以使用,它可以监听数据的变化,让vue作出变化;但是它有一个bug,object.defineProperty只能监听初始化的数据,如果程序运行到一半,你给data中的对象或者数组添加新的属性,它就无法监听到,那么数据变视图就不会出现变化;在 2.x 版本里,不管数据多大,都会在一开始就为其创建观察者。当数据很大时,这可能会在页面载入时造成明显的性能压力;2.x 版本中,你使用 Vue.set 来给对象新增一个属性时,这个对象的所有 watcher 都会重新运行;
vue3对bug进行了解决;vue3采用的是ES2015的最新的规范Proxy来进行proxy的惰性监听,它是在你使用数据的时候才进行监听,但是ie不支持,vue为了解决不兼容的问题,单独给IE进行适配:传统浏览器使用Proxy进行监听,但是在ie中还是使用2.0的object.defineProty进行监听;3.x 版本,只会对「被用于渲染初始可见部分的数据」创建观察者,而且 3.x 的观察者更高效。3.x 版本中,只有依赖那个属性的 watcher 才会重新运行。
使用proxy的优点:
①使用proxy不污染源对象,会返回一个新对象,defineProperty是注入型的,会破坏源对象。 ②使用proxy只需要监听整个源对象的属性,不需要循环使用Object.definedProperty监听对象的属性。③ 使用proxy可以获取到对象属性的更多参数,使用definedProperty只能获取到监听属性的新值newValue。
二、底层的变化
3.0底层全部都是使用ts编写的,今后vue3更好的于ts进行结合;
三、属性声明方式
2.0都是使用vue的属性的方式来创建数据、方法和计算属性等内容;
3.0中改变了api的方式进行创建,把变量、方法和计算属性等内容封装为一个个的方法,使用方法的方式进行调用;
四、文件结构
①移除了配置文件目录,config 和 build 文件夹②移除了 static 文件夹,新增 public 文件夹,并且 index.html 移动到 public 中③在 src 文件夹中新增了 views 文件夹,用于分类 视图组件 和 公共组件
④3.0版本中项目环境变量配置文件没有了(dev.env.js / prod.env.js)我们可以通过在项目根目录下手动创建不同环境的配置文件,具体的环境变量名称由package.json中运行参数决定,下面举个例子添加development、production和uat版本的环境变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // .env.delelopment NODE_ENV=development VUE_APP_MODE=development BASE_URL=/develop // .env.production NODE_ENV=production VUE_APP_MODE=production BASE_URL=/api // .env.uat NODE_ENV=production VUE_APP_MODE=uat BASE_URL=/uat // package.json ---- "scripts" : { "serve" : "vue-cli-service serve" , "build:uat" : "vue-cli-service build --mode uat" , // 通过 --mode来运行不同的环境,自动识别到.env.uat配置文件 "build:production" : "vue-cli-service build --mode production" , "lint" : "vue-cli-service lint" }, ---- |
⑤3.0版本中不同环境的webpack配置文件也没有了(webpack.base.conf.js / webpack.dev.conf.js / webpack.prod.conf.js)
同样,我们也可以再根目录中创建vue.config.js文件来进行webpack和vue的一些配置
1 const path = require('path') 2 3 module.exports = { 4 publicPath: './', // 基本路径,打包时加上. 5 outputDir: process.env.outputDir, // 输出文件目录 6 lintOnSave: false, // eslint-loader 是否在保存的时候检查 7 // see https://github.com/vuejs/vue-cli/blob/dev/docs/webpack.md 8 // webpack配置 9 chainWebpack: (config) => { 10 config.resolve.symlinks(true) 11 }, 12 configureWebpack: (config) => { 13 if (process.env.VUE_APP_MODE === 'production') { 14 // 为生产环境修改配置... 15 config.mode = 'production' 16 } else { 17 // 为开发环境修改配置... 18 config.mode = 'development' 19 } 20 Object.assign(config, { 21 // 开发生产共同配置 22 resolve: { 23 alias: { 24 '@': path.resolve(__dirname, './src'), 25 '@c': path.resolve(__dirname, './src/components'), 26 '@p': path.resolve(__dirname, './src/views') 27 } // 别名配置 28 } 29 }) 30 }, 31 productionSourceMap: false, // 生产环境是否生成 sourceMap 文件 32 // css相关配置 33 css: { 34 // extract: true, // 是否使用css分离插件 ExtractTextPlugin 35 sourceMap: false, // 开启 CSS source maps? 36 loaderOptions: { 37 css: {}, // 这里的选项会传递给 css-loader 38 less: { 39 modifyVars: { 40 // less vars,customize ant design theme 41 42 // 'primary-color': '#F5222D', 43 // 'link-color': '#F5222D', 44 // 'border-radius-base': '4px' 45 }, 46 // DO NOT REMOVE THIS LINE 47 javascriptEnabled: true 48 }, 49 postcss: { 50 plugins: [ 51 // 把px单位换算成rem单位 52 require('postcss-pxtorem')({ 53 rootValue: 75, // 换算的基数(设计图750的根字体为32) 54 selectorBlackList: ['.van-'], // 要忽略的选择器并保留为px。 55 propList: ['*'], // 可以从px更改为rem的属性。 56 minPixelValue: 2 // 设置要替换的最小像素值。 57 }), 58 require('autoprefixer') 59 ] 60 // plugins: [ 61 // require('autoprefixer') 62 // ] 63 } // 这里的选项会传递给 postcss-loader 64 }, // css预设器配置项 详见https://cli.vuejs.org/zh/config/#css-loaderoptions 65 // modules: false, // 启用 CSS modules for all css / pre-processor files. 66 requireModuleExtension: true 67 }, 68 parallel: require('os').cpus().length > 1, // 是否为 Babel 或 TypeScript 使用 thread-loader。该选项在系统的 CPU 有多于一个内核时自动启用,仅作用于生产构建。 69 pwa: {}, // PWA 插件相关配置 see https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-pwa 70 // webpack-dev-server 相关配置 71 devServer: { 72 open: false, // 自动打开浏览器 73 host: '0.0.0.0', // 允许外部ip访问 74 port: 8000, // 端口 75 https: false, // 启用https 76 overlay: { 77 warnings: true, 78 errors: true 79 }, // 错误、警告在页面弹出 80 // proxy: 'http://localhost:4000' // 配置跨域处理,只有一个代理 81 proxy: { 82 '/api': { 83 target: '<url>', 84 ws: true, 85 changeOrigin: true 86 }, 87 '/foo': { 88 target: '<other_url>' 89 } 90 }, // 配置多个代理 91 }, 92 // 第三方插件配置 93 pluginOptions: {} 94 }
⑥main.js 文件
1 import { createApp } from 'vue' 2 import App from './App.vue' 3 4 createApp(App).mount('#app')
1 import { createApp } from 'vue' 2 import App from './App.vue' 3 import router from './router' 4 5 // 将创建的 App 搞个别名 6 const app = createApp(App) 7 8 // 使用路由配置 9 app.use(router) 10 11 // 挂载运行 12 app.mount('#app')
⑦路由router文件
1 // 可以根据路由模式的不同,后面俩可以只引用一个 2 import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router' 3 import Home from '@/views/Home.vue' 4 5 // 构建我们的页面路由配置,可以看到,这里和原来的写法并无二致。 6 const routes = [ 7 { 8 path: '/', 9 component: Home 10 }, { 11 path: '/about', 12 component: () => import('@/views/About.vue'), 13 } 14 ] 15 16 const router = createRouter({ 17 // 使用 hash 模式构建路由( url中带 # 号的那种) 18 history: createWebHashHistory(), 19 // 使用 history 模式构建路由 ( url 中没有 # 号,但生产环境需要特殊配置) 20 // history: createWebHistory(), 21 routes 22 }) 23 export default router
⑧setUp()函数
1 <template> 2 <router-link to="/about">点这里去关于我们页面</router-link> 3 <div class="home"> 4 这里是一个计数器 >>> <span class="red">{{count}}</span> <br> 5 <button @click="countAdd">{{btnText}}</button> 6 </div> 7 </template> 8 9 <script> 10 // ref 是 vue 3.0 的一个重大变化,其作用为创建响应式的值 11 import { ref } from 'vue' 12 // 导出依然是个对象,不过对象中只有一个 setup 函数 13 export default { 14 setup () { 15 // 定义一个不需要改变的数据 16 const btnText = '点这个按钮上面的数字会变' 17 // 定义一个 count 的响应式数据,并赋值为 0 18 const count = ref(0) 19 // 定义一个函数,修改 count 的值。 20 const countAdd = () => { 21 count.value++ 22 } 23 // 导出一些内容给上面的模板区域使用 24 return { 25 btnText, 26 count, 27 countAdd 28 } 29 } 30 } 31 </script> 32 <style lang="scss"> 33 .home { 34 line-height: 2; 35 .red { 36 color: red; 37 } 38 } 39 </style>
可以一个函数就是一个组件,多方便啊!其次,在 setup 函数中 return 出去的东西,可以在模板区域直接使用,也不必理会 this 这个神奇的东西。然后就是 ref 这个函数,我们可以从 vue 中引入它,它传入一个值作为参数,返回一个基于该值的 响应式 Ref 对象,该对象中的值一旦被改变和访问,都会被跟踪到,通过修改 count.value 的值,可以触发模板的重新渲染,显示最新的值。
⑨reactive
和 ref
的区别就是,reactive
是处理对象或者数组的。
13、vue2.7有什么变化?
Vue2.7支持你的项目在不升级Vue3的情况下使用Vue3的特性,例如Composition Api
、setup
、Css v-bind
等。
14、对vue 中keep-alive的理解
概念:keep-alive是vue的内置组件,当它动态包裹组件时,会缓存不活动的组件实例,它自身不会渲染成一个DOM元素也不会出现在父组件链中
作用:在组件切换过程中将状态保留在内存中,防止重复渲染DOM,减少加载时间以及性能消耗,提高用户体验。
生命周期函数:Activated在keep-alive组件激活时调用,deactivated在keep-alive组件停用时调用
react:
1、react原理
2、react周期
注意:只要使用了constructor()就必须写super(),否则会导致this指向错误。
componentDidMount()组件第一次渲染完成,此时dom节点已经生成,可以在这里调用ajax请求,返回数据setState后组件会重新渲染
componentWillUnmount ()在此处完成组件的卸载和数据的销毁。
componentDidUpdate(prevProps,prevState)组件更新完毕后,react只会在第一次初始化成功会进入componentDidmount,之后每次重新渲染后都会进入这个生命周期,这里可以拿到prevProps和prevState,即更新前的props和state。
render函数会插入jsx生成的dom结构,react会生成一份虚拟dom树,在每一次组件更新时,在此react会通过其diff算法比较更新前后的新旧DOM树,比较以后,找到最小的有差异的DOM节点,并重新渲染。
getDerivedStateFromProps(nextProps, prevState)
这两者最大的不同就是:
在 componentWillReceiveProps 中,我们一般会做以下两件事,一是根据 props 来更新 state,二是触发一些回调,如动画或页面跳转等。
- 在老版本的 React 中,这两件事我们都需要在 componentWillReceiveProps 中去做。
- 而在新版本中,官方将更新 state 与触发回调重新分配到了 getDerivedStateFromProps 与 componentDidUpdate 中,使得组件整体的更新逻辑更为清晰。而且在 getDerivedStateFromProps 中还禁止了组件去访问 this.props,强制让开发者去比较 nextProps 与 prevState 中的值,以确保当开发者用到 getDerivedStateFromProps 这个生命周期函数时,就是在根据当前的 props 来更新组件的 state,而不是去做其他一些让组件自身状态变得更加不可预测的事情。
3、react和vue不同
①监听数据变化实现的原理不同
Vue通过 getter/setter以及一些函数的劫持,能精确知道数据变化。
react默认是通过比较引用的方式(diff)进行的,如果不优化可能导致大量不必要的VDOM的重新渲染。为什么React不精确监听数据变化呢?这是因为Vue和React设计理念上的区别,Vue使用的是可变数据,而React更强调数据的不可变,两者没有好坏之分,Vue更加简单,而React构建大型应用的时候更加鲁莽。
②数据流的不同
Vue1.0中可以实现两种双向绑定:父子组件之间,props可以双向绑定;组件与DOM之间可以通过v-model双向绑定。Vue2.x中去掉了第一种,也就是父子组件之间不能双向绑定了(但是提供了一个语法糖自动帮你通过事件的方式修改),并且Vue2.x已经不鼓励组件对自己的 props进行任何修改了。
React一直不支持双向绑定,提倡的是单向数据流,称之为onChange/setState()模式。不过由于我们一般都会用Vuex以及Redux等单向数据流的状态管理框架,因此很多时候我们感受不到这一点的区别了。
③Hoc和Mixins
Vue组合不同功能的方式是通过mixin,Vue中组件是一个被包装的函数,并不简单的就是我们定义组件的时候传入的对象或者函数。比如我们定义的模板怎么被编译的?比如声明的props怎么接收到的?这些都是vue创建组件实例的时候隐式干的事。由于vue默默帮我们做了这么多事,所以我们自己如果直接把组件的声明包装一下,返回一个HoC,那么这个被包装的组件就无法正常工作了。
React组合不同功能的方式是通过HoC(高阶组件)。React最早也是使用mixins的,不过后来他们觉得这种方式对组件侵入太强会导致很多问题,就弃用了mixinx转而使用HoC。高阶组件本质就是高阶函数,React的组件是一个纯粹的函数,所以高阶函数对React来说非常简单。
④组件通信的区别
Vue中有三种方式可以实现组件通信:父组件通过props向子组件传递数据或者回调,虽然可以传递回调,但是我们一般只传数据;子组件通过事件向父组件发送消息;通过V2.2.0中新增的provide/inject来实现父组件向子组件注入数据,可以跨越多个层级。
React中也有对应的三种方式:父组件通过props可以向子组件传递数据或者回调;可以通过 context 进行跨层级的通信,这其实和 provide/inject 起到的作用差不多。React 本身并不支持自定义事件,而Vue中子组件向父组件传递消息有两种方式:事件和回调函数,但Vue更倾向于使用事件。在React中我们都是使用回调函数的,这可能是他们二者最大的区别。
⑤模板渲染方式不同
在表层上,模板的语法不同,React是通过JSX渲染模板。而Vue是通过一种拓展的HTML语法进行渲染,但其实这只是表面现象,毕竟React并不必须依赖JSX。
在深层上,模板的原理不同,这才是他们的本质区别:React是在组件JS代码中,通过原生JS实现模板中的常见语法,比如插值,条件,循环等,都是通过JS语法实现的,更加纯粹更加原生。而Vue是在和组件JS代码分离的单独的模板中,通过指令来实现的,比如条件语句就需要 v-if 来实现对这一点,这样的做法显得有些独特,会把HTML弄得很乱。
举个例子,说明React的好处:react中render函数是支持闭包特性的,所以我们import的组件在render中可以直接调用。但是在Vue中,由于模板中使用的数据都必须挂在 this 上进行一次中转,所以我们import 一个组件完了之后,还需要在 components 中再声明下,这样显然是很奇怪但又不得不这样的做法。
⑥渲染过程不同
Vue可以更快地计算出Virtual DOM的差异,这是由于它在渲染过程中,会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。
React在应用的状态被改变时,全部子组件都会重新渲染。通过shouldComponentUpdate这个生命周期方法可以进行控制,但Vue将此视为默认的优化。
如果应用中交互复杂,需要处理大量的UI变化,那么使用Virtual DOM是一个好主意。如果更新元素并不频繁,那么Virtual DOM并不一定适用,性能很可能还不如直接操控DOM。
⑦框架本质不同
Vue本质是MVVM框架,由MVC发展而来;
React是前端组件化框架,由后端组件化发展而来。
⑧Vuex和Redux的区别
从表面上来说,store注入和使用方式有一些区别。在Vuex中,$store被直接注入到了组件实例中,因此可以比较灵活的使用:使用dispatch、commit提交更新,通过mapState或者直接通过this.$store来读取数据。在Redux中,我们每一个组件都需要显示的用connect把需要的props和dispatch连接起来。另外,Vuex更加灵活一些,组件中既可以dispatch action,也可以commit updates,而Redux中只能进行dispatch,不能直接调用reducer进行修改。
从实现原理上来说,最大的区别是两点:Redux使用的是不可变数据,而Vuex的数据是可变的,因此,Redux每次都是用新state替换旧state,而Vuex是直接修改。Redux在检测数据变化的时候,是通过diff的方式比较差异的,而Vuex其实和Vue的原理一样,是通过getter/setter来比较的,这两点的区别,也是因为React和Vue的设计理念不同。React更偏向于构建稳定大型的应用,非常的科班化。相比之下,Vue更偏向于简单迅速的解决问题,更灵活,不那么严格遵循条条框框。因此也会给人一种大型项目用React,小型项目用Vue的感觉。
4、React就是这么使用虚拟DOM的。当我们使用jsx语法定义一个模板时,React会用它生成一个由JavaScript描述的虚拟DOM树,并将其保存在JavaScript内存中。这个虚拟DOM树还保留了我们在模板中定义的数据和视图的绑定关系,这为React自动根据数据变化更新视图提供了可能。随后当数据发生变化时,React重新生成一个虚拟DOM树,通过对比两个虚拟DOM树的差异,React就可以知道该如何高效地更新视图。接着它就会调用原生的DOM接口,去更新真实DOM。
4、type和interface区别
1、type可以声明 基本类型,联合类型,元组 的别名,interface不行
2、type 语句中可以使用 typeof 获取类型实例
3、type 支持类型映射,interface不支持
4、interface能够声明合并,type不能
5、常用的hooks
useState 状态钩子
useContext 共享状态钩子
useReducer(): Action 钩子
useEffect(): 副作用钩子
6、函组件与类组件区别:
1. 语法上的区别:
函数式组件是一个纯函数,它是需要接受props参数并且返回一个React元素就可以了。类组件是需要继承React.Component的,而且class组件需要创建render并且返回React元素,语法上来讲更复杂。
2. 调用方式
函数式组件可以直接调用,返回一个新的React元素;类组件在调用时是需要创建一个实例的,然后通过调用实例里的render方法来返回一个React元素。
3. 状态管理
函数式组件没有状态管理,类组件有状态管理。
4. 使用场景
类组件没有具体的要求。函数式组件一般是用在大型项目中来分割大组件(函数式组件不用创建实例,所有更高效),一般情况下能用函数式组件就不用类组件,提升效率。
7、react中获取上一个state
创建一个自定义钩子usePrevious
,该钩子将返回传递给它的任何参数的先前值。
1 //custom Hook 2 function usePrevious(data){ 3 const ref = React.useRef(); 4 React.useEffect(()=>{ 5 ref.current = data 6 }, [data]) 7 return ref.current 8 }
获得以前的状态计数值为:
1 var prevCount = usePrevious(count)
然后,prevCount
将return语句内的变量用作:
1 <div>Previous Count Value: {prevCount}</div>
8、diff
diff比较只会在同层级进行, 不会跨层级比较。
所以diff是:广度优先算法。
时间复杂度:O(n)
项目经验:
1、项目里面的前端鉴权是怎么实现的?
使用vuex保存全局状态,并做数据持久化
vuex里面面定义token变量来表示用户是否登录,初始值为 " "
1 import createPersistedState from 'vuex-persistedstate' 3 export default new Vuex.Store({ 4 state: { 5 token: '', 6 }, 7 mutations: { 8 setIsLogin(state, isLogin) { //登录成功调用 9 state.token = isLogin; 10 }, 11 FedLogOut(state) { //退出登陆执行 12 state.token='' 13 } 14 } 15 actions, 16 getters, 17 plugins:[createPersistedState({ //vuex数据固化到本地缓存,数据持久化 18 storage: window.localStorage 19 })] 20 });
配置request请求拦截器 request请求拦截器:发送请求前统一处理,如:设置请求头headers、应用的版本号、终端类型等。
1 service.interceptors.request.use( 2 config => { 3 if (store.state.token) { 4 // 为请求头对象,添加token验证的Authorization字段 5 config.headers.Authorization = store.state.token; 6 } 7 return config 8 }, 9 error => { 10 // do something with request error 11 console.log(error) // for debug 12 return Promise.reject(error) 13 } 14 )
esponse响应拦截器 response响应拦截器:有时候我们要根据响应的状态码来进行下一步操作,例如:由于当前的token过期,接口返回401未授权,那我们就要进行重新登录的操作。
1 service.interceptors.response.use( 2 response => { 3 Toast.clear() 4 const res = response.data 5 if (res.status && res.status !== 200) { 6 // 登录超时,token过期返回401,重新登录 7 if (res.status === 401) { 8 store.dispatch('FedLogOut').then(() => { 9 router.replace({ 10 path: '/login' 11 //登录成功后跳入浏览的当前页面 12 // query: {redirect: router.currentRoute.fullPath} 13 }) 14 }) 15 } 16 return Promise.reject(res || 'error') 17 } else { 18 return Promise.resolve(res) 19 } 20 }, 21 error => { 22 Toast.clear() 23 console.log('err' + error) // for debug 24 return Promise.reject(error) 25 } 26 )
2、手写函数防抖和函数节流
函数节流:不断触发一个函数后,执行第一次,只有大于设定的执行周期后才会执行第二次
1 /* 2 节流函数:fn:要被节流的函数,delay:规定的时间 3 */ 4 function throttle(fn,delay){ 5 // 记录上一次函数出发的时间 6 var lastTime = 0 7 return function(){ 8 // 记录当前函数触发的时间 9 var nowTime = new Date().getTime() 10 // 当当前时间减去上一次执行时间大于这个指定间隔时间才让他触发这个函数 11 if(nowTime - lastTime > delay){ 12 // 绑定this指向 13 fn.call(this) 14 //同步时间 15 lastTime = nowTime 16 } 17 } 18 }
函数防抖:不断触发一个函数,在规定时间内只让最后一次生效,前面都不生效
1 function debounce(fn,delay){ 2 var timer = null 3 // 清除上一次延时器 4 return function(){ 5 clearTimeout(timer) 6 // 重新设置一个新的延时器 7 timer = setTimeout(() => { 8 fn.call(this) 9 }, delay); 10 } 11 }
3、浏览器渲染过程
浏览器渲染的过程主要包括以下五步:
-
浏览器将获取的HTML文档并解析成DOM树。(构建DOM树)
-
处理CSS标记,构成层叠样式表模型CSSOM(CSS Object Model)。(css解析)
-
将DOM和CSSOM合并为渲染树(rendering tree)将会被创建,代表一系列将被渲染的对象。(构建渲染树)
-
渲染树的每个元素包含的内容都是计算过的,它被称之为布局layout。浏览器使用一种流式处理的方法,只需要一次pass绘制操作就可以布局所有的元素。(渲染树布局)
-
将渲染树的各个节点绘制到屏幕上,这一步被称为绘制painting.(渲染树绘制)
4、回流重绘
reflow(回流):当浏览器发现某个部分发生了变化从而影响了布局,这个时候就需要倒回去重新渲染,大家称这个回退的过程叫 reflow。 常见的reflow是一些会影响页面布局的操作,诸如Tab,隐藏等。reflow 会从 html 这个 root frame 开始递归往下,依次计算所有的结点几何尺寸和位置,以确认是渲染树的一部分发生变化还是整个渲染树。reflow几乎是无法避免的,因为只要用户进行交互操作,就势必会发生页面的一部分的重新渲染,且通常我们也无法预估浏览器到底会reflow哪一部分的代码,因为他们会相互影响。
repaint(重绘): repaint则是当我们改变某个元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性时,屏幕的一部分要重画,但是元素的几何尺寸和位置没有发生改变。
需要注意的是,display:none 会触发 reflow,而visibility: hidden属性则并不算是不可见属性,它的语义是隐藏元素,但元素仍然占据着布局空间,它会被渲染成一个空框,这在我们上面有提到过。所以visibility:hidden 只会触发 repaint,因为没有发生位置变化。
我们不能避免reflow,但还是能通过一些操作来减少回流:
-
用transform做形变和位移.
-
通过绝对位移来脱离当前层叠上下文,形成新的Render Layer。
-
另外有些情况下,比如修改了元素的样式,浏览器并不会立刻reflow 或 repaint 一次,而是会把这样的操作积攒一批,然后做一次 reflow,这又叫异步 reflow 或增量异步 reflow。但是在有些情况下,比如resize 窗口,改变了页面默认的字体等。对于这些操作,浏览器会马上进行 reflow。
5,优化渲染效率
- CSS注意事项
使用CSS3动画造成页面的不流畅和卡顿问题,其潜在原因往往还是页面的回流和重绘,减少页面动画元素对其他元素的影响是提高性能的根本方向。
1、设置动画元素 position 样式为absolute或 fixed,可避免动画的进行对页面其它元素造成影响,导致其重绘和重排的发生;
2、避免使用margin,top,left,width,height等属性执行动画,用 transform 进行替代;
- JS注意事项
1、解决js同步加载问题
①、将js文件放在页面底部,即</body>标签之前。因为html文件默认是按照顺序从上到下依次加载的,这样就可以先渲染dom节点,再加载js
②、使用 H5 的async属性,用法和特点如下
1 <script src = "test.js" anysc></script> 2 //加载脚本时不阻塞页面渲染 3 //使用这个属性的脚本中不能调用document.write方法 4 //可以只写属性名,不写属性值。写法如上 5 //H5新增属性 6 //脚本在下载后立即执行,同时会在window的load事件之前执行,所以有可能出现脚本执行顺序被打乱的情况
③、使用HTML的defer属性,用法和特点如下(前三点和anysc相同)
1 <script src = "test.js" defer></script> 2 //加载脚本时不阻塞页面渲染 3 //使用这个属性的脚本中不能调用document.write方法 4 //可以只写属性名,不写属性值。写法如上 5 //H4属性 6 //脚本在页面解析完之后,按照原本的顺序执行,同时会在document的DOMContentLoaded之前执行
2、避免频繁操作DOM元素
①使用虚拟dom,createDocumentFragment()方法
②、设置DOM元素的display属性为none再操作该元素
③、复制DOM元素到内存中再对其进行操作
④、用局部变量缓存样式信息从而避免频繁获取DOM数据
⑤、合并多次DOM操作
- 其他
1、资源压缩与合并。
HTML代码压缩:压缩在文本中有意义,而在HTML中不需要的字符。比如,空格、制表符、换行符,还有一些其他意义的字符,如HTML注释也可以被压缩。
CSS代码压缩:删除无效的代码和css语义合并。
JS的压缩和紊乱:使用在线网站压缩、使用html-minifier工具、使用uglifyjs2进行压缩。
文件合并:将多个js/css小文件合并为一个文件,减少网络请求次数。
注:css压缩与js的压缩和紊乱比html压缩收益要大的多,同时css代码和js代码比html代码多的多。所以,css与js代码压缩非常有必要!
2、浏览器缓存
缓存作用:对于web应用来说,缓存是提升页面性能同时减少服务器压力的利器。
强缓存:不会向服务器发送请求,直接从缓存中读取资源,在Chrome控制台的network选项中可以看到该请求的状态码是 200,但是 size 的标识为 from dist cache 或者 from memory cache
response header:response header里的过期时间,浏览器再次加载该资源时,如果在有效时间内,则使用强缓存。
Last-Modified 和 If-Modified-Since:二者都是记录页面最后修改时间的 HTTP 头信息,Last-Modified 是由服务器往客户端发送的HTTP, If-Modified-Since 是客户端往服务器端发送的头。
再次请求本地缓存的 cache 页面时,客户端会通过 If-Modified-Since 头先将服务器端发过来的 Last-Modified 最后修改时间戳发送回去,这是为了让服务器端进行验证,通过这个时间戳判断客户端的页面是否是最新的。如果不是最新的,则返回新的内容,如果是最新的,则返回 304 告诉客户端其本地 cache 的页面时最新的,于是客户端就可以直接从本地加载页面了,这样在网络上传输的数据就会大大减少,同时也减轻了服务器端的负担。而在一些ajax应用中,要求回获取的数据永云是最新的,而不是读取缓存中的数据,做这样的设置是很有必要的。
3、CDN预解析
CDN服务提供商会有全国各个省份部署节点, 将网站静态资源部署到CDN后, 用户在访问页面时, CDN静态资源会从就近的CDN节点上加载资源. 当请求至达CDN节点后, 节点会判断资源是缓存是否有效, 若有效, 直接返回给用户, 若无效, 会从CDN服务器加载最新的资源返回给用户同时将资源保存一份到该CDN节点上, 以便后续的访问用户使用. 因此, 只在该地区有一个用户先加载了资源, 在CDN中建立了缓存, 该地区的其他用户都能受益。
4、DNS预解析
6、网站性能优化
http 请求方面,减少请求数量,请求体积,对应的做法是,对项目资源进行压缩,控制项目资源的 dns 解析在2到4个域名,提取公告的样式,公共的组件,雪碧图,缓存资源,
压缩资源,提取公共资源压缩,提取 css ,js 公共方法
不要缩放图片,使用雪碧图,使用字体图表(阿里矢量图库)
使用 CDN,抛开无用的 cookie
减少重绘重排,CSS属性读写分离,最好不要用js 修改样式,dom 离线更新,渲染前指定图片的大小
js 代码层面的优化,减少对字符串的计算,合理使用闭包,首屏的js 资源加载放在最底部
7、懒加载和预加载
懒加载也叫延迟加载:JS图片延迟加载,延迟加载图片或符合某些条件时才加载某些图片。
预加载:提前加载图片,当用户需要查看时可直接从本地缓存中渲染。
两种技术的本质:两者的行为是相反的,一个是提前加载,一个是迟缓甚至不加载。
懒加载对服务器前端有一定的缓解压力作用,预加载则会增加服务器前端压力。
懒加载的意义及实现方式有:
意义:
懒加载的主要目的是作为服务器前端的优化,减少请求数或延迟请求数。
实现方式:
1.第一种是纯粹的延迟加载,使用setTimeOut或setInterval进行加载延迟.
2.第二种是条件加载,符合某些条件,或触发了某些事件才开始异步下载。
3.第三种是可视区加载,即仅加载用户可以看到的区域,这个主要由监控滚动条来实现,一般会在距用户看到某图片前一定距离遍开始加载,这样能保证用户拉下时正好能看到图片。
预加载的意义及实现方式有:
意义:
预加载可以说是牺牲服务器前端性能,换取更好的用户体验,这样可以使用户的操作得到最快的反映。
实现方式:
1.用CSS和JavaScript实现预加载;
2.仅使用JavaScript实现预加载;
3.使用Ajax实现预加载。
常用的是new Image():
设置其src来实现预载,再使用onload方法回调预载完成事件。
只要浏览器把图片下载到本地,同样的src就会使用缓存,这是最基本也是最实用的预载方法。
当Image下载完图片头后,会得到宽和高,因此可以在预载前得到图片的大小(方法是用记时器轮循宽高变化)。
8、express框架
Express是一个基于Node平台的web应用开发框架,它提供了一系列的强大特性,帮助你创建各种web应用。
在终端中使用npm install express --save命令进行下载
express框架文档 https://www.expressjs.com.cn/
基本步骤(导入express)
1、导入express框架 let express = require ('express')
2、通过express函数,创建并返回一个web服务器对象 let app = express()
3、使用这个web服务器对象,开启一个web服务,并监听一个端口号 app.listen(8000,()=>{console.log('服务器已开启')})
中间件
1、定义
中间件就是一堆方法,可以接收客户端发来的请求、可以对请求做出响应,也可以将请求继续交给下一个中间件继续处理。
2、app.use中间件用法
app.use 匹配所有的请求方式,可以直接传入请求处理函数,代表接收所有的请求。所有的请求,都会先走use,作用是拦截器。
1 app.use((req,res,next)=>{ 2 //表示允许跨域请求 3 res.setHeader('Access-Control-Allow-Origin','*') 4 //next方法,表示继续往下执行 5 next() 6 })
实际开发中,我们用use中间件方法里面去判断用户的权限,从而确定该用户能否继续请求相关的接口。
跨域
1、定义
同源策略:协议名,主机名(域名或IP地址),端口号必须完全相同。违背同源策略就是跨域。
Ajax请求,必须遵循同源策略
2、解决跨域
设置响应头,允许跨域请求 respose.setHeader('Access-Control-Allow-Origin','*')
3、允许自定义请求头信息 res.setHeader('Access-Control-Allow-Origin','*')
定义服务接口(路由)
1、get请求接口
(1)get请求参数的获取
req.qurey接收前端传递的GET请求参数,框架内部会将GET参数转换为对象并返回
(2)定义get请求接口
req是请求对象,里面保存的是客户端传过来的请求参数。
res是响应对象,用于给客户端响应结果。
res.send()将结果返回给前端。
1 app.get('/getStudents',(req,res)=>{ 2 res.send("hello") 3 })
2、定义post请求接口
(1)post请求参数的获取
req.body接收前端传递过来的POST请求参数。
接收POST请求参数,服务器需要进行设置。
通过express.json()中间件,解析表单中的JSON格式数据。
通过express.urlencoded()中间件,解析表单中的url-encoded格式数据。
1 //设置允许接受json格式数据({'name':'张三','age':19}) 2 app.use(express.json()) 3 //设置允许接受urlendoded格式数据('name=张三&age=19') 4 app.use(express.urlendoded({extended:false}))
(2)定义post请求接口
1 app.get('/addStudents',(req,res)=>{ 2 console.log(req.body)//接受请求参数 3 res.send('hello') 4 })
9、websocket 握手过程
①、WebSocket和HTTP之间的关系
WebSocket和HTTP一样都是基于TCP的应用层协议。
WebSocket协议和HTTP协议是两种不同的东西。客户端开始建立WebSocket连接时要发送一个header标记了 Upgrade的HTTP请求,表示请求协议升级。所以服务器端做出响应的简便方法是,直接在现有的HTTP服务器软件和现有的端口上实现WebSocket协议,然后再回一个状态码为101的HTTP响应完成握手,再往后发送数据时就没 HTTP的事了。也就是说WebSocket只是使用HTTP协议来完成一部分握手。
②、socket中TCP的三次握手建立连接详解
我们知道tcp建立连接要进行“三次握手”,即交换三个分组。大致流程如下:
-
- 客户端向服务器发送一个SYN J
- 服务器向客户端响应一个SYN K,并对SYN J进行确认ACK J+1
- 客户端再想服务器发一个确认ACK K+1
只有就完了三次握手,但是这个三次握手发生在socket的那几个函数中呢?请看下图:
从图中可以看出,当客户端调用connect时,触发了连接请求,向服务器发送了SYN J包,这时connect进入阻塞状态;服务器监听到连接请求,即收到SYN J包,调用accept函数接收请求向客户端发送SYN K ,ACK J+1,这时accept进入阻塞状态;客户端收到服务器的SYN K ,ACK J+1之后,这时connect返回,并对SYN K进行确认;服务器收到ACK K+1时,accept返回,至此三次握手完毕,连接建立。
总结:客户端的connect在三次握手的第二个次返回,而服务器端的accept在三次握手的第三次返回。
③、socket中TCP的四次握手释放连接详解
图示过程如下:
-
-
某个应用进程首先调用close主动关闭连接,这时TCP发送一个FIN M;
-
另一端接收到FIN M之后,执行被动关闭,对这个FIN进行确认。它的接收也作为文件结束符传递给应用进程,因为FIN的接收意味着应用进程在相应的连接上再也接收不到额外数据;
-
一段时间之后,接收到文件结束符的应用进程调用close关闭它的socket。这导致它的TCP也发送一个FIN N;
-
接收到这个FIN的源发送端TCP对它进行确认。
-
这样每个方向上都有一个FIN和ACK。
10、跨域
同源指的是协议、域名、端口 都要保持一致
①JSONP方式,只支持GET请求,不支持POST请求。缺点: get请求,前后端都要修改
②反向代理,ngixn
③配置浏览器(我配置了谷歌,属性->目标> 在后面追加 --args --disable-web-security --user-data-dir 注意有个空格)。设置成功打开浏览器是出现已栏提示证明已成功配置。
④vue 项目跨域配置
Vue-cli 创建的项目,可以直接利用 Node.js 代理服务器,通过修改vue proxyTable接口实现跨域请求
proxyTable: { '/api': { //代理地址 target: 'http://10.1.0.34:8000/', //需要代理的地址 changeOrigin: true, //是否跨域 secure: false, pathRewrite: { '^/api': '/' //本身的接口地址没有 '/api' 这种通用前缀,所以要rewrite,如果本身有则去掉 } } }
api指的就是target的地址
参数proxyTable详解:
vue-cli的config文件里的参数:proxyTable,这个参数主要是一个地址映射表,你可以通过设置将复杂的url简化,例如我们要请求的地址是api.xxxxxxxx.com/list/1
,可以按照如下设置:
proxyTable: { '/list': { target: 'http://api.xxxxxxxx.com', pathRewrite: { '^/list': '/list' } } }
这样我们在写url的时候,只用写成/list/1
就可以代表api.xxxxxxxx.com/list/1
.
这样实现跨域的原理是利用本地虚拟的服务器,代接受发送请求
⑤proxy代理服务器解决跨域
首先我们在本地开发,域名都是localhost,当我们需要请求后台数据时,就会出现跨域的问题,下面就是在vue.config.js配置文件里:
devServer: { proxy: { // detail: https://cli.vuejs.org/config/#devserver-proxy '/api': { target: `http://10.24.4.214:8098/api`, changeOrigin: true, pathRewrite: { '^/api' : '' } } } }
⑥后台配置请求head
header('Access-Control-Allow-Origin:*');//允许所有来源访问
header('Access-Control-Allow-Method:POST,GET');//允许访问的方式
⑦Cors设置跨域允许
js文件中设置
11、webpack配置
webpack
可以看做是模块打包机:它做的事情是,分析你的项目结构,找到 JavaScript
模块以及其他的一些浏览器不能直接运行的扩展语言(Scss
,TypeScript
等),并将其转换和打包为合适的格式供浏览器使用
使用 webpack 前的准备
npm init :创建一个 package.json 文件。它是标准的 npm 说明文件,包括当前项目的依赖模块、自定义的脚本任务等
cnpm install webpack --save-dev :安装 webpack
文件目录的构建方面:一般由两个目录构成:app(存放原始数据和 JS 模块),public(存放供浏览器读取的文件,即 webpack 打包生成的文件,和 html 源代码)
创建根目录下 webpack.config.js 文件,这是 webpack 的配置文件,基本的配置如下:
1 module.exports = { 2 entry: __dirname + "/app/main.js", // 已多次提及的唯一入口文件 3 output: { 4 path: __dirname + "/public", // 打包后的文件存放的地方 5 filename: "bundle.js" // 打包后输出文件的文件名 6 } 7 };
‘__dirname’是 node.js 中的全局变量,它指向当前执行脚本所在的目录
package.json
方面的配置:(配置好后,直接可以在命令行:npm start
即可进行打包编译,需要注意的是,除了 "start"
之外,所有的自定义命令都需要通过 npm run 命令名称
的形式进行)
1 "scripts": { 2 "start": "webpack" 3 }
webpack 提供了一种对应编译文件和源文件的方法,使得编译后的代码可读性更高,更容易调试。
1 module.exports = { 2 // 小到中型的项目中,eval-source-map 是一个很好的选择 3 devtool: 'eval-source-map', // 注意:只应该在开发阶段使用它 4 entry: __dirname + "/app/main.js", 5 output: { 6 path: __dirname + "/public", 7 filename: "bundle.js" 8 } 9 };
正式书写代码时,通过 module.exports
的方式进行模块的导出,通过 require
的方式进行模块的导入
同时建立多个入口文件:
1 module.exports = { 2 // 可以同时定义多个入口文件 3 entry: { 4 'index': __dirname + '/app/main.js', 5 'indexT': __dirname + '/app/main2.js' 6 }, 7 output: { 8 path: __dirname + '/build', 9 filename: '[name]-[hash].js' // 定义多个出口文件时的命名 10 }, 11 }
使用 webpack 构建本地服务器
cnpm install webpack-dev-server --save-dev
:安装 webpack-dev-server
在 webpack.config.js
中进行配置
1 module.exports = { 2 devtool: 'eval-source-map', 3 4 entry: __dirname + "/app/main.js", 5 output: { 6 path: __dirname + "/public", 7 filename: "bundle.js" 8 }, 9 10 devServer: { 11 contentBase: "./public", // 本地服务器所加载的页面所在的目录 12 historyApiFallback: true, // 不跳转 13 inline: true // 实时刷新 14 } 15 };
之后,在 package.json 中的 scripts 对象添加命令,方便开启本地服务器:
"server": "webpack-dev-server --open"
此时,通过在终端输入:npm run server
就可以本地的 8080 端口查看结果
1. 基本配置-入口:
入口起点(entry point)指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。
1 entry:string | array | object 2 string:是一个字符串形式的相对路径,找到某一个具体的文件 3 array:['string','string'] 4 object:{ 5 a:'string', 6 b:'string' 7 } 8 1.单个入口 9 entry:string | array 10 2.对象形式,配置多个路径 11 entry:{ 12 app:'./路径', 13 other:'./路径', 14 some:'./路径' 15 } 16 说明:entry中的入口文件打包时会打包相关的有依赖关系的对应文件
2.基本配置-出口
output 属性告诉 webpack 在哪里输出它所创建的 bundles
1 output: { 2 // 存储路径:要求是绝对路径 3 path: path.resolve(__dirname, 'dist'), 4 filename: 'my-first-webpack.bundle.js' 5 filename:'[name].js' 6 } 7 1.单出口 8 { 9 filename:'app.js' 10 } 11 2.多出口 12 [name] 叫占位符,name会自动找entry对象中的键名 13 { 14 filename:'[name].js' 15 }
3.基本配置-loaders
loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行处理。
1 module: { 2 rules: [ 3 // es6->es5 4 { 5 test: /\.js$/, 6 use: { 7 loader: 'babel-loader' 8 options: { 9 presets: ['@babel/preset-env'] 10 } 11 }, 12 // include 包含作用,限定转换的范围 13 include:path.resolve(__dirname, 'src'), 14 // exclude 是排除作用 15 exclude:[path.resolve(__dirname, 'src')] 16 } 17 ] 18 }
4.基本配置-plugins
webpack插件(自动打开浏览器、热更新等)
1.HtmlWebpackPlugin插件:自动创建html页面
安装:
1 npm install --save-dev html-webpack-plugin
配置:
1 1.引入到webpack.config.js中 2 2.在plugins中配置 3 plugins:[ 4 new HtmlWebpackPlugin({ 5 template:'./index.html' 6 }) 7 ]
2.ExtractTextWebpackPlugin插件:提取css到单独文件
安装:
1 npm install --save-dev extract-text-webpack-plugin@next
配置:
1 { 2 test: /\.css$/, 3 use:ExtractTextPlugin.extract({ 4 fallback: "style-loader", 5 use: "css-loader" 6 }) 7 } 8 plugins:[ 9 new ExtractTextPlugin("styles.css") 10 ]
Loaders
通过使用不同的 loader,webpack 有能力调用外部的脚本或者工具,实现对不同格式的文件的处理
如分析转换 scss 为 css,把 ES6,ES7 转为现代浏览器兼容的 JS 文件等等
Loaders 需要单独安装并且需要在 webpack.config.js 中的 modules 关键字下进行配置,配置包括以下方面:
1. test :一个用以匹配 loaders 所处理文件的扩展的正则表达式(必须!)
2. loader :loader 的名称(必须!)
3. include / exclude :手动添加必须处理的文件(文件夹)或屏蔽不需要处理的文件(文件夹)(可选)
4. query :为 loaders 提供额外的设置选项(可选)
5、webpack中package-lock.json的作用
1 其实用一句话来概括很简单,就是锁定安装时的包的版本号,并且需要上传到git,以保证其他人在npm install时大家的依赖能保证一致。
原来package.json文件只能锁定大版本,也就是版本号的第一位,并不能锁定后面的小版本,你每次npm install都是拉取的该大版本下的最新的版本,为了稳定性考虑我们几乎是不敢随意升级依赖包的,这将导致多出来很多工作量,测试/适配等,所以package-lock.json文件出来了,当你每次安装一个依赖的时候就锁定在你安装的这个版本。
12、Babel
Babel
:其实是一个编译 JavaScript
的平台,它可以编译 ES6
,ES7
,基于 JavaScript
的扩展语言:React
的 JSX
,TypeScript
等等
Babel
是几个模块化的包,其核心功能位于称为 babel-core
的 npm
包中,对于每个我们需要的功能或扩展,都需要单独安装响应的包,如:
- 解析 ES6
的 babel-env-preset
包
- 解析 JSX
的 babel-preset-react
包
安装他们:cnpm install babel-core babel-loader babel-preset-env babel-preset-react --save-dev
cnpm install react react-dom --save
在 webpack.config.js 中配置 Babel:
1 module: { 2 rules: [ 3 { 4 test: /(\.jsx|\.js)$/, 5 use: { 6 loader: "babel-loader", 7 options: { 8 presets: [ 9 "env", "react" 10 ] 11 } 12 }, 13 exclude: /node_modules/ 14 } 15 ] 16 }
配置完成后,就可以使用 ES6
以及 JSX
语法了
13、事件循环
对于 JS 运行中的任务,JS 有一套处理收集,排队,执行的特殊机制,我们把这套处理机制称为事件循环。
JS 单线程指的是 javascript 引擎(如V8)在同一时刻只能处理一个任务。但这并不是说浏览器在同一个时刻只能处理一件事情,实际上 ajax 等异步任务不是在 JS 引擎上运行的,ajax 在浏览器处理网络的模块中执行,此时不会影响到 JS 引擎的任务处理。
-
异步任务 ajax I/O 等得到结果时,会将其回调作为一个任务添加到任务队列,排队等待执行。
-
当 JS 线程中的任务执行完毕,会读取任务队列 Queue,并将队列中的第一个任务添加到 JS 线程中并执行。
-
循环 3 4 步,异步任务完成后不断地往任务队列中添加任务,线程空闲时从任务列表读取任务并执行。
事件循环下的宏任务与微任务
通常我们把异步任务分为宏任务与微任务,它们的区分在于:
-
宏任务(macro-task):一般是 JS 引擎和宿主环境发生通信产生的回调任务,比如 setTimeout,setInterval 是浏览器进行计时的,其中回调函数的执行时间需要浏览器通知到 JS 引擎,网络模块, I/O处理的通信回调也是。包含有 setTimeout,setInterval,DOM事件回调,ajax请求结束后的回调,整体 script 代码,setImmediate。
-
微任务(micro-task):一般是宏任务在线程中执行时产生的回调,如 Promise,process.nextTick,Object.observe(已废弃), MutationObserver(DOM监听),这些都是 JS 引擎自身可以监听到回调。
上面我们了解了宏任务与微任务的分类,那么为什么我们要将其分为宏任务与微任务呢?主要是因为其添加到事件循环中的任务队列的机制不同。
执行宏任务模块代码,碰到微任务,放到任务队列,继续执行宏任务模块代码(直到没有了,才去执行任务队列里的任务)。任务队列里的任务,微任务永远先执行,才去执行任务队列里的宏任务。
其中相同类型的宏任务或微任务会按照回调的先后顺序进行排序,而不同任务类型的任务会有一定的优先级,按照不同类型任务区分
宏任务优先级,主代码块 > setImmediate > MessageChannel > setTimeout / setInterval
微任务优先级,process.nextTick > Promise > MutationObserver
14、如何实现一个可设置过期时间的 localStorage
要过期就必须要记录时间,我们的思路是,设置值得时候就将当前时间记录进去,然后获取值得时候判断一下当前时间和之前的时间差是否在某个范围之内,若果超出范围,则清空当前项,并返回null
要将时间加入到值中就必须要定义一个格式:
1 Storage.prototype.setExpire=(key, value, expire) =>{ 2 let obj={ 3 data:value, 4 time:Date.now(), 5 expire:expire 6 }; 7 localStorage.setItem(key,JSON.stringify(obj));
包括下面3个字段
- data 实际的值
- time 当前时间戳
- expire 过期时间
因为localStorage 设置的值不能为对象, 所以这里使用了 JSON.stringify 方法将其转为字符串,最终在使用的时候得转回来。
接着我们添加一个获取的方法:
1 Storage.prototype.getExpire= key =>{ 2 let val =localStorage.getItem(key); 3 if(!val){ 4 return val; 5 } 6 val =JSON.parse(val); 7 if(Date.now()-val.time>val.expire){ 8 localStorage.removeItem(key); 9 return null; 10 } 11 return val.data;
测试方法如下
1 localStorage.setExpire("token",'xxxxxx',5000); 2 window.setInterval(()=>{ 3 console.log(localStorage.getExpire("token")); 4 },1000)
本质上我们的思路并非是要定时去清理过期的项,而是在获取的时候判断是否过期,如果过期再去清除该项。
当然这种方案能解决一时的问题, 但是如果要设置任意键的有效期, 使用这种方案就需要编写多个定时器, 「维护成本极高, 且不利于工程化复用」。
-
用**「localStorage」**存一份{key(键): expire(过期时间)}的映射表
-
重写**「localStorage API」**, 对方法进行二次封装
类似的代码如下:
1 const store = { 2 // 存储过期时间映射 3 setExpireMap: (key, expire) => { 4 const expireMap = localStorage.getItem('EXPIRE_MAP') || "{}" 5 localStorage.setItem( 6 'EXPIRE_MAP', 7 JSON.stringify({ 8 ...JSON.parse(expireMap), 9 key: expire 10 })) 11 }, 12 setItem: (key, value, expire) => { 13 store.setExpireMap(key, expire) 14 localStorage.setItem(key, value) 15 }, 16 getItem: (key) => { 17 // 在取值之前先判断是否过期 18 const expireMap = JSON.parse( 19 localStorage.getItem('EXPIRE_MAP') || "{}" 20 ) 21 if(expireMap[key] && expireMap[key] < Date.now()) { 22 return localStorage.getItem(key) 23 }else { 24 localStorage.removeItem(key) 25 return null 26 } 27 } 28 // ... 29 }
为了减少维护成本和空间占用, 并支持一定的灵活控制和容错能力
-
将过期时间存到 key 中, 如 dooring|6000, 每次取值时通过分隔符“|”来将 key 和 expire 取出, 进行判断
-
将过期时间存到 value 中, 如 1.0.0|6000, 剩下的同1
-
直接使用 xijs 这个 javascript 工具库
15、手写快排
1 function sort(arr,left,right){ 2 if(left>=right){ 3 return 4 } 5 let symbol=arr[left] 6 let i=left 7 let j=right 8 while(i!=j){ 9 while(i<j&&arr[j]>=symbol) 10 j--; 11 while(i<j&&arr[i]<=symbol) 12 i++ 13 if(i<j){ 14 let temp=arr[i] 15 arr[i]=arr[j] 16 arr[j]=temp 17 } 18 19 } 20 let temp1=arr[i] 21 arr[i]=arr[left] 22 arr[left]=temp1 23 sort(arr,0,i-1); 24 sort(arr,i+1,right); 25 } 26 var a=[9,5,11,4,1,7,6] 27 sort(a,0,a.length-1) 28 console.log(a)
16、项目部署,上线问题
vue 项目开发好了之后,在项目目录下执行 npm run build
命令打包, 打包好的项目一般放在vue项目的 dist
文件中
dist
文件夹是打包的默认输出文件夹(可以在配置文件中配置)
通常前端开发将项目打包好之后就可以在服务器上部署了
部署的时候通常使用 Nginx 做反向代理
把项目,丢在nginx的网站目录下
然后再配置里面指向index.html
nginx 配置文件 简单示例
vue_nginx.conf
1 server{ 2 # 监听的端口 3 listen 19000; 4 # 使用域名,没有域名使用 _ 标识 5 server_name _; 6 # 项目根路径 7 root /var/wwwroot/rms2/dist; 8 location / { 9 # http_core核心模块所带的指令,主要是能替代一些rewrite的指令,提高解析效率 10 try_files $uri $uri/ /index.html; 11 } 12 location /prod-api { 13 # 重写 break表示重写之后会停止后续的重写规则 14 # 例如 : 15 # 看到前端请求是 http://192.168.10.90:19000/pro-api/user/login 16 # 重写后的请求是 http://192.168.10.90:19000/user/login 17 rewrite "^/prod-api/(.*)$" /$1 break; 18 # proxy_pass 反向代理 19 # 此处的作用是vue前端项目中需要访问的实际后端接口地址 20 # 例如 : 21 # 前端的请求 http://192.168.10.90:19000/pro-api/user/login 22 # 实际后端接口请求 http://200.100.10.1:8080/user/login 23 proxy_pass http://200.100.10.1:8080; 24 } 25 }
17、Node 事件循环,js 事件循环差异
Node.js 的事件循环分为6个阶段
浏览器和Node 环境下,microtask 任务队列的执行时机不同
Node.js中,microtask 在事件循环的各个阶段之间执行
浏览器端,microtask 在事件循环的 macrotask 执行完之后执行
递归的调用process.nextTick()会导致I/O starving,官方推荐使用setImmediate()
18、label标签的作用是什么?
<label>标签在HTML中的作用是定义input元素的标注。
如果您在 label 元素内点击文本,就会触发此控件。就是说,当用户选择该标签时,浏览器就会自动将焦点转到和标签相关的表单控件上。
<label> 标签的 for 属性应当与相关元素的 id 属性相同。"for" 属性可把 label 绑定到另外一个元素。请把 "for" 属性的值设置为相关元素的 id 属性的值。
19、script标签
把<script>标签放在<head>中意味着必须等到全部的js代码都下载解析和执行完成以后,才开始展现页面内容,为避免这个问题一般把js代码全部放在<body>元素内容后面
script标签不带defer和async属性:同步模式,脚本获取和执行都是同步,页面会被阻塞,浏览器都会按照<script>元素在页面中出现的先后顺序对他们依次进行解析
async属性:html5的新属性,只适合用于外部脚本文件,异步模式,通过createElement创建的script标签其属性async默认为true
defer属性:异步模式,只适合外部脚本文件,会被延迟到整个页面都解析完毕后再运行,脚本加载不阻塞页面的解析,同时带有defer的脚本彼此之间,能保证其执行顺序
defer
是“渲染完再执行”,async
是“下载完就执行”。另外,如果有多个defer
脚本,会按照它们在页面出现的顺序加载,而多个async
脚本是不能保证加载顺序的。
加载 es6模块的时候设置 type=module,异步加载不会造成阻塞浏览器,页面渲染完再执行,可以同时加上async属性,异步执行脚本(利用顶层的this等于undefined这个语法点,可以侦测当前代码是否在 ES6 “模块”之中)
20、DOM指令
1).新建节点
document.createElement(“元素名”) // 新建一个元素节点
document.createAttribute(“属性名”) // 新建一个属性节点
document.createTextNode(“文本内容”) // 创建一个文本节点
document.createDocumentFragment() // 新建一个DOM片段
2).添加、移除、替换、插入:
appendChild() // 向节点的子节点末尾添加新的子节点
removerChild() // 移除
parentNode.replaceChild(newChild, oldChild );用新节点替换父节点中已有的子节点
insertBeform() // 在已有的子节点前插入一个新的子节点
document.createElement()是在对象中创建一个对象,要与appendChild() 或 insertBefore()方法联合使用。其中,appendChild() 方法在节点的子节点列表末添加新的子节点。insertBefore() 方法在节点的子节点列表任意位置插入新的节点。
3).查找
document.getElementById() // 通过元素id查找,唯一性
document.getElementByClassName() // 通过class名称查找
document.getElementsByTagName() // 通过标签名称查找
document.getElementsByName() // 通过元素的Name属性的值查找
getAttribute 获取元素中的某一项属性
setAttribute() 方法添加指定的属性,并为其赋指定的值。 element.setAttribute(attributename,attributevalue)
addEventListener() 方法方法用于向指定元素添加事件句柄
21、普通对象,set对象和map对象
set对象:Set数据结构类似数组,但所有成员的值唯一。 Set本身为一个构造函数,用来生成 Set数据结构,使用 add方法来添加新成员。
1 let a = new Set(); 2 [1,2,2,1,3,4,5,4,5].forEach(x=>a.add(x)); 3 for(let k of a){ 4 console.log(k) 5 }; 6 // 1 2 3 4 5 7 基础使用: 8 9 let a = new Set([1,2,3,3,4]); 10 [...a]; // [1,2,3,4] 11 a.size; // 4
map对象:由于传统的 JavaScript对象只能用字符串当做键,给开发带来很大限制,ES6增加 Map数据结构,使得各种类型的值(包括对象)都可以作为键。 Map结构提供了“值=>值”的对应,是一种更完善的 Hash 结构实现。
1 let a = new Map(); 2 let b = {name: 'leo' }; 3 a.set(b,'my name'); // 添加值 4 a.get(b); // 获取值 5 a.size; // 获取总数 6 a.has(b); // 查询是否存在 7 a.delete(b); // 删除一个值 8 a.clear(); // 清空所有成员 无返
22、websocket
一、什么是 WebSocket- 两者都位于应用层,都依赖TCP协议
- WebSocket 协议一般以ws://或wss://开头
- HTTP 不支持全双工通信,一般使用轮询方式
- 1、建立在 TCP 协议之上,服务器端的实现比较容易。
-
2、与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
-
3、数据格式比较轻量,性能开销小,通信高效。
-
4、可以发送文本,也可以发送二进制数据。
-
5、没有同源限制,客户端可以与任意服务器通信。
四、websocket的缺点
由于传输数据需要进行二次解析,增加开发成本及难度
五、在项目中使用
在项目中调用如下:
配置 webpack 代理
心跳检测&断线重连
为了保证连接稳定,需要考虑一些异常情况,如网络波动导致连接中断,服务器超时等。
心跳检测即客户端定时向服务端发送心跳消息,保持连接稳定;
断线重连即发送消息前,检测连接状态,若连接中断,尝试n次连接;
封装如下:
六、demo
23、从输入url到页面加载完成经历了什么
(一)url解析:地址栏输入地址,浏览器对其解析,判断url合法性,是否有可用缓存等等;
(二)DNS解析:域名解析系统(DNS)查找对应的ip地址
(三)建立TCP连接(三次握手):浏览器向服务器发起TCP连接,与服务器建立三次握手;
(四)HTTP请求:浏览器将http请求数据发给服务器;
(五)HTTP响应:服务器处理收到的请求,返回响应结果给浏览器
(六)关闭TCP连接(四次挥手):数据传输完成受,还要结果四次挥手才能结束连接
(七)页面渲染:浏览器解析响应结果,进行页面渲染;
24、浏览器兼容
浏览器内核主要分为两种,一是渲染引擎,另一个是js 引擎,所以浏览器兼容性问题一般指:css兼容、js兼容
1、不同浏览器的标签默认的外补丁( margin )和内补丁(padding)不同
解决方案: css 里增加通配符 * { margin: 0; padding: 0; }
2、IE6双边距问题;在 IE6中设置了float , 同时又设置margin , 就会出现边距问题
解决方案:设置display:inline;
我们最常用的就是div+CSS布局了,而div就是一个典型的块属性标签,横向布局的时候我们通常都是用div float实现的,横向的间距设置如果用margin实现,这就是一个必然会碰到的兼容性问题。
3、边距重叠问题;当相邻两个元素都设置了margin 边距时,margin 将取最大值,舍弃最小值;
解决方案:为了不让边重叠,可以给子元素增加一个父级元素,并设置父级元素为overflow:hidden;
4、当标签的高度设置小于10px,在IE6、IE7中会超出自己设置的高度
解决方案:超出高度的标签设置overflow:hidden,或者设置line-height的值小于你的设置高度
这种情况一般出现在我们设置小圆角背景的标签里。出现这个问题的原因是IE8之前的浏览器都会给标签一个最小默认的行高的高度。即使你的标签是空的,这个标签的高度还是会达到默认的行高。
5、图片默认有间距
解决方案:使用float 为img 布局
因为img标签是行内属性标签,所以只要不超出容器宽度,img标签都会排在一行里,但是部分浏览器的img标签之间会有个间距。去掉这个间距使用float是正道。(我的一个学生使用负margin,虽然能解决,但负margin本身就是容易引起浏览器兼容问题的用法,所以我禁止他们使用)
6、IE9一下浏览器不能使用opacity
解决方案:opacity: 0.5;filter: alpha(opacity = 50);filter: progid:DXImageTransform.Microsoft.Alpha(style = 0, opacity = 50);
7、cursor:hand 显示手型在safari 上不支持
解决方案:统一使用 cursor:pointer;
8、两个块级元素,父元素设置了overflow:auto;子元素设置了position:relative ;且高度大于父元素,在IE6、IE7会被隐藏而不是溢出;
解决方案:父级元素设置position:relative;
25、web前端优化都有哪些压缩方法
1.html压缩
HTML代码压缩就是压缩一些在文本文件中有意义,但是在HTML中不显示的字符,包括空格,制表符,换行符等,还有一些其他意义的字符,如HTML注释也可以被压缩;
如何进行html压缩
使用在线网站进行压缩;
nodejs提供的html-minifier工具;
后端模板引擎渲染压缩;
2.css代码压缩
分为两部分:
无效代码的压缩;
css语义合并;
如何进行css压缩
使用在线网站进行压缩;
使用html-minifier对html中的css进行压缩;
使用clean-css对css进行压缩;
3.js压缩与混乱(丑化)
包括:
无效字符的删除(空格,回车等);
剔除注释;
代码语义的缩减和优化;
代码保护(如果代码不经处理,客户端可直接窥探代码漏洞);
JS压缩与混乱(丑化)
使用在线网站进行压缩
使用html-minifier对html中的js进行压缩;
使用uglify.js2对js进行压缩;
4.文件合并
文件与文件之间有插入的上行请求,会增加N-1个网络延迟;
受丢包问题的影响更严重:因为每次请求都可能出现丢包的情况,减少请求能有效减少丢包情况;
keep-alive本身也存在问题:经过代理服务器时可能会被断开;
使用建议
公共库合并:将不经常发生变化的公共组件库文件进行合并;
将不同页面的js文件单独合并:比如在单页面应用SPA中,当路由跳转到具体的页面时才请求该页面需要的js文件;
如何进行文件合并
使用在线网站进行文件合并;
使用nodejs实现文件合并;
使用webpack等前端构件化工具也可以很好地实现;
26、require和import区别
遵循规范:
- require 是 AMD 规范引入方式
- import是 ES6 的一个语法标准,如果要兼容浏览器的话必须转化成 ES5 的语法
调用时间:
- require是运行时调用,所以require理论上可以运用在代码的任何地方
- import是编译时调用,所以必须放在文件开头
本质:
- require 是赋值过程。module.exports后面的内容是什么,require的结果就是什么,比如对象、数字、字符串、函数等,然后再把require的结果赋值给某个变量,它相当于module.exports的传送门
- import 是解构过程,但是目前所有的引擎都还没有实现import,我们在node中使用babel支持ES6,也仅仅是将ES6转码为ES5再执行,import语法会被转码为require
require/exports 是运行时动态加载,import/export 是静态编译
require/exports 输出的是一个值的拷贝,import/export 模块输出的是值的引用
27、前端安全问题及解决方案
详见:https://blog.csdn.net/m_sy530/article/details/109162274
28、白屏问题及解决方式
原因:1、静态资源加载失败,如:js、css文件加载失败,导致白屏。 2、网络延迟,JS加载延迟 ,会阻塞页面。 3、传输数据过大,需要一定时间进行传输。 4、DOM渲染时间过长。 5、浏览器兼容问题
方法:1.减少DOM树渲染的时间(HTML层级不要太深,标签语义化…) 2.减少HTTP请求次数和请求大小 3.把CSS放在Head中,将js放在body的最后,避免阻塞 4.使用ssr预渲染
29、continue break return 在循环中的作用
1、continue:指的是跳出当前循环,即不执行continue后的语句,直接进入下次循环。
2、break:指的是跳出for循环本身,不再进行之后的循环,但可以执行for循环之外的语句。
3、return:指的是跳出for循环,且不执行for循环之外的语句,直接跳出当前函数,返回return后的值。
本文来自博客园,作者:冰中焱,转载请注明原文链接:https://www.cnblogs.com/Blod/p/15865018.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通