Vue
实现双向绑定的原理
原理
vue数据双向绑定通过‘数据劫持’ + 发布订阅模式实现
数据劫持
指的是在访问或者修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作或者修改返回结果
典型的有
- Object.defineProperty()
- es6中Proxy对象
vue2.x使用Object.defineProperty();
vue3.x使用Proxy;
发布订阅模式
定义:对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知
订阅发布模式中事件统一由处理中心处理,订阅者发布者互不干扰。
优点:实现更多的控制,做权限处理,节流控制之类,例如:发布了很多消息,但是不是所有订阅者都要接收
// 实现一个处理中心
let event = {
clientList: {}, // 订阅事件列表
// 订阅
on(key, fn){
// 如果这个事件没有被订阅,那么创建一个列表用来存放事件
if(!this.clientList[key]) {
this.clientList[key] = []
}
// 将事件放入已有的事件列表中
this.clientList[key].push(fn);
},
// 发布
trigger(type, args){
let fns = this.clientList[type] // 拿到这个事件的所有监听
if(!fns || fns.length === 0){ // 如果没有这条消息的订阅者
return false
}
// 如果存在这个事件的订阅,那么遍历事件列表,触发对应监听
fns.forEach(fn => {
// 可以在此处添加过滤等处理
fn(args)
})
}
}
Vue中如何实现
利用Object.defineProperty();把内部解耦为三部分
Observer: 递归的监听对象上的所有属性,当属性改变时触发对应的watcher
watcher(观察者):当蒋婷的数据值修改时,执行相应的回调函数,更新模板内容
dep:链接observer和watcher,每一个observer对应一个dep,内部维护一个数组,保存与该observer相关的watcher
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NXlbzVUA-1641822685521)(image/18491406-0f1347e1ca0cbfaa.png)]
在new Vue的时候,在Observer中通过Object.defineProperty()达到数据劫持,代理所有数据的getter和setter属性,在每次触发setter的时候,都会通过Dep来通知Watcher,Watcher作为Observer数据监听器与Compile模板解析器之间的桥梁,当Observer监听到数据发生改变的时候,通过Updater来通知Compile更新视图
而Compile通过Watcher订阅对应数据,绑定更新函数,通过Dep来添加订阅者,达到双向绑定
proxy 实现观察者模式
观察者模式(Observer mode)指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行
const person = observable({
name: '张三',
age: 20
});
function print() {
console.log(`${person.name}, ${person.age}`)
}
observe(print);
person.name = '李四';
// 输出
// 李四, 20
代码中。对象person是观察目标,函数print是观察者。一旦数据发生变化,print就会自动执行
使用proxy实现一个最简单观察者模式,即实现observable和observe这两个函数。
思路是observable函数返回一个原始对象的proxy代理,拦截复制操作。触发充当观察者的各个函数
const queue = new Set();
const observe = fn => queue.add(fn);
const observable = obj => new Proxy(obj, {set});
function set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
queue.forEach(observer => observer());
return result;
}
上面代码中,先定义了一个Set集合,所有观察者函数都放进这个集合,然后,observable函数返回原始对象的代理,拦截赋值操作。
拦截函数set中,自动执行所有观察者
Vue中的key值的作用
用于管理可复用的元素。因为Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。这么做使 Vue 编译变得非常快
Vue会尽可能的复用当前页面上所有的元素,如果元素没有独立的key值的话,如果我们只修改了元素的属性,那Vue会重复使用页面上的元素,只是给它修改一个属性
在diff算法
中,Vue使用 key 值来判断元素是否发生更改,以达到重复使用页面上所有可复用元素,特别是在列表渲染中,Vue通过key值来判断元素是否需要更新,如果元素只是更换位置的话,就不需要重新渲染,这也是为什么我们在列表渲染的时候为什么始终不建议使用元素的索引值来作为 key 值,因为索引值始终会发生改变,会增加Vue的渲染成本
Vue核心之虚拟DOM (vdom)
Vue中的 diff原理
总结
- diff算法的本质是
找出两个对象之间的差异
- diff算法的核心是
子节点数组对比
, 思路是通过首尾两端对比
- key的作用 主要是
- 决定节点是否可以复用
- 建立key-index的索引,主要是替换遍历,提升性能
谈谈对$nextTick的理解及使用场景
对nextTick的理解
- Vue的视图更新是通过数据驱动的,当数据发生改变的时候,将当前的数据更改保存在队列中,然后异步的更新视图
- 由于Vue的视图更新是异步的,所以我们在修改完成数据之后,不一定当前的View已经发生改变,于是就有了nextTick
- nextTick是在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
nextTick的使用场景
- 如果要在created()钩子函数中进行的DOM操作,由于created()钩子函数中还未对DOM进行任何渲染,所以无法直接操作,需要通过 n e x t T i c k ( ) 来 完 成 。 在 c r e a t e d ( ) 钩 子 函 数 中 进 行 的 D O M 操 作 , 不 使 用 nextTick()来完成。在created()钩子函数中进行的DOM操作,不使用 nextTick()来完成。在created()钩子函数中进行的DOM操作,不使用nextTick()会报错
- 更新数据后,想要使用js对新的视图进行操作时
- 在使用某些第三方插件时 ,这些插件需要dom动态变化后重新应用该插件,这时候就需要使用$nextTick()来重新应用插件的方法
nextTick的使用方法
-
使用
Vue.nextTick
全局方法// 修改数据 vm.msg = 'Hello' // DOM 还没有更新 Vue.nextTick(function () { // DOM 更新了执行的回调 }) // 作为一个 Promise 使用 (2.1.0 起新增,详见接下来的提示) Vue.nextTick() .then(function () { // DOM 更新了 })
-
在Vue实例对象中,使用
this.$nextTick
方法new Vue({ // ... methods: { // ... example () { // 修改数据 this.message = 'changed' // DOM 还没有更新 this.$nextTick(function () { // DOM 现在更新了 // `this` 绑定到当前实例 this.doSomethingElse() }) } } })
Vue组件中的data为什么必须是函数
首先我们要明白,组件的创建就是为了可以重复使用
Vue的data的两种定义方式:object 和 function
object是引用数据类型,如果使用object定义组件的 data 的话,每次重复使用组件的data 都是内存的同一个地址,一个数据改变了其他也改变了;
而function构成一个局部作用域,每次复用组件的时候,都会返回一个新的object,这个新的object与别的组件的data没有任何关联,是一个新的引用
达到每一个组件都独立维护自己的数据,而不是多个组件维护同一个数据
keep-alive组件
为什么使用keep-alive?
在Vue中,我们使用component内置组件或者vue-router切换视图的时候,由于Vue会主动卸载不使用的组件,所以我们不能保存组件之前的状态,而我们经常能遇到需要保存之前状态的需求,例如搜索页(保存搜索记录),列表页(保存之前的浏览记录)等等
keep-alive的作用?
keep-alive是一个Vue的内置组件,它能将不活动的组件保存下来,而不是直接销毁,当我们再次访问这个组件的时候,会先从keep-alive中存储的组件中寻找,如果有缓存的话,直接渲染之前缓存的,如果没有的话,再加载对应的组件。
作为抽象组件,keep-alive是不会直接渲染在DOM中的
keep-alive的属性?
keep-alive提供了三个可选属性
-
include
字符串或数组或正则表达式。只有名称匹配的组件会被缓存。// **字符串**只缓存a组件 <keep-alive include="a"> <component :is="componentName"></component> </keep-alive> // **字符串**只缓存a组件和b组件 <keep-alive include="a,b"> <component :is="componentName"></component> </keep-alive> // **正则表达式,需要使用v-bind **只缓存a组件和b组件 <keep-alive :include="/a|b/"> <component :is="componentName"></component> </keep-alive> // **数组,需要使用v-bind **只缓存a组件和b组件 <keep-alive :include="[a,b]"> <component :is="componentName"></component> </keep-alive>
-
exclude
- 字符串或数组或正则表达式。名称匹配的组件不会被缓存。// **字符串**不缓存a组件 <keep-alive exclude="a"> <component :is="componentName"></component> </keep-alive> // **字符串**不缓存a组件和b组件 <keep-alive exclude="a,b"> <component :is="componentName"></component> </keep-alive> // **正则表达式,需要使用v-bind **不缓存a组件和b组件 <keep-alive :exclude="/a|b/"> <component :is="componentName"></component> </keep-alive> // **数组,需要使用v-bind **不缓存a组件和b组件 <keep-alive :exclude="[a,b]"> <component :is="componentName"></component> </keep-alive>
-
max
- 数字类型。表示最多可以缓存多少组件实例。<keep-alive :max="10"> <!--表示最多缓存十个组件实例--> <component :is="view"></component> </keep-alive>
对于keep-alive需要知道的事情?
keep-alive提供了两个生命钩子,分别是activated与deactivated。
因为keep-alive会将组件保存在内存中,并不会销毁以及重新创建,所以不会重新调用组件的created等方法,需要用activated与deactivated这两个生命钩子来得知当前组件是否处于活动状态。
<script>
export default {
name: 'componentA',
activated() {
console.log('组件激活了')
}
deactivated() {
console.log('组件被缓存了')
}
}
</script>
计算属性 computed
什么是计算属性? 什么情况使用?
计算属性
computed是Vue的计算属性
为什么会有计算属性
我们都知道的是,在Vue的模板指令中我们可以书写一些简单的计算,但是,如果我们在模板指令中书写太多的逻辑运算的话,会使得代码难以维护,例如
<div>{{ count * price* discount + deliver }}</div>
我们不需要关注这个运算的结果是什么,想象一下,当你的项目中在模板指令存在这样的运算的时候,你的代码将变得难以解读
什么情况下使用计算属性
当我们需要一个值或者一个数据,而这个数据需要通过一些逻辑运算才能得到的时候,我们更希望将它放在计算属性内,这样的话我们可以将整个项目对于数据的复杂运算逻辑全部集中管理在计算属性内
计算属性如何使用
在一个计算属性里可以完成各种复杂的逻辑,包括运算、函数调用等,只要最终返回一个结果就可以。计算属性可以依赖多个Vue 实例的数据,还可以依赖其他计算属性; 而且计算属性不仅可以依赖当前Vue 实例的数据,还可以依赖其他实例的数据
只要其中任一数据变化,计算属性就会重新执行,视图也会更新。
// template
<div>{{totalPrice}}</div>
// script
computed: {
totalPrice() {
return this.count * this.price* this.discount + this.deliver;
}
}
这样我们就实现了一个最基础的计算属性
而计算属性还有一些稍微高级的用法
// template
<div>{{totalPrice}}</div>
// script
computed: {
totalPrice: {
get() {
return this.count * this.price* this.discount + this.deliver;
}
set(newValue) {
this.count = (newValue - this.deliver) / this.discount / this.price
}
}
}
每一个计算属性都包含一个getter 和一个setter ,我们上面的示例中,利用getter函数返回了计算属性的值,并且提供一个setter 函数, 当手动修改计算属性的值就像修改一个普通数据那样时,就会触发setter 函数,执行一些自定义的操作.
Compute和methods 的区别
- computed计算属性是基于内部的响应式依赖来进行计算并缓存的,所谓的响应式依赖就是被我们的Vue实例所监听的数据
- computed计算属性是拥有缓存的,我们每次访问同一个计算属性,只要内部依赖没有发生改变,它都不会重新计算
- methods方法是调用函数,我们多次使用就等于多次调用了这个函数,函数是没有缓存的,所以每次都重新计算了,当我们的内部依赖发生改变的时候,都会重新render页面,此时页面上所有调用了这个函数的地方都会再次重新调用这个函数
- 这也是我们为什么要使用计算属性,而不是使用methods来计算一个数据。
Computed和Watch的区别
https://blog.csdn.net/weixin_41849462/article/details/107608409
computed 类似 过滤器, 计算对绑定到view的数据进行处理
- 支持缓存,只有依赖数据发生改变,才会重新进行计算
- 不支持异步,当computed内有异步操作时无效,无法监听数据的变化
- computed 属性值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data中声明过或者父组件传递的props中的数据通过计算得到的值
- 如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是一个多对一或者一对一,一般用 computed
- 如果computed 属性属性值是函数,那么默认会走get方法;函数的返回值就是属性的属性值;在computed中的,属性都有一个get和一个set方法,当数据变化时,调用set方法。
watch
- 不支持缓存,数据变,直接会触发相应的操作
- watch支持异步
- 监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值
- 当一个属性发生变化时,需要执行对应的操作;一对多
- 监听数据必须是data中声明过或者父组件传递过来的props中的数据,当数据变化时,触发其他操作,函数有两个参数:
Vue3 新特性
节点打 Tag
静态节点
<span>value</span>
动态节点
<span>{{value}}</span>
patchFlag
vue3.0底层,会自动识别某个节点是否是动态的,如果是动态的会自动生成标识(不同的动态会有不同的标识对应,如内容文本的动态,或者id的动态),从而在每次更新dom时,直接跳过哪些静态的节点,直接定位到动态的节点,大大节省效率。
事件开缓存
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mCc3K4jD-1641822685522)(image/20201127144303319.png)]
一般为一个节点设置了监听时间,每次页面进行更新,就会重新生成新的监听函数,启用了cacheHandlers,就会在第一次更新的时候进行自动识别是否可以缓存,如果可以就进行缓存,这样页面更新就不需要重新生成,尤其是在组件上,极大地减少了子组件的不必要刷新和资源消耗。
响应式 Proxy
Proxy(vue3.0) vs Object.defineProperty (vue2.0)
Vue2.0响应式原理
- 响应化过程需要遍历data,props等,消耗较大
- 不支持Set/Map、class、数组等类型
- 新加或删除属性无法监听
- 数组响应化需要额外实现
- 对应的修改语法有限制
Vue3.0响应式原理:使用ES6的Proxy来解决这些问题。
通过Proxy代理,来拦截对data的一系列的操作。
28
Fragments
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UogevgQ7-1641822685523)(image/20201127144225460.png)]
template中不需要用一个div包裹即没必要只有一个根节点,可以多个标签(节点)并列
Vue2.0 的声明周期有哪些,分别解释其意思
- beforeCreate Vue实例创建之前,此时访问不到数据
- created Vue实例创建完成,此时已经可以访问到data和methods等
- beforeMount Vue实例的挂载到真实DOM结构之前
- mounted Vue实例的挂载到真实的DOM节点完成之后
- beforeUpdate Vue实例的数据修改,DOM修改之前
- updated Vue实例的数据修改完成,重新render之后
- beforeDestory Vue实例卸载之前
- destoryed Vue实例卸载完成
- activated 当使用keep-alive组件的时候,才会有的生命周期钩子,表示组件激活时
- deactivated 当使用keep-alive组件的时候,才会有的生命周期钩子,表示组件取消激活时
- errorCaptured (2.5+) 补获错误边界钩子,当子孙组件报错的时候,父组件会触发这个钩子函数,并返回三个参数,第一个参数是 错误对象 ,第二个参数是 报错的子孙组件 ,第三个参数是 报错的子孙组件的具体哪个地方报错。
- serverPrefetch (2.6+) 用来处理ssr,允许在渲染过程中等待异步数据
详述组件通信
-
收,props一般使用对象的形式,通过parent和children来传值
export default { name: 'child' props: { list: { type: Array, // 定义数据类型,可以是绝大部分javascript数据类型 required: true, // 属性是必须的 default: [1,2,3], // 默认值 validator: function(value) { // 自定义校验规则,返回true表示校验通过,返回false表示校验出错 if(value.indexOf(1)>-1) { return true }else { return false } } } } }
-
子传父, 子组件通过 $emit向父组件发布事件,而父组件通过 v-on监听对应的事件,并触发函数,得到子组件发布的事件携带的值,通过parent和children来传值
-
兄弟组件通信
-
通过父组件进行中转
-
通过中央通信总线的方式,在需要通信的组件内使用 emit 和on 来发布和监听事件,并进行通信传值
// 中央通信总线 Vue.prototype.$bus = new Vue({}); // 组件A this.$bus.$emit('handleClick',1); // 组件B this.$bus.$on('handleClick',value => { console.log(value); })
-
路由
vue-router参数传递方法详述及区别
-
首先在Vue中router路由跳转分为两种
router.push(路由信息)
- 编程式导航<router-link to="路由信息"></router-link>
- 声明式导航 -
这两种跳转方式传递参数是没有太大的区别的
-
vue-router传参有两种方式, query和params
query传参
<router-link to="/?tab=all">query传参</router-link>
this.$router.push('/?tab=all')
<router-link :to="{ path: '/', query: {tab: 'all' }}">query传参</router-link>
this.$router.push({ path: '/', query: {tab: 'all' }})
params传参
<router-link to="/detail/1">query传参</router-link>
this.$router.push('/detail/1') //提前在路由中配置好了
<router-link :to="{ path: '/', params: { id: 1 } }">params传参</router-link>
this.$router.push({ path: '/', params: { id: 1 } })
query和params传参的区别
- params是路由的一部分,必须要在路由后面添加参数名。query是拼接在url后面的参数,没有也没关系。
- params一旦设置在路由,params就是路由的一部分,如果这个路由有params传参,但是在跳转的时候没有传这个参数,会导致跳转失败或者页面会没有内容。
- query会直接显示在 ? 后边 url?tab=all 而params是显示在路由的子路由上 url/name
- 获取方式不同,query使用this. r o u t e . q u e r y 获 取 , p a r a m s 使 用 t h i s . route.query获取,params使用this. route.query获取,params使用this.route.params获取
- 使用场景不同,query一般用来做搜索或者列表页,而params一般用来做详情页
详述导航卫士
全局导航守卫
-
router.beforeEach 全局前置守卫 当一个导航触发时,前置守卫最先触发
-
一般在这个守卫方法中进行全局拦截,比如必须满足某种条件(用户登录等)才能进入路由的情况
router.beforeEach((to, from, next) => { console.log('全局前置守卫:beforeEach -- next需要调用') next() })
项目remote: 通过路由前置守卫控制登录权限问题.
-
-
router.beforeResolve (v 2.5.0+) 全局解析守卫 和beforeEach类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用 即在 beforeEach 和 组件内beforeRouteEnter 之后
-
router.afterEach 全局后置守卫 在所有路由跳转结束的时候调用
路由导航守卫
- 路由导航卫视
beforeEnter 路由前置守卫 可直接定义在路由配置上,和beforeEach方法参数、用法相同
组件路由首页
beforeRouteEnter
在渲染该组件的对应路由被确认前调用,用法和参数与beforeEach类似
注意:
-
此时组件实例还未被创建,不能访问this
-
可以通过传一个回调给 next来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数
beforeRouteEnter (to, from, next) { // 这里还无法访问到组件实例,this === undefined next( vm => { // 通过 `vm` 访问组件实例 }) }
-
可以在这个守卫中请求服务端获取数据,当成功获取并能进入路由时,调用next并在回调中通过
vm
访问组件实例进行赋值等操作 -
beforeRouteEnter
触发在导航确认、组件实例创建之前:beforeCreate之前;而next中函数的调用在mounted之后:为了确保能对组件实例的完整访问
beforeRouteUpdate
在当前路由改变,并且该组件被复用时调用- 对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,组件实例会被复用,该守卫会被调用
- 当前路由query变更时,该守卫会被调用
beforeRouteLeave
导航离开该组件的对应路由时调用,可以访问组件实例this
,next需要被主动调用,不能传回调
Vue 指令
v-if 和 v-for 的优先级
-
v-for的优先级更高,会先进行循环,再进行 if 判断
-
永远不建议同时使用 v-for 和 v-if ,因为会导致性能低下,增加渲染成本
-
当真的需要进行判断是否渲染列表的时候,在列表循环的外部添加 v-if
<template> <div> <ul v-if="list.length>5"> <li v-for="(item,index) in list" :key=index>{{item}}</li> </ul> </div> </template>
-
当需要对循环的每一项进行判断是否渲染的时候,在computed计算属性中先行计算,并直接渲染对应的计算属性即可
<template> <div> <ul> <!--不建议这么写 <li v-for="(item,index) in list" v-if="item>5" :key=index>{{item}}</li> --> <li v-for="(item,index) in showList" :key=index>{{item}}</li> </ul> </div> </template> <script> export default { name: 'App', data() { return { list: [1,2,3,4,5,6,7,8,9,10] } }, computed: { showList() { return this.list.filter(item => item>5); } } } </script>
v-show和v-if有什么区别?及使用场景
- v-if是通过添加和删除DOM节点来控制显示隐藏,v-show是通过操作css的display属性来控制显示隐藏;
- v-if拥有更高的切换成本,v-show拥有更高的渲染成本;
- 当组件或DOM不需要经常进行切换的时候,使用v-if;当组件或DOM需要经常进行切换的时候,使用v-show。
常见指令
-
v-text 主要用来渲染纯文本内容,类似于原生JS的innerText
<div v-text="msg"></div> // msg 的值为纯文本,不会解析标签
-
v-html 主要用来渲染带有html标签的文本内容,可以渲染原生HTML标签
<div v-html="msg"></div> // msg 的值可以是HTML标签,会解析标签
-
v-if 主要用来实现条件渲染,会根据条件是否成立来决定是否渲染当前元素
<div v-if="true">条件可以是布尔值和条件表达式</div> // v-if后边的条件可以是布尔值和条件表达式,当条件为true的时候渲染当前元素
-
v-else-if 必须配合 v-if 使用,当v-if的条件不成立的时候,继续执行判断,当条件成立的收渲染当前的元素
<div v-if="5>5">条件可以是布尔值和条件表达式</div> <div v-else-if="5===5"> v-if的条件不成立的时候,开始判断v-else-if,当v-else-if的条件成立的时候渲染 </div>
-
v-else 必须配合 v-if 或者 v-else-if 使用,当v-if的条件不成立的时候,渲染 v-else 的元素
<div v-if="type==='success'">成功</div> <div v-else-if="type==='error'">警告</div> <div v-else>当之前的所有条件都不成立的时候渲染当前元素</div>
-
v-show Vue的条件渲染的第二种方式,当条件成立的时候渲染当前元素
<div v-show="true">条件可以是布尔值和条件表达式</div>
-
v-for Vue的列表渲染,用来渲染一组数据,支持for in;for of 两种遍历方式,需要注意的是,每一次遍历的时候,我们都要给元素添加一个key值,这个key值在它的兄弟元素之前必须是独一无二的,我们一般都使用id来作为这个key值,这个key值我们使用了v-bind绑定,
<div v-for="item in list" :key="item.id">{{item.value}}</div> // list = [{id: 1, value: '第一个'}, {id: 2, value: '第二个'},{id: 3, value: '第三个'}]
-
v-bind Vue的属性绑定,将当前的属性绑定到Vue的实例上,使其可以使用js表达式或者js数据,v-bind的简写为:一个冒号;
<div v-bind:class="['a','b']">这是v-bind的元素</div> <div :class="['a','b']">上下是等价的</div>
当然,我们更能使用Vue中定义的数据
<div v-bind:class="divClass">使用变量</div> // divClass = ['a','b',{active: 5>0}];
-
v-on Vue中的事件绑定方式,可以监听所有原生事件,以及Vue中的自定义事件(使用$emit发布的事件);v-on 简写为 @ 符号,并且,在Vue中,还为我们提供了很多的事件修饰符,稍后我们一块看一看
<button v-on:click="clickBtn">按钮</button> <button @click="clickBtn">上下是等价的</button> // clickBtn是定义在Vue的methods属性中的方法,即触发事件之后触发的函数
-
v-model Vue提供的数据双向绑定,可以实现表单元素的数据双向绑定,
<input type="text" v-model="inputValue" /> // 此时inputValue就与我们的input实现了双向数据绑定,当input的值发生改变的时候,inputValue也会同步发生改变 // 反之,当inputValue发生改变的时候,input的值也会发生改变
-
v-pre 跳过当前元素和其子元素的编译过程,直接显示原始的DOM标签及内容,跳过大量的没有指令的节点加速编译
<div> <span>{{message}}</span> <span v-pre>{{message}}</span> <!-- 当前元素不执行编译,直接显示{{message}}的纯文本 --> </div>
-
v-clock 防止页面加载时出现 vue 的变量名。提升用户体验感,避免用户看到编译之前的变量
-
v-once 被v-once绑定的元素只会被编译渲染一遍,之后的每次渲染,它和它的子元素都会被当成一个静态元素来跳过渲染和编译,用来更加优化性能
<div v-once>{{msg}}</div> <div>{{msg}}</div> <!-- 第一次渲染的时候,会将msg的值渲染在两个div中, 当msg的值发生改变的时候,再次进行渲染,此时, 第一个div会被当做静态元素,不再执行渲染,只有第二个 div的值会发生改变 -->
Vue 常用修饰符
v-model修饰符
-
.lazy
默认情况下,v-model会同步输入框的值和数据(input事件触发数据同步),可以通过.lazy修饰符,转变为在change发生时才触发数据同步
<input type="text" v-model.lazy="msg"/>
-
.number
自动将input的数据转换为number类型的数据
-
.trim
过滤input数据两边的空格,只能去除两边的空格,不能去除中间的,类似于string的trim方法
v-on事件修饰符
- .stop 阻止事件冒泡(阻止事件向上传递)
- .prevent 阻止默认事件(阻止元素的默认事件,如form提交会重载页面等)
- .captrue 使用事件捕获的方式触发事件(事件会向下传递)
- .self 只有当事件时从元素本身触发的才会触发事件(当e.target为当前元素的时候)
- .once 事件只会触发一次
- .passive 告知浏览器不阻止事件的默认行为
<!-- 阻止单击事件继续传播 -->
<button @click.stop="clickThis"></button >
<!-- 提交事件不再重载页面 -->
<form @submit.prevent="onSubmit"></form>
<!-- 修饰符可以串联 -->
<button @click.stop.prevent="clickThis"></button >
<!-- 只有修饰符 -->
<form @submit.prevent></form>
<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即元素自身触发的事件先在此处处理,然后才交由内部元素进行处理 -->
<div @click.capture="clickThis">...</div>
<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div @click.self="clickThis">...</div>
<!-- 点击事件将只会触发一次 -->
<button @click.once="doThis"></button>
<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
<!-- 而不会等待 `onScroll` 完成 -->
<!-- 这其中包含 `event.preventDefault()` 的情况 -->
<div v-on:scroll.passive="onScroll">...</div>
按键修饰符 当事件触发的 keyCode 是对应的 keyCode 时触发
- .enter
- .tab
- .delete 捕获删除del和退格back
- .esc
- .space
- .up
- .down
- .left
- .right
- 我们还可以通过Vue的全局配置来自定义按键修饰符
vue.config.keyCodes.f1 = 112;
<input @keyup.enter="save" /> // 当按下enter键的时候触发save方法
系统按键修饰符 当事件触发的同时按下了系统按键时才会正确的触发事件
- ctrl
- .alt
- .shift
- .meta 在window键盘上对应的是win键,在mac系统上对应的是command
- 在系统按键修饰符中触发keyup事件的时候,并不需要将系统按键修饰符松开,你必须要保持一直按下对应的系统按键,只需要弹起常规按键即可
<button @click.ctrl="clickThis">按钮</button> // 当按着ctrl键的同时触发click事件才可以触发clickThis方法
<input @keyup.alt.up="changeInput"/> // 当按下alt的同时使用 up 键触发input的keyup事件才会执行changeInput方法
事件精准匹配修饰符
- .exact 用来精准的控制当只有对应的系统修饰符按下的时候才会触发事件
<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button @click.ctrl="onClick">A</button>
<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>
<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button @click.exact="onClick">A</button>
鼠标按钮修饰符
- .left 鼠标左键
- .right 鼠标右键
- .middle 鼠标中键
v-on可以监听多个方法吗?
v-on 是可以同时监听多个方法的,但是必须是监听不同的事件,当监听同样的事件的时候,只会触发第一次监听时候的方法
<div @click="clickThis" @mouseenter="enterThis" @mouseleave="leaveThis"></div>
// 这些事件都可以被触发
<div @click="firstClick" @click="secondClick"></div>
// 这个时候,vue会报错,因为不能使用v-on重复监听同一个事件
//但是这么些是正确的
<div @click="secondClick();firstClick()"></div>
自定义指令
自定义指令是什么?以及自定义指令的使用场景
在Vue中,有很多内置指令,但是这些指令只能满足我们最基础的使用,当我们在实际项目中遇到了必须要对DOM进行操作的逻辑时,我们就用到了自定义指令
自定义指令的钩子函数 (这里我们可以直接看官网的介绍)
- bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
- inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
- update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新。
- componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
- unbind:只调用一次,指令与元素解绑时调用。
钩子函数的参数
el
- 绑定指令的DOM元素,可以用来直接操作DOM元素
binding
- 绑定指令时得到的值、修饰符、参数等
VNode
- Vue生成的虚拟DOM节点
oldVNode
- 修改之前的VNode,只能在update函数和componentUpdated中调用
自定义指令的使用方法
Vue.directive('drag', {
bind(el,binding,VNode) {
console.log('指令绑定成功')
}
inserted(el,binding,VNode) {
console.log('当前DOM节点已经插入到父节点')
}
upate(el,binding,VNode,oldVNode) {
console.log('当前VNode发生更新')
}
componentUpated(el,binding,VNode,oldVNode) {
console.log('当前VNode更新成功')
}
unbind(el,binding,VNode) {
console.log('指令与当前元素解绑')
}
})
<div v-drag>绑定指令的div</div>
父组件获取异步动态数据传递给子组件,报错如何解决?
这个问题一般出现在对象,或者我们直接调用数组指定下标的内容时会出现,因为数组的话,一般我们都是执行列表渲染,数组内没有数据是不会执行遍历的,而对象不一样,如果没有对应的属性,还是会调用,只是返回undefined,而我们再调用undefined上的属性时,就会出现报错
在父组件给子组件传值的时候,给子组件加一个判断,如果数据没有请求到就不渲染当前组件
<div v-if="list.length>0">
<child-compnent></child-component>
</div>
data() {
return {
list: []
}
}
// 执行异步请求数据的方法和生命周期钩子等等
methods: {
getData() {
// ·····
}
}
Vue 中的slot
在Vue中,我们一般使用UI界面来划分组件,而每一个UI界面可以划分很多个组件,不同的UI界面中难免会有相似之处,这种相似的地方,我们如何通过一种优雅的方式来达到复用这个UI组件呢?就是使用slot插槽了
slot插槽,其实就是用来做内容分发,使得可以最大程度的复用组件,达到每次使用同一个组件的时候可以根据情况创建不同的内容的功能
匿名插槽
- 不具有name属性的slot就是匿名插槽,也可以叫默认插槽
- 由父组件提供样式和内容,子组件只负责显示
- 匿名插槽/默认插槽只可以使用一次
<slot>
里边写的是默认值</slot>
- 在子组件内定义slot时,内部可以定义默认值,当父组件有内容分发的时候,显示父组件的内容,没有的时候,显示默认内容
作用域插槽 (带数据的插槽)
-
父组件只提供样式,子组件提供内容
-
在slot上面绑定数据
-
子组件的值可以传给父组件使用
-
使用v-slot必须使用template; 之前使用的是slot-scope,但是这个属性已经在2.6.0废弃了,现在使用v-slot指令来代替原有的slot-scope属性
-
scope返回的值是slot标签上返回的所有属性值,并且是一个对象的形式保存起来
// 子组件中 <slot :data="data" :message="message"></slot> // 父组件中 <template v-slot="scope"> <div class="data-list"> <span v-for="(item,index) in scope.data" :key="index">{{item}}</span> </div> <div class="data-message"> {{scope.message}} </div> </template>
对MVC,MVP,MVVM的理解
MVC
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y1FpvE7c-1641822685524)(image/18087456-921680f6bcf3c947.png)]
M 表示 Model , 专门用来处理数据模型。
V 表示View, 专注页面布局和数据显示。
C 表示Controller 专注于控制,执行业务逻辑,操作模型和视图
MVC的数据之间通信都是单向的
View(视图层) 传送指令到 Controller(控制层)
Controller(控制层) 完成业务逻辑后,要求 Model(模型层) 改变状态
Model(模型层) 将新的数据发送到 View(视图层),用户得到反馈
在MVC中,虽然View与Model之间的耦合度非常小,只需要Model修改的时候通知View发生改变即可,但是它们之间还有有很重要的联系,于是,就有了MVP
MVP
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ufWBhRVr-1641822685524)(image/18087456-a19315f211e22f25.png)]
M 表示 Model,专门用来处理数据模型
V 表示View,专门用来处理用户视图
P 表示Presenter,用来处理业务逻辑,在MVC的基础上,修改了通信方向
MVP中,View和Model之间没有任何通信关系,所有的通信和业务逻辑都放在Presenter层中
View(视图层) 发送指令到 Presenter层,
Presenter层 处理业务逻辑,要求 Model(模型层) 改变状态
Model(模型层) 修改状态之后,发送指令到 Presenter层
Presenter层通知View(视图层)做出改变
在MVP中,所有的通信都是双向的,View和Model不会直接发生通信,都通过Presenter层进行传递
在MVP中,所有的业务逻辑都写在Presenter层中,会导致Presenter层过于臃肿
在MVP中,View只负责显示视图,不包含任何业务逻辑,导致View层过薄,不具备任何主动性
由于在MVP模式中,所有的业务逻辑都放在Presenter层中,必须通过Presenter层来修改View层的界面,所以出现了MVVM
MVVM
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E58NS5GE-1641822685525)(image/18087456-6cc240fb5457c999.png)]
M 表示 Model,专门用来处理数据模型
V 表示View,专门用来处理用户视图
VM 表示ViewModel,用来使的View视图层与Model层双向绑定,View的任何变动都会通知ViewModel,而Model的任何变动,也都会通知ViewModel,而不论哪一项发生改变,都会使对应的视图/数据模型同步发生改变
在MVVM中,与MVP一样,所有的通信都是双向的,数据与视图不直接发生依赖,全部通过VM层来进行双向绑定
所有的业务逻辑都由VM来进行处理,但是在View层和Model层修改都会通过VM来双向的绑定修改
浏览器渲染页面常见问题
Web前端性能优化——如何提高页面加载速度
提高性能
研究表明:用户最满意的打开网页时间是2-5秒,如果等待超过10秒,99%的用户会关闭这个网页。
1. 减少HTTP请求
图片地图
将所有点击提交到同一个url,同时提交用户点击的x、y坐标,服务器端根据坐标映射响应
todo: 目前没看懂
CSS Sprites
使用CSS Sprites还有可能降低下载量,可能大家会认为合并后的图片会比分离图片的总和要大,因为还有可能会附加空白区域。实际上,合并后的图片会比分离的图片总和要小,因为它降低了图片自身的开销,譬如颜色表、格式信息等。
字体图标
在可以大量使用字体图标的地方我们可以尽可能使用字体图标,字体图标可以减少很多图片的使用,从而减少http请求,字体图标还可以通过CSS来设置颜色、大小等样式。
合并脚本和样式表
将多个样式表或者脚本文件合并到一个文件中,可以减少HTTP请求的数量从而缩短效应时间。
然而合并所有文件对许多人尤其是编写模块化代码的人来说是不能忍的,而且合并所有的样式文件或者脚本文件可能会导致在一个页面加载时加载了多于自己所需要的样式或者脚本,对于只访问该网站一个(或几个)页面的人来说反而增加了下载量,所以大家应该自己权衡利弊。
2. 使用CDN
CDN(内容发布网络)是一组分布在多个不同地理位置的Web服务器,用于更加有效地向用户发布内容。在优化性能时,向特定用户发布内容的服务器的选择基于对网络慕课拥堵的测量。例如,CDN可能选择网络阶跃数最小的服务器,或者具有最短响应时间的服务器。
CDN还可以进行数据备份、扩展存储能力,进行缓存,同时有助于缓和Web流量峰值压力。
CDN的缺点:
1、响应时间可能会受到其他网站流量的影响。CDN服务提供商在其所有客户之间共享Web服务器组。
2、如果CDN服务质量下降了,那么你的工作质量也将下降
3、无法直接控制组件服务器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vNMIw0yH-1641822685526)(image/110009_78zj_2903254.png)]
3. 使用HTTP缓存
4. 压缩组件
从HTTP1.1开始,Web客户端可以通过HTTP请求中的Accept-Encoding头来表示对压缩的支持
Accept-Encoding: gzip,deflate
如果Web服务器看到请求中有这个头,就会使用客户端列出来的方法中的一种来进行压缩。Web服务器通过响应中的Content-Encoding来通知 Web客户端。
Content-Encoding: gzip
5. 将样式表放在头部
将样式表放在头部对于实际页面加载的时间并不能造成太大影响,但是这会减少页面首屏出现的时间,使页面内容逐步呈现,改善用户体验,防止“白屏”。
将样式表放在文档底部会阻止浏览器中的内容逐步出现。为了避免当样式变化时重绘页面元素,浏览器会阻塞内容逐步呈现,造成“白屏”。这源自浏览器的行为:如果样式表仍在加载,构建呈现树就是一种浪费,因为所有样式表加载解析完毕之前务虚会之任何东西
6. 将脚本放在底部
js的下载和执行会阻塞Dom树的构建(严谨地说是中断了Dom树的更新),所以script标签放在首屏范围内的HTML代码段里会截断首屏的内容。
7. 避免CSS表达式
用js事件处理机制来动态改变元素的样式,使函数运行次数在可控范围之内。
8. 使用外部的JavaScript和css
内联脚本或者样式可以减少HTTP请求,按理来说可以提高页面加载的速度。然而在实际情况中,当脚本或者样式是从外部引入的文件,浏览器就有可能缓存它们,从而在以后加载的时候能够直接使用缓存,而HTML文档的大小减小,从而提高加载速度。
影响因素:
1、每个用户产生的页面浏览量越少,内联脚本和样式的论据越强势。譬如一个用户每个月只访问你的网站一两次,那么这种情况下内联将会更好。而如果该用户能够产生很多页面浏览量,那么缓存的样式和脚本将会极大减少下载的时间,提交页面加载速度。
2、如果你的网站不同的页面之间使用的组件大致相同,那么使用外部文件可以提高这些组件的重用率。
CDN实现原理
工作原理
CDN网络是在用户和服务器之间增加Cache层,主要是通过接管DNS实现,将用户的请求引导到Cache上获得源服务器的数据,从而降低网络的访问时间。
首先,让我们看一下传统的未加缓存服务的访问过程:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i4QDZ5WC-1641822685526)(image/20181018151508334)]
-
如图可以看出,传统的网络访问的流程如下:
-
用户输入访问的域名,操作系统向 LocalDns 查询域名的ip地址;
-
LocalDns向 ROOT DNS 查询域名的授权服务器(这里假设LocalDns缓存过期);
-
ROOT DNS将域名授权dns记录回应给 LocalDns;
-
LocalDns得到域名的授权dns记录后,继续向域名授权dns查询域名的ip地址;
-
域名授权dns 查询域名记录后,回应给 LocalDns;
-
LocalDns 将得到的域名ip地址,回应给用户端;
-
用户得到域名ip地址后,访问站点服务器;
-
站点服务器应答请求,将内容返回给客户端.
下面让我们看一下使用CDN缓存后的网站的访问过程:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iKo5jBCK-1641822685526)(image/20181018152139324)]
如上图,是使用CDN缓存后的网络访问流程
- 用户输入访问的域名,操作系统向 LocalDns 查询域名的ip地址;
- LocalDns向 ROOT DNS 查询域名的授权服务器(这里假设LocalDns缓存过期);
- ROOT DNS将域名授权dns记录回应给 LocalDns;
- LocalDns得到域名的授权dns记录后,继续向域名授权dns查询域名的ip地址;
- 域名授权dns 查询域名记录后(一般是CNAME),回应给 LocalDns;
- LocalDns 得到域名记录后,向智能调度DNS查询域名的ip地址;
- 智能调度DNS 根据一定的算法和策略(比如静态拓扑,容量等),将最适合的CDN节点ip地址回应给 LocalDns;
- LocalDns 将得到的域名ip地址,回应给用户端;
- 用户得到域名ip地址后,访问站点服务器。
综上,CDN网络是在用户和服务器之间增加Cache层,主要是通过接管DNS实现,将用户的请求引导到Cache上获得源服务器的数据,从而降低网络的访问的速度。
精灵图原理
CSS Sprites其实就是把网页中一些背景图片整合到一张图片文件中,再利用CSS的"background-image",“background-repeat”,"background-position"的组合进行背景定位,background-position可以用数字精确地定位出背景图片的位置。
- 优点:
①减少网页的http请求,从而加快了网页加载速度,提高用户体验。
②减少图片的体积,因为每个图片都有一个头部信息,把多个图片放到一个图片里,就会共用同一个头信息,从而减少了字节数。
③解决了网页设计师在图片命名上的困扰,只需对一张集合的图片上命名就可以了,不需要对每一个小元素进行命名。
④更换风格方便,只需要在一张或少张图片上修改图片的颜色或样式,整个网页的风格就可以改变。
- 缺点:
①在宽屏,高分辨率的屏幕下的自适应页面,你的图片如果不够宽,很容易出现背景断裂。
②CSS Sprites在开发的时候,要通过photoshop或其他工具测量计算每一个背景单元的精确位置。
③在维护的时候比较麻烦,如果页面背景有少许改动,一般就要改这张合并的图片。
④精灵图不能随意改变大小和颜色。改变大小会失真模糊,降低用户体验,Css3新属性可以改变精灵图颜色,但是比较麻烦,并且新属性有兼容问题,现在一般用字体图标代替精灵图。