HTML5
新特性
语义化标签: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(宽度是设计稿宽度的一半)。我们可以
- 设置 html 的 font-size 为 100*屏幕宽度/设计稿宽度
- 在写 CSS 时设置 div 的宽度是 3.75rem(计算时用设计稿标注值除以100),边框宽度为 1px
假设用户在逻辑像素宽度是 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 只是为了方便就是,事实上可以取任意值
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也不会改变对象的实际宽度
CSS3
新增的一些特性
圆角border-radius,阴影box-shadow,文字阴影text-shadow,
线性渐变(gradient),旋转(transform)
媒体查询(@media),多栏布局(colums)
边框图片:border-image
CSS3动画
https://zhuanlan.zhihu.com/p/...
css三栏布局(圣杯布局),左右固定宽度,中间自适应
绝对定位法
原理是将左右两边使用absolute定位,因为绝对定位使其脱离文档流,后面的 middle 会自然流动到他们上面,然后使用margin属性,留出左右元素的宽度,既可以使中间元素自适应屏幕宽度。
left
middle
right
自身浮动法
原理就是对左右分别左浮动和右浮动,float使左右两个元素脱离文档流,中间元素正常在正常文档流中。对中间文档流使用margin指定左右外边距进行定位。
该布局法的不足是三个元素的顺序,middle一定要放在最后,middle占据文档流位置,所以一定要放在最后,左右两个元素位置没有关系。当浏览器窗口很小的时候,右边元素会被挤到下一行。
left
right
middle
flex 布局法
原理就是为父元素添加样式display:flex,左右固定宽度,中间设置flex:1,middle一定要放在中间。
left
middle
right
Css浏览器兼容
根据不同的浏览器内核添加不同的css前缀
Chrome 内核 之前Webkit,已改Blink内核
FireFox火狐 内核 Gecko
Ssfari苹果 内核 Webkit
IE浏览器 内核 Trident
Opera欧朋 内核 现已改用Google Chrome的Blink内核
-moz-
火狐浏览器-Webkit-
safari谷歌浏览器等使用Webkit引擎的浏览器-o-
Opera浏览器(早期)-ms-
IE
实现垂直居中
对已知高度块级元素进行垂直居中
- 绝对定位,配合top:50%和负margin-top(元素高度一半)进行垂直居中
.content{
position: absolute;
top: 50%;
left: 50%;
margin-top: -10em; /* 为元素height/2 */
margin-left: -10em;
width: 20em;
height: 20em;
background-color: aqua;
}
- 绝对定位,配合top:0;bottom:0;和margin:auto进行垂直居中
.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 1到底是什么
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常用属性
flex-direction:主轴的方向(即项目的排列方向)
- row(默认值):主轴为水平方向,起点在左端。
- row-reverse:主轴为水平方向,起点在右端
- column:主轴为垂直方向,起点在上沿。
- column-reverse:主轴为垂直方向,起点在下沿。
flex-wrap属性:(轴线)
- flex-wrap: nowrap | wrap | wrap-reverse;
justify-content属性:定义了项目在主轴上的对齐方式。
- justify-content: flex-start | flex-end | center | space-between | space-around;
align-items属性:定义项目在交叉轴上如何对齐。
- align-items: flex-start | flex-end | center | baseline | stretch;
align-content属性:定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。
- align-content: flex-start | flex-end | center | space-between | space-around | stretch;
flex-grow属性:定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大
- flex-grow:
; / default 0 /
JS
var、let、const 的区别?
var 声明的变量有变量提升的特性,而 let、const 没有
同一作用域下 let 和 const 不能重复声明同名变量,而var可以
var 声明的变量会挂载到 windows 对象上,所以使用 var 声明的是全局变量
const声明的是常量,必须赋初值,一旦声明不能再次赋值修改,如果声明的是复合类型数据,可以修改其属性
js中的数据类型
基础类型:number,string,null,undefiend,boolean,symbol
引用类型:Object,Function,Date,RE正则
=== 与 ==
== 在比较类型不同的变量时,会进行数据类型转化,将二者转换成数据类型相同的变量,再进行比较。
=== 比较的二者数据类型不一样时,直接返回false。
判断某个属性是否存在对象中
in 运算符:如果指定的属性在指定的对象或其原型链中,则in 运算符返回true。
hasOwnProperty(key):表示是否有自己的属性。这个方法会查找一个对象是否有某个属性,但是不会去查找它的原型链。
Object.key():获取对象所有的key,返回一个数组,可判断属性是否存在数组中
判断this指向?箭头函数的this指向什么?
普通函数:普通函数this指向默认window,严格模式下this为undefiend
在构造函数以及类中的this:构造函数和类需要配合 new 使用, 而 new 关键字会将构造函数中的 this 指向实例对象,所以 this 指向 实例对象。
绑定事件函数的this:谁调用就指向谁。
在箭头函数中:箭头函数没有自己的 this,会继承其父作用域的 this。
call apply bind 的作用与区别?
作用:改变函数内部 this 的指向
区别:
- call 和 apply 会调用函数,而 bind 不会调用
- call 和 bind 传递参数为 逐个传入,而 apply 的参数必须为数组形式
什么是闭包?
闭包是指能够访问另一个函数作用域中的变量的一个函数。 在js中,只有函数内部的子函数才能访问局部变量, 所以闭包可以理解成 “定义在一个函数内部的函数”。
闭包的作用
利用闭包可以突破作用链域,将函数内部的变量和方法传递到外部,让外部函数可以访问到内部函数的变量和方法
闭包的优点
正常的函数,在执行完之后,函数里面声明的变量会被垃圾回收机制处理掉。但是形成闭包的函数在执行之后,不会被回收,依旧存在内存中。
闭包的缺点
因为变量不会被回收,所以内存中一直存在,耗费内存。
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有三个状态:
- Pending(进行中)
- Resolved(已完成)
- Rejected(已拒绝)
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是单线程的脚本语言,同一时间只能做同一件事,以至于执行时间比较长的代码就会发生阻塞,为了解决这一问题,js将代码执行的模式分为两种,同步和异步:
- 同步模式: 就是前一个任务执行完成后,再执行下一个任务,程序的执行顺序与任务的排列顺序是一致的、同步的;
- 异步模式: 则完全不同,每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行队列上的后一个任务,而是执行回调函数;后一个任务则是不等前一个任务的回调函数的执行而执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。
事件循环:
- 执行全局Script 同步代码,这些同步代码有一些是同步语句,有一些是异步语句(比如setTimeout等)(这个过程中触发的回调函(比如resolve,)数进入宏/微队列);
- 全局Script执行完毕(执行栈清空),
- 执行微任务队列(微任务进入执行栈),直到微任务队列清空(注意,如果在执行microtask的过程中,又产生了microtask,那么会加入到队列的末尾,也会在这个周期被调用执行;)
- 取出宏队列队首的任务,进入执行栈;
- 执行宏任务(宏任务执行过程中可能产生新的宏任务或微任务,分别入队列,宏队列可能不为空,进入宏队列队尾)
- 如此反复循环,直到所有任务清空(执行栈清空)
同步任务,宏任务,微任务:
- 同步任务:script(整体代码)
- 宏任务:setTimeout,setInterval,setImmediate(node中),I/O (比如Ajax操作从网络读取数据),UI render(渲染)
- 微任务:process.nextTick,Promise,Async/Await(实际就是promise),MutationObserver(html5新特性)
// 执行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
JS new 一下会发生什么?
假如有一个构造函数Foo(){...}
当代码 new Foo(…) 执行时,会发生以下事情:
- 一个继承自 Foo.prototype 的新对象被创建。
- 使用指定的参数调用构造函数 Foo,并将 this 绑定到新创建的对象。new Foo 等同于 new Foo(),也就是没有指定参数列表,Foo 不带任何参数调用的情况。
- 由构造函数返回的对象就是 new 表达式的结果。如果构造函数没有显式返回一个对象,则使用步骤1创建的对象。(一般情况下,构造函数不返回值,但是可以手动return返回对象,来覆盖正常的对象创建步骤)
JS原型链
例如:定义一个person对象
let person = {
name:"",
age:30
...
}
person对象并没有toString,valueOf等方法,但是可以调用,因为当对象在调用方法的时候,如果对象上没有定义,就回去obj.prototype
或者__ prop__
里查找,如果obj.prototype
或者__ prop__
里还没有就回继续往上查找,直到最顶层都还没有就回返回undefined 这样一个链式结构就是原型链,原型链最顶层是null
__prop__
是各浏览器厂商实现的
可以在原型链上定义方法
person.prototype.eat = function () {
...
}
改变原型:将prototype
指向另一个prototype
即可
JS实现继承
JS数组相关方法
改变原数组
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);
- new Set() 配合 Array.from
Array.from(new Set([...]))
- 循环判断法
let newArr = []
datas.forEach((item, index, arr) => {
if (!newArr.includes(item)) newArr.push(item)
})
console.log(newArr);
- set 加扩展运算符[...]
datas = [...new Set(datas)]
console.log(datas);
- Map对象加filter
/**
*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));
Map 和 Set
本地存储
cookie
cookie用于保存网站的一些登录信息,验证密钥等,最大可保存4k数据,调用api的时候会自动携带cookie在HTTP请求头中,如果使用cookie保存过多数据会带来性能问题
localStorage
用于长久保存整个网站的数据,保存的数据没有过期时间,直到手动去除,一般5M左右
sessionStorage
用于临时保存同一窗口(或标签页)的数据,在关闭窗口或标签页之后将会删除这些数据,一般5M左右
操作localStorage和sessionStorage
- 通过
setItem(key, value)
设置数据, - 通过
getItem(key)
获取数据, - 通过
removeItem(key)
移除数据 - 通过
clear()
清除所有数据
JS浏览器缓存 -- 强缓存与协商缓存
强缓存:不会向服务器发送请求,直接从缓存中读取资源,可以设置两种 HTTP Header 来实现:Expires、Cache-Control
Expires
- 缓存过期时间用来指定资源的过期时间,是服务器端的具体的时间点,结合 last-modified 来使用
- 是 HTTP/1 的产物,受限于本地时间,如果本地时间修改可能会造成缓存失效
Cache-Control
- 可以在请求头或响应头中来设置,多个指令配合使用达到多个目的
- 是 HTTP/1.1 的产物,如果和 Expires同时存在,那么它的优先级要高,所以 Expires 的存在成为了一种兼容性的写法
协商缓存
- 强缓存的依据来自于缓存是否过期,而不关心服务端文件是否已经更新,这可能会导致加载的文件不是服务端最新的内容,此时我们就需要用到协商缓存
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发送请求,由服务器根据缓存标识决定是否使用缓存的过程,主要分两种情况
- 协商缓存生效返回 304 和 Not Modified
- 协商缓存失效返回 200 和请求的结果
- 协商缓存可以通过设置两种 HTTP Header 来实现:Last-Modified 和 ETag
VUE
特性
渐进式,组件化开发,数据响应式,双向数据绑定,数据驱动视图
vue指令
v-if v-else v-else-if v-show v-for v-bind v-ones v-on v-html v-text v-model
vue修饰符
- lazy,在默认情况下,
v-model
在每次input
事件触发后将输入框的值与数据进行同步 。你可以添加lazy
修饰符,相当于在onchange事件触发更新。 - trim,过滤首尾空格
- number 将值转换成number类型
事件修饰符:
- stop,阻止事件冒泡
- prevent,阻止事件默认行为
- self,点击事件绑定的本身才会触发事件
- once 一次性事件
- native 转化为原生事件
v-if 和 v-show的区别
if 和 show都是控制dom的显示与隐藏,不同的在于if是直接删除dom show是通过cssdisplay: block / none;
控制domd的显示隐藏,不会删除dom
需要频繁切换的场景下建议使用show,如果是简单的dom结构显示隐藏两者都可以
为什么vue中data必须是一个函数
为了保证组件的独立性和可复用性:data是一个函数,组件实例化的时候这个函数将会被调用,返回一个对象,计算机会给这个对象分配一个内存地址,你实例化几次,就分配几个内存地址,他们的地址都不一样,所以每个组件中的数据不会相互干扰,改变其中一个组件的状态,其它组件不变。
key的作用
当vue在进行新旧虚拟dom diff
对比的时候,patch函数会通过元素的tag和key来判断是不是同一个元素,如果不设置key就默认为undefined,对比的时候两个元素就会认为是同一个,导致视图没更新,例如两个input相互切换,没有key切换的时候就回保留上一个input元素的value值,还有一种情况就是列表渲染的时候,当没有设置key的时候,列表发生改变时所有列表都会更新,设置key之后只会更新对应的列表项
vue中computed和watch的区别
computed
- 支持缓存,只有依赖数据发生改变,才会重新进行计算
- 不支持异步,当computed内有异步操作时无效,无法监听数据的变化
- computed 属性值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data中声明过的数据通过计算得到的
- 如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是一个多对一或者一对一,一般用computed
- 如果computed属性属性值是函数,那么默认会走get方法;函数的返回值就是属性的属性值;在computed中的,属性都有一个get和一个set方法,当数据变化时,调用set方法。
watch
- 不支持缓存,数据变,直接会触发相应的操作;
- watch支持异步;
- 监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值;
- 当一个属性发生变化时,需要执行对应的操作;一对多;
computed和methods的区别
- computed只能作为属性使用,methods只能作为方法调用
- computed具有getter和setter,因此可以赋值,而methods不能
- computed无法接受多个参数,而methods可以
- computed具有缓存,执行过一次之后第二次就直接使用缓存的结果而不回在此执行computed,而methods没有缓存
进阶:
vue对methods的处理比较简单,只需要遍历methods配置中的每个属性,将其对应的函数使用bind绑定当前组件实例后复制其引用到组件实例中即可,这样就可以使用this调用
Vue它会遍历computed配置中的所有属性,为每一个属性创建一个Watcher对象,并传入一个函数,该函数的本质其实就是computed配置中的getter,这样一来,getter运行过程中就会收集依赖,但是这个watcher不回立即执行,只有在该computed属性被使用的时候才会执行,
vue组件通信
父子之间通信:
prop,event,style和class,natvie修饰符,sync修饰符,$listeners,v-model,$parent和$children,$slots和$scopedSlots,ref
跨组件通信:
vuex,router,eventBus,store模式,Provide和Inject
vue数据响应式(双向数据绑定)原理
vue的数据响应只要通过4个核心部件来实现的,分别是:
- Observer
- Dep
- Watcher
- Scheduler
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执行事件,从而更新视图。
v-model的原理
v-model可以作用到表单元素或者自定义组件上,
作用在表单元素上有两种情况,
- 普通表单元素,例如input时,生成一个value属性和input方法
- 单选框或者复选框元素,生成一个checked属性和一个change方法
作用在自定义组件时默认生成一个value属性和一个input方法,也可以通过配置组件内部的model来改变默认的属性和方法
// Comp
const Comp = {
model: {
prop: "number", // 默认为 value
event: "change" // 默认为 input
}
// ...
}
vue生命周期
分为五个阶段,初始化阶段(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方法,移除自身所有的依赖和事件监听,彻底销毁组件实例
vue中mixin用法
mixin可以全局混入,也可以组件混入,通过mixin混入的生命周期会和组件的生命周期进行合并,冲突的方法以组件里的为主,执行顺序:
- 对于created,mounted 等生命周期函数 mixin文件中的代码先执行,组件中的后执行
- 对于data中定义的字段,组件中定义组件覆盖mixin中同名字段
- 对于 method中的同名方法,组件内的同名方法覆盖mixin中的方法
vue自定义指令
- 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');
}
})
vue相关优化
- 列表使用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执行的时间很长,而且执行完后浏览器要渲染的元素过多,从而导致页面白屏
打包体积过大需要自行优化打包体积,本节不予讨论
本节仅讨论渲染内容太多的问题。
一个可行的办法就是延迟装载组件,让组件按照指定的先后顺序依次一个一个渲染出来
延迟装载是一个思路,本质上就是利用
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缓存组件
- 长列表优化,当有大量列表需要展示的时候,不需要一次性将所有列表渲染出来,只需要渲染可视区域及可视区域以下的一部分列表,并为这些已经渲染的列表设置绝对定位,让后通过计算滚动位置来不停更新开始区域的列表即可,不需要渲染所有的数据列表(插件:VueVirtualScroller)
keep-alive
keep-alive组件是vue的内置组件,用于缓存内部组件实例。这样做的目的在于,keep-alive内部的组件切回时,不用重新创建组件实例,而直接使用缓存中的实例,一方面能够避免创建组件带来的开销,另一方面可以保留组件的状态。
keep-alive具有include和exclude属性,通过它们可以控制哪些组件进入缓存。另外它还提供了max属性,通过它可以设置最大缓存数,当缓存的实例超过该数时,vue会移除最久没有使用的组件缓存。
受keep-alive的影响,其内部所有嵌套的组件都具有两个生命周期钩子函数,分别是
activated
和deactivated
,它们分别在组件激活和失活时触发。第一次activated
触发是在mounted
之后vue过渡和动画
使用内置组件Transition实现动画
Transition
组件会监控slot
中唯一根元素的出现和消失,并会在其出现和消失时应用过渡效果具体的监听内容是:
- 它会对新旧两个虚拟节点进行对比,如果旧节点被销毁,则应用消失效果,如果新节点是新增的,则应用进入效果
- 如果不是上述情况,则它会对比新旧节点,观察其
v-show
是否变化,true->false
应用消失效果,false->true
应用进入效果
TransitionGroup组件可以监听列表的变化它会对列表的新增元素应用进入效果,删除元素应用消失效果,对被移动的元素应用
v-move
样式被移动的元素之所以能够实现过渡效果,是因为
TransisionGroup
内部使用了Flip过渡方案vue组件封装
主要包含三部分:prop、event、slot
- props表示组件接收的参数,最好用对象的写法,这样可以针对每个属性设置类型、默认值或自定义校验属性的值,此外还可以通过type、validator等方式对输入进行验证
- slot可以给组件动态插入一些内容或组件,是实现高阶组件的重要途径;当需要多个插槽时,可以使用具名slot
- event是子组件向父组件传递消息的重要途径
VUE3
vue3相比较vue2的一些变化
- vue3去掉了vue构造函数,通过导出一个createApp的方法来创建一个vue项目,
- 新增了组合式Api,这样同一个功能的所有业务代码都可以写到一起而不必分散到组件的各个模块中,
- 对于响应式数据的实现方式不同,放弃了Object.defineProperty的实现方式,而是使用Proxy,
v-if
的优先级现在高于v-for
v-model的实现方式有所不同
当对自定义组件使用
v-model
指令时,绑定的属性名由原来的value
变为modelValue
,事件名由原来的input
变为update:modelValue
去掉了
.sync
修饰符,它原本的功能由v-model
的参数替代- 组件的
model
配置被移除 允许自定义
v-model
修饰符
为什么vue3中去掉了vue构造函数?
vue2的全局构造函数带来了诸多问题:
- 调用构造函数的静态方法会对所有vue应用生效,不利于隔离不同应用
- vue2的构造函数集成了太多功能,不利于tree shaking,vue3把这些功能使用普通函数导出,能够充分利用tree shaking优化打包体积
- vue2没有把组件实例和vue应用两个概念区分开,在vue2中,通过new Vue创建的对象,既是一个vue应用,同时又是一个特殊的vue组件。vue3中,把两个概念区别开来,通过createApp创建的对象,是一个vue应用,它内部提供的方法是针对整个应用的,而不再是一个特殊的组件。
vue3数据响应式的理解
vue3不再使用Object.defineProperty的方式定义完成数据响应式,而是使用Proxy。
除了Proxy本身效率比Object.defineProperty更高之外,由于不必递归遍历所有属性,而是直接得到一个Proxy。所以在vue3中,对数据的访问是动态的,当访问某个属性的时候,再动态的获取和设置,这就极大的提升了在组件初始阶段的效率。
同时,由于Proxy可以监控到成员的新增和删除,因此,在vue3中新增成员、删除成员、索引访问等均可以触发重新渲染,而这些在vue2中是难以做到的。vue3中如何定义响应式数据(reactivity api)
API 传入 返回 备注 reactive
plain-object
对象代理
深度代理对象中的所有成员 readonly
plain-object
orproxy
对象代理
只能读取代理对象中的成员,不可修改 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/basi...isReadonly
判断某个数据是否是通过 readonly
创建的isRef
判断某个数据是否是一个 ref
对象composition api相比于option api有哪些优势?
从两个方面回答:
- 为了更好的逻辑复用和代码组织
- 更好的类型推导
有了composition api,配合reactivity api,可以在组件内部进行更加细粒度的控制,使得组件中不同的功能高度聚合,提升了代码的可维护性。对于不同组件的相同功能,也能够更好的复用。
相比于option api,composition api中没有了指向奇怪的this,所有的api变得更加函数式,这有利于和类型推断系统比如TS深度配合。setup
// component export default { setup(props, context){ // 该函数在组件属性被赋值后立即执行,早于所有生命周期钩子函数 // props 是一个对象,包含了所有的组件属性值 // context 是一个对象,提供了组件所需的上下文信息 } }
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 某个依赖变化导致组件重新渲染时 DebuggerEvent:
- target: 跟踪或触发渲染的对象
- key: 跟踪或触发渲染的属性
- type: 跟踪或触发渲染的方式
VUEX
vuex是vue的数据状态管理器,内部分别为:
- state数据仓库模块,
- mutations模块,用来改变state的状态,同步执行,
- actions模块,提交一个mutations方法改变state状态,异步执行
- getter模块,可以对state进行计算操作,相当于state的计算属性
- modules模块,可以让vuex分模块管理,让每一个模块拥有独立的state,mutations,action,getter模块,方便数据状态的管理以及数据状态变化的追踪
VUEX数据状态持久化
将state仓库的数据备份到localStorage中,或者使用相关插件,例如:persistedstate
VUEX的getter特性
- getter可以对state进行计算操作,相当于state的计算属性
- 便于在组件之间复用getter计算属性
VUEX的mutations和action的异同
- action类似于mutations,不同之处在于action提交的是mutations,而不是直接改变state的状态
- mutations是同步执行,action是一步执行
vue - Router路由
router-link
用于路由跳转,通过to指定跳转路径,默认情况下router-link会被渲染成一个a标签,可以通过指定tag改变最终渲染的标签,active-class可以指定被激活的样式。
router-view
路由的出口,跳转的组件在router-view里渲染
路由重定向
通过在路由对象里配置redirect实现重定向
路由模式
- hash模式,兼容所有浏览器,地址栏会带有#哈希值
- history模式,只兼容之处HTML5 history Api的浏览器,地址栏不会带有#哈希值
- abstract模式,支持所有js运行环境,如node环境,如果不在浏览器环境里运行,路由会自动切换到该模式
路由守卫
- 全局钩子: beforeEach、 afterEach、beforeResolve
- 单个路由里面的钩子: beforeEnter
- 组件内路由钩子函数:beforeRouteEnter、 beforeRouteUpdate、 beforeRouteLeave
// 全局前置路由钩子函数 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
钩子。 - 触发 DOM 更新。
- 调用
beforeRouteEnter
守卫中传给next
的回调函数,创建好的组件实例会作为回调函数的参数传入。
路由跳转
在 Vue 实例中,可以通过
$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是一个基于 promise 的HTTP库,支持promise所有的API
- 它可以拦截请求和响应
- 它可以转换请求数据和响应数据,并对响应回来的内容自动转换成JSON类型的数据
- 安全性更高,客户端支持防御XSRF
axios相关的配置属性
- ‘url’是用于请求的服务器url
- ‘method’ 是创建请求时使用的方法,默认是get,
- 'baseURL’将自动加在url前面,除非url是一个绝对URL。
- ’transformRequest‘允许在向服务器发送前,修改请求数据,只能用在’put\post\patch’这几个请求方法。
- ‘headers’自定义请求头。
- ‘params’是即将与请求一起发送的url参数,必须是一个无格式对象或URLSearchParams对象
- ‘auth’表示原告i使用HTTP基础验证,并提供票据,这将设置一个Authorization头,覆写掉现有的热比一使
- headers设置的自定义Authorization头
- ‘proxy’定义代理服务器的主机名称和端口
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); });