提高代码的可读性,页面内容结构化,便于开发人员的代码编写,同时提高的用户体验;有利于SEO ,便于搜索引擎爬虫爬取有效信息。
常见的语义化标签:
~标签:标题标签,h1等级最高,h6等级最低
:用于定义页面的介绍展示区域,通常包括网站logo、主导航、全站链接以及搜索框
:定义页面的导航链接部分区域
:定义页面的主要内容,一个页面只能使用一次。
:定义页面独立的内容,它可以有自己的header、footer、sections等
:元素用于标记文档的各个部分,例如长表单文章的章节或主要部分
:一般用于侧边栏
:文档的底部信息
:呈现小号字体效果
:用于强调文本
src和href都是用来引用外部的资源,它们的区别如下:
src:
表示对资源的引用,它指向的内容会嵌入到当前标签所在的位置。src会将其指向的资源下载并应⽤到⽂档内,如请求js脚本。当浏览器解析到该元素时,会暂停其他资源的下载和处理,直到将该资源加载、编译、执⾏完毕,所以⼀般js脚本会放在页面底部。
href:
表示超文本引用,它指向一些网络资源,建立和当前元素或本文档的链接关系。当浏览器识别到它他指向的⽂件时,就会并⾏下载资源,不会停⽌对当前⽂档的处理。
常用在a、link等标签上。
meta 标签由 name 和 content 属性定义,用来描述网页文档的属性,比如网页的作者,网页描述,关键词等,除了HTTP标准固定了一些name作为大家使用的共识,开发者还可以自定义name。
常用的meta标签:
charset,用来描述HTML文档的编码类型:
<meta charset="UTF-8" >
keywords,页面关键词:
<meta name="keywords" content="关键词" />
description,页面描述:
<meta name="description" content="页面描述内容" />
refresh,页面重定向和刷新:
<meta http-equiv="refresh" content="0;url=" />
viewport,适配移动端,可以控制视口的大小和比例:
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
viewport的content 参数有以下几种:
robots,搜索引擎索引方式:
<meta name="robots" content="index,follow" />
robots的content 参数有以下几种:
iframe 元素会创建包含另外一个文档的内联框架(即行内框架)。
优点:
缺点:
(1)SVG: SVG可缩放矢量图形(Scalable Vector Graphics)是基于可扩展标记语言XML描述的2D图形的语言,SVG基于XML就意味着SVG DOM中的每个元素都是可用的,可以为某个元素附加Javascript事件处理器。在 SVG 中,每个被绘制的图形均被视为对象。如果 SVG 对象的属性发生变化,那么浏览器能够自动重现图形。
其特点如下:
(2)Canvas: Canvas是画布,通过Javascript来绘制2D图形,是逐像素进行渲染的。其位置发生改变,就会重新进行绘制。
其特点如下:
依赖分辨率
不支持事件处理器
弱的文本渲染能力
能够以 .png 或 .jpg 格式保存结果图像
最适合图像密集型的游戏,其中的许多对象会被频繁重绘
(1)渐进增强(progressive enhancement) : 主要是针对低版本的浏览器进行页面重构,保证基本的功能情况下,再针对高级浏览器进行效果、交互等方面的改进和追加功能,以达到更好的用户体验。
(2)优雅降级 graceful degradation: 一开始就构建完整的功能,然后再针对低版本的浏览器进行兼容。
两者区别:
这是一种为 HTML 元素添加额外数据信息的方式,被称为 自定义属性。
我们可以直接在元素标签上声明这样的数据属性:
<div id="mydiv" data-message="Hello,world" data-num="123"></div>
也可以使用 JavaScript 来操作元素的数据属性:
let mydiv = document.getElementById('mydiv')
// 读取
console.log(mydiv.dataset.message)
// 写入
mydiv.dataset.foo = "bar!!!"
注意:在各种现代前端框架出现后,这种原生的自定义属性已经变得不太常用了, 以前的使用频率非常高, 。
指的是没有网络连接的时候,可以正常访问应用,与网络连接时更新缓存文件
在 cache.manifest 文件中编写需要离线存储的资源:
在离线状态时,操作 window.applicationCache 进行离线缓存的操作。
如何清除缓存:更新 manifest 文件,通过 javascript 操作,清除浏览器缓存。
选择器 | 格式 | 优先级权重 |
---|---|---|
id选择器 | #id | 100 |
类选择器 | #classname | 10 |
属性选择器 | a[ref=“eee”] | 10 |
伪类选择器 | li:last-child | 10 |
标签选择器 | div | 1 |
伪元素选择器 | li:after | 1 |
相邻兄弟选择器 | h1+p | 0 |
子选择器 | ul>li | 0 |
后代选择器 | li a | 0 |
通配符选择器 | * | 0 |
注意事项:
块标签:div、h1~h6、ul、li、table、p、br、form。
两者都是外部引用CSS的方式,它们的区别如下:
link是XHTML标签,除了加载CSS外,还可以定义RSS等其他事务;@import属于CSS范畴,只能加载CSS。
link引用CSS时,在页面载入时同时加载;@import需要页面网页完全载入以后加载。
link是XHTML标签,无兼容问题;@import是在CSS2.1提出的,低版本的浏览器不支持。
link支持使用Javascript控制DOM去改变样式;而@import不支持。
transition是过度属性, 强调过度,它的实现需要触发一个事件(比如鼠标移动上去,焦点,点击等)才执行动画。它类似于flash的补间动画,设置一个开始关键帧,一个结束关键帧。
animation是动画属性, 它的实现不需要触发事件,设定好时间之后可以自己执行,且可以循环一个动画。它也类似于flash的补间动画,但是它可以设置多个关键帧(用@keyframe定义)完成动画。
伪类是通过在元素选择器上加⼊伪类改变元素状态,⽽伪元素通过对元素的操作进⾏对元素的改变。
伪元素: 在内容元素的前后插入额外的元素或样式,但是这些元素实际上并不在文档中生成。它们只在外部显示可见,但不会在文档的源代码中找到它们,因此,称为“伪”元素。例如:
p::before {content:"第一章:";}
p::after {content:"Hot!";}
p::first-line {background:red;}
p::first-letter {font-size:30px;}
**伪类:**将特殊的效果添加到特定选择器上。它是已有元素上添加类别的,不会产生新的元素。
a:hover {color: #FF00FF}
p:first-child {color: red}
盒模型由内容(content)、内边距(padding)、边框(border)、外边距(margin)组成。盒模型分为IE盒模型和W3C标准盒模型。
W3C标准盒模型 又叫content-box,元素宽度/高度由border+padding+content组成。(属性width,height只包含内容content,不包含border和padding)
IE盒模型 又叫border-box,元素宽度/高度由content组成。(属性width,height包含border和padding,指的是content+padding+border。)
box-sizing: content-box表示标准盒模型(默认值)
box-sizing: border-box表示IE盒模型(怪异盒模型)
使用场景:
通用方案
使用媒体查询,根据不同设备按比例设置html文字大小,然后页面元素使用rem作为尺寸单位,当html大小改变时,元素也会发生改变,从而达到等比缩放的适配
以750的尺寸为例,把屏幕划分为15等份,那么html根字号的大小就是750/15=50px。rem就是元素的px/根子号。100px的宽度就等于100/50=2rem
优: 有一定适用性,换算也较为简单
劣: 有兼容性的坑,对不同手机适配不是非常精准;需要设置多个媒体查询来适应不同 手机,单某款手机尺寸不在设置范围之内,会导致无法适配
网易方案
拿到设计稿除以 100,得到宽度 rem 值
通过给 html 的 style 设置 font-size,把 1 里面得到的宽度 rem 值代入 x document.documentElement.style.fontSize = document.documentElement.clientWidth / x + ‘px’;
设计稿 px/100 即可换算为 rem
优: 通过动态根 font-size 来做适配,基本无兼容性问题,适配较为精准,换算简便
劣: 无 viewport 缩放,且针对 iPhone 的 Retina 屏没有做适配,导致对一些手机的适配不是很到位
手淘方案
拿到设计稿除以 10,得到 font-size 基准值
引入 flexible
不要设置 meta 的 viewport 缩放值
设计稿 px/ font-size 基准值,即可换算为 rem
优: 通过动态根 font-size、viewport、dpr 来做适配,无兼容性问题,适配精准。
劣: 需要根据设计稿进行基准值换算,在不使用 sublime text 编辑器插件开发时, 单位计算复杂
加载性能:
选择器性能:
渲染性能:
可维护性、健壮性:
overflow: hidden; // 溢出隐藏
text-overflow: ellipsis; // 溢出用省略号显示
white-space: nowrap; // 规定段落中的文本不进行换行
overflow: hidden; // 溢出隐藏
text-overflow: ellipsis; // 溢出用省略号显示
display:-webkit-box; // 作为弹性伸缩盒子模型显示。
-webkit-box-orient:vertical; // 设置伸缩盒子的子元素排列方式:从上到下垂直排列
-webkit-line-clamp:3; // 显示的行数
用CSS 实现长宽为浏览器窗口一半的正方形
已知父元素宽高用%
width: 50%;
padding-top: 50%;
background-color: red;
用vw
width: 50vw;
height: 50vh;
background-color: red;
这个可以用伪类来实现
.line::before {
display: block;
content: "";
height: 1px;
left: -50%;
position: absolute;
background-color: #333333;
width: 200%; //设置为插入元素的两倍宽高
-webkit-transform: scale(0.5);
transform: scale(0.5);
box-sizing: border-box;
}
向上
width:0;
height:0;
border-left:30px solid transparent;
border-right:30px solid transparent;
border-bottom:30px solid red;
用CSS实现扇形的思路和三角形基本一致,就是多了一个圆角的样式,实现一个90°的扇形:
div{
border: 100px solid transparent;
width: 0;
heigt: 0;
border-radius: 100px;
border-top-color: red;
}
什么是BFC?
BFC全称是Block Formatting Context,意思就是块级格式化上下文。你可以把BFC看做一个容器,容器里边的元素不会影响到容器外部的元素。
BFC有什么特性?
如何创建BFC?
给父级元素添加以下任意样式
BFC有什么作用?
页面布局常用的方法有浮动、定位、flex、grid网格布局、栅格系统布局
浮动:
优点:兼容性好。
缺点:浮动会脱离标准文档流,因此要清除浮动。我们解决好这个问题即可。
绝对定位
优点:快捷。
缺点:导致子元素也脱离了标准文档流,可实用性差。
flex 布局(弹性盒)
优点:解决上面两个方法的不足,flex布局比较完美。移动端基本用 flex布局。
阮一峰Flex 布局教程
网格布局(grid)
CSS3中引入的布局,很好用。代码量简化了很多。
栅格系统布局
优点:可以适用于多端设备
一般两栏布局指的是左边一栏宽度固定,右边一栏宽度自适应,两栏布局的具体实现: 两栏布局
三栏布局一般指的是页面中一共有三栏,左右两栏宽度固定,中间自适应的布局,三栏布局的具体实现:三栏布局
1、利用定位实现两侧固定中间自适应
1.1)父盒子设置左右 padding 值
1.2)给左右盒子的 width 设置父盒子的 padding 值,然后分别定位到 padding 处.
1.3)中间盒子自适应
2、利用 flex 布局实现两侧固定中间自适应
2.1)父盒子设置 display:flex;
2.2)左右盒子设置固定宽高
2.3)中间盒子设置 flex:1 ;
3、利用 bfc 块级格式化上下文, 实现两侧固定中间自适应
3.1)左右固定宽高,进行浮动
3.2)中间 overflow: hidden;
.father {
position: relative;
}
.child {
position: absolute;
left: 50%; top: 50%;
transform: translate(-50%,-50%);
}
.father {
position: relative;
}
.child {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
}
.father {
position: relative;
}
.child {
position: absolute;
top: 50%;
left: 50%;
margin-top: -50px; /* 自身 height 的一半 */
margin-left: -50px; /* 自身 width 的一半 */
}
.father {
display: flex;
justify-content:center;
align-items:center;
}
响应式网站设计(Responsive Web design)是一个网站能够兼容多个终端,而不是为每一个终端做一个特定的版本。
关于原理: 基本原理是通过媒体查询(@media)查询检测不同的设备屏幕尺寸做处理。 关于兼容: 页面头部必须有mate声明的viewport。
<meta name="’viewport’" content="”width=device-width," initial-scale="1." maximum-scale="1,user-scalable=no”"/>
基本类型
引用数据类型
注:**Object.prototype.toString.call()**适用于所有类型的判断检测
参考详情
let const参考详情
ES6新特性?
了解关于es6的更多知识可以看阮一峰——ES6 入门教程
1、数组
2、栈
3、队列
4、链表
5、树
6、散列表
7、堆
8、图
八大数据结构分类 参考详见
在操作系统中,内存被分为栈区和堆区
在数据结构中:
数据的储存方式
理解:主要是为了设计私有的方法和变量。
1、尽量避开,不使用闭包,或者不同的使用场景使用对应的替代技术
2、在可能存在泄漏的地方把标识符引用null ==> re=null
数据类型的方法一般可以通过:typeof、instanceof、constructor、toString 四种常用方法
console.log(typeof("123")) // string
let str = "123"
console.log(str instanceof String) //true
console.log((10).constructor) // Number() { [native code] }
- instanceof的实现原理:
验证当前类的原型prototype是否会出现在实例的原型链__proto__上,只要在它的原型链上,则结果都为true。因此,instanceof 在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype,找到返回true,未找到返回false。
- Object.prototype.toString.call原理
Object.prototype.toString 表示一个返回对象类型的字符串,call()方法可以改变this的指向,那么把Object.prototype.toString()方法指向不同的数据类型上面,返回不同的结果
<script>
var exp1 ;
console.log(typeof exp1);//undefined
console.log(typeof exp2);//undefined
console.log(exp1 == undefined);//true
console.log(exp2 == undefined);//报错
console.log(typeof exp1 == 'undefined');//true
console.log(typeof exp2 == 'undefined');//true typeof运算符可以用于未被声明的变量
</script>
var func = function(){
console.log("你好");
}
var obj = {"name":"john"};
console.log(typeof 5); //number
console.log(typeof 'name'); //string
console.log(typeof false); //boolean
console.log(typeof null); // object
console.log(typeof undefined); // undefined
console.log(typeof func); // object
console.log(typeof obj); // object
//所有的返回值的类型都会 字符串 类型,注意都是小写字母;但是有一点缺陷就是函数和对象以及DOM对象返回的值都是object,所以typeof用来监测数据类型的时候,如果监测基本数据类型还比较可靠,但是监测对象的时候却无太大作用。
</script>
在 JavaScript 第一个版本中,所有值都存储在 32 位的单元中,每个单元包含一个小的 类型标签(1-3 bits) 以及当前要存储值的真实数据。类型标签存储在每个单元的低位中,共有五种数据类型:
000: object - 当前存储的数据指向一个对象。
1: int - 当前存储的数据是一个 31 位的有符号整数。
010: double - 当前存储的数据指向一个双精度的浮点数。
100: string - 当前存储的数据指向一个字符串。
110: boolean - 当前存储的数据是布尔值。
如果最低位是 1, 则类型标签标志位的长度只有一位, 如果最低位是 0, 则类型标签标志位的长度占三位, 为存储其他四种数据类型提供了额外两个 bit 的长度。
有两种特殊数据类型:
那也就是说 null 的类型标签也是 000,和 Object 的类型标签一样, 所以会被判定为 Object。
|| 和 && 首先会对第一个操作数执行条件判断,如果其不是布尔值就先强制转换为布尔类型,然后再执行条件判断。
|| 和 && 返回它们其中一个操作数的值,而非条件判断的结果
if(typeof obj == 'undefined'){
var obj = {}
}
if (myObj === undefined) {
var myObj = { };
}
相同点: 都是判定两个值是否相等 不同点:== 只比较值不比较类型,而 ===会判断类型
原型
①所有引用类型都有一个__proto__(隐式原型)属性,属性值是一个普通的对象
②所有函数都有一个prototype(原型)属性,属性值是一个普通的对象
③所有引用类型的__proto__属性指向它构造函数的prototype
var a = [1,2,3];
a.__proto__ === Array.prototype; // true
原型链
当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果没有找到,则会去它的__proto__隐式原型上查找,即它的构造函数的prototype,如果还没有找到就会再在构造函数的prototype的__proto__中查找,这样一层一层向上查找就会形成一个链式结构,我们称为原型链。
原型,原型链 参考详见
常见的:
this是一个在运行时才进行绑定的引用,在不同的情况下它可能会被绑定不同的对象。
第一种是函数调用模式,当一个函数不是一个对象的属性时,直接作为函数来调用时,this 指向全局对象。
第二种是方法调用模式,如果一个函数作为一个对象的方法来调用时,this 指向这个对象。
第三种是构造器调用模式,如果一个函数用 new 调用时,函数执行前会新创建一个对象,this 指向这个新创建的对象。
第四种是 apply 、 call 和 bind 调用模式,这三个方法都可以显示的指定调用函数的 this 指向。其中 apply 方法接收两个参数:一个是 this 绑定的对象,一个是参数数组。call 方法接收的参数,第一个是 this 绑定的对象,后面的其余参数是传入函数执行的参数。也就是说,在使用 call() 方法时,传递给函数的参数必须逐个列举出来。bind 方法通过传入一个对象,返回一个 this 绑定了传入对象的新函数。这个函数的 this 指向除了使用 new 时会被改变,其他情况下都不会改变。
this绑定的优先级
new绑定优先级 > 显示绑定优先级 > 隐式绑定优先级 > 默认绑定优先级
js 数组详细操作方法及解析合集 --掘金
JavaScript常用数组操作方法,包含ES6方法
ES6——数组(方法总结)
————————————————
是否会改变原数组
会改变: push()pop(),shift(),unshift(),splice(),sort(),reverse()。
不变: concat(),split(),slice()。
参考详见
借助ES6提供的Set结构 new Set() 简单好用 强烈推荐
直接给一个新的数组里面,利用es6的延展运算符
var arr = [1,9,8,8,7,2,5,3,3,3,2,3,1,4,5];
console.log(arr);
function noRepeat(arr){
var newArr = [...new Set(arr)]; //利用了Set结构不能接收重复数据的特点
return newArr
}
var arr2 = noRepeat(arr)
console.log(arr2);
利用 filter() 去重
var arr =['apple','apps','pear','apple','orange','apps'];
var newArr = arr.filter(function(item,index){
return arr.indexOf(item) === index; // 因为indexOf 只能查找到第一个
});
console.log(newArr);
利用双重for循环
var arr = [1,9,8,8,7,2,5,3,3,3,2,3,1,4,5,444,55,22];
console.log(arr);
function noRepeat(arr){
for (var i = 0; i < arr.length; i++) {
for (var j = 0; j < arr.length; j++) {
if (arr[i] == arr[j] && i != j) {
//将后面重复的数删掉
arr.splice(j, 1);
}
}
}
return arr;
}
var arr2 = noRepeat(arr);
console.log(arr2);
注 :如果有多维数组如 [1,[2],[3,[2,3,4,5]] ] 先扁平化再去重,用Array.flat(Infinity)实现扁平化。
var arr = [123,203,23,13,34,65,65,45,89,13,1];
function func(a,b){
return a-b;
}
console.log(arr.sort(func));
//循环递归
var arr = [[1, 2, 8, [6, 7]], 3, [3, 6, 9], 4]
function getNewArr(arr) {
// 定义新数组用于存储所有元素
var newArr = []
// 遍历原数组中的每个元素
for (var i = 0; i < arr.length; i++) {
// 判断当前元素是否为数组
if (Array.isArray(arr[i])) {
// 若当前元素为数组时,调用函数本身继续判断,通过 concat 方法连接函数返回的数组
newArr = newArr.concat(getNewArr(arr[i]))
} else { // 若不是数组直接将当前元素追加到新数组的末尾
newArr.push(arr[i])
}
}
// 循环结束将新数组返回
return newArr
}
console.log(arr) // [[1, 2, 8, [6, 7]], 3, [3, 6, 9], 4]
console.log(getNewArr(arr)) // [1, 2, 8, 6, 7, 3, 3, 6, 9, 4]
//flat()
let arr = [[1,2],[3,4],[[6,7],[[8],[9,10]]]];
let el = arr.flat(Infinity);
console.log(el); // [1, 2, 3, 4, 6, 7, 8, 9, 10];
// 利用arr.some判断当数组中还有数组的话,递归调用flatten扁平函数(利用es6展开运算符扁平), 用concat连接,最终返回arr;
function steamroller4(arr){
while(arr.some(item=> Array.isArray(item))){
// arr=[].concat.apply([],arr)
arr=[].concat(...arr)
}
return arr
}
console.log(steamroller4(arr))
//toString、split和map结合
var arr = [[1, 2, 8, [6, 7]], 3, [3, 6, 9], 4]
function getNewArr(arr) {
var newArr = arr.toString().split(',').map(function(item) {
// 使用 + 号将当前元素转成数字
return +item
})
return newArr
}
console.log(getNewArr(arr)) // [1, 2, 8, 6, 7, 3, 3, 6, 9, 4]
let arr = [2, 2, 1, 45, 58, [154, 4, 14, 14, [4587, 45, 125, [145, 20, 145, 23, [12, 10]]]]]
arr = Array.from(new Set(arr.flat(Infinity))).sort((a, b) => a - b)
console.log(arr)
参考详见
for…of 是ES6新增的遍历方式,允许遍历一个含有iterator接口的数据结构(数组、对象等)并且返回各项的值,和ES3中的for…in的区别如下
总结: for…in 循环主要是为了遍历对象而生,不适用于遍历数组;for…of 循环可以用来遍历数组、类数组对象,字符串、Set、Map 以及 Generator 对象。
1、相同点
2、不同点
3.使用场景
性能上来说for>forEach>map
- 冒泡排序: 比较所有相邻元素,如果第一个比第二个大,则交换它们。
- 选择排序: 找到数组中的最小值,选中它并将其放置在第一位。
- 插入排序: 从第二个数开始往前比,比它大就往后排。
- 归并排序: 把数组劈成两半,再递归地对数组进行“分”操作,直到分成一个个单独的数。
- 快速排序: 从数组中任意选择一个基准,所有比基准小的元素放到基准前面,比基准大的元素放到基准的后面。
参考详见
原因:JavaScript是单线程,所有任务需要排队,前一个任务结束,才会执行后一个任务。
所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。
同步任务: 在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
异步任务: 不进入主线程、而进入"任务队列"的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
同步和异步任务分别进入不同的执行环境, 先执行同步任务,把异步任务放入循环队列当中挂起,等待同步任务执行完,再执行队列中的异步任务。异步任务先执行微观任务,再执行宏观任务。一直这样循环,反复执行。
事件循环Event Loop又叫事件队列,两者是一个概念
微任务: Promise.then、catch、finally、async/await。
宏任务: 整体代码 Script、UI 渲染、setTimeout、setInterval、Dom事件、ajax事件。
详情可看JavaScript 运行机制详解:再谈Event Loop
注:js中同步队列、异步队列
同步: 前一个任务结束后再执行后一个任务,程序的执行顺序与任务的排列顺序是一致的,同步的;同步任务都在主线程上执行,形成一个执行栈;
异步: 同时执行多个任务,异步任务(大部分异步任务为回调函数)会被放在任务队列当中
回调函数的层层嵌套,就叫做回调地狱。回调地狱会造成代码可复用性不强,可阅读性差,可维护性(迭代性差),扩展性差等等问题。
async function async1() {
console.log("async1 start") //二
await async2()
console.log("async1 end") //六
}
async function async2() {
console.log("async2") //三
}
console.log("script start") //一
setTimeout(function () {
console.log("setTimeOut") //八
}, 0)
async1()
new Promise(function (resolve) {
console.log("promise1") //四
resolve()
}).then(function () {
console.log("promise2") //七
})
console.log("script end") //五
事件参考详见
setTimeout(function () {
console.log("setTimeOut1") //六
new Promise(function (resolve) {
resolve()
}).then(function () {
new Promise(function (resolve) {
resolve()
}).then(function () {
console.log("then4") //八
})
console.log("then2") //七
})
})
new Promise(function (resolve) {
console.log("promise1") //一
resolve()
}).then(function () {
console.log("then1") //三
})
setTimeout(function () {
console.log("setTimeOut2") //九
})
console.log(2) //二
queueMicrotask(() => {
console.log("queueMicrotask1") //四
})
new Promise(function (resolve) {
resolve()
}).then(function () {
console.log("then3") //五
})
Promise参考详见
promise本身只是一个容器,真正异步的是它的两个回调resolve()和reject()
promise本质 不是控制 异步代码的执行顺序(无法控制) , 而是控制异步代码结果处理的顺序
(1)有三种状态
当把一件事情交给promise时,它的状态就是Pending,任务完成了状态就变成了Resolved、没有完成失败了就变成了Rejected。
如何改变 promise 的状态
resolve(value): 如果当前是 pending 就会变为 resolved
reject(error): 如果当前是 pending 就会变为 rejected
抛出异常: 如果当前是 pending 就会变为 rejected
注意: 一旦从进行状态变成为其他状态就永远不能更改状态了。
new Promise((resolve,reject)=>{ … })
一般情况下都会使用new Promise()来创建promise对象,但是也可以使用promise.resolve和promise.reject这两个方法:
Promise.resolve
Promise.resolve(value)的返回值也是一个promise对象,可以对返回值进行.then调用,代码如下:
//成功函数
Promise.resolve(11).then(function(value){
console.log(value); // 打印出11
});
Promise.reject
Promise.reject 也是new Promise的快捷形式,也创建一个promise对象。代码如下:
//失败函数
Promise.reject(new Error(“出错了!!”));
then
then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。其中第二个参数可以省略。 then方法返回的是一个新的Promise实例(不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
catch (Promise原型上的)
该方法相当于then方法的第二个参数,指向reject的回调函数。不过catch方法还有一个作用,就是在执行resolve回调函数时,如果出现错误,抛出异常,不会停止运行,而是进入catch方法中。
finally
finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。
服务器使用 Promise 处理请求,然后使用finally方法关掉服务器。
server.listen(port)
.then(function () {
// ...
})
.finally(server.stop);
all
all方法可以完成并发任务, 它接收一个数组,数组的每一项都是一个promise对象,返回一个Promise实例。当数组中所有的promise的状态都达到resolved的时候,all方法的状态就会变成resolved,如果有一个状态变成了rejected,那么all方法的状态就会变成rejected。
race
race方法和all一样,接受的参数是一个每项都是promise的数组,但是与all不同的是,当最先执行完的事件执行完之后,就直接返回该promise对象的值。如果第一个promise对象状态变成resolved,那自身的状态变成了resolved;反之第一个promise变成rejected,那自身状态就会变成rejected。
any
它接收一个数组,数组的每一项都是一个promise对象,该方法会返回一个新的 promise,数组内的任意一个 promise 变成了resolved状态,那么由该方法所返回的 promise 就会变成resolved状态。如果数组内的 promise 状态都是rejected,那么该方法所返回的 promise 就会变成rejected状态,
resolve、reject
用来生成对应状态的Promise实例
all: 成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。
race: 哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
any: 返回最快的成功结果,如果全部失败就返回失败结果。
简单表达: 由 then()指定的回调函数执行的结果决定
详细表达:
如果抛出异常, 新 promise 变为 rejected, 参数为抛出的异常
如果返回的是非 promise 的任意值, 新 promise 变为 resolved, value 为返回的值
如果返回的是另一个新 promise, 此 promise 的结果就会成为新 promise 的结果
参考详见
async 函数它就是 Generator 函数的语法糖,也就是处理异步操作的另一种高级写法
async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。
async function fn(args) {
// ...
}
// 等同于
function fn(args) {
return spawn(function* () { // spawn函数就是自动执行器
// ...
});
}
await 等待的是一个表达式,这个表达式的计算结果是 Promise 对象或者其它值(换句话说,就是没有特殊限定)。await 不仅仅用于等 Promise 对象,它可以等任意表达式的结果,所以,await 后面实际是可以接普通函数调用或者直接量的。
await 表达式的运算结果取决于它等的是什么。
从 ES2022 开始,允许在模块的顶层独立使用await命令,使得上面那行代码不会报错了。它的主要目的是使用await解决模块异步加载的问题。
import { AsyncFun } from 'module'
await AsyncFun()
console.log(123)
async函数内部的异常可以通过 .catch()或者 try/catch来捕获,区别是
async函数错误捕获,以登录功能为例
async function getCatch () {
await new Promise(function (resolve, reject) {
reject(new Error('登录失败'))
}).catch(error => {
console.log(error) // .catch()能捕获到错误信息
})
console.log('登录成功') // 但是成功信息也会执行
}
async function getCatch () {
try {
await new Promise(function (resolve, reject) {
reject(new Error('登录失败'))
})
console.log('登录成功') // try抛出错误之后,就不会执行这条语句
} catch (error) {
console.log(error) // catch语句能捕获到错误信息
}
}
1.Promise的出现解决了传统callback函数导致的“地狱回调”问题,但它的语法导致了它向纵向发展行成了一个回调链,遇到复杂的业务场景,这样的语法显然也是不美观的。而async await代码看起来会简洁些,使得异步代码看起来像同步代码,await的本质是可以提供等同于”同步效果“的等待异步返回能力的语法糖,只有这一句代码执行完,才会执行下一句。
2.async await与Promise一样,是非阻塞的。
3.async await是基于Promise实现的,可以说是改良版的Promise,它不能用于普通的回调函数。
详情参考
Set、Map、WeakSet、WeakMap是ES2015中新增的几个对象:
set类似于数组,但是成员的值都是唯一的,没有重复的值。
Set本身是一个构造函数,用来生成 Set 数据结构。
Set和WeakSet与数组类似,准确的它他们是集合,这两者的区别就是Set可以存储任何数据类型,而WeakSet只能存储对象的引用,而且是弱引用;
Set对象在实际开发中最常见的就是实现数据去重,示例代码如下:
const arr = [1, 2, 2, 3, 4, 3, 5]
const set = new Set(arr)
// set对象可以使用 ... 展开 所有项
console.log([...set]) // [ 1, 2, 3, 4, 5 ]
map类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。
Map 和WeakMap 与对象类似,存储方式是键值对形式的,这两者的区别Map的键值对都是可以是任意的而WeakMap键必须是对象的引用而值可以是任意类型的。
创建临时对象,并将this指向临时对象
//创建节点
document.createElement("div");
//创建一个文本节点
document.createTextNode("content");
//用来创建一个文档碎片
document.createDocumentFragment();
//创建属性节点
document.createAttribute('custom');
//获取节点 传入任何有效的css 选择器,即可选中单个 DOM元素(首个)
document.querySelector('.element')
document.querySelector('#element')
document.querySelector('div')
document.querySelector('[name="username"]')
document.querySelector('div + p > span')
//所有
document.querySelectorAll("p");
//获取DOM元素的方法
document.getElementById('id属性值');返回拥有指定id的对象的引用
document.getElementsByClassName('class属性值');返回拥有指定class的对象集合
document.getElementsByTagName('标签名');返回拥有指定标签名的对象集合
document.getElementsByName('name属性值'); 返回拥有指定名称的对象结合
document/element.querySelector('CSS选择器'); 仅返回第一个匹配的元素
document/element.querySelectorAll('CSS选择器'); 返回所有匹配的元素
document.documentElement; 获取页面中的HTML标签
document.body; 获取页面中的BODY标签
document.all['']; 获取页面中的所有元素节点的对象集合型
//更新节点
innerHTML
//删除节点 删除一个节点,首先要获得该节点本身以及它的父节点,然后,调用父节点的removeChild把自己删掉
**removeChild**
// 拿到待删除节点:
const self = document.getElementById('to-be-removed');
// 拿到父节点:
const parent = self.parentElement;
// 删除:
const removed = parent.removeChild(self);
removed === self; // true
⼜称为事件传播,是⻚⾯中接收事件的顺序。DOM2级事件规定的事件流包括了3个阶段:
1.事件捕获阶段,为截获事件提供了机会
2.实际的⽬标元素接收到事件
3.事件冒泡阶段,可在这个阶段对事件做出响应
事件开始由最具体的元素(⽂档中嵌套层次最深的那个节点)接收到后,开始逐级向上传播到较为不具体的节点。
如果点击了上面页面代码中的 按钮,那么该 click 点击事件会沿着 DOM 树向上逐级传播,在途经的每个节点上都会发生,具体顺序如下:
事件开始由较为不具体的节点接收后,然后开始逐级向下传播到最具体的元素上。
事件捕获的最大作用在于:事件在到达预定⽬标之前就可以捕获到它。
如果仍以上面那段 HTML 代码为例,当点击按钮后,在事件捕获的过程中,document 对象会首先接收到这个 click 事件,然后再沿着 DOM 树依次向下,直到 。具体顺序如下:
事件委托,就是利用了事件冒泡的机制,在较上层位置的元素上添加一个事件监听函数,来管理该元素及其所有子孙元素上的某一类的所有事件。
适用场景:在绑定大量事件的时候,可以选择事件委托
优点
阻止事件冒泡
e.stopPropagation**()
阻止默认事件,3种方式
e.preventDefault();//谷歌及IE8以上
window.event.returnValue = false; //IE8及以下
return false; //无兼容问题(但不能用于节点直接onclick绑定函数)
浅拷贝
常见的浅拷贝:
参考详见
深拷贝
深拷贝开辟一个新的栈,两个对象属完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性
常见的深拷贝方式有:
function deepCopy1(obj) {
let o = {}
for(let key in obj) {
o[key] = obj[key]
}
return o
}
let obj = {
a:1,
b: undefined,
c:function() {},
}
console.log(deepCopy1(obj))
function deepClone(obj){
let objClone = Array.isArray(obj)?[]:{};
if(obj && typeof obj==="object"){
for(key in obj){
if(obj.hasOwnProperty(key)){
//判断ojb子元素是否为对象,如果是,递归复制
if(obj[key]&&typeof obj[key] ==="object"){
objClone[key] = deepClone(obj[key]);
}else{
//如果不是,简单复制
objClone[key] = obj[key];
}
}
}
}
return objClone;
}
let a = [1,2,3,4]
let b = deepClone(a);
a[0] = 2;
console.log(a,b);
1、使用 location.href:window.location.href =“url”
2、使用 location.replace: window.location.replace(“url”);
对于算法来说 无非就是时间换空间 空间换时间
1、深度优先不需要记住所有的节点, 所以占用空间小, 而广度优先需要先记录所有的节点占用空间大
2、深度优先有回溯的操作(没有路走了需要回头)所以相对而言时间会长一点
3、深度优先采用的是堆栈的形式, 即先进后出
4、广度优先则采用的是队列的形式, 即先进先出
什么是箭头函数,有什么特征
使用 “箭头” ( => ) 来定义函数. 箭头函数相当于匿名函数, 并且简化了函数定义
箭头函数的特征:
共同点 :
不同点:
应用场景
调用时间
// CommonJS 的写法
const moduleA = require('moduleA');
const func1 = moduleA.func1;
const func2 = moduleA.func2;
// ES6 的写法
import { func1, func2 } from 'moduleA';
使用export取代module.exports。
// commonJS 的写法
var React = require('react');
var Breadcrumbs = React.createClass({
render() {
return <nav />;
}
});
module.exports = Breadcrumbs;
// ES6 的写法
import React from 'react';
class Breadcrumbs extends React.Component {
render() {
return <nav />;
}
};
export default Breadcrumbs;
参考详见1
参考详见2
HTTP:客户端与服务器之间数据传输的格式规范,表示“超文本传输协议”。
TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接。
表面区别
PUT请求 是向服务器端发送数据,从而修改数据的内容,但是不会增加数据的种类等,也就是说无论进行多少次PUT操作,其结果并没有不同。(可以理解为时更新数据)
POST请求 是向服务器端发送数据,该请求会改变数据的种类等资源,它会创建新的内容。(可以理解为是创建数据)
含义:从建立连接到断开连接一共七个步骤,就是三次招手四次挥手
http1.0默认关闭,需要手动开启。http1.1后默认开启
作用: 使客户端到服务器端的链接持续有效(长连接),当出现对服务器的后续请求时,keep-Alive功能避免了建立或者重新建立链接。
使用方法: 在请求头中加上Connection:keep-alive。
优点:
缺点: 本来可以释放的资源仍旧被占用。有的请求已经结束了,但是还一直连接着。
解决方法: 服务器设置过期时间和请求次数,超过这个时间或者次数就断掉连接。
ISO于1978年开发的一套标准架构ISO模型,被引用来说明数据通信协议的结构和功能。
OSI在功能上可以划分为两组:
网络群组:物理层、数据链路层、网络层
使用者群组:传输层、会话层、表示层、应用层
OSI七层网络模型 | TCP/IP四层概念模型 | 对应网络协议 |
---|---|---|
7:应用层 | 应用层 | HTTP、RTSP TFTP(简单文本传输协议)、FTP、 NFS(数域筛法,数据加密)、WAIS`(广域信息查询系统) |
6:表示层 | 应用层 | Telnet(internet远程登陆服务的标准协议)、Rlogin、SNMP(网络管理协议)、Gopher |
5:会话层 | 应用层 | SMTP(简单邮件传输协议)、DNS(域名系统) |
4:传输层 | 传输层 | TCP(传输控制协议)、UDP(用户数据报协议)) |
3:网络层 | 网际层 | ARP(地域解析协议)、RARP、AKP、UUCP(Unix to Unix copy) |
2:数据链路层 | 数据链路层 | FDDI(光纤分布式数据接口)、Ethernet、Arpanet、PDN(公用数据网)、SLIP(串行线路网际协议)PPP(点对点协议,通过拨号或专线方建立点对点连接发送数据) |
1:物理层 | 物理层 | SMTP(简单邮件传输协议)、DNS(域名系统) |
其中高层(7、6、5、4层)定义了应用程序的功能,下面三层(3、2、1层)主要面向通过网络的端到端的数据流
对称加密和非对称加密是安全传输层里的加密算法
对称加密
对称加密的特点是文件加密和解密使用相同的密钥,即加密密钥也可以用作解密密钥,这种方法在密码学中叫做对称加密算法,对称加密算法使用起来简单快捷,密钥较短,且破译困难通信的双⽅都使⽤同⼀个秘钥进⾏加密, 解密。 ⽐如,两个人事先约定的暗号,就属于对称加密。
优点:
计算量小、加密速度快、加密效率高。
缺点:
在数据传送前,发送方和接收方必须商定好秘钥,然后双方保存好秘钥。如果一方的秘钥被泄露,那么加密信息也就不安全了
使用场景: 本地数据加密
非对称加密
通信的双方使用不同的秘钥进行加密解密,即秘钥对(私钥 + 公钥)。
特征: 私钥可以解密公钥加密的内容, 公钥可以解密私钥加密的内容
优点:
非对称加密与对称加密相比其安全性更好
缺点:
加密和解密花费时间长、速度慢,只适合对少量数据进行加密。
使用场景: https 会话前期、CA 数字证书、信息加密、登录认证等
token也可以称做令牌,一般由 uid+time+sign(签名)+[固定参数] 组成
uid: 用户唯一身份标识
time: 当前时间的时间戳
sign: 签名, 使用 hash/encrypt 压缩成定长的十六进制字符串,以防止第三方恶意拼接
固定参数(可选): 将一些常用的固定参数加入到 token 中是为了避免重复查库
token在客户端一般存放于localStorage,cookie,或sessionStorage中。在服务器一般存于数据库中
token 的认证流程
用户登录,成功后服务器返回Token给客户端。
客户端收到数据后保存在客户端
客户端再次访问服务器,将token放入headers中 或者每次的请求 参数中
服务器端采用filter过滤器校验。校验成功则返回请求数据,校验失败则返回错误码
token可以抵抗csrf,cookie+session不可以
session时有状态的,一般存于服务器内存或硬盘中,当服务器采用分布式或集群时,session就会面对负载均衡问题。负载均衡多服务器的情况,不好确认当前用户是否登录,因为多服务器不共享session
客户端登陆传递信息给服务端,服务端收到后把用户信息加密(token)传给客户端,客户端将token存放于localStroage等容器中。客户端每次访问都传递token,服务端解密token,就知道这个用户是谁了。通过cpu加解密,服务端就不需要存储session占用存储空间,就很好的解决负载均衡多服务器的问题了。这个方法叫做JWT(Json Web Token)
什么是无感刷新
后台返回的token是有时效性的,时间到了,你在交互后台的时候,后台会判断你的token是否过期(安全需要),如果过期了就会逼迫你重新登陆!
token无感刷新其本质是为了优化用户体验,当token过期时不需要用户跳回登录页重新登录,而是当token失效时,进行拦截,发送刷新token的ajax,获取最新的token进行覆盖,让用户感受不到token已经过期
实现无感刷新
1、后端返回过期时间,前端判断token过期时间,去调用刷新token接口。
缺点:需要后端额外提供一个Token过期时间的字段;使用了本地时间判断,若本地时间篡改,特别是本地时间比服务器时间慢时,拦截会失败。
2、写个定时器,定时刷新Token接口。缺点:浪费资源,消耗性能,不建议采用。
3、在响应拦截器中拦截,判断Token 返回过期后,调用刷新token接口。
从本质上说,进程和线程都是 CPU 工作时间片的一个描述:
进程是资源分配的最小单位,线程是CPU调度的最小单位。
cookie: 其实最开始是服务器端用于记录用户状态的一种方式,由服务器设置,在客户端存储,然后每次发起同源请求时,发送给服务器端。cookie 最多能存储 4 k 数据,它的生存时间由 expires 属性指定,并且 cookie 只能被同源的页面访问共享。
sessionStorage: html5 提供的一种浏览器本地存储的方法,它借鉴了服务器端 session 的概念,代表的是一次会话中所保存的数据。它一般能够存储 5M 或者更大的数据,它在当前窗口关闭后就失效了,并且 sessionStorage 只能被同一个窗口的同源页面所访问共享。
localStorage: html5 提供的一种浏览器本地存储的方法,它一般也能够存储 5M 或者更大的数据。它和 sessionStorage 不同的是,除非手动删除它,否则它不会失效,并且 localStorage 也只能被同源页面所访问共享。
API localStorage和sessionStorageAPI是类似的
xxxxxStorage.setItem('key',value)
// 接收一个键和值作为参数,把键值对添加到存储中
// 如果键名存在,则更新其对应的值。
xxxxxStorage.getItem('key')
// 接收一个键名作为参数,返回键名对应的值
xxxxxStorage.removeItem('key')
// 接收一个键名作为参数,并把该键名从存储中删除
xxxxxStorage.clear()
// 清空存储中的所有数据
同源策略: protocol(协议)、domain(域名)、port(端口)三者必须一致。
跨域 指的是浏览器不能执行其他网站的脚本,它是由浏览器的同源策略造成的,是浏览器对 javascript 施加的安全限制,防止他人恶意攻击网站,跨域问题 其实就是浏览器的同源策略造成的。
常见的:
1、JSONP跨域
原理:利用script标签可以跨域请求资源,将回调函数作为参数拼接在url中。后端收到请求,调用该回调函数,并将数据作为参数返回去,注意设置响应头返回文档类型,应该设置成javascript。只支持 GET 请求
2、跨域资源共享(CORS)
目前最常用的一种解决办法,通过设置后端允许跨域实现。
3、nginx反向代理
跨域限制的时候浏览器不能跨域访问服务器,node中间件和nginx反向代理,都是让请求发给代理服务器,静态页面面和代理服务器是同源的,然后代理服务器再向后端服务器发请求,服务器和服务器之间不存在同源限制。
4、WebSocket协议跨域 websocket是一种网络通信协议,是HTML5开始提供的一种在单个TCP连接上进行全双工通信的协议,这个对比着HTTP协议来说,HTTP协议是一种无状态的、无连接的、单向的应用层协议,通信请求只能由客户端发起,服务端对请求做出应答处理。
HTTP协议无法实现服务器主动向客户端发起消息,websocket连接允许客户端和服务器之间进行全双工通信,以便任一方都可以通过建立的连接将数据推送到另一端。websocket只需要建立一次连接,就可以一直保持连接状态
5、proxy
前端配置一个代理服务器(proxy)代替浏览器去发送请求:因为服务器与服务器之间是可以通信的不受同源策略的影响。
详细可看九种常见的前端跨域解决办法
6、iframe跨域
7、postMessage
参考详见
CDN(Content Delivery Network,内容分发网络) 是指一种通过互联网互相连接的电脑网络系统,利用最靠近每位用户的服务器,更快、更可靠地将音乐、图片、视频、应用程序及其他文件发送给用户,来提供高性能、可扩展性及低成本的网络内容传递给用户。
CDN的作用
CDN一般会用来托管Web资源(包括文本、图片和脚本等),可供下载的资源(媒体文件、软件、文档等),应用程序(门户网站等)。使用CDN来加速这些资源的访问。
CDN的使用场景
使用第三方的CDN服务: 如果想要开源一些项目,可以使用第三方的CDN服务
使用CDN进行静态资源的缓存: 将自己网站的静态资源放在CDN上,比如js、css、图片等。可以将整个项目放在CDN上,完成一键部署。
直播传送: 直播本质上是使用流媒体进行传送,CDN也是支持流媒体传送的,所以直播完全可以使用CDN来提高访问速度。CDN在处理流媒体的时候与处理普通静态文件有所不同,普通文件如果在边缘节点没有找到的话,就会去上一层接着寻找,但是流媒体本身数据量就非常大,如果使用回源的方式,必然会带来性能问题,所以流媒体一般采用的都是主动推送的方式来进行。
懒加载的概念
懒加载也叫做延迟加载、按需加载,指的是在长网页中延迟加载图片数据,是一种较好的网页性能优化的方式。在比较长的网页或应用中,如果图片很多,所有的图片都被加载出来,而用户只能看到可视窗口的那一部分图片数据,这样就浪费了性能。
如果使用图片的懒加载就可以解决以上问题。在滚动屏幕之前,可视化区域之外的图片不会进行加载,在滚动屏幕时才加载。这样使得网页的加载速度更快,减少了服务器的负载。懒加载适用于图片较多,页面列表较长(长列表)的场景中。
懒加载的特点
减少无用资源的加载: 使用懒加载明显减少了服务器的压力和流量,同时也减小了浏览器的负担。
提升用户体验: 如果同时加载较多图片,可能需要等待的时间较长,这样影响了用户体验,而使用懒加载就能大大的提高用户体验。
防止加载过多图片而影响其他资源文件的加载 : 会影响网站应用的正常使用。
懒加载的实现原理
图片的加载是由src引起的,当对src赋值时,浏览器就会请求图片资源。根据这个原理,我们使用HTML5 的data-xxx属性来储存图片的路径,在需要加载图片的时候,将data-xxx中图片的路径赋值给src,这样就实现了图片的按需加载,即懒加载。
注意:data-xxx 中的xxx可以自定义,这里我们使用data-src来定义。
<img data-src="./pic/1.png" alt="">
<img data-src="./pic/2.png" alt="">
懒加载的实现重点在于确定用户需要加载哪张图片,在浏览器中,可视区域内的资源就是用户需要的资源。所以当图片出现在可视区域时,获取图片的真实地址并赋值给图片即可。
参考详见1 参考详见2
导入vueuse插件,使用vueuse封装的useIntersectionObserver监听对应的DOM元素,通过里面的isIntersecting属性的布尔值判断来设置img的src
<template>
<div>
<div style="height:2000px"></div>
<img ref="target" src="" alt="">
</div>
</template>
<script lang='ts' setup>
import { ref,reactive } from 'vue'
import {useIntersectionObserver} from '@vueuse/core'
const target = ref<HTMLImageElement|null>(null)
useIntersectionObserver(target, ([{isIntersecting}]) =>{
const src = 'https://ts3.cn.mm.bing.net/th?id=OIP-C.Zxtf2X2EddV-g7hKyBhilAHaQB&w=161&h=350&c=8&rs=1&qlt=90&o=6&pid=3.1&rm=2'
if(isIntersecting){
if(target.value){
target.value.src = src
}
}
})
</script>
封装为自定义指令
export default function (app: App<Element>) {
app.directive('lazy', {
mounted(el, binding) {
// el:选中的元素,value:传过来的属性,这里是图片地址
const { stop } = useIntersectionObserver(el,([{ isIntersecting }]) => {
if(isIntersecting){ // 判断元素是否在可视区域
el.src = binding.value
el.onerror = function() {
el.src = defaultImg//默认图片
}
}
stop()
})
},
})
}
我们可以使用 @vueuse/core 中的 useIntersectionObserver 来实现监听进入可视区域行为,但是必须配合vue3.0的组合API的方式才能实现。
import { useIntersectionObserver } from '@vueuse/core'
import { ref } from 'vue'
/**
* 数据懒加载函数
* @param {Function} apiFn - Api函数
* @constructor
*/
export const useLazyData = (apiFn) => {
const target = ref(null)
const result = ref([])
// stop 停止观察
const { stop } = useIntersectionObserver(
// 监听的目标函数
target,
([{ isIntersecting }], observeElement) => {
// isIntersecting是否进入可视区
if (isIntersecting) {
stop()
// 调用API函数获取数据
apiFn().then(data => {
result.value = data.result
})
}
}
)
return { result, target }
}
<template>
<div ref="target">
<!-- 面板内容 -->
<ul>
<li v-for="item in goods" :key="item.id">
<img :src="item.picture" alt="">
<p>{{ item.name }}</p>
<p>{{ item.price }}</p>
</li>
</ul>
</div>
</template>
<script setup>
import { findNew } from '@/api/home'
import { useLazyData } from '@/hooks'
const { result: goods, target } = useLazyData(findNew)
</script>
这两种方式都是提高网页性能的方式,两者主要区别是一个是提前加载,一个是迟缓甚至不加载。懒加载对服务器前端有一定的缓解压力作用,预加载则会增加服务器前端压力。
- 懒加载也叫延迟加载,指的是在长网页中延迟加载图片的时机,当用户需要访问时,再去加载,这样可以提高网站的首屏加载速度,提升用户的体验,并且可以减少服务器的压力。它适用于图片很多,页面很长的电商网站的场景。
- 预加载指的是将所需的资源提前请求加载到本地,这样后面在需要用到时就直接从缓存取资源。 通过预加载能够减少用户的等待时间,提高用户的体验。我了解的预加载的最常用的方式是使用 js 中的 image 对象,通过为 image对象来设置 scr 属性,来实现图片的预加载。
仅使用CSS实现预加载。
body:after {
content: "";
display: block;
position: absolute;
background: url("../image/manage/help/help_item2_01.png?v=201707241359") no-repeat
-10000px -1000px,url("../image/manage/help/help_item2_02.png?v=201707241359") no-repeat
-10000px -1000px,url("../image/manage/help/help_item2_03.png?v=201707241359") no-repeat
-10000px -1000px,url("../image/manage/help/help_item2_04.png?v=201707241359") no-repeat
-10000px -1000px;
width: 0;
height: 0
}
仅使用JavaScript实现预加载。
//存放图片路径的数组
var imgSrcArr = [
'imgsrc1',
'imgsrc2',
'imgsrc3',
'imgsrc4'
];
var imgWrap = [];
function preloadImg(arr) {
for(var i =0; i< arr.length ;i++) {
imgWrap[i] = new Image();
imgWrap[i].src = arr[i];
}
}
preloadImg(imgSrcArr);
//或者延迟的文档加载完毕在加载图片
$(function () {
preloadImg(imgSrcArr);
})
什么是回流(重排),哪些操作会导致回流
当渲染树中部分或者全部元素的尺寸、结构或者属性发生变化时,浏览器会重新渲染部分或者全部文档的过程就称为回流。
下面这些操作会导致回流:
在触发回流(重排)的时候,由于浏览器渲染页面是基于流式布局的,所以当触发回流时,会导致周围的DOM元素重新排列,它的影响范围有两种:
当页面中某些元素的样式发生变化,但是不会影响其在文档流中的位置时,浏览器就会对元素进行重新绘制,这个过程就是重绘。
下面这些操作会导致回流:
注意: 当触发回流时,一定会触发重绘,但是重绘不一定会引发回流。
如何避免回流与重绘?
减少回流与重绘的措施:
浏览器的渲染队列
浏览器针对页面的回流与重绘,进行了自身的优化——渲染队列
浏览器会将所有的回流、重绘的操作放在一个队列中,当队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会对队列进行批处理。这样就会让多次的回流、重绘变成一次回流重绘。
**防抖:**触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间
节流: 高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率。
应用场景:
window对象的resize、scroll事件
拖拽时候的mousemove
射击游戏中的mousedown、keydown事件
文字输入、自动完成的keyup事件
代码实现
// //防抖
function debounce(fn, date) {
let timer //声明接收定时器的变量
return function (...arg) { // 获取参数
timer && clearTimeout(timer) // 清空定时器
timer = setTimeout(() => { // 生成新的定时器
//因为箭头函数里的this指向上层作用域的this,所以这里可以直接用this,不需要声明其他的变量来接收
fn.apply(this, arg) // fn()
}, date)
}
}
//--------------------------------
// 节流
function debounce(fn, data) {
let timer = +new Date() // 声明初始时间
return function (...arg) { // 获取参数
let newTimer = +new Date() // 获取触发事件的时间
if (newTimer - timer >= data) { // 时间判断,是否满足条件
fn.apply(this, arg) // 调用需要执行的函数,修改this值,并且传入参数
timer = +new Date() // 重置初始时间
}
}
}
// --------------------------------
box.addEventListener('click', debounce(function (e) {
if (e.target.tagName === 'BUTTON') {
console.log(111);
}
}, 2000))
静态模块打包工具
分析、压缩、打包代码
loader直译为"加载器"。webpack将一切文件视为模块,但是webpack原生是只能解析js文件,如果想将其他文件也打包的话,就会用到loader。 所以loader的作用是让webpack拥有了加载和解析非JavaScript文件的能力。
loader就是用于解析文件的 (类似游戏地图)
例如:css-loader 、style-loader、image-loader
Plugin直译为"插件"。Plugin可以扩展webpack的功能,让webpack具有更多的灵活性。 在 webpack
运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 webpack 提供的 API 改变输出结果。
说人话:插件就是拓展功能的 (类似游戏的作弊器)
例如:html-webpack-plugin,
插件范围很广 : 只要不是webapck原生的功能,都可以理解为插件
loader : 一种特殊的插件,主要是用在webpack编译环节,帮我们编译各种文件的
raw-loader:加载文件原始内容(utf-8)
file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件 (处理图片和字体)
url-loader:与 file-loader 类似,区别是用户可以设置一个阈值,大于阈值会交给 file-loader 处理,小于阈值时返回文件 base64 形式编码 (处理图片和字体)
source-map-loader:加载额外的 Source Map 文件,以方便断点调试
svg-inline-loader:将压缩后的 SVG 内容注入代码中
image-loader:加载并且压缩图片文件
json-loader 加载 JSON 文件(默认包含)
handlebars-loader: 将 Handlebars 模版编译成函数并返回
babel-loader:把 ES6 转换成 ES5
ts-loader: 将 TypeScript 转换成 JavaScript
awesome-typescript-loader:将 TypeScript 转换成 JavaScript,性能优于 ts-loader
sass-loader:将SCSS/SASS代码转换成CSS
css-loader:加载 CSS,支持模块化、压缩、文件导入等特性
style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS
postcss-loader:扩展 CSS 语法,使用下一代 CSS,可以配合 autoprefixer 插件自动补齐 CSS3 前缀
eslint-loader:通过 ESLint 检查 JavaScript 代码
tslint-loader:通过 TSLint检查 TypeScript 代码
define-plugin:定义环境变量 (Webpack4 之后指定 mode 会自动配置)
ignore-plugin:忽略部分文件
html-webpack-plugin:简化 HTML 文件创建 (依赖于 html-loader)
web-webpack-plugin:可方便地为单页应用输出 HTML,比 html-webpack-plugin 好用
uglifyjs-webpack-plugin:不支持 ES6 压缩 (Webpack4 以前)
terser-webpack-plugin: 支持压缩 ES6 (Webpack4)
webpack-parallel-uglify-plugin: 多进程执行代码压缩,提升构建速度
mini-css-extract-plugin: 分离样式文件,CSS 提取为独立文件,支持按需加载 (替代extract-text-webpack-plugin)
serviceworker-webpack-plugin:为网页应用增加离线缓存功能
clean-webpack-plugin: 目录清理
ModuleConcatenationPlugin: 开启 Scope Hoisting
speed-measure-webpack-plugin: 可以看到每个 Loader 和 Plugin 执行耗时 (整个打包耗时、每个 Plugin 和 Loader 耗时)
webpack-bundle-analyzer: 可视化 Webpack 输出文件的体积 (业务组件、依赖第三方模块)
Loader 本质就是一个函数,在该函数中对接收到的内容进行转换,返回转换后的结果。 因为 Webpack 只认识
JavaScript,所以 Loader 就成了翻译官,对其他类型的资源进行转译的预处理工作。
Plugin 就是插件,基于事件流框架 Tapable,插件可以扩展 Webpack 的功能,在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。
Loader 在 module.rules 中配置,作为模块的解析规则,类型为数组。每一项都是一个 Object,内部包含了 test(类型文件)、loader、options (参数)等属性。
Plugin 在 plugins 中单独配置,类型为数组,每一项是一个 Plugin 的实例,参数都通过构造函数传入
Webpack 的热更新又称热替换(Hot Module Replacement),缩写为 HMR。 这个机制可以做到不用刷新浏览器而将新变更的模块替换掉旧的模块。
HMR的核心就是客户端从服务端拉去更新后的文件,准确的说是 chunk diff (chunk 需要更新的部分),实际上 WDS 与浏览器之间维护了一个 Websocket,当本地资源发生变化时,WDS 会向浏览器推送更新,并带上构建时的 hash,让客户端与上一次资源进行对比。客户端对比出差异后会向 WDS 发起 Ajax 请求来获取更改内容(文件列表、hash),这样客户端就可以再借助这些信息继续向 WDS 发起 jsonp 请求获取该chunk的增量更新。
后续的部分(拿到增量更新之后如何处理?哪些状态该保留?哪些又需要更新?)由 HotModulePlugin 来完成,提供了相关 API 以供开发者针对自身场景进行处理,像react-hot-loader 和 vue-loader 都是借助这些 API 实现 HMR。
代码分割的本质其实就是在源代码直接上线和打包成唯一脚本main.bundle.js这两种极端方案之间的一种更适合实际场景的中间状态。
「用可接受的服务器性能压力增加来换取更好的用户体验。」
源代码直接上线:虽然过程可控,但是http请求多,性能开销大。
打包成唯一脚本:一把梭完自己爽,服务器压力小,但是页面空白期长,用户体验不好。
(1)优化 Loader
对于 Loader 来说,影响打包效率首当其冲必属 Babel 了。因为 Babel 会将代码转为字符串生成 AST,然后对 AST 继续进行转变最后再生成新的代码,项目越大,转换代码越多,效率就越低。
(2)HappyPack
受限于 Node 是单线程运行的,所以 Webpack 在打包的过程中也是单线程的,特别是在执行 Loader 的时候,长时间编译的任务很多,这样就会导致等待的情况。
HappyPack 可以将 Loader 的同步执行转换为并行的,这样就能充分利用系统资源来加快打包效率了
(3)DllPlugin
DllPlugin 可以将特定的类库提前打包然后引入。这种方式可以极大的减少打包类库的次数,只有当类库更新版本才有需要重新打包,并且也实现了将公共代码抽离成单独文件的优化方案。
(4)代码压缩
在 Webpack3 中,一般使用 UglifyJS 来压缩代码,但是这个是单线程运行的,为了加快效率,可以使用 webpack-parallel-uglify-plugin 来并行运行 UglifyJS,从而提高效率。
在 Webpack4 中,不需要以上这些操作了,只需要将 mode 设置为 production 就可以默认开启以上功能。代码压缩也是我们必做的性能优化方案,当然我们不止可以压缩 JS 代码,还可以压缩 HTML、CSS 代码,并且在压缩 JS 代码的过程中,我们还可以通过配置实现比如删除 console.log 这类代码的功能。
(1)按需加载
在开发 SPA 项目的时候,项目中都会存在很多路由页面。如果将这些页面全部打包进一个 JS 文件的话,虽然将多个请求合并了,但是同样也加载了很多并不需要的代码,耗费了更长的时间。那么为了首页能更快地呈现给用户,希望首页能加载的文件体积越小越好,这时候就可以使用按需加载,将每个路由页面单独打包为一个文件。当然不仅仅路由可以按需加载,对于 loadash 这种大型类库同样可以使用这个功能。
(2)Scope Hoisting
Scope Hoisting 会分析出模块之间的依赖关系,尽可能的把打包出来的模块合并到一个函数中去。
(3)Tree Shaking
Tree Shaking 可以实现删除项目中未被引用的代码。可以通过在启动webpack时追加参数 --optimize-minimize 来实现
⽤webpack优化前端性能是指优化webpack的输出结果,让打包的最终结果在浏览器运⾏快速⾼效。
压缩代码: 删除多余的代码、注释、简化代码的写法等等⽅式。可以利⽤webpack的 UglifyJsPlugin 和 ParallelUglifyPlugin 来压缩JS⽂件, 利⽤ cssnano (css-loader?minimize)来压缩css
利⽤CDN加速: 在构建过程中,将引⽤的静态资源路径修改为CDN上对应的路径。可以利⽤webpack对于 output 参数和各loader的 publicPath 参数来修改资源路径
Tree Shaking: 将代码中永远不会⾛到的⽚段删除掉。可以通过在启动webpack时追加参数 --optimize-minimize 来实现
Code Splitting (自动): 将代码按路由维度或者组件分块(chunk),这样做到按需加载,同时可以充分利⽤浏览器缓存
提取公共第三⽅库: SplitChunksPlugin插件来进⾏公共模块抽取,利⽤浏览器缓存可以⻓期缓存这些⽆需频繁变动的公共代码
1、什么是长缓存
浏览器在用户访问页面的时候,为了加快加载速度,会对用户访问的静态资源进行存储,但是每一次代码升级或者更新,都需要浏览器去下载新的代码,最方便的更新方式就是引入新的文件名称,只下载新的代码块,不加载旧的代码块,这就是长缓存。
2、具体实现
在Webpack中,可以在output给出输出的文件制定chunkhash,并且分离经常更新的代码和框架代码,通NameModulesPlugin或者HashedModulesPlugin使再次打包文件名不变
在Webpack中,import不仅仅是ES6module的模块导入方式,还是一个类似require的函数,我们可以通过import(‘module’)的方式引入一个模块,import()返回的是一个Promise对象;使用import()方式就可以实现 Webpack的按需加载
在import()里可以添加一些注释,如定义该chunk的名称,要过滤的文件,指定引入的文件等等,这类带有特殊功能的注释被称为神器注释。
Babel 的主要工作是对代码进行转译。 (解决兼容, 解析执行一部分代码)
let a = 1 + 1 => var a = 2
复制代码
转译分为三阶段:
我们可以通过 AST Explorer 工具来查看 Babel 具体生成的 AST 节点。
执行npm run dev 时候最先执行的build/dev-server.js文件,该文件主要完成下面几件事情:
1、检查node和npm的版本、引入相关插件和配置
2、webpack对源码进行编译打包并返回compiler对象
3、创建express服务器
4、配置开发中间件(webpack-dev-middleware)和热重载中间件(webpack-hot-middleware)
5、挂载代理服务和中间件
6、配置静态资源
7、启动服务器监听特定端口(8080)
8、自动打开浏览器并打开特定网址(localhost:8080)
参考详见
定义:
虚拟DOM本质上是一个树型结构的JS对象,通过对象来表示真实的DOM结构。因为它不是真实的dom,所以才叫做虚拟dom。 tag用来描述标签,props用来描述属性,children用来表示嵌套的层级关系。
原理
虚拟 dom 相当于在 js 和真实 dom 中间加了一个缓存,利用 dom diff 算法避免了没有必要 的 dom 操作,从而提高性能。
const vnode = {
tag: 'div',
props: {
id: 'container',
},
children: [{
tag: 'div',
props: {
class: 'content',
},
text: 'This is a container'
}]
}
具体实现步骤如下:
步骤简化
在vue中一般都是通过修改元素的state,订阅者根据state的变化进行编译渲染,底层的实现可以简单理解为三个步骤:
1、用JavaScript对象结构表述dom树的结构,然后用这个树构建一个真正的dom树,插到浏览器的页面中。
2、当状态改变了,也就是我们的state做出修改,vue便会重新构造一棵树的对象树,然后用这个新构建出来的树和旧树进行对比(只进行同层对比),记录两棵树之间的差异。
3、把记录的差异再重新应用到所构建的真正的dom树,视图就更新了。
优点
不足
虚拟DOM同样也有缺点,首次渲染大量DOM时,由于多了一层虚拟DOM的计算,会比innerHTML插入慢。
为了避免不必要的渲染,按需更新,虚拟DOM会采用Diff算法进行虚拟DOM节点比较,比较节点差异,从而确定需要更新的节点,再进行渲染。vue采用的是深度优先,同层比较的策略。(在diff中,只对同层的子节点进行比较,放弃跨级的节点比较,使得时间复杂从O(n3)降低值O(n),也就是说,只有当新旧children都为多个子节点时才需要用核心的Diff算法进行同层级比较。)
参考详情
新节点与旧节点的比较主要是围绕三件事来达到渲染目的
在新老虚拟DOM对比时:
优点:
缺点:
解决首次加载慢的问题
多页面:
一个应用多个页面,页面跳转时整个页面都刷新,每次都请求一个新的页面
(1)MVC
M: model数据模型, V:view视图模型, C: controller控制器
MVC 通过分离 Model、View 和 Controller 的方式来组织代码结构。其中 View 负责页面的显示逻辑,Model 负责存储页面的业务数据,以及对相应数据的操作。并且 View 和 Model 应用了观察者模式,当 Model 层发生改变的时候它会通知有关 View 层更新页面。Controller 层是 View 层和 Model 层的纽带,它主要负责用户与应用的响应操作,当用户与页面产生交互的时候,Controller 中的事件触发器就开始工作了,通过调用 Model 层,来完成对 Model 的修改,然后 Model 层再去通知View视图更新。
(2)MVVM
MVVM 分为 Model、View、ViewModel:
详情参考
当一个Vue实例创建时,Vue会遍历data中的属性,用 Object.defineProperty(vue3.0使用proxy )将它们转为 getter/setter,并且在内部追踪相关依赖,在属性被访问和修改时通知变化。 每个组件实例都有相应的 watcher 程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的setter被调用时,会通知watcher重新计算,从而致使它关联的组件得以更新。
Vue的两核心
响应式原理
Vue响应式的原理就是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty() 来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
使用方法
**Object.defineProperty( obj, prop, descriptor )**
三个参数:
obj 要定义的对象
prop 要定义或修改的属性名称或 Symbol
descriptor 要定义或修改的属性描述符(配置对象)
缺点:
在对一些属性进行操作时,使用这种方法无法拦截,比如通过下标方式修改数组 数据或者给对象新增属性,这都不能触发组件的重新渲染,因为 Object.defineProperty 不能拦截到这些操作。更精确的来说,对于数组而言,大部分操作都是拦截不到的,只是 Vue 内部通过重写函数的方式解决了这个问题。
在 Vue3.0 中已经不使用这种方式了,而是通过使用 Proxy 对对象进行代理,从而实现数据劫持。 使用Proxy 的好处是它可以完美的监听到任何方式的数据改变,唯一的缺点是兼容性的问题,因为 Proxy 是 ES6 的语法。
Object.defineProperty 的优势如下:
兼容性好,支持 IE9,而 Proxy 的存在浏览器兼容性问题,而且无法用 polyfill 磨平,因此 Vue 的作者才声明需要等到下个大版本( 3.0 )才能用 Proxy 重写。
参考详见
监测机制的改变
Vue3支持碎片(Fragments)
API模式不同
建立数据的方式不同
生命周期钩子不同 — Lifecyle Hooks
setup() :开始创建组件之前,在beforeCreate和created之前执
行。创建的是data和method
onBeforeMount() : 组件挂载到节点上之前执行的函数。
onMounted() : 组件挂载完成后执行的函数。
onBeforeUpdate(): 组件更新之前执行的函数。
onUpdated(): 组件更新完成之后执行的函数。
onBeforeUnmount(): 组件卸载之前执行的函数。
onUnmounted(): 组件卸载完成后执行的函数
若组件被包含,则多出下面两个钩子函
onActivated(): 被包含在中的组件,会多出两个生命周期钩子函数。被激活时执行 。
onDeactivated(): 比如从 A组件,切换到 B 组件,A 组件消失时执行。
父子传参不同,子组件通过defineProps()进行接收,并且接收这个函数的返回值进行数据操作。
总结: vue3 性能更高, 体积更小, 更利于复用, 代码维护更方便
vue2.0生命周期
v-if 会调用addIfCondition方法,生成vnode的时候会忽略对应节点,render的时候就不会渲染;
v-show 会生成vnode,render的时候也会渲染成真实节点,只是在render过程中会在节点的属性中修改show属性值,也就是常说的display;
v-html 会先移除节点下的所有节点,调用html方法,通过addProp添加innerHTML属性,归根结底还是设置innerHTML为v-html的值。
Vue 处理指令时,v-for 比 v-if 具有更高的优先级, 虽然用起来也没报错好使, 但是性能不高, 如果你有5个元素被v-for循环, v-if也会分别执行5次.而VUE3 里 v-if 比v-for 具有更高的优先级。
提升vue渲染性能
使用index 作为 key和没写基本上没区别,因为不管数组的顺序怎么颠倒,index 都是 0, 1, 2…这样排列,导致 Vue 会复用错误的旧子节点,做很多额外的工作。
Computed:
它支持缓存,只有依赖的数据发生了变化,才会重新计算
不支持异步,当Computed中有异步操作时,无法监听数据的变化
computed的值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data声明过,或者父组件传递过来的props中的数据进行计算的。
如果一个属性是由其他属性计算而来的,这个属性依赖其他的属性,一般会使用computed
如果computed属性的属性值是函数,那么默认使用get方法,函数的返回值就是属性的属性值;在computed中,属性有一个get方法和一个set方法,当数据发生变化时,会调用set方法。
Watch:
它不支持缓存,数据变化时,它就会触发相应的操作
支持异步监听
监听的函数接收两个参数,第一个参数是最新的值,第二个是变化之前的值
当一个属性发生变化时,就需要执行相应的操作
监听数据必须是data中声明的或者父组件传递过来的props中的数据,当发生变化时,会触发其他操作,函数有两个的参数:
当想要执行异步或者昂贵的操作以响应不断的变化时,就需要使用watch。
总结:
computed 计算属性 : 依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值。
watch 侦听器 : 更多的是观察的作用,无缓存性,类似于某些数据的监听回调,每当监听的数据变化时都会执行回调进行后续操作。
运用场景:
(1)props / $emit (父子)
vue2 父组件通过props向子组件传递数据,子组件通过$emit和父组件通信。
vue3子组件通过defineProps()进行接收,并且接收这个函数的返回值进行数据操作。
子组件向父组件传值
$emit绑定一个自定义事件,当这个事件被执行的时就会将参数传递给父组件,而父组件通过v-on监听并接收参数。
(2)依赖注入 provide / inject(父子、祖孙)
这种方式就是Vue中的依赖注入,该方法用于父子组件之间的通信。当然这里所说的父子不一定是真正的父子,也可以是祖孙组件,在层数很深的情况下,可以使用这种方法来进行传值。就不用一层一层的传递了。
provide / inject是Vue提供的两个钩子,和data、methods是同级的。并且provide的书写形式和data一样。
在父组件中:
<template>
<div>
<h2>CHINESE PORT</h2>
<h2>这是父组件: {{ father }}</h2>
<son-com />
</div>
</template>
<script>
import sonCom from './sonCom.vue'
export default {
components: { sonCom },
provide() {
return {
sonDate: '子组件数据',
}
},
data() {
return {
father: '父组件数据'
}
},
}
</script>
在子组件中:
<template>
<div>
<h3>这是子组件: {{ sonDate }}</h3>
</div>
</template>
<script>
export default {
inject: ['sonDate']
}
</script>
注意: 依赖注入所提供的属性是非响应式的。
(3)ref / $refs (父子,兄弟)
ref: 这个属性用在子组件上,它的引用就指向了子组件的实例。可以通过实例来访问组件的数据和方法。
给dom元素加 ref="refname",然后通过this.$refs.refname进行获取dom元素
this.$refs.refname 获取dom元素
this.$refs.refname.msg 获取子组件中的data
this.$refs.refname.open() 调用子组件中的方法
例子
//子组件
<template>
<div>{{lcmsg}}</div>
</template>
<script>
export default {
data() {
return {
lcmsg: '我是子组件'
}
},
methods: {
watchMsg() {
this.lcmsg = '点我重新赋值'
}
}
}
</script>
//父组件
<template>
<div @click="getData">
<children ref="children"></children>
</div>
</template>
<script>
import children from 'components/children.vue'
export default {
components: {
children
},
data() {
return {}
},
methods: {
getData() {
//返回一个对象
this.$refs.children
//返回子组件中的lcmsg
this.$refs.children.lcmsg
//调用children的watchMsg方法
this.$refs.children.watchMsg()
}
}
}
</script>
(4)$parent / $children (父子)
子组件获取到了父组件的parentVal值,父组件改变了子组件中message的值。 需要注意:
(5)eventBus事件总线($emit / $on)(任意组件通信)
eventBus事件总线适用于父子组件、非父子组件等之间的通信
参考详见
(6)vuex
原理: Vuex是专门为vue.js应用程序设计的状态管理工具。
构成:
this.$store.state. 获取对应的state值
...mapState([]) computed 里获取对应的值
mapMutations mapgetters mapActions
this.$store.commit('方法','传值') 执行对应方法
store.dispatch('increment') action异步
Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一
为什么 Vuex 的 mutation 中不能做异步操作?
每个mutation执行完成后都会对应到一个新的状态变更,这样devtools就可以打个快照存下来,然后就可以实现 time-travel 了。如果mutation支持异步操作,就没有办法知道状态是何时更新的,无法很好的进行状态的追踪,给调试带来困难。
原因: 因为vuex里的数据是保存在运行内存中的,当页面刷新时,页面会重新加载vue实例,vuex里面的数据就会被清空。
解决方法: 将vuex中的数据直接保存到浏览器缓存中。(一般是用sessionStorage)
slot是什么
slot又名插槽,是Vue的内容分发机制,组件内部的模板引擎使用slot元素作为承载分发内容的出口。插槽slot是子组件的一个模板标签元素,而这一个标签元素是否显示,以及怎么显示是由父组件决定的。
比如布局组件、表格列、下拉选、弹框显示内容等
默认插槽 (匿名插槽)
<template>
<div>
<slot></slot>
</div>
</template>
具名插槽
有时我们一个组件里面需要多个插槽。我们怎么来区分多个slot,而且不同slot的显示位置也是有差异的.对于这样的情况, 元素有一个特殊的特性:name。这个特性可以用来定义额外的插槽:
注意:一个不带 name 的 出口会带有隐含的名字“default”。
<div>
<slot name="header"></slot>
</div>
//父
<children>
<template slot="header">
<h1>Here might be a page title</h1>
</template>
作用域插槽
自 2.6.0 起有所更新。已废弃的使用 slot-scope
<template>
<div>
<slot :user="user">{{user.firstName}}</slot>
</div>
</template>
<!-- old -->
<children>
<template slot="default" slot-scope="slotProps">
{{ slotProps.user.firstName }}
</template>
</children>
<!-- new -->
<children>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
</children>
< keep-alive >是Vue的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM。
< keep-alive > 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。
使用场景: 多表单切换,对表单内数据进行保存
当组件在内被切换,它的activated和 deactivated这两个生命周期钩子函数将会被对应执行 。
keep-alive的使用
在组件跳转之前使用后置路由守卫判断组件是否缓存
( beforeRouteLeave( to, from, next ){ from.meta.keepAlive = false }
keep-alive的两个钩子函数
activated | deactivated |
---|---|
在 keep-alive 组件激活时调用 | 在keep-alive 组件停用时调用 |
该钩子函数在服务器端渲染期间不被调用 | 该钩子在服务器端渲染期间不被调用 |
使用keep-alive会将数据保留在内存中,如果要在每次进入页面的时候获取最新的数据,需要在 activated阶段获取数据,承担原来created钩子函数中获取数据的任务。
注意: 只有组件被keep-alive包裹时,这两个生命周期函数才会被调用,如果作为正常组件使用,是不会被调用的
其实一句话就可以把$nextTick这个东西讲明白:就是你放在nextTick当中的操作不会立即执行,而是等数据更新、DOM更新完成之后再执行,
作用 解决了异步渲染获取不到更新后DOM的问题
nextTick的原理:nextTick本质是返回一个Promise 。
Route和router的区别
1.router-view 是渲染视图的
2.router-link组件支持用户在具有路由功能的应用中 (点击) 导航。 通过to属性指定目标地址,默认渲染成带有正确链接的a标签(超链接或锚)
路由模式设置
hash路由 : 地址栏URL中的#符号 http://localhost:3000/#/abc ,不会被包括在HTTP请求中,对后端完全没有影响,改变hash不会重新加载页面 ,每次改变会触发hashchange事件
history路由: http://localhost:3000/abc(需要服务器支持,否则找的是文件夹) 利用了 HTML5 新增的 pushState() 和 replaceState() 方法,在原有的back、forward、go 的基础上,添加对历史记录修改的功能,history需要后端配合,每次刷新会发送请求。因此,如果后端没有做处理的话,就会因找不到资源而报404错误。
动态路由
const routes = [
{
path: "/",
component: () => import("@/views/Main.vue"),
redirect: "/index",
children: [
{
path: "/index/:id",
component: () => import("@/views/Index.vue"),
},
],
},
];
一个“路径参数”使用冒号 : 标记。当匹配到一个路由时,参数值会被设置到 this.$route.params,可以在每个组件内使用。
methods: {
goPage(val) {
this.state = val;
this.$router.push({
path: "/index2/" + val,
});
},
},
、、
<louter-link to ='/index2/123'>
路由懒加载
- 方式一: 结合Vue的异步组件和Webpack的代码分析 const Home = resolve => { require.ensure(['../ components/Home.vue'], () => { resolve(require('../components/Home.vue')) })};
- 方式二: AMD写法 const About = resolve => require([' ../ components/ About.vue'], resolve);
- 方式三: 在ES6中,我们可以有更加简单的写法来组织Vue异步组件和Webpack的代码分割 const Home = () => import(' . ./ components/Home.vue ' )
通过router-link路由导航跳转传递
<router-link to=`/a/${id}`>routerlink传参</router-link>
跳转时使用push方法拼接携带参数。
this.$router.push({
path: `/getlist/${id}`,
})
```javascript
**通过路由属性中的name来确定匹配的路由,通过params来传递参数。**
```javascript
this.$router.push({
name: 'Getlist',
params: {
id: id
}
})
使用path来匹配路由,然后通过query来传递参数。
this.$router.push({
path: '/getlist',
query: {
id: id
}
})
分别是:全局路由守卫、组件路由守卫、独享路由守卫
一.全局守卫
router.beforeEach((to,from,next)=>{})
to:进入到哪个路由去,from:从哪个路由离开,next:函数,决定是否展示你要看到的路由页面。 它是全局的,即对 整个单页应用(SPA) 中的所有路由都生效,所以当定义了全局的前置守卫,在进入每一个路由之前都会调用这个回调,那么如果你在回调中对路由的跳转条件判断出错,简单点就是死循环…因为你遗漏了某种路由跳转的情况,守卫会一直执行。所以在使用全局前置守卫的时候一定要判断清楚可能会出现的路由跳转的情况。
router.afterEach((to, from) => {})
只有两个参数,to:进入到哪个路由去,from:从哪个路由离全局后置守卫是整个单页应用中每一次路由跳转后都会执行其中的回调。所以多用于路由跳转后的相应页面操作,并不像全局前置守卫那样会在回调中进行页面的重定向或跳转。
全局解析守卫
其中也接收三个参数to、from、next,并且这个钩子函数与beforeEach类似,也是路由跳转前触发,官方解释其与beforeEach的区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。
即在 beforeEach 和 组件内beforeRouteEnter 之后,afterEach之前调用。
router.beforeResolve((to,from,next) => {
console.log(to,from);
next()
})
二.组件内的守卫
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
三.路由独享的守卫
因为独享守卫通常是某一个或几个页面独有的,所以我们通常会将路由独享守卫写在/src/router/index.js里;
// router/index.js
{
path: '/detail',
name: 'detail',
component: () => import('@/views/Detail.vue'),
beforeEnter: (to,from,next) => {
if(!sessionStorage.getItem('username')) {
alert('请先登录')
// next({name: 'login'})
router.push('/login')
} else {
next()
}
}
}
注:因为我们在index.js里我们使用脚手架(vue.cli)创建Vue项目时,
已经声明了router,所以我们可以使用 router.push('/')跳转页面。
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue
组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。
它就是一个对象而已,只不过这个对象里面可以包含Vue组件中的一些常见配置,如data、methods、created等等。
分为局部混入和全局混入,一般是局部混入,全局混入注册后,在任意组件都可以使用,谨慎使用全局混入对象,因为会影响到每个单独创建的 Vue 实例 (包括第三方模板)。
全局使用:
import baseMixin from './mixin/baseMixin'
createApp(App).mixin(baseMixin).mount('#app')
局部引用
export default {
mixins: [baseMixin],
setup() {
let text = '这个是vue3版本mixin的方法记录'
return {
text
}
}
}
使用场景
多个组件都会使用到的数据和方法,可以使用mixin,通过mixin文件里面定义好属性和方法,组件调用的时候会产生属性和方法的合并,共同使用。其中mixin中的钩子会优先执
mixin 和 mixins 区别
mixin 用于全局混入,会影响到每个组件实例,通常插件都是这样做初始化的。
虽然文档不建议在应用中直接使用 mixin,但是如果不滥用的话也是很有帮助的,比如可以全局混入封装好的 ajax 或者一些工具函数等等。
mixins 应该是最常使用的扩展组件的方式了。如果多个组件中有相同的业务逻辑,就可以将这些逻辑剥离出来,通过 mixins 混入代码,比如上拉下拉加载数据这种逻辑等等。 另外需要注意的是 mixins 混入的钩子函数会先于组件内的钩子函数执行,并且在遇到同名选项的时候也会有选择性的进行合并。
因为JavaScript的特性所导致,在component中,data必须以函数的形式存在,不可以是对象。
组建中的data写成一个函数,数据以函数返回值的形式定义,这样每次复用组件的时候,都会返回一份新的data,相当于每个组件实例都有自己私有的数据空间,它们只负责各自维护的数据,不会造成混乱。而单纯的写成对象形式,就是所有的组件实例共用了一个data,这样改一个全都改了。
问题: 数据虽然更新了, 但是页面没有更新
原因:
解决方案:
通过Vue.set向响应式对象中添加一个property,并确保这个新 property同样是响应式的,且触发视图更新
直接使用Object.assign()添加到对象的新属性不会触发更新
应创建一个新的对象,合并原对象和混入对象的属性
如果你发现你自己需要在 Vue中做一次强制更新,99.9% 的情况,是你在某个地方做错了事
$forceUpdate迫使Vue 实例重新渲染
PS:仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。
总结
如果为对象添加少量的新属性,可以直接采用Vue.set()
如果需要为新对象添加大量的新属性,则通过Object.assign()创建新对象
如果你实在不知道怎么操作时,可采取$forceUpdate()进行强制刷新 (不建议)
PS:vue3是用过proxy实现数据响应式的,直接动态添加新属性仍可以实现数据响应式
push(); pop(); shift(); unshift(); splice(); sort(); reverse()
(1)编码阶段
尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher
v-if和v-for不能连用
如果需要使用v-for给每项元素绑定事件时使用事件代理
SPA 页面采用keep-alive缓存组件
在更多的情况下,使用v-if替代v-show
key保证唯一
使用路由懒加载、异步组件
防抖、节流
第三方模块按需导入
长列表滚动到可视区域动态加载
图片懒加载
(2)SEO优化
预渲染
服务端渲染SSR
(3)打包优化
压缩代码
Tree Shaking/Scope Hoisting
使用cdn加载第三方模块
多线程打包happypack
splitChunks抽离公共文件
sourceMap优化
(4)用户体验
SSR也就是服务端渲染,也就是将Vue在客户端把标签渲染成HTML的工作放在服务端完成,然后再把html直接返回给客户端
SSR的优势:
SSR的缺点:
参考详情
安装动态懒加载所需插件;使用CDN资源。
reactive() API 有两条限制:
角色权限管理共分为三级,分别是页面级、行为级(控件级)、接口级
1.页面级
通过 vue-router 中的路由守卫来进行控制
{
path: 'roles',
name: 'Roles',
component: RouterView,
meta: {
title: '角色管理',
},
// 自定义 PermissionGuard 方法,见下文
beforeEnter: PermissionGuard,
children: [
// 此处省略
]
}
自定义 PermissionGuard 方法
export default function (to: RouteLocationNormalized, from: RouteLocationNormalized, next: Function) {
// PermissionHandler 为验证是否有权限的方法,返回一个布尔值,见下文
const hasPrivilege = PermissionHandler(to.meta?.roleKeys);
if (!hasPrivilege) {
// 没有权限跳转到无权限页,并将原本要访问的路由记录在 query 中
return next(`/unauthorized?origin_target=${to.path}`);
}
return next();
}
2.行为级(控件级)
通过自定义的指令来实现,行为级就是针对那些可能会发生操作行为的控件进行显示隐藏的设置,比如最常见的增删改查按钮等。
3.接口级
接口级则是需要后台实现,在接口设计时每个接口会对应一个权限,前端需要收集不同角色在每个页面及控件处使用到的接口。前端动态配置角色权限时,向后台发送新的接口权限列表,后台则根据当前用户的角色查找对应的接口权限列表,来判断该用户请求的接口是否可以执行,功能逻辑与前端类似,前端那要做的则是维护这个接口权限列表。
都是现代化打包工具
启动方式不一样。vite在启动的时候不需要打包,所以不用分析模块与模块之间的依赖关系,不用进行编译。这种方式就类似于我们在使用某个UI框架的时候,可以对其进行按需加载。同样的,vite也是这种机制,当浏览器请求某个模块时,再根据需要对模块内容进行编译。按需动态编译可以缩减编译时间,当项目越复杂,模块越多的情况下,vite明显优于webpack.
热更新方面,效率更高。当改动了某个模块的时候,也只用让浏览器重新请求该模块,不需要像webpack那样将模块以及模块依赖的模块全部编译一次。
缺点
vite相关生态没有webpack完善,vite可以作为开发的辅助。
参考详情
参考详见
参考详见