语义化标签:header, footer, article, section, nav, menu, hgroup等
更多的HTML5表单特性,例如calendar、date、time、email、url、search,
支持Video
新增Local Storage,使用Local Storage可以永久存储大的数据片段在客户端(除非主动删除),目前大部分浏览器已经支持
viewport缩放方案
在写 HTML、CSS 对设计稿进行还原时不关注屏幕尺寸的差异,而是直接按设计稿的标注来开发。比如设计稿里标注的文字字号是30px,CSS里就设置文字字号30px。
页面开发好后,在 HTML 的 head 标签里加入 。通过js动态计算屏幕的逻辑像素宽度,
initial-scale:网页初始缩放值
动态rem方案
在使用单位控制页面元素大小时,可以使用固定单位 px,也可以使用相对单位 rem。2rem 等于 html 标签 font-size 的 2 倍。
基于这个原理,对于需要适配屏幕等比缩放的元素可以选用 rem 作为单位,对于不需要等比缩放的元素依旧使用 px 作为单位。
只要调整html标签的 font-size,就能让所有使用 rem 单位的元素跟随着发生变化,而使用 px 单位的元素不受影响。
插件:postcss
例如:设计师交付的设计稿宽度是 750px,设计稿上一个 div 的标注尺寸是 375px(宽度是设计稿宽度的一半)。我们可以
假设用户在逻辑像素宽度是 375px 的设备上打开页面,则 html 的 font-size 是100*375/750 = 50px,div 的宽度是 3.75rem ,即 187.5px 正好是屏幕宽度的一半。
假设用户在逻辑像素宽度是 428px 的设备上打开页面,则 html 的 font-size 是100*428/750 = 57.07px,div的宽度是 3.75rem ,即 214px 正好是屏幕宽度的一半。
注:font-size= 100 只是为了方便就是,事实上可以取任意值
<script>
const WIDTH = 750 //如果是尺寸的设计稿在这里修改
const setView = () => {
//设置html标签的 fontSize * 屏幕宽度 / 设计稿宽度
document.documentElement.style.fontSize = (100*screen.width/WIDTH) + 'px'
}
window.onresize = setView; // 如果窗口大小发生改变,就触发 setView 事件
setView()
</script>
vm适配方案
vw 是相对单位,1vw 表示屏幕宽度的 1%。基于此,我们可以把所有需要适配屏幕大小等比缩放的元素都使用 vw 作为单位。不需要缩放的元素使用 px 做单位。
媒体查询
使用css media工具不同的屏幕大小适配不同的样式
标准盒子模型:(box-sizing:content-box;)
padding和border不被包含在定义的width和height之内,对象的实际宽度等于设置的width值和border、padding之和
IE盒子模型:(box-sizing:border-box;)
padding和border被包含在定义的width和height之内。对象的实际宽度就等于设置的width值,即使定义有border和padding也不会改变对象的实际宽度
圆角border-radius,阴影box-shadow,文字阴影text-shadow,
线性渐变(gradient),旋转(transform)
媒体查询(@media),多栏布局(colums)
边框图片:border-image
CSS3动画
产考
https://zhuanlan.zhihu.com/p/434788923
绝对定位法
原理是将左右两边使用absolute定位,因为绝对定位使其脱离文档流,后面的 middle 会自然流动到他们上面,然后使用margin属性,留出左右元素的宽度,既可以使中间元素自适应屏幕宽度。
<div class="main">
<div class="left">leftdiv>
<div class="middle">middlediv>
<div class="right">rightdiv>
div>
自身浮动法
原理就是对左右分别左浮动和右浮动,float使左右两个元素脱离文档流,中间元素正常在正常文档流中。对中间文档流使用margin指定左右外边距进行定位。
该布局法的不足是三个元素的顺序,middle一定要放在最后,middle占据文档流位置,所以一定要放在最后,左右两个元素位置没有关系。当浏览器窗口很小的时候,右边元素会被挤到下一行。
<div class="main">
<div class="left">leftdiv>
<div class="right">rightdiv>
<div class="middle">middlediv>
div>
flex 布局法
原理就是为父元素添加样式display:flex,左右固定宽度,中间设置flex:1,middle一定要放在中间。
<div class="main">
<div class="left">leftdiv>
<div class="middle">middlediv>
<div class="right">rightdiv>
div>
根据不同的浏览器内核添加不同的css前缀
Chrome 内核 之前Webkit,已改Blink内核
FireFox火狐 内核 Gecko
Ssfari苹果 内核 Webkit
IE浏览器 内核 Trident
Opera欧朋 内核 现已改用Google Chrome的Blink内核
-moz-
火狐浏览器
-Webkit-
safari谷歌浏览器等使用Webkit引擎的浏览器
-o-
Opera浏览器(早期)
-ms-
IE
对已知高度块级元素进行垂直居中
.content{
position: absolute;
top: 50%;
left: 50%;
margin-top: -10em; /* 为元素height/2 */
margin-left: -10em;
width: 20em;
height: 20em;
background-color: aqua;
}
.content{
position: absolute;
margin:auto;
top: 0;
bottom: 0;
left: 0;
right: 0;
height: 200px; /*要求指明元素高度*/
background-color: aqua;
}
设置position:absolute;和calc()函数实现垂直居中
.content{
position: absolute;
top:calc(50% - 10em); /*calc(50% - 元素本身高度一半)*/
left: calc(50% - 20em); /*注意使用时减号间有空格*/
width: 40em;
height: 20em;
background-color: aqua;
}
对未知高度块级元素进行垂直居中
设置position:absolute;和transform:traslate(x,y)实现水平垂直居中
.content{
position: absolute;
margin:auto;
top: 50%;
left: 50%;
transform:translate(-50%,-50%); /*针对元素本身向左以及向上移动50%*/
background-color: aqua;
}
基于flex的解决方案
.parent{
display: flex;
background-color: beige;
}
.content{
margin: auto; /*自动相对于父元素水平垂直居中*/
background-color: aqua;
}
flex实际上是flex-grow、flex-shrink和flex-basis三个属性的缩写。
flex:1 ==> flex:1 1 auto
**flex-grow:**属性指定了flex容器中剩余空间的多少应该被分配给项目。flex-grow设置的值为扩张因子,默认为0,剩余空间将会按照这个权重分别分配给子元素项目。
**flex-shrink:**属性指定了flex元素的收缩规则。flex元素仅在默认宽度之和大于容器的时候才会发生收缩。默认属性值为1,所以在空间不够的时候,子项目将会自动缩小。
**flex-basis:**属性指定了flex元素在主轴方向上的初始大小。如果不使用box-sizing改变盒模型的话,那么这个属性就决定了flex元素的内容的尺寸。如果设置了flex-basis值,那么元素占用的空间为flex-basis值;如果没有设置或者设置为auto,那么元素占据的空间为元素的width/height值。
参考
**flex-direction:**主轴的方向(即项目的排列方向)
flex-wrap属性:(轴线)
**justify-content属性:**定义了项目在主轴上的对齐方式。
**align-items属性:**定义项目在交叉轴上如何对齐。
**align-content属性:**定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。
**flex-grow属性:**定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大
var 声明的变量有变量提升的特性,而 let、const 没有
同一作用域下 let 和 const 不能重复声明同名变量,而var可以
var 声明的变量会挂载到 windows 对象上,所以使用 var 声明的是全局变量
const声明的是常量,必须赋初值,一旦声明不能再次赋值修改,如果声明的是复合类型数据,可以修改其属性
基础类型:number,string,null,undefiend,boolean,symbol
引用类型:Object,Function,Date,RE正则
== 在比较类型不同的变量时,会进行数据类型转化,将二者转换成数据类型相同的变量,再进行比较。
=== 比较的二者数据类型不一样时,直接返回false。
**in 运算符:**如果指定的属性在指定的对象或其原型链中,则in 运算符返回true。
**hasOwnProperty(key):**表示是否有自己的属性。这个方法会查找一个对象是否有某个属性,但是不会去查找它的原型链。
**Object.key()*获取对象所有的key,返回一个数组,可判断属性是否存在数组中
**普通函数:**普通函数this指向默认window,严格模式下this为undefiend
**在构造函数以及类中的this:**构造函数和类需要配合 new 使用, 而 new 关键字会将构造函数中的 this 指向实例对象,所以 this 指向 实例对象。
**绑定事件函数的this:**谁调用就指向谁。
**在箭头函数中:**箭头函数没有自己的 this,会继承其父作用域的 this。
作用:改变函数内部 this 的指向
区别:
闭包是指能够访问另一个函数作用域中的变量的一个函数。 在js中,只有函数内部的子函数才能访问局部变量, 所以闭包可以理解成 “定义在一个函数内部的函数”。
闭包的作用
利用闭包可以突破作用链域,将函数内部的变量和方法传递到外部,让外部函数可以访问到内部函数的变量和方法
闭包的优点
正常的函数,在执行完之后,函数里面声明的变量会被垃圾回收机制处理掉。但是形成闭包的函数在执行之后,不会被回收,依旧存在内存中。
闭包的缺点
因为变量不会被回收,所以内存中一直存在,耗费内存。
同步任务是指在主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务。
异步任务指的是,不进入主线程、而进入"任务队列"的任务,只有等主线程任务执行完毕,"任务队列"的任务才会进入主线程执行。
回调函数
setTimeout(() => {
// f1的任务代码
callback();
},1000)
事件监听.
实现原理也是利用定时器的原理去把函数放入事件队列里,等全部执行完毕之后,才会执行事件队列里的方法
发布/订阅
在Vue中可以使用 EventBus
来作为沟通桥梁的概念,就像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件
// 事件注册
EventBus.$emit("事件", '参数');
// 事件监听
EventBus.$on("事件", e => {
console.log('回调函数')
});
// 关闭事件
EventBus.$off("事件",{
console.log('回调函数')
})
Promises
简单说,它的思想是,每一个异步任务返回一个Promise对象,该对象有一个then方法,允许指定回调函数。比如,f1的回调函数f2,可以写成:f1().then(f2);
async await语法糖
async修饰的方法会返回一个promises对象
await只能放在被async修饰的函数内。可以理解为等待。
await 修饰的如果是Promise对象:可以获取Promise中返回的内容(resolve或reject的参数),且取到值后语句才会往下执行;
如果不是Promise对象:把这个非promise的东西当做await表达式的结果。
promise有三个状态:
promise的状态一旦发生改变就注定结果,不可逆转,通过new Promise()来使用promise
当Promise执行成功后会执行resolved()函数,通过then回调返回结果,结果就是resolve()的参数,then返回的也是一个Promise对象,可以链式调用
当Promise内部发生错误后会执行rejected()函数,通过catch处理错误消息,
Promise的其他方法
promise.all()
传入一个数组,当所有事务都处理完成之后再返回结果,例如通过all去请求一系列api,所有api返回结果后promise才调用resolve回调,返回一个数组
promise.race()
传入一个数组,当其中一个事务处理完成后就回返回结果,
js是单线程的脚本语言,同一时间只能做同一件事,以至于执行时间比较长的代码就会发生阻塞,为了解决这一问题,js将代码执行的模式分为两种,同步和异步:
事件循环:
同步任务,宏任务,微任务:
// 执行setTimeout
setTimeout(() => {
// 向宏任务里面添加一个任务
console.log(1);
}, 0)
// 执行同步代码并输出
console.log(2);
//执行 Promise
new Promise((resolve, reject) => {
// 执行同步代码并输出
console.log(3);
// 向微队列里面添加一个任务
resolve(4)
}).then(e => {
// 执行微任务
console.log(e);
})
// 执行同步代码并输出
console.log(5); // 结果:2,3,5,4,1
假如有一个构造函数Foo(){…}
当代码 new Foo(…) 执行时,会发生以下事情:
例如:定义一个person对象
let person = {
name:"",
age:30
...
}
person对象并没有toString,valueOf等方法,但是可以调用,因为当对象在调用方法的时候,如果对象上没有定义,就回去obj.prototype
或者__ prop__
里查找,如果obj.prototype
或者__ prop__
里还没有就回继续往上查找,直到最顶层都还没有就回返回undefined 这样一个链式结构就是原型链,原型链最顶层是null
__prop__
是各浏览器厂商实现的
可以在原型链上定义方法
person.prototype.eat = function () {
...
}
**改变原型:**将prototype
指向另一个prototype
即可
参考(command + 点击)
改变原数组
Array.push(),向数组的末尾添加一个或多个元素,并返回新的数组长度。原数组改变。
Array.pop(),删除并返回数组的最后一个元素,若该数组为空,则返回undefined。原数组改变。
Array.unshift(),向数组的开头添加一个或多个元素,并返回新的数组长度。原数组改变。
Array.shift(),删除数组的第一项,并返回第一个元素的值。若该数组为空,则返回undefined。原数组改变。
Array.concat(arr1,arr2…),合并两个或多个数组,生成一个新的数组。原数组不变。
Array.join(),将数组的每一项用指定字符连接形成一个字符串。默认连接字符为 “,” 逗号。
Array.reverse(),将数组倒序。原数组改变。
Array.sort(),对数组元素进行排序。按照字符串UniCode码排序,原数组改变。
**Array.splice(index,howmany,arr1,arr2…)**删除元素并添加元素,从index位置开始删除howmany个元素,并将arr1、arr2…数据从index位置依次插入。howmany为0时,则不删除元素。原数组改变。
**Array.splice()**从start开始,end之前结束,不到end;如果不给end值,从start开始到数组结束。start可以给负值,-1表示数组最后位置,-2表示倒数第二个,以此类推,顾前不顾后。
不改变原数组
Array.map(function),原数组的每一项执行函数后,返回一个新的数组。原数组不变。
Array.forEach(function),用于调用数组的每个元素,并将元素传递给回调函数。原数组不变
Array.filter(function),过滤数组中,符合条件的元素并返回一个新的数组。
Array.every(function),对数组中的每一项进行判断,若都符合则返回true,否则返回false。例如:1是否都小于[1,2,3,4]
Array.some(function),对数组中的每一项进行判断,若都不符合则返回false,否则返回true。例如:1是否存在于[1,2,3,4,5]数组中
Array.reduce(function),reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。
let datas = [1, 4, 2, 5, 4, 2, 6, 4, 7, 4, 3, 6, 3, 7, 8, 32, 564, 23, 45, 234, 65, 34, 6, 34, 5, 23]
//检测当前值在原数组中第一次出现的位置在与当前下标做对比[皱眉]
datas = datas.filter((item, index, arr) => {
/**
* indexOf(item,start)
* item 必须。查找的元素。
* start 规定在数组中开始检索的位置
*/
return arr.indexOf(item, 0) === index
})
console.log(datas);
Array.from(new Set([...]))
let newArr = []
datas.forEach((item, index, arr) => {
if (!newArr.includes(item)) newArr.push(item)
})
console.log(newArr);
datas = [...new Set(datas)]
console.log(datas);
/**
*Map对象是ES6提供的一个新的数据结构,
*其中has的办法是返回一个布尔值,表示某个值是否存在当前的Mp对象之中,
*set的办法是给Map对象设置key/value。
*/
function unique(arr) {
let map = new Map()
return arr.filter(item => !map.has(item) && map.set(item, 1))
}
console.log(unique(datas));
参考 command + 单击
cookie
cookie用于保存网站的一些登录信息,验证密钥等,最大可保存4k数据,调用api的时候会自动携带cookie在HTTP请求头中,如果使用cookie保存过多数据会带来性能问题
localStorage
用于长久保存整个网站的数据,保存的数据没有过期时间,直到手动去除,一般5M左右
sessionStorage
用于临时保存同一窗口(或标签页)的数据,在关闭窗口或标签页之后将会删除这些数据,一般5M左右
操作localStorage和sessionStorage
通过setItem(key, value)
设置数据,
通过getItem(key)
获取数据,
通过removeItem(key)
移除数据
通过clear()
清除所有数据
**强缓存:**不会向服务器发送请求,直接从缓存中读取资源,可以设置两种 HTTP Header 来实现:Expires、Cache-Control
Expires
Cache-Control
协商缓存
参考 command + 点击
渐进式,组件化开发,数据响应式,双向数据绑定,数据驱动视图
v-if v-else v-else-if v-show v-for v-bind v-ones v-on v-html v-text v-model
v-model
在每次 input
事件触发后将输入框的值与数据进行同步 。你可以添加 lazy
修饰符,相当于在onchange事件触发更新。事件修饰符:
if 和 show都是控制dom的显示与隐藏,不同的在于if是直接删除dom show是通过cssdisplay: block / none;
控制domd的显示隐藏,不会删除dom
需要频繁切换的场景下建议使用show,如果是简单的dom结构显示隐藏两者都可以
为了保证组件的独立性和可复用性:data是一个函数,组件实例化的时候这个函数将会被调用,返回一个对象,计算机会给这个对象分配一个内存地址,你实例化几次,就分配几个内存地址,他们的地址都不一样,所以每个组件中的数据不会相互干扰,改变其中一个组件的状态,其它组件不变。
当vue在进行新旧虚拟dom diff
对比的时候,patch函数会通过元素的tag和key来判断是不是同一个元素,如果不设置key就默认为undefined,对比的时候两个元素就会认为是同一个,导致视图没更新,例如两个input相互切换,没有key切换的时候就回保留上一个input元素的value值,还有一种情况就是列表渲染的时候,当没有设置key的时候,列表发生改变时所有列表都会更新,设置key之后只会更新对应的列表项
computed
支持缓存,只有依赖数据发生改变,才会重新进行计算
不支持异步,当computed内有异步操作时无效,无法监听数据的变化
computed 属性值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data中声明过的数据通过计算得到的
如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是一个多对一或者一对一,一般用computed
如果computed属性属性值是函数,那么默认会走get方法;函数的返回值就是属性的属性值;在computed中的,属性都有一个get和一个set方法,当数据变化时,调用set方法。
watch
不支持缓存,数据变,直接会触发相应的操作;
watch支持异步;
监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值;
当一个属性发生变化时,需要执行对应的操作;一对多;
进阶:
vue对methods的处理比较简单,只需要遍历methods配置中的每个属性,将其对应的函数使用bind绑定当前组件实例后复制其引用到组件实例中即可,这样就可以使用this调用
Vue它会遍历computed配置中的所有属性,为每一个属性创建一个Watcher对象,并传入一个函数,该函数的本质其实就是computed配置中的getter,这样一来,getter运行过程中就会收集依赖,但是这个watcher不回立即执行,只有在该computed属性被使用的时候才会执行,
父子之间通信:
prop,event,style和class,natvie修饰符,sync修饰符, l i s t e n e r s , v − m o d e l , listeners,v-model, listeners,v−model,parent和 c h i l d r e n , children, children,slots和$scopedSlots,ref
跨组件通信:
vuex,router,eventBus,store模式,Provide和Inject
参考 (command + 单击)
vue的数据响应只要通过4个核心部件来实现的,分别是:
Observer的作用很简单,主要是通过Object.defineProperty将数据包装成一个带有getter/setter的特殊属性,读取数据的时候通过getter方法,写入数据的时候通过setter方法
其中对对象和数组的处理有所不同,对象是直接通过Observer去包装,而数组则是重写了能够改变数组的一些方法,例如push
, pop
, shift
, unshift
, splice
, sort
, reverse
,然后在通过observeArray方法去遍历数组里的数据,是数据具有响应式
Dep主要是收集依赖和通知依赖更新,当数据被使用的时候就回调用getter方法,get里面会通过dep实例调用dep.depend()方法,收集依赖;当数据被改变的时候就回通知依赖更新,set里面通过dep实况调用dep.notify()方法通知所有订阅者依赖更新。
当某个函数在执行的过程中,使用到了响应式数据时,vue就会为响应式数据创建一个watcher实例,当数据发生改变时,vue不直接通知相关依赖更新,而是通知依赖对应的watcher实例去执行。
watcher实例不直接更新视图,而是交给scheduler
调度器,scheduler
维护一个事件队列通过nextTick执行事件,从而更新视图。
参考 ( command + 单击)
v-model可以作用到表单元素或者自定义组件上,
作用在表单元素上有两种情况,
作用在自定义组件时默认生成一个value属性和一个input方法,也可以通过配置组件内部的model来改变默认的属性和方法
// Comp
const Comp = {
model: {
prop: "number", // 默认为 value
event: "change" // 默认为 input
}
// ...
}
参考 ( command + 单击
分为五个阶段,初始化阶段(beforeCreate,created),模版编译阶段(生成render函数),挂载阶段(beforemunt,munted),更新阶段(beforeUpdate,update),销毁阶段(before Destroy,destroyed)
**初始化阶:**段主要设置一些所有属性到vue实例中,并处理computed,methods,data,provide,inject属性,使用代理模式将这些属性挂载到vue实例中
**编译阶段:**主要将模版编译成render函数
**挂载阶段:**主要将render函数运行获得的虚拟DOM按照每一个节点创建对应的elm属性,即真实DOM,在这一过程中,会收集所有动态数据的依赖
**更新阶段:**动态数据发生改变后之前收集的依赖Watcher会被重新运行,促发一个叫做updateCompontent的函数,该函数会出现执行render函数获得新的vnode,并把新的vnode作为参数过_update函数,在执行update函数的过程中,会触发patch函数对比新旧两颗dom树
**销毁阶段:**组件离开或者所销毁的时候,会调用组件的$destroy方法删除组件,该方法会先后调用beforeDestroy和destroy方法,移除自身所有的依赖和事件监听,彻底销毁组件实例
参考(command + 单机)
mixin可以全局混入,也可以组件混入,通过mixin混入的生命周期会和组件的生命周期进行合并,冲突的方法以组件里的为主,执行顺序:
bind 只调用一次,指令第一次绑定到元素时候调用,用这个钩子可以定义一个绑定时执行一次的初始化动作。
inserted:被绑定的元素插入父节点的时候调用(父节点存在即可调用,不必存在document中)
update: 被绑定于元素所在模板更新时调用,而且无论绑定值是否有变化,通过比较更新前后的绑定值,忽略不必要的模板更新
componentUpdate :被绑定的元素所在模板完成一次更新更新周期的时候调用
unbind: 只调用一次,指令与元素解绑的时候调用
Vue.directive("hello",{
bind:function(el,bingind,vnode){
//只调用一次,指令第一次绑定到元素时候调用,
//用这个钩子可以定义一个绑定时执行一次的初始化动作。
el.style["color"] = bingind.value;
console.log("1-bind");
},
inserted:function(){
//被绑定的元素插入父节点的时候调用(父节点存在即可调用,不必存在document中)
console.log("2-inserted");
},
update:function(){
//被绑定于元素所在模板更新时调用,而且无论绑定值是否有变化
//通过比较更新前后的绑定值,忽略不必要的模板更新
console.log("3-update");
},
componentUpdated:function(){
//被绑定元素所在模板完成一次更新周期时调用
console.log('4 - componentUpdated');
},
unbind:function(){
//只调用一次,指令与元素解绑时调用。
console.log('5 - unbind');
}
})
列表使用Key
静态数据使用对象冻结,比如长列表,列表数据不需要增删改的情况下可以使用Object.freeze冻结对象,被冻结的对象不回被响应化
使用函数式组件,在不需要组件实例的组件中使用函数式组件
Vue.component('my-component', {
functional: true,
// Props 是可选的
props: {
// ...
},
// 为了弥补缺少的实例
// 提供第二个参数作为上下文
render: function (createElement, context) {
// ...
}
})
使用计算属性,如果模板中某个数据会使用多次,并且该数据是通过计算得到的,使用计算属性以缓存它们
使用非实时绑定的表单
当使用v-model
绑定一个表单项时,当用户改变表单项的状态时,也会随之改变数据,从而导致vue
发生重渲染(rerender
),这会带来一些性能的开销。
特别是当用户改变表单项时,页面有一些动画正在进行中,由于JS执行线程和浏览器渲染线程是互斥的,最终会导致动画出现卡顿。
我们可以通过使用lazy
或不使用v-model
的方式解决该问题,但要注意,这样可能会导致在某一个时间段内数据和表单项的值是不一致的。
在频繁切换显示隐藏的时候使用v-show代替v-if
对于频繁切换显示状态的元素,使用v-show可以保证虚拟dom树的稳定,避免频繁的新增和删除元素,特别是对于那些内部包含大量dom元素的节点,这一点极其重要
关键字:频繁切换显示状态、内部包含大量dom元素
使用延迟装载(defer)
首页白屏时间主要受到两个因素的影响:
打包体积过大
巨型包需要消耗大量的传输时间,导致JS传输完成前页面只有一个 需要立即渲染的内容太多 JS传输完成后,浏览器开始执行JS构造页面。 但可能一开始要渲染的组件太多,不仅JS执行的时间很长,而且执行完后浏览器要渲染的元素过多,从而导致页面白屏 打包体积过大需要自行优化打包体积,本节不予讨论 本节仅讨论渲染内容太多的问题。 一个可行的办法就是延迟装载组件,让组件按照指定的先后顺序依次一个一个渲染出来 延迟装载是一个思路,本质上就是利用 使用Keep- alive缓存组件 长列表优化,当有大量列表需要展示的时候,不需要一次性将所有列表渲染出来,只需要渲染可视区域及可视区域以下的一部分列表,并为这些已经渲染的列表设置绝对定位,让后通过计算滚动位置来不停更新开始区域的列表即可,不需要渲染所有的数据列表(插件:VueVirtualScroller) keep-alive组件是vue的内置组件,用于缓存内部组件实例。这样做的目的在于,keep-alive内部的组件切回时,不用重新创建组件实例,而直接使用缓存中的实例,一方面能够避免创建组件带来的开销,另一方面可以保留组件的状态。 keep-alive具有include和exclude属性,通过它们可以控制哪些组件进入缓存。另外它还提供了max属性,通过它可以设置最大缓存数,当缓存的实例超过该数时,vue会移除最久没有使用的组件缓存。 受keep-alive的影响,其内部所有嵌套的组件都具有两个生命周期钩子函数,分别是 使用内置组件Transition实现动画 具体的监听内容是: TransitionGroup组件可以监听列表的变化它会对列表的新增元素应用进入效果,删除元素应用消失效果,对被移动的元素应用 被移动的元素之所以能够实现过渡效果,是因为 主要包含三部分:prop、event、slot 参考 command + 单击 vue3去掉了vue构造函数,通过导出一个createApp的方法来创建一个vue项目, 新增了组合式Api,这样同一个功能的所有业务代码都可以写到一起而不必分散到组件的各个模块中, 对于响应式数据的实现方式不同,放弃了Object.defineProperty的实现方式,而是使用Proxy, v-model的实现方式有所不同 当对自定义组件使用 去掉了 组件的 允许自定义 vue2的全局构造函数带来了诸多问题: vue3不再使用Object.defineProperty的方式定义完成数据响应式,而是使用Proxy。 应用: 判断: 从两个方面回答: 有了composition api,配合reactivity api,可以在组件内部进行更加细粒度的控制,使得组件中不同的功能高度聚合,提升了代码的可维护性。对于不同组件的相同功能,也能够更好的复用。 context对象的成员 新增钩子函数说明: DebuggerEvent: vuex是vue的数据状态管理器,内部分别为: 将state仓库的数据备份到localStorage中,或者使用相关插件,例如:persistedstate 用于路由跳转,通过to指定跳转路径,默认情况下router-link会被渲染成一个a标签,可以通过指定tag改变最终渲染的标签,active-class可以指定被激活的样式。 路由的出口,跳转的组件在router-view里渲染 通过在路由对象里配置redirect实现重定向 完整的路由钩子触发流程 **在 Vue 实例中,可以通过
requestAnimationFrame
事件分批渲染内容,它的具体实现多种多样// mixins
export default function(maxFrameCount) {
return {
data() {
return {
frameCount: 0,
};
},
mounted() {
const refreshFrameCount = () => {
requestAnimationFrame(() => {
this.frameCount++;
if (this.frameCount < maxFrameCount) {
refreshFrameCount();
}
});
};
refreshFrameCount();
},
methods: {
defer(showInFrameCount) {
return this.frameCount >= showInFrameCount;
},
},
};
}
keep-alive
activated
和deactivated
,它们分别在组件激活和失活时触发。第一次activated
触发是在mounted
之后vue过渡和动画
Transition
组件会监控slot
中唯一根元素的出现和消失,并会在其出现和消失时应用过渡效果
v-show
是否变化,true->false
应用消失效果,false->true
应用进入效果v-move
样式
TransisionGroup
内部使用了Flip过渡方案vue组件封装
VUE3
vue3相比较vue2的一些变化
v-if
的优先级现在高于 v-for
v-model
指令时,绑定的属性名由原来的value
变为modelValue
,事件名由原来的input
变为update:modelValue
<!-- vue2 -->
<ChildComponent :value="pageTitle" @input="pageTitle = $event" />
<!-- 简写为 -->
<ChildComponent v-model="pageTitle" />
<!-- vue3 -->
<ChildComponent
:modelValue="pageTitle"
@update:modelValue="pageTitle = $event"
/>
<!-- 简写为 -->
<ChildComponent v-model="pageTitle" />
.sync
修饰符,它原本的功能由v-model
的参数替代<!-- vue2 -->
<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />
<!-- 简写为 -->
<ChildComponent :title.sync="pageTitle" />
<!-- vue3 -->
<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />
<!-- 简写为 -->
<ChildComponent v-model:title="pageTitle" />
model
配置被移除v-model
修饰符<Compon v-model.cap="data1" v-model:text="data2" />
为什么vue3中去掉了vue构造函数?
vue3数据响应式的理解
除了Proxy本身效率比Object.defineProperty更高之外,由于不必递归遍历所有属性,而是直接得到一个Proxy。所以在vue3中,对数据的访问是动态的,当访问某个属性的时候,再动态的获取和设置,这就极大的提升了在组件初始阶段的效率。
同时,由于Proxy可以监控到成员的新增和删除,因此,在vue3中新增成员、删除成员、索引访问等均可以触发重新渲染,而这些在vue2中是难以做到的。vue3中如何定义响应式数据(reactivity api)
API
传入
返回
备注
reactive
plain-object
对象代理
深度代理对象中的所有成员
readonly
plain-object
or proxy
对象代理
只能读取代理对象中的成员,不可修改
ref
any
{ value: ... }
对value的访问是响应式的
如果给value的值是一个对象,
则会通过reactive
函数进行代理
如果已经是代理,则直接使用代理
computed
function
{ value: ... }
当读取value值时,
会根据情况决定是否要运行函数
reactive
或ref
readonly
ref
computed
API
含义
isProxy
判断某个数据是否是由
reactive
或readonly
isReactive
判断某个数据是否是通过
reactive
创建的
详细:https://v3.vuejs.org/api/basic-reactivity.html#isreactive
isReadonly
判断某个数据是否是通过
readonly
创建的
isRef
判断某个数据是否是一个
ref
对象composition api相比于option api有哪些优势?
相比于option api,composition api中没有了指向奇怪的this,所有的api变得更加函数式,这有利于和类型推断系统比如TS深度配合。setup
// component
export default {
setup(props, context){
// 该函数在组件属性被赋值后立即执行,早于所有生命周期钩子函数
// props 是一个对象,包含了所有的组件属性值
// context 是一个对象,提供了组件所需的上下文信息
}
}
成员
类型
说明
attrs
对象
同
vue2
的this.$attrs
slots
对象
同
vue2
的this.$slots
emit
方法
同
vue2
的this.$emit
生命周期函数
vue2 option api
vue3 option api
vue 3 composition api
beforeCreate
beforeCreate
不再需要,代码可直接置于setup中
created
created
不再需要,代码可直接置于setup中
beforeMount
beforeMount
onBeforeMount
mounted
mounted
onMounted
beforeUpdate
beforeUpdate
onBeforeUpdate
updated
updated
onUpdated
beforeDestroy
改 beforeUnmount
onBeforeUnmount
destroyed
改unmounted
onUnmounted
errorCaptured
errorCaptured
onErrorCaptured
-
新renderTracked
onRenderTracked
-
新renderTriggered
onRenderTriggered
钩子函数
参数
执行时机
renderTracked
DebuggerEvent
渲染vdom收集到的每一次依赖时
renderTriggered
DebuggerEvent
某个依赖变化导致组件重新渲染时
VUEX
VUEX数据状态持久化
VUEX的getter特性
VUEX的mutations和action的异同
vue - Router路由
router-link
router-view
路由重定向
路由模式
路由守卫
// 全局前置路由钩子函数
beforeEach(to,from,next){
next()
}
//全局后置路由钩子函数
afterEach(to,from){}
// 全局解析守卫 这和 beforeEach 类似
beforeResolve(){}
// 路由独享守卫
const routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: (to, from) => {
// reject the navigation
return false
},
},
]
//组件内的守卫
const UserDetails = {
template: `...`,
beforeRouteEnter(to, from) {
// 在渲染该组件的对应路由被验证前调用
// 不能获取组件实例 `this` !
// 因为当守卫执行时,组件实例还没被创建!
},
beforeRouteUpdate(to, from) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
// 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
},
beforeRouteLeave(to, from) {
// 在导航离开渲染该组件的对应路由时调用
// 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
},
}
beforeRouteLeave
守卫。beforeEach
守卫。beforeRouteUpdate
守卫(2.2+)。beforeEnter
。beforeRouteEnter
。beforeResolve
守卫(2.5+)。afterEach
钩子。beforeRouteEnter
守卫中传给 next
的回调函数,创建好的组件实例会作为回调函数的参数传入。路由跳转
$router
访问路由实例。可以调用 this.$router.push
**导航到不同的页面// 字符串路径
router.push('/users/eduardo')
// 带有路径的对象
router.push({ path: '/users/eduardo' })
// 命名的路由,并加上参数,让路由建立 url
router.push({ name: 'user', params: { username: 'eduardo' } })
// 带查询参数,结果是 /register?plan=private
router.push({ path: '/register', query: { plan: 'private' } })
// 带 hash,结果是 /about#team
router.push({ path: '/about', hash: '#team' })
// 向前移动一条记录,与 router.forward() 相同
router.go(1)
记录滚动位置
const router = createRouter({
history: createWebHashHistory(),
routes: [...],
scrollBehavior (to, from, savedPosition) {
// return 期望滚动到哪个的位置
return {
top:0,
left:0
}
}
})
axios
特点
axios相关的配置属性
axios请求拦截器与响应拦截器
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
axios.interceptors.response.use(function (response) {
// 对响应数据做点什么
return response;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});