面试
一、
vue
说说
vue
动态权限绑定渲染列表(权限列表渲染)
1.
首先请求服务器
,
获取当前用户的权限数据
,
比如请求
this.$http.get("rights/list");
2.
获取到权限数据之后
,
在列表中使用
v-if v-if-else
的组合来展示不同的内容
<template>
<div>
<!--
面包屑导航区
-->
<el-breadcrumb
separator-class
=
"el-icon-arrow-right"
>
<el-breadcrumb-item
:to
=
"{ path: '/home' }"
>
首页
</el-breadcrumb-item>
<el-breadcrumb-item>
权限管理
</el-breadcrumb-item>
<el-breadcrumb-item>
权限列表
</el-breadcrumb-item>
</el-breadcrumb>
<!--
卡片视图
-->
<el-card>
<el-table
:data
=
"rightsList"
border stripe
>
<el-table-column
type
=
"index"
label
=
"#"
></el-table-column>
<el-table-column
label
=
"
权限名称
"
prop
=
"authName"
></el-table-column>
<el-table-column
label
=
"
路径
"
prop
=
"path"
></el-table-column>
<el-table-column
label
=
"
权限等级
"
prop
=
"level"
>
<template
slot-scope
=
"scope"
>
<el-tag
v-if
=
"scope.row.level === '0'"
>
一级
</el-tag>
<el-tag
type
=
"success"
v-else-if
=
"scope.row.level === '1'"
>
二级
</el-
tag>
<el-tag
type
=
"danger"
v-else
>
三级
</el-tag>
</template>
</el-table-column>
</el-table>
</el-card>
</div>
</template>
<script>
export default
{
data
() {
return
{
//
权限列表
rightsList
: []
};
},
created
() {
this
.
getRightsList
();
},
methods
: {
async getRightsList
() {
//
获取权限列表数据
const
{
data
:
res
}
=
await this
.
$http
.
get
(
"rights/list"
);
if
(
res
.
meta
.
status
!==
200
) {
Vue
用的哪种设计模式
属于发布订阅模式
,
在
vue
中使用
observer
和
definereactive
两个方法的结合对数据进行递归劫持
,
然后通过
watch
这个类来对属性进行订阅
,Dep
类用于解耦合
,
当数据变更的时候先触发数据的
set
方法
,
然后调用
Dep.notiify
通知视图更新
说说
vue
操作真实
dom
性能瓶颈
vue
性能瓶颈的几种情况
1.
一次渲染大量的数据的时候
,
存在大量数据并且都是复杂类型的时候
,
会导致
vue
对数据的劫持时间
和渲染时间变长
, js
连续执行时间过长,会导致页面长时间无法交互,而且渲染时间太慢,用户一
次交互反馈的时间过长。
优化方案
:
可以使用
requestAnimation
这个方法
,
将数据进行分割
,
分批次渲染
,
减少了
js
的
连续运行时间,并且加快了渲染时间,利用加长总运行时间换取了渲染时间,用户既能快速
得到反馈,而且不会因为过长时间的
js
运行而无法与页面交互。
2.
当页面中存在大量数据
,
只是修改了一小部分导致页面也会导致页面卡顿
,
因为
vue
的更新以组件为
粒度进行更新的,只要修改了当前组件中所使用的数据,组件就会整个去进行更新
,
造成大量的时
间浪费
优化方案
:
将不同的模块划分成不同的组件
,
这样有效降低虚拟
dom
的
diff
运算时间过长的问题
,
比如将大量数据的模块单独放一个组件
,
其它放一个组件
,
由于
vue
是以组件为粒度更新
,
修改
其它组件的情况下不会导致
table
的重新
diff,
提升页面响应速度高达几百倍
3.
动态插槽作用域或者静态插槽的更新
使用插槽作用域来替换这两种操作方式
,
一样能提升性能
,
因为使用
插槽作用域
之后
,
插槽内容
会被封装到一个函数中
,
被子组件渲染
,
而不是在父组件
Vue
中如何获取
dom
、操作
dom
、更新
dom
如何获取
dom
?在
Vue
中提供了一种特别的方式来获取
dom
,即给
dom
加上个
ref
属性,那么就可以通过
this.$refs.
名字来获取到该
dom
元素。
如何操作
dom
、更新
dom
?通过
refs.
名字就可以拿到对应的真实
dom
,然后就可以用原生
JS
进行操作和
更新。当然
vue
框架本身就是不需要
dom
操作的,通过修改相应的数据并再配合指令、模板语法就可以轻
松的操作和更新
dom
。
return this
.
$message
.
error
(
"
获取权限列表失败!
"
);
}
this
.
rightsList
=
res
.
data
;
}
}
};
</script>
<style
lang
=
'less'
scoped
>
</style>
Vue
的双向数据绑定原理是什么
在
Vue2.x
中,双向数据绑定是通过 数据劫持 结合 发布订阅模式的方式来实现的,也就是说数据和视图同
步,数据发生变化,视图跟着变化,视图变化,数据也随之发生改变。核心:关于
VUE
双向数据绑定,
其核心是
Object.defineProperty()
方法。
Vue3.x
则是用
ES6
的语法
Proxy
对象来实现的。
Object.defineProperty()
的缺点:
1.
只能监听对象
(Object)
,不能监听数组的变化,无法触发
push, pop, shift, unshift,splice, sort,
reverse
。
2.
必须遍历对象的每个属性
3.
只能劫持当前对象属性,如果想深度劫持,必须深层遍历嵌套的对象。
Proxy
的优点:
1. Proxy
可以直接监听对象而非属性。
2. Proxy
可以直接监听数组的变化。
3. Proxy
有多达
13
种拦截方法
,
不限于
apply
、
ownKeys
、
deleteProperty
、
has
等等是
Object.defineProperty
不具备的。
4. Proxy
返回的是一个新对象
,
我们可以只操作新的对象达到目的
,
而
Object.defineProperty
只能遍历对
象属性直接修改。
5. Proxy
作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利。
mvvm
框架是什么
MVVM
是
Model-View-ViewModel
的简写。它本质上就是
MVC
(
Model-View-Controller
)的改进版。在开发过
程中,由于需求的变更或添加,项目的复杂度越来越高,代码量越来越大,此时我们会发现
MVC
维护起
来有些吃力,尤其
Controller
控制层非常的厚重,非常的庞大,难以维护。
所以有人想到把
Controller
的数据和逻辑处理部分从中抽离出来,用一个专门的对象去管理,这个对象就
是
ViewModel
。
ViewModel
是由前端开发人员组织生成和维护的视图数据层。在这一层,前端开发者对从
后端获取的
Model
数据进行转换处理,做二次封装,以生成符合
View
层使用预期的视图数据模型。
由于实现了双向绑定,
ViewModel
的内容会实时展现在
View
层,这是激动人心的,因为前端开发者再也
不必低效又麻烦地通过操纵
DOM
去更新视图,
MVVM
框架已经把最脏最累的一块做好了,我们开发者只
需要处理和维护
ViewModel
,更新数据视图就会自动得到相应更新,真正实现数据驱动开发。
let
arr
=
[];
let
proxy
=
new
Proxy
(
arr
, {
get
:
function
(
obj
,
prop
){
return
obj
[
prop
];
},
set
:
function
(
obj
,
prop
,
value
){
obj
[
prop
]
=
value
;
//
可以被监听到变化
return
true
;
}
});
setTimeout
(()
=>
{
proxy
.
push
(
1
);
},
2000
)
谈谈
Vue
的
token
存储
在前后端完全分离的情况下,
Vue
项目中实现
token
验证大致思路如下:
1
、第一次登录的时候,前端调后端的登陆接口,发送用户名和密码
2
、后端收到请求,验证用户名和密码,验证成功,就给前端返回一个
token
3
、前端拿到
token
,将
token
存储到
localStorage
和
vuex
中,并跳转路由页面
4
、前端每次跳转路由,就判断
localStroage
中有无
token
,没有就跳转到登录页面,有则跳转到对应路
由页面
5
、每次调后端接口,都要在请求头中加
token
6
、后端判断请求头中有无
token
,有
token
,就拿到
token
并验证
token
,验证成功就返回数据,验证失败
(例如:
token
过期)就返回
401
,请求头中没有
token
也返回
401
7
、如果前端拿到状态码为
401
,就清除
token
信息并跳转到登录页面
知道
nextTick
的作用吗,谈谈对它的理解,是什么,怎么用
当你设置
vm.message = 'new message'
,该组件不会立即重新渲染。当刷新队列时,组件会在下一个事件
循环
“tick”
中更新。多数情况我们不需要关心这个过程,但是如果你想基于更新后的
DOM
状态来做点什
么,这就可能会有些棘手。虽然
Vue.js
通常鼓励开发人员使用
“
数据驱动
”
的方式思考,避免直接接触
DOM
,但是有时我们必须要这么做。为了在数据变化之后等待
Vue
完成更新
DOM
,可以在数据变化之后
立即使用
Vue.nextTick(callback)
。这样回调函数将在
DOM
更新完成后被调用。例如:
<div
id
=
"example"
>
{{message}}
</div>
var
vm
=
new
Vue
({
el
:
'#example'
,
data
: {
message
:
'old message'
}
})
vm
.
message
=
'new message'
//
更改数据
vm
.
$el
.
textContent
// 'old message'
Vue
.
nextTick
(
function
() {
vm
.
$el
.
textContent
// 'new message'
})
nextTick
和
setTimeout
区别
首先
Vue
在更新
DOM
时是异步执行的,也就是说数据变了,
DOM
不会立即改变,那么我们是如何知道
DOM
什么时候会改变呢?也就是说如何知道异步后的触发时机呢?
可以通过
nextTick
方法,这个方法在源码内,先监听是否具备
Promise.then
,利用
promise
来监听,如果
当前环境不支持
promise
,那么就降级采用
MutationObserver
,如果
MutationObserver
不支持的话,那么就
降级采用
setImmediate
,如果
setImmediate
不支持的话,那么就使用
setTimeout(fn, 0)
。
所以说
nextTick
和
setTimeout
区别总结就是:
nextTick
会先尝试使用
promise
、
MutationObserver
、
setImmediate
这些技术去监听,如果都不支持才会采用
setTimeout
vue
中为什么用虚拟
dom
而不操作真实
dom
起初我们在使用
JS/JQuery
时,不可避免的会大量操作
DOM
,而
DOM
的变化又会引发回流或重绘,从而降
低页面渲染性能。那么怎样来减少对
DOM
的操作呢?此时虚拟
DOM
应用而生,所以虚拟
DOM
出现的主要
目的就是为了减少频繁操作
DOM
而引起回流重绘所引发的性能问题的!
虚拟
DOM(Virtual Dom)
,起始本质上就是一个
JS
对象,当数据发生变化时,我们不直接操作真实
DOM
,因
为很昂贵,我们去操作这个
JS
对象,就不会触发大量回流重绘操作,再加上
diff
算法,可以找到两次虚拟
DOM
之间改变的部分,从而最小量的去一次性更新真实
DOM
,而不是频繁操作
DOM
,性能得到了大大的
提升。
虚拟
DOM
还有一个好处,可以渲染到
DOM
以外的平台,实现
SSR
、同构渲染这些高级特性,
Weex
等框
架应用的就是这一特性。
Vue
如何进行组件传值
父向子组件传值,可以利用
prop
方式。
子向父组件传值,可以利用自定义事件
$emit
方式。
多层级组件传值,可以使用
provide/inject
无关系的组件传值,利用
vuex
状态管理
说说
vue
里面的父子通信
父
->
子
:
通过
Prop
向子组件传递数据,子组件通过
props
属性来接收。
子
->
父
:
父组件自定义事件,子组件利用
$emit
来完成。
<blog-post
title
=
"My journey with Vue"
></blog-post>
Vue
.
component
(
'blog-post'
, {
props
: [
'title'
],
template
:
'<h3>{{ title }}</h3>'
//
获取父组件的值
})
<!--
拿到子组件传递的数据
$event
即
0.1-->
<blog-post
v-on:enlarge-text
=
"postFontSize += $event"
></blog-post>
Vue.component('blog-post', {
props: ['title'],
template: '
<h3
v-on:click
=
"$emit('enlarge-text', 0.1)"
>
{{ title }}
</h3>
'
})
谈谈如何实现
vue
组件通信和传值方式 (两个问题为同一个答案问法不一
样)
这类问题 首先分类 表明了解的比较多 具体就没说完 或者漏了 面试官也不会计较很多
组件通信的四大类 父与子 子与父 子与子 跨层级
在细说各种方式 加入自己的理解
1
、
props
和
$emit
父组件向子组件传递数据是通过
prop
传递的,子组件传递数据给父组件是通过
$emit
触发事件
2
、
$attrs
和
$listeners
3
、中央事件总线
bus
上面两种方式处理的都是父子组件之间的数据传递,而如果两个组件不是父子关系呢?这种情况下可以使用中央事件总
线的方式。新建一个
Vue
事件
bus
对象,然后通过
bus.$emit
触发事件,
bus.$on
监听触发的事件。
4
、
provide
和
inject
父组件中通过
provider
来提供变量,然后在子组件中通过
inject
来注入变量。不论子组件有多深,只要调用了
inject
那么就可以注入
provider
中的数据。而不是局限于只能从当前父组件的
prop
属性来获取数据,只要在父组件
的生命周期内,子组件都可以调用。
5
、
v-model
父组件通过
v-model
传递值给子组件时,会自动传递一个
value
的
prop
属性,在子组件中通过
this.$emit(‘input’,val)
自动修改
v-model
绑定的值
6
、
$parent
和
$children
说说
vue
中
Key
值的作用
说说
vue
中的虚拟
dom
和
diff
算法
7
、
boradcast
和
dispatch
8
、
vuex
处理组件之间的数据交互 如果业务逻辑复杂,很多组件之间需要同时处理一些公共的数据,这个时候才有上
面这一些方法可能不利于项目的维护,
vuex
的做法就是将这一些公共的数据抽离出来,然后其他组件就可以对这个公
共数据进行读写操作,这样达到了解耦的目的。
关于这个可以的
key
的作用 首先表明
key
不是一定要有的 不写可以代码也可以跑 但是建议加上
然后指出可以用的地方
key
在
v-for
循环可以用用 在表单元素中也可以用
key
减少
缓存
一般说
key
只要说配合
v-for
的使用
key
是为
Vue
中的
vnode
标记的唯一
id,
通过这个
key,
我们的
diff
操作可以更准确、更快速
diff
算法的过程中
,
先会进行新旧节点的首尾交叉对比
,
当无法匹配的时候会用新节点的
key
与旧节点进行比对
,
然后超
出差异能讲清楚
diff
算法就继续讲
diff
程可以概括为:
oldCh
和
newCh
各有两个头尾的变量
StartIdx
和
EndIdx
,它们的
2
个变量相互比较,一共有
4
种
比较方式。如果
4
种比较都没匹配,如果设置了
key
,就会用
key
进行比较,在比较的过程中,变量会往中间靠,一旦
StartIdx>EndIdx
表明
oldCh
和
newCh
至少有一个已经遍历完了,就会结束比较
,
这四种比较方式就是首、尾、旧尾
新头、旧头新尾
.
准确
:
如果不加
key,
那么
vue
会选择复用节点
(Vue
的就地更新策略
),
导致之前节点的状态被保留下来
,
会产生一系列
的
bug.
快速
: key
的唯一性可以被
Map
数据结构充分利用
,
相比于遍历查找的时间复杂度
O(n),Map
的时间复杂度仅
仅为
O(1)
讲完以后 还要补充一点自己的看法
建议使用主键比如
id
Virtual DOM
其实就是一棵以
JavaScript
对象
( VNode
节点
)
作为基础的树,用对象属性来描述节点,实际上
它只是一层对真实
DOM
的抽象。最终可以通过一系列操作使这棵树映射到真实的
DOM
上
下面就是一个真实
DOM
映射到虚拟
DOM
的例子:
<ul id='list'>
<li class='item'>Item 1</li>
<li class='item'>Item 2</li>
<li class='item'>Item 3</li>
</ul>
var element = {
tagName: 'ul', //
节点标签名
props: { // DOM
的属性,用一个对象存储键值对
id: 'list'
},
children: [ //
该节点的子节点
{tagName: 'li', props: {class: 'item'}, children: ["Item 1"]},
{tagName: 'li', props: {class: 'item'}, children: ["Item 2"]},
{tagName: 'li', props: {class: 'item'}, children: ["Item 3"]},
]
}
在补充点虚拟
DOM
的好处
具备跨平台的优势
实现虚拟
DOM
的过程
vue3.0
有了解过吗,你觉得
vue3.0
好吗,好在哪
这种问题 是开放的 多说就是都是对的 可以讲差异 也可以讲新增 的知识点
比如说 常用的
api
特别好用
1. ref
、
toRefs
、
toRef
、
isRef
ref
用于定义响应式变量、快捷
DOM
访问。
基本语法:
const a = ref(1) // {value:1}
基础使用:
一般用于定义
String
、
Number
、
Boolean
这种基于数据类型,外在表现统一使用
.value
访问。
补充:
ref
还可以访问
DOM
对象或者组件实例对象,可做
DOM
操作。
toRef
、
toRefs
用于把一个
object
的变量,变成响应式的变量。
基本语法:
const msg = toRef(obj, key)
//
把
obj[key]
变成响应式的
基本语法:
const { msg } = toRefs(obj)
//
把整个
obj
都变成响应式的
unref
返回一个变量的值
基本语法:
const x = unref(x)
//
如果
x
是
ref
变量则返回
x.value
,如果
x
不是
ref
变量则直接返回
x
。
isRef
用于判断一个变量是不是
ref
响应式变量
基本语法:
const bol = isRef(x)
由于
Virtual DOM
是以
JavaScript
对象为基础而不依赖真实平台环境,所以使它具有了跨平台的能力,比如说
浏览器平台、
Weex
、
Node
等。
操作原生
DOM
慢,
js
运行效率高。我们可以将
DOM
对比操作放在
JS
层,提高效率。
因为
DOM
操作的执行速度远不如
Javascript
的运算速度快,因此,把大量的
DOM
操作搬运到
Javascript
中,运用
patching
算法来计算出真正需要更新的节点,最大限度地减少
DOM
操作,从而显著提高性能。
Virtual DOM
本质上就是在
JS
和
DOM
之间做了一个缓存。可以类比
CPU
和硬盘,既然硬盘这么慢,我们就在
它们之间加个缓存:既然
DOM
这么慢,我们就在它们
JS
和
DOM
之间加个缓存。
CPU
(
JS
)只操作内存
(
Virtual DOM
),最后的时候再把变更写入硬盘(
DOM
)
提升渲染性能
Virtual DOM
的优势不在于单次的操作,而是在大量、频繁的数据更新下,能够对视图进行合理、高效的更新。
diff
算法
vdom
因为是纯粹的
JS
对象,所以操作它会很高效,但是
vdom
的变更最终会转换成
DOM
操作,为了实现高效的
DOM
操
作,一套高效的虚拟
DOM diff
算法显得很有必要
diff
算法包括一下几个步骤:
用
JavaScript
对象结构表示
DOM
树的结构;然后用这个树构建一个真正的
DOM
树,插到文
档当中
当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较
(diff)
,记录两棵树差异
把
2
所记录的差异应用到步骤
1
所构建的真正的
DOM
树上
(patch)
,视图就更新了
diff
算法是通过同层的树节点进行比较而非对树进行逐层搜索遍历的方式,所以时间复杂度只有
O(n)
,是一种相当高
效的算法
function
useUpdBoxStyle
() {
const
el
=
ref
(
null
)
const
updateStyle
=
color
=>
{
el
.
value
.
style
.
color
=
color
}
return
[
el
,
updateStyle
]
}
1. shallowRef
、
triggerRef
shallowRef
用于性能优化,只对对象的第一层进行
proxy
基本语法:
const obj = shallowRef({a:1,b:{c:{d:{e:2}}}})
triggerRef
用于手动触发那些
shallowRef
的变量进行更新视图
基本语法:
triggerRef(obj)
//
当
obj.value.b.c.d
发生变化,
triggerRef(obj)
强制更新视图。
customRef
自定义
ref
,把
ref
变量拆成
get/set
的写法
基本语法:
customRef((track, trigger) =>({get,set})
function
useObj
() {
const
obj
=
{
a
:
1
,
b
: {
c
: {
d
:
2
}}}
const
obj1
=
ref
(
obj
)
const
obj2
=
shallowRef
(
obj
)
// console.log('obj1', obj1)
// console.log('obj2', obj2)
const
changeObj
=
(
obj
,
newD
)
=>
{
// obj1.value.b.c.d = 100
obj
.
value
.
b
.
c
.
d
=
newD
triggerRef
(
obj
)
}
return
[[
obj1
,
obj2
],
changeObj
]
}
1. reactive
、
readonly
reactive
用于定义响应式变量(引用数据类型)
基本语法:
const arr = reactive([]) // {value: []}
ref
和
reactive
是什么关系呢?
ref
背后是使用
reactive
来实现的。
shallowReactive
用于性能优化,只对对象的第一层进行
proxy
基本语法:
const c = shallowReactive({a:{b:{c:100}}})
//
只对这个对象的第一层进行
proxy
readonly
把响应式变量变成
“
只读的
”
,如果修改就报警告。
基本语法:
const user = readonly({name:1,age:2})
isReadonly
用于判断一个变量是否是
readonly
的,返回布尔值
基本语法:
const bol = isReadonly(x)
isProxy
用于判断一个变量是否是响应式的,返回布尔值
基本语法:
const bol = isProxy(x)
isReactive
用于判断一个变量是否是
reactive
响应式变量,返回布尔值
基本语法:
const bol = isReactive(x)
function
useUser
() {
const
user
=
readonly
(
reactive
({
name
:
'list'
,
age
:
30
}))
console
.
log
(
'user'
,
user
)
// setTimeout(()=>user.age=40, 2000)
const
x
=
1
const
y
=
readonly
({
a
:
1
,
b
:{
c
:
3
}})
console
.
log
(
'
是否被
proxy
拦截过
'
,
isProxy
(
user
),
isProxy
(
x
),
isProxy
(
y
.
b
))
return
user
}
1. toRaw
、
markRaw
toRaw
用于返回一个响应式变量的原始值
基本语法:
const a3 = toRow(reactive(a1))
// a1===a3
是
true
markRaw
用于把一个普通变量标记成
“
不可
proxy”
的
基本语法:
const b2 = markRaw(b1)
// b2
是无法被
reactive
的
function
useRaw
() {
const
a1
=
{
title
:
100
}
const
a2
=
reactive
(
a1
)
const
a3
=
toRaw
(
a2
)
console
.
log
(
'toRow(a2)===a1'
,
a3
===
a1
)
console
.
log
(
'a2===a1'
,
a2
===
a1
)
return
[
a1
,
a2
,
a3
]
}
1. computed
、
watch
、
watchEffect
computed
用于对响应式变量进行二次计算,当它依赖的响应式变量发生变化时会重新计算
基本语法:
const c = computed(()=>c1.value*c2.value)
//
只读
基本语法:
const c = computed({get:()=>c1.value*c2.value,set:
(newVal)=>c1.value=newVal})
//
可写可读
watch
用于监听响应式变量的变化,组件初始化它不执行
基本语法:
const stop = watch(x, (new,old)=>{})
//
调用
stop()
可以停止监听
基本语法:
const stop = watch([x,y], ([newX,newY],[oldX,oldY])=>{})
watchEffect
用于监听响应式变量的变化,组件初始化会执行
基本语法:
const stop = watchEffect(()=>ajax({cate,page,size}))
export default function useWatchComputed() {
const c1 = ref(10)
const c2 = ref(20)
const c3 = computed(()=>c1.value*c2.value) //
只读
//
可读也可写
const c4 = computed({
get: ()=>c1.value*c2.value,
set: (newVal)=>{
c1.value = parseInt(newVal) / c2.value
}
})
const stop1 = watch(c4, (newC4, oldC4)=>console.log('c4
变了
', newC4, oldC4))
const stop2 = watch([c1,c2], ([newC1,newC2],[oldC1,oldC2])=>{
console.log('[c1,c2]
新值:
', [newC1, newC2])
console.log('[c1,c2]
旧值:
', [oldC1, oldC2])
})
const stop3 = watchEffect(()=>{console.log('watch effect', c1.value, c2.value)})
const update = (c,v) => c.value = v
return [[c1,c2,c3,c4],[stop1,stop2,stop3,update]]
2
:也可以说亮点
1.
性能比
vue2.x
快
1.2~2
倍
2.
支持
tree-shaking
,按需编译,体积比
vue2.x
更小
3.
支持组合
API
4.
更好的支持
TS
5.
更先进的组
3.
更可以说性能
1.diff
算法更快
vue2.0
是需要全局去比较每个节点的,若发现有节点发生变化后,就去更新该节点
vue3.0
是在创建虚拟
dom
中,会根据
DOM
的的内容会不会发生内容变化,添加静态标记, 谁有
flag
!比较
谁。
2
、静态提升
vue2
中无论元素是否参与更新,每次都会重新创建,然后再渲染
vue3
中对于不参与更新的元素,会做静
态提升,只被创建一次,在渲染时直接复用即可
3
、事件侦听缓存
默认情况下,
onclick
为动态绑定,所以每次都会追踪它的变化,但是因为是同一函数,没有必要追踪变
化,直接缓存复用即可
在之前会添加静态标记
8
会把点击事件当做动态属性 会进行
diff
算法比较, 但是在事件监听缓存之后就
没有静态标记了,就会进行缓存复用
v-model
有了解过吗,原理是什么
这种原理性问题 不要直接说不清楚 不了解
先讲下使用
v-model
本质上是一个语法糖,可以看成是
value + input
方法的语法糖。可以通过
model
的
prop
属性和
event
事件来进行自定义。
2
、
v-model
是
vue
的双向绑定的指令,能将页面上控件输入的值同步更新到相关绑定的
data
属性, 也会在
更新
data
绑定属性时候,更新页面上输入控件的值。
然后再来讲细节
vue
的双向绑定是由数据劫持结合发布者-订阅者模式实现的,那么什么是数据劫持?
vue
是如何进行数
据劫持的?说白了就是通过
Object.defineProperty()
来劫持对象属性的
setter
和
getter
操作,在数据变动时
做你想要做的事情
我们已经知道实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器
Observer
,用来监听所有属性。如果属性发生变化了,就需要告诉订阅者
Watcher
看是否需要更新。因为
订阅者是有很多个,所以我们需要有一个消息订阅器
Dep
来专门收集这些订阅者,然后在监听器
Observer
和订阅者
Watcher
之间进行统一管理的。接着,我们还需要有一个指令解析器
Compile
,对每个节点元素
进行扫描和解析,将相关指令(如
v-model
,
v-on
)对应初始化成一个订阅者
Watcher
,并替换模板数据
或者绑定相应的函数,此时当订阅者
Watcher
接收到相应属性的变化,就会执行对应的更新函数,从而更
新视图。因此接下去我们执行以下
3
个步骤,实现数据的双向绑定:
1.
实现一个监听器
Observer
,用来劫持并监听所有属性,如果有变动的,就通知订阅者。
}
2.
实现一个订阅者
Watcher
,每一个
Watcher
都绑定一个更新函数,
watcher
可以收到属性的变化通知并执
行相应的函数,从而更新视图。
3.
实现一个解析器
Compile
,可以扫描和解析每个节点的相关指令(
v-model
,
v-on
等指令),如果节点存
在
v-model
,
v-on
等指令,则解析器
Compile
初始化这类节点的模板数据,使之可以显示在视图上,然后
初始化相应的订阅者(
Watcher
)
3
:最后补一下
vue2.0
里面用
Object.defineProperty 3.0
里面用
new Proxy
一个监听每个属性 一个监听整
个对象
VUE
组件如何与
iframe
通信问题
像这种问题其实问的不是特别详情 面试者可能不懂题目的意思 但是我们要学会揣摩 面试官的问题
如果不知道 你就直说
vue
的组件通信 在讲
iframe
的页面获取
v
vue
组件内嵌一个
iframe
,现在想要在
iframe
内获取父组件内信息,采用的是
H5
新特性
PostMessage
来解决
跨域问题
采用
postMessage
内涵两个
API:
onMessage
:消息监听
postMessage
:消息发送
代码和例子
用过
VUE
的自定义指令吗?自定义指令的方法有哪些
这种问题一样的 先回答经常用的一些指定 比如
v-for v-if v-model v-show
等等之类的 指令分为全局和
局部的
然后在回答自定义指令
<div class="mapbox">
<iframe name="map" src="http://localhost:8083/setposition.html?add='add'">
</iframe>
</div>
clearMap(){
let map = document.getElementsByName("map")[0].contentWindow
map.postMessage("clearMap","*")
}
iframe
内:
window.addEventListener('message', function (evt) {
if (evt.data == 'clearMap'){
clearMap()
}
//event.data
获取传过来的数据
});
通过
directive
来自定义指令,自定义指令分为全局指令和局部指令,自定义指令也有几个的钩子函数,常用的有
bind
和
update
,当
bind
和
update
时触发相同行为,而不关心其它的钩子时可以简写。一个表达式可以使用多
个过滤器。过滤器之间需要用管道符
“
|
”
隔开。其执行顺序从左往右。
当修改
data
时
Vue
的组件重渲染是异步还是同步
这个问题很有意思 因为平时我们一般问题异步和同步指的是 数据请求 同步和异步问题
这里加上了组件 还有修改
data
这里给大家写个例子
以此可以说明
数据更新是同步的 但是视图更新是异步的
解决这个问题需要使用
$nextTick
解决视图异步更新的问题
.sync
修饰器的作用是
首先看到
.sync
我们需要知道这是个修饰器 类似修饰器还有
.stop .prevent
之类
其实这个修饰符就是
vue
封装了 子组件要修改父组件传过来的动态值的语法糖,省去了父组件需要写的
方法,但是子组件
emit
时要加上
update
在有些情况下,我们可能需要对一个
prop
进行
“
双向绑定
”
。不幸的是,真正的双向绑定会带来维护上的
问题,因为子组件可以修改父组件,且在父组件和子组件都没有明显的改动来源
代码解释
<body>
<div id="app">
<div id="main">{{num}}</div>
<button @click="add">
更新
</button>
</div>
</body>
<script>
new Vue({
el:"#app",
data:{
num:10
},
methods:{
add(){
this.num++;
console.log(this.num)//11
console.log(document.getElementById("main").innerHTML);//10
}
}
})
</script>
//
这里父组件,要给子组件传一个
title
的值
<template>
<div>
<t-title :title.sync="fatherTitle"></t-title>
</div>
</template>
<script>
import tTitle from './blocks/list';
export default {
name: 'test1',
components: { tTitle },
data() {
这里关键就是
emit
里的参数要写成
'update'+ ':' +'
要修改的
props'
以前是用的
this.$emit("
自定义方法
")
vue
多组件嵌套通信方式
这个问题其实也是属于组件通信 常见的组件通信有 父传子 子传父 子传子 以及跨层级
这个多组件嵌套通信其实就是跨层级的另一种问法
多组件通信
方法一:
props
一层 一层的传递
方法二:依赖注入
provide
声明
inject
接收
方法三:利用公共的
bus = new Vue() bus.$on
声明
bus.$emit()
调用
方法四:使用
vuex
全局的状态管理
vue
如何让
css
只在当前组件生效
return {
fatherTitle: '
父组件给的标题
'
};
},
}
</script>
//
子组件
<template>
<div>
<h3>{{ title }}</h3>
<button @click="changeTitle">
改变
</button>
</div>
</template>
<script>
export default {
props:{
title: {type: String, default: '
默认值
11'}
},
methods: {
changeTitle() {
this.$emit("update:title", "
子组件要改自己的标题
");
}
}
};
</script>
当前组件
<style>
写成
<style scoped>
加上
scoped
就可以了
这个
style
中一般还有
lang lang
可以是
less scss stylus
等等
不加
scoped
就是全局的样式
Vue
的
keep-live
用过吗?作用是什么?
1.
没有用过
2.
用过,它的作用是可以在组件切换时,保存其包裹的组件的状态,使其不被销毁,防止多次渲
染。
keepalive,
添加这个会比平常多生命周期吗
? keepalive
已经缓存了
,
但是想
跳回去的时候添加新的属性在哪个生命周期里实现
会比平常的组件多两个生命周期钩子函数,分别是:
activated
和
deactivated
。使用
keep
-
alive
包裹的
组件在切换时不会被销毁,而是缓存到内存中并执行
deactivated
钩子函数,再次渲染后会执行
activated
钩子函数。如果再一次跳回显示组件的时候可以在
activated
中做处理
说一下
keep-alive
的关联生命周期
会比平常的组件多两个生命周期钩子函数,分别是:
activated
和
deactivated
。使用
keep
-
alive
包裹的
组件在切换时不会被销毁,而是缓存到内存中并执行
deactivated
钩子函数,再次渲染后会执行
activated
钩子函数。
Vue
创建项目的指令是什么
1.
使用的官方
cli
脚手架,如果是低于
3.0
的版本,使用
npm init
2. cli
的版本大于
3.0
的使用
vue create
3.
可以使用
vite
直接搭建项目,命令为
npm init vite@latest
,根据提示,一步一步操作就好
参考链接
Getting Started | Vite
Vue CLI
vue
中如何使用
ref
绑定
通过为组件或者标签添加
ref
属性,可以在
js
代码中使用全局
api
$refs
获取
dom
元素或者组件,其上
的方法或者属性可以直接进行操作。
vue
导航守卫与
jq
导航拦截器的介绍
vue
的导航守卫一般指的是路由导航守卫,作用是在页面跳转的时候可以执行一个钩子函数。
导航守卫使用最多的是全局守卫
router.beforeEach
主要是用来验证用户的登陆状态。它接收三个参数
to, from, next
to:
即将要进入的路由对象
from:
当前导航要离开的路由
next:
一个回调函数, 一定要调用这个方法,不然路由不会继续往下
jq
导航拦截器没有听过,一般在
jQuery
的作用就是对
dom
元素做操作,
jQuery
的核心功能是元素
选择器。至于提到的导航器可能是一类第三方
jQuery
插件?或者网络请求拦截,如果是网络请求拦
截,那么
jQuery
发起请求的话,可以封装一个全局
ajax
请求插件,通过设置
ajaxSetup
实现
vue
常用哪些命令
1. v-model
指令,用于表单输入。
2. v-on
指令,用于事件绑定。
3. v-bind
指令,用于动态绑定一个值和传入变量。
4. v-once
指令,事件只能用一次,无论点击几次,执行一次之后都不会再执行。
5. v-html
指令,会将
span
的内容替换成
rawHtml
的属性值,直接作为
HTML
代码解析。
6. v-for
指令,与
HTML
标签结合使用,用于数据的遍历摆放。
7. v-if
指令,用来进行条件判断的,直接操作
dom
。
8. v-else
指令,用来进行条件判断的,与
v-if
指令连用,意义为条件不成立时执行。
9. v-show
指令,根据真假切换元素的显示状态。
vue
中插槽共有几种,及插槽的作用
三种:默认插槽、具名插槽、作用域插槽
默认插槽
默认插槽就是指,的作用类似于占符。
上述的子组件
child
里定义的
slot
被
span
标签给代替了,如果子组件里没有定义
slot
,则
span
标
签会被直接忽略,且一个子组件里只能定义一个单个插槽。
具名插槽
可以通过设置
name
属性,指定显示的位置
定义一个 组件:
//
参考这个
https://www.runoob.com/jquery/ajax-ajaxsetup.html
$
.
ajaxSetup
({
// url: 'demo_ajax_load.txt',
beforeSend
() {
//
发起请求之前执行
},
complete
() {
//
所有的请求成功之后执行
},
});
//
定义一个全局子组件
Vue
.
component
(
'child'
, {
template
:
'<div><slot></slot></div>'
,
});
var
vm
=
new
Vue
({
el
:
'#root'
,
});
<!--
引用
child
组件
-->
<div
id
=
"root"
>
<child>
<span>
我是占位符
</span>
</child>
</div>
<div
class
=
"container"
>
<header>
<slot
name
=
"header"
></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot
name
=
"footer"
></slot>
</footer>
</div>
<base-layout>
<template
v-slot:header
>
<h1>
Here might be a page title
</h1>
</template>
<p>
A paragraph for the main content.
</p>
<p>
And another one.
</p>
<template
v-slot:footer
>
<p>
Here's some contact info
</p>
</template>
</base-layout>
作用域插槽
父组件替换插槽标签,但是内容由子组件来提供
<body>
<div
id
=
"root"
>
<child>
<!--
定义一个插槽,该插槽必须放在
template
标签内
-->
<template
slot-scope
=
"props"
>
<li>
{{props.value}}
</li>
</template>
</child>
<!--!--
定义不同的渲染方式
-->
<child>
<!--slot-scope="props"
作用是使
props
指向子组件中定义的
<slot>-->
<template
slot-scope
=
"props"
>
<h1>
{{props.value}}
</h1>
</template>
</child>
</div>
</body>
<script>
//
定义一个全局子组件
Vue
.
component
(
'child'
, {
data
:
function
() {
return
{
list
: [
1
,
2
,
3
,
4
],
};
},
template
:
'<div><ul><slot v-for="value in list" :value=value></slot></ul></div>'
,
});
vue
如何使用插件
直接安装,引入就能使用。
vue
还是
js
,只要是
js
那么所有的插件使用都是相同的方式,引入绑定到对
应的节点或者操作对应的节点就好。
Vue
组件懒加载,图片懒加载
组件懒加载
1.
结合路由插件使用的时候使用
import
方式实现
图片懒加载
使用
Vue
封装过组件吗
?
有哪些
?
讲一下他们是怎么实现的
比如做后台管理中,很多模块经常会复用,比如侧边导航组件、项目中常用的
echarts
图表的封装(比如
折线图、柱状图等)
封装组件需要考虑复用性:
预留插槽
slot,
多次调用如果 子组件视图结构不一样那么就要 在 子组件
template
预留好 插槽(单个
插槽、具名插槽,作用域插槽)
var
vm
=
new
Vue
({
el
:
'#root'
,
});
</script>
//
第一步注释
import
导入的文件
// import About from '../components/About.vue';
//
第二步将引入组件的方式以箭头函数的方式异步引入
const
routes
=
[
{
path
:
'/about'
,
component
: ()
=>
import
(
/* webpackChunkName: 'about' */
'../components/About.vue'
)
}
]
````
2.
引入组件的时候使用回调函数的方式引入,比如
```js
//
组件懒加载
const IconList = () => import('components/base/icon-list');
export default {
components: {
IconList,
},
};
````
就是在加载页面的时候,如果页面中的图片过多,可以使用占位符的方式替换没有在可是区域内的图片,只加载当前需
要现实的图片。监听滚动条的位置,当图片标签出现在可视区域的时候,重置图片的路径为真是路径,然后展示图片地
址。一般在实际开发的时候都直接使用图片懒加载插件实现。还有一种解决方案就是使用页面骨架屏效果,也是类似占
位显示,当数据加载完成之后替换掉占位显示的内容
考虑到数据传递,定义
props
组件接收父组件传递的数据,同时需要注意单向数据流,
props
不能直
接修改,
$emit
自定义事件,父组件修改
业务逻辑不要在子组件中处理,子组件在不同父组件中调用时,业务处理代码不同,切记不要直接
在子组件中处理业务,应该子组件
$emit
自定义事件,将数据传递给父组件,父组件处理业务。
说说
vuex
的管理操作或理解
vuex
是
vue
的一个状态管理插件,采用集中式管理方式,来管理项目中多个组件的公共状态。
vuex
有一个仓库概念,将组件公共的
state
存储在仓库的
state
属性中,
state
是只读的,组件只能使用,不
能直接修改,修改需要通过 仓库中的
mutations
模块来修改,这样的好处是 当数据修改便于溯源,且不
会因为 多个组件 直接修改数据,导致 组件间数据的互相影响, 同时 当我们仓库中有一个
state
数据需要
请求 数据接口才能获取时,
vuex
设计了一个
action
模块,在
action
模块中发送异步请求,得到数据后,
提交
mutation
来修改
state
。当
state
发生改变后组件自动刷新,在组件中可以
commit mutation
或者
dispatch action
来修改
state
。
具体工作流程如下图
说说
Vuex
的工作流程
vuex
的仓库有
5
个模块,分别是
state
,
mutations, actions, getters, modules
我们将组件的公共状态定义在
vuex
仓库的
state
中
,state
是只读的,无法直接修改,必须调动仓库中的某个
mutation
才能修改状态,
getters
可以理解为
vuex
中的计算属性,当我们在某个组件中使用
vuex
中的某个
state
时,不是直接使用原值,而是需要派生出一个新的值,就可以定义
getters
,可以在组件中获取。当
依赖的
state
发生改变,此时
getters
会重新计算得到新值,同时
action
中可以发送异步请求,得到数据
后,
commit mutation
来给
state
赋值
具体代码如下:
仓库代码
const
store
=
new
Vuex
.
Store
({
state
: {
items
: []
//
定义一个公共的购物车数据
},
getters
: {
//
可以基于已有的
state
派生新的状态
selectedItems
(
state
) {
//
过滤购物车中未选中的商品
return
state
.
items
.
filter
(
item
=>
item
.
selected
)
}
},
mutations
: {
//
定义
mutation
来修改
state
INIT_ITEMS
(
state
,
items
){
state
.
items
=
items
}
},
actions
: {
// action
可以发送异步请求,得到数据后
commit mutation
将请求结果传入
FETCH_ITEMS
({
commit
},
params
=
{}){
//
调用封装好的 接口函数
fetchItem
(
params
).
then
(
res
=>
{
if
(
res
.
data
.
code
===
200
) {
commit
(
'INIT_ITEMS'
,
res
.
data
.
data
)
}
})
}
}
})
组件中使用 使用
vuex
//
获取
state
this
.
$store
.
state
.
items
//
直接获取
{
computed
: {
...
mapState
([
'items'
])
//
助手函数获取
}
}
//
获取
getters
this
.
$store
.
getters
.
selectedItems
//
直接获取
{
computed
: {
...
mapGetters
([
'selectedItems'
])
//
助手函数获取
}
}
//
组件中提交
action
this
.
$store
.
dispatch
(
'FETCH_ITEMS'
, {
token
:
'xxx'
})
{
methods
: {
...
mapActions
([
'FETCH_ITEMS'
])
//
助手函数 直接调用
this.FETCH_ITEMS(params)
触发
}
}
//
组件中也可以直接
commit mutation
this
.
$store
.
commit
(
'INIT_ITEMS'
[,
参数
])
{
vuex
项目中怎么使用?工作原理是什么?
原则
:
中小型项目中,如果组件的公共状态不多的情况下,不建议使用
vuex
,反而会增加代码复杂度,想要
组件通信,直接通过
event bus
即可,中大型项目中,多个组件公共状态较多情况下,建议使用
vuex
vuex
的具体工作流程如下
:
在仓库
state
中定义公共状态,
action
中发送异步请求,得到数据后调用
mutation
赋值给
state,
组件中使
用
state,
也可以在组件中
dispatch action
和触发
mutation
来修改
state,
视图刷新
具体代码如下:
仓库代码
组件中使用 使用
vuex
methods
:{
...
mapMutations
([
'INIT_ITEMS'
])
//
助手函数 直接调用
this.INIT_ITEMS(
参数
)
}
}
const
store
=
new
Vuex
.
Store
({
state
: {
items
: []
//
定义一个公共的购物车数据
},
getters
: {
//
可以基于已有的
state
派生新的状态
selectedItems
(
state
) {
//
过滤购物车中未选中的商品
return
state
.
items
.
filter
(
item
=>
item
.
selected
)
}
},
mutations
: {
//
定义
mutation
来修改
state
INIT_ITEMS
(
state
,
items
){
state
.
items
=
items
}
},
actions
: {
// action
可以发送异步请求,得到数据后
commit mutation
将请求结果传入
FETCH_ITEMS
({
commit
},
params
=
{}){
//
调用封装好的 接口函数
fetchItem
(
params
).
then
(
res
=>
{
if
(
res
.
data
.
code
===
200
) {
commit
(
'INIT_ITEMS'
,
res
.
data
.
data
)
}
})
}
}
})
//
获取
state
this
.
$store
.
state
.
items
//
直接获取
{
computed
: {
...
mapState
([
'items'
])
//
助手函数获取
}
Vuex
中处理异步需要在什么地方写
异步处理需要在 仓库的
actions
中定义
Action
类似于
mutation
,不同在于:
Action
提交的是
mutation
,而不是直接变更状态。
Action
可以包含任意异步操作。
我们可以在
action
中发送异步请求,成功后触发
mutation
将结果传入,在
mutation
赋值给
state
}
//
获取
getters
this
.
$store
.
getters
.
selectedItems
//
直接获取
{
computed
: {
...
mapGetters
([
'selectedItems'
])
//
助手函数获取
}
}
//
组件中提交
action
this
.
$store
.
dispatch
(
'FETCH_ITEMS'
, {
token
:
'xxx'
})
{
methods
: {
...
mapActions
([
'FETCH_ITEMS'
])
//
助手函数 直接调用
this.FETCH_ITEMS(params)
触发
}
}
//
组件中也可以直接
commit mutation
this
.
$store
.
commit
(
'INIT_ITEMS'
[,
参数
])
{
methods
:{
...
mapMutations
([
'INIT_ITEMS'
])
//
助手函数 直接调用
this.INIT_ITEMS(
参数
)
}
}
const
store
=
new
Vuex
.
Store
({
state
: {
items
: []
//
定义一个公共的购物车数据
},
mutations
: {
//
定义
mutation
来修改
state
INIT_ITEMS
(
state
,
items
){
state
.
items
=
items
}
},
actions
: {
// action
可以发送异步请求,得到数据后
commit mutation
将请求结果传入
FETCH_ITEMS
({
commit
},
params
=
{}){
//
调用封装好的 接口函数
fetchItem
(
params
).
then
(
res
=>
{
if
(
res
.
data
.
code
===
200
) {
commit
(
'INIT_ITEMS'
,
res
.
data
.
data
)
}
})
}
}
})
请你谈谈你对
vuex
的理解
vuex
是专为
vue
设计的状态管理工具
,
可用于父子组件和非父子组件的全局组件通信。应用的状态集中放
在
store
中,改变状态必须要经过
commit
,同步改变状态是提交
mutations
,异步是先通过
actions
再通过
mutations
。
一共有
5
大模块
- state
存放状态
- getters
就像计算属性一样,
getter
的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会
被重新计算。
可以对
state
中的数据做一些处理
- mutations
更改
Vuex
的
store
中的状态的唯一方法是提交
mutation
,通过
store.commit
提交到
mutations
模块
- actions
actions
是异步的改变
state
中状态的方法,通过
store.dispatch
来提交到
mutations
模块,再通过提交
commit
来更改
state
中的状态
- modules
Vuex
允许我们将
store
分割成模块(
module
)。每个模块拥有自己的
state
、
mutation
、
action
、
getter
、
甚至是嵌套子模块
——
从上至下进行同样方式的分割
vuex—
个模块中改变
state
中数据
,
其他模块如何获取
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,
store
对象
就有可能变得相当臃肿。
为了解决以上问题,
Vuex
允许我们将
store
分割成
模块(
module
)
。每个模块拥有自己的
state
、
mutation
、
action
、
getter
、甚至是嵌套子模块
——
从上至下进行同样方式的分割:
vuex
模块化后,需要另一个模块的
state
变化,可以在这个模块中定义
getters
获取,具体代码如下
//
模块
a
const
modulea
=
{
namespaced
:
true
,
state
: {
num
:
10
},
mutations
: {
ADD_NUM
(
state
,
n
) {
state
.
num
+=
n
}
}
}
//
模块
b
const
moduleb
=
{
namespaced
:
true
,
state
: {
num
:
10
},
getters
: {
//
在这里拿到 模块
a numstate
moduleaNum
(
state
,
getters
,
rootState
,
rootGetters
) {
//
模块下的
getter
有四个参数分别是当前模块的
state
,当前模块的
getters
,以及根
state
个根
getters
可以通过
rootState
获取其他模块的
state
return
rootState
.
modulea
.
num
vuex
的状态是怎样的,怎么改变状态
vuex
的状态储存在仓库的
state
属性中,
state
是只读的,无法直接修改必须调用
mutation
才能修改
你在项目中哪里使用
vuex
,
vuex
的应用场景
原则
:
中小型项目中,如果组件的公共状态不多的情况下,不建议使用
vuex
,反而会增加代码复杂度,想要
组件通信,直接通过
event bus
即可,中大型项目中,多个组件公共状态较多情况下,建议使用
vuex
在项目中,多个组件的公共状态可以存储的
vuex
中,比如电商网站的购物车数据,可以存储在
vuex
中。
后台管理角色鉴权中的 不同角色的侧边栏数据,以及 不同角色可以访问的路由数据可以存储的
vuex
中,
拿到数据储存。
vuex
的数据丢失知道吗 怎么解决
原理:可以利用缓存,将
vuex
中的
state
,在缓存中备份一下,当状态发生改变时,同步缓存的的备份。
同时当刷新时,去缓存中的备份,给
state
赋值
实际开发中我们一般利用
vuex
一个插件来实现
vuex-persistedstate
具体代码如下
安装
}
},
mutations
: {
ADD_NUM
(
state
,
n
) {
state
.
num
+=
n
}
}
}
const
store
=
new
Vuex
.
Store
({
state
: {
num
:
10
},
mutations
: {
ADD_NUM
(
state
,
n
) {
state
.
num
+=
n
}
}
})
//
在组件中直接出发
mutaion
this
.
$store
.
commit
(
'ADD_NUM'
,
10
)
//
或者助手函数 提交
mutation
{
methods
: {
...
mapMutations
([
'ADD_NUM'
])
}
}
//
直接调用即可
this
.
ADD_NUM
(
10
)
使用
vuex
怎么拿数据
获取
vuex state
中的
count
数据方法有
:
方法
1
:
this.$store.state.count
直接使用;
获取
vuex getters
中
getCount
数据的方法
:
方法
1
:
this.$store.getters.getCount
直接使用。
说说
Vuex
原理
vuex
是一个专门为
vue
构建的状态管理工具,主要是为了解决 多组间之间状态共享问题。强调的是
集中式管理,(组件与组件之间的关系变成了组件与仓库之间的关系)
vuex
的核心包括:
state
(存放状态)、
mutations
(同步的更改状态)、
actions
(发送异步请求,拿
到数据)、
getters
(根据之前的状态派发新的状态)、
modules
(模块划分)
state
发布一条新的数据,在
getters
里面根据状态派发新的状态,
actions
发送异步请求获取数据,
然后在
mutations
里面同步的更改数据
应用场合:购物车的数据共享、登入注册
vuex
仓库数据很多,怎么管理
使用
moduls
模块划分和文件拆分来管理数据很多的问题。
例如:我们可以在
modules
中进行模块划分,比如用户相关模块放入
user
中,
文章信息相关模块放入
article
中。
代码如下:
npm
i vuex-persistedstate
-S
import
Vuex
from
"vuex"
;
import
createPersistedState
from
"vuex-persistedstate"
;
const
store
=
new
Vuex
.
Store
({
// ...
plugins
: [
createPersistedState
()],
});
方法
2
:
`import { mapState } from vuex `
然后把
`...mapState('count')
放入
computed
中
`,
然后直接使用
count
变量。
方法
2
:
`import { mapGetters } from vuex `
然后把
`...mapGetters ('getCount')
放入
computed
中
`
,然后直接使用
getCount
变量。
modules:{
user:{ //
跟用户相关的数据放这
state:{
},
geeters:{
},
mutations:{
vuex
做数据集中管理,
mutations
和
actions
分别是做什么的,为什么不能用
mutations
处理异步数据
? mutations
和
actions
分别是做什么的
?
mutations
和
action
都是用来改变
Vuex store
的状态的;
mutations
提供的回调函数是同步的;而
actions
提
供的方法是异步的,此外,
actions
的方法最终还是通过调用
mutations
的方法来实现修改
vuex
的状态的。
?
为什么不能用
mutations
处理异步数据
?
官方文档说明:
“
在
mutation
中混合异步调用会导致你的程序很难调试。例如,当你能调用了两个
包含异步回调的
mutation
来改变状态,你怎么知道什么时候回调和哪个先回调呢?这就是为什么
我们要区分这两个概念。在
Vuex
中,我们将全部的改变都用同步方式实现。我们将全部的异步操
作都放在
Actions
中。
”
actions
和
mutations
并不是为了解决竞态问题,而是为了能用
devtools
追踪状态变化。事实上在
vuex
里
面
actions
只是一个架构性的概念,并不是必须的,说到底只是一个函数,你在里面想干嘛都可以,只要
最后触发
mutation
就行。异步竞态怎么处理那是用户自己的事情。
vuex
真正限制你的只有
mutation
必
须是同步的这一点(在
redux
里面就好像
reducer
必须同步返回下一个状态一样)。同步的意义在于这样
每一个
mutation
执行完成后都可以对应到一个新的状态(和
reducer
一样),这样
devtools
就可以打个
snapshot
存下来,然后就可以随便
time-travel
了。如果你开着
devtool
调用一个异步的
action
,你可以
清楚地看到它所调用的
mutation
是何时被记录下来的,并且可以立刻查看它们对应的状态。其实我有个
点子一直没时间做,那就是把记录下来的
mutations
做成类似
rx-marble
那样的时间线图,对于理解应用
的异步状态变化很有帮助。
vuex
中
actions
与
mutations
的区别
Mutation
更改
Vuex
的
store
中的状态的唯一方法是提交
mutation
。
Vuex
中的
mutation
非常类似于事
件:每个
mutation
都有一个字符串的 事件类型
(type)
和 一个 回调函数
(handler)
。这个回调函数就是我
们实际进行状态更改的地方,并且它会接受
state
作为第一个参数;
Action Action
类似于
mutation
,不同在于:
},
actions:{
},
},
article:{ //
跟文章相关的保存在这里
state:{
},
geeters:{
},
mutations:{
},
actions:{
},
}
}
Action
提交的是
mutation
,而不是直接变更状态。
Action
可以包含任意异步操作。
总体来说:
actions
1
、用于通过提交
mutation
改变数据
2
、会默认将自身封装为一个
Promise
3
、可以包含任意的异步操作
mutations
1
、通过提交
commit
改变数据
2
、只是一个单纯的函数
3
、不要使用异步操作,异步操作会导致变量不能追踪
说说
vue
如何进行路由配置
一、安装
本地环境安装路由插件
vue-router
:
cnpm install vue-router --save-dev
二、配置
两种配置方法:在
main.js
中
||
在
src/router
文件夹下的
index.js
中
这里只说在
src/router/index.js
中的
1.
引入
import Vue from 'vue'
import Router from 'vue
-
router'
注意这个
Router
是自定义的名字,这里叫这个名字后,下边都要用到的
1.
使用
/
注册:
Vue.use(Router)
2.
配置
配置路由:
3.
引入路由对应的组件地址:
import Home from '@/components/Home'
import Home from '@/components/Content’
4.
在
main.js
中调用
index.js
的配置:
import router from './router'
5. App.vue
页面使用(展示)路由:
把这个标签放到对应位置:
<router
-
view></router
-
view>
export default new Router({
routes: [
{
path : ‘/’, //
到时候地址栏会显示的路径
name : ‘Home’,
component : Home // Home
是组件的名字,这个路由对应跳转到的组件。。注意
component
没
有加
“s”.
},
{
path : ‘/content’,
name : ‘Content’,
component : Content
}
],
mode: "history"
})
6.
路由切换(原来的
等地方):把切换标签和链接改成:
<router
-
link to="/">
切换到
Home
组件
</router
-
link><router
-
link to="/content">
切
换到
Content
组件
</router
-
link>
//
这里,
to
里边的参数和配置时,
path
的路径一样即可
谈谈
Vue
路由守卫
1
、路由守卫 是什么
简单来说,导航守卫就是路由跳转前、中、后过程中的一些钩子函数,这个函数能让你操作一些其他的
事儿,这就是导航守卫。
官方解释,
vue-router
提供的导航守卫主要用来通过跳转或取消的方式守卫导航。
2
、路由守卫分类
导航守卫分为:全局的、组件内的、单个路由独享三种
2.1
全局的
指路由实例上直接操作的钩子函数,他的特点是所有配置路由的组件都会触发
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
})
全局路由的钩子函数包括
beforeEach
在路由跳转前触发,参数包括
to,from,next
(参数会单独介绍)三个,这个钩子作用主要是用于登录验证
beforeResolve
(
2.5+
)
这个钩子和
beforeEach
类似,也是路由跳转前触发,参数也是
to,from,next
三个,与
beforeEach
的区别参
考官网。
afterEach
是在路由跳转完成后触发,参数包括
to,from
,它发生在
beforeEach
和
beforeResolve
之后,
beforeRouteEnter
(组件内守卫)之前。
2.2
路由独享的
指在单个路由配置的时候也可以设置的钩子函数
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
路由独享的钩子函数包括
beforeEnter
与全局的
beforeEach
完全相同,如果都设置则在
beforeEach
之后紧随执行,参数
to
、
from
、
next
2.3
组件内的
指在组件内执行的钩子函数,类似于组件内的生命周期
export default{
data(){
//...
},
beforeRouteEnter (to, from, next) {
//
在渲染该组件的对应路由被
confirm
前调用
//
不!能!获取组件实例
this
//
因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
//
在当前路由改变,但是该组件被复用时调用
//
举例来说,对于一个带有动态参数的路径
/foo/:id
,在
/foo/1
和
/foo/2
之间跳转的时候,
//
由于会渲染同样的
Foo
组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
//
可以访问组件实例
this
},
beforeRouteLeave (to, from, next) {
//
导航离开该组件的对应路由时调用
//
可以访问组件实例
this
}
}
组件内的路由钩子函数包括
beforeRouteEnter
路由进入组件之前调用,参数包括
to
,
from
,
next
。该钩子在全局守卫
beforeEach
和独享守卫
beforeEnter
之后,全局
beforeResolve
和全局
afterEach
之前调用,要注意的是该守卫内访问不到组件的实例,也就是
this
为
undefined
,也就是他在
beforeCreate
生命周期前触发。
beforeRouteUpdate
在当前路由改变时,并且该组件被复用时调用,可以通过
this
访问实例。参数包括
to
,
from
,
next
。
beforeRouteLeave
导航离开该组件的对应路由时调用,可以访问组件实例
this
,参数包括
to
,
from
,
next
。
3
、路由守卫回调参数
to
:目标路由对象;
from
:即将要离开的路由对象;
next
:他是最重要的一个参数,他相当于佛珠的线,把一个一个珠子逐个串起来。
路由守卫中页面跳转运用了哪些钩子函数
应用场景
1
:可进行一些页面跳转前处理,例如判断需要登录的页面进行拦截,做登录跳转!!
router.beforeEach((to, from, next) => {
if (to.meta.requireAuth) {
//
判断该路由是否需要登录权限
if (cookies('token')) {
//
通过封装好的
cookies
读取
token
,如果存在,
name
接下一步如果不存在,那跳转回登录页
next()//
不要在
next
里面加
"path:/",
会陷入死循环
}
else {
next({
path: '/login',
query: {redirect: to.fullPath}//
将跳转的路由
path
作为参数,登录成功后跳转到该
路由
})
应用场景
2
,进入页面登录判断、管理员权限判断、浏览器判断
应用场景
3
:当页面中有未关闭的窗口
,
或未保存的内容时
,
阻止页面跳转
谈谈
Vue
路由模式,路由有哪些模式
在
vue-router
路由对象中,路由有两种模式:
hash
和
history
,而默认的是
hash
模式
.
前端路由目前主要有两种方法:
1
、利用
url
的
hash,
就是常用的锚点(
#
)操作,类似页面中点击某小图标,返回页面顶部,
JS
通过
hashChange
事件来监听
url
的改变,
IE7
及以下需要轮询进行实现。一般常用框架的路由机制都是用的这种
方法,例如
Angualrjs
自带的
ngRoute
和二次开发模块
ui-router
,
react
的
react-route,vue-route…
2
、利用
HTML5
的
History
模式,使
url
看起来类似普通网站,以
”/”
分割,没有
”#”
,但页面并没有跳转,不
过使用这种模式需要服务器端的支持,服务器在接收到所有的请求后,都指向同一个
html
文件,通过
historyAPI
,监听
popState
事件,用
pushState
和
replaceState
来实现。
}
}
else {
next()
}
})
//
使用钩子函数对路由进行权限跳转
router.beforeEach((to, from, next) => {
const role = localStorage.getItem('ms_username');
if(!role && to.path !== '/login'){
next('/login');
}else if(to.meta.permission){
//
如果是管理员权限则可进入,这里只是简单的模拟管理员权限而已
role === 'admin' ? next() : next('/403');
}else{
//
简单的判断
IE10
及以下不进入富文本编辑器,该组件不兼容
if(navigator.userAgent.indexOf('MSIE') > -1 && to.path === '/editor'){
Vue.prototype.$alert('vue-quill-editor
组件不兼容
IE10
及以下浏览器,请使用更高版本
的浏览器查看
', '
浏览器不兼容通知
', {
confirmButtonText: '
确定
'
});
}else{
next();
}
}
})
beforeRouteLeave (to, from, next) {
//
判断是否弹出框的状态和保存信息与否
if (this.dialogVisibility === true) {
this.dialogVisibility = false //
关闭弹出框
next(false) //
回到当前页面
,
阻止页面跳转
}else if(this.saveMessage === false) {
alert('
请保存信息后退出
!') //
弹出警告
next(false) //
回到当前页面
,
阻止页面跳转
}else {
next() //
否则允许跳转
}
路由守卫的作用,全局和局部使用的差异是什么
全局路由守卫:就是在整个网页中,只要发生了路由的变化,都会触发。全局导航守卫主要包括两个函
数,分别为:
beforeEach
、
afterEach
。
局部路由守卫
:
(组件内的守卫)只有当前路由使用。
路由加载之前触发:
beforeRouteEnter (to, from, next)
更新路由之前触发:
beforeRouteUpdate (to, from, next)
离开当前路由之前触发:
beforeRouteLeave (to, from, next)
写出路由传参的具体实现
方式一:
params
传参(显示参数)
params
传参(显示参数)又可分为 声明式 和 编程式 两种方式
1
、声明式
router-link
该方式是通过
router-link
组件的
to
属性实现,该方法的参数可以是一个字符串路径,或者一个描述
地址的对象。使用该方式传值的时候,需要子路由提前配置好参数,例如:
//
子路由配置
{
path: '/child/:id',
component: Child
}
//
父路由组件
进入
Child
路由
2
、编程式
this.$router.push
使用该方式传值的时候,同样需要子路由提前配置好参数,例如:
//
子路由配置
{
path: '/child/:id',
component: Child
}
//
父路由编程式传参
(
一般通过事件触发
)
this.$router.push({
path:'/child/${id}',
})
在子路由中可以通过下面代码来获取传递的参数值
this.$route.params.id
方式二:
params
传参(不显示参数)
params
传参(不显示参数)也可分为 声明式 和 编程式 两种方式,与方式一不同的是,这里是通过路
由的别名
name
进行传值的
1
、声明式
router-link
该方式也是通过
router-link
组件的
to
属性实现,例如:
进入
Child
路由
2
、编程式
this.$router.push
使用该方式传值的时候,同样需要子路由提前配置好参数,不过不能再使用
:/id
来传递参数了,因为父
路由中,已经使用
params
来携带参数了,例如:
//
子路由配置
{
path: '/child,
name: 'Child',
component: Child
}
//
父路由编程式传参
(
一般通过事件触发
)
this.$router.push({
name:'Child',
params:{
id:123
}
})
在子路由中可以通过下面代码来获取传递的参数值
this.$route.params.id
注意:上述这种利用
params
不显示
url
传参的方式会导致在刷新页面的时候,传递的值会丢失
方式三:
query
传参(显示参数)
query
传参(显示参数)也可分为 声明式 和 编程式 两种方式
1
、声明式
router-link
该方式也是通过
router-link
组件的
to
属性实现,不过使用该方式传值的时候,需要子路由提前配置好路
由别名(
name
属性),例如:
//
子路由配置
{
path: '/child,
name: 'Child',
component: Child
}
//
父路由组件
进入
Child
路由
2
、编程式
this.$router.push
使用该方式传值的时候,同样需要子路由提前配置好路由别名(
name
属性),例如:
//
子路由配置
{
path: '/child,
name: 'Child',
component: Child
}
//
父路由编程式传参
(
一般通过事件触发
)
this.$router.push({
name:'Child',
params:{
id:123
}
})
在子路由中可以通过下面代码来获取传递的参数值
this.$route.query.id
vue
路由有哪几种
this.$router.push(obj)
跳转到指定
url
路径,并想
history
栈中添加一个记录,点击后退会返回到上一个页面
this.$router.replace(obj)
跳转到指定
url
路径,但是
history
栈中不会有记录
this.$router.go(n)
向前或者向后跳转
n
个页面,
n
可为正整数或负整数
说一下
vue
路由跳转方式
1
、
router-link
【实现跳转最简单的方法】
<router
-
link to='
需要跳转到的页面的路径
>
浏览器在解析时,将它解析成一个类似于
<a>
的标签。
div
和
css
样式略
2
、
this.$router.push({ path:’/user’})
3
、
this.$router.replace{path
:
‘/’ }
类似,不再赘述
vue
路由的数据传参,
params
,
query
的区别。使用
params
什么时候会生
效,什么时候不会生效,
params
有时会失效,为什么
query
和
params
区别
query
类似
get,
跳转之后页面
url
后面会拼接参数
,
类似
?id=1,
非重要性的可以这样传
,
刷新页面
id
还在
params
类似
post,
跳转之后页面
url
后面不会拼接参数
,
但是刷新页面
id
会消失
注意:如果提供了
path
,
params
会被忽略而导致失效。
通过官网我们知道路由中的
name
是不能重复的,而
path
是可以的。所以在函数式编程中,我们可以用变
量来控制路径。
路由守卫路由拦截如何配置
通常在项目里,我们需要用户进行登录,才能让用户查看项目。在后台管理系统中,会根据不同的用户
权限展示不同的内容。
在用户访问页面之前,我们通过全局前置守卫对路由进行拦截,看看你是不是可以通过。通过的标准是
否登录,如果登录就通过放行,没有通过就打回。
<li >
<router-link
to
=
"keyframes"
>
点击验证动画效果
</router-link>
</li>
//
不需要路由验证页面
const whiteList = ['login', 'index']
router.beforeEach((to, from, next) => {
//
确定用户是否已登录
const hasToken = false //
这里就是路由是否通过标准,一般都是通过
token
来验证
if (hasToken) { //
登录
if (to.path === '/login') {
//
如果已登录,请重定向到主页
next({ path: '/index' })
return
}
next()
} else {
if (whiteList.indexOf(to.name) !== -1) {
//
在免费登录白名单中,直接进入
next()
} else {
//
没有访问权限的其他页将重定向到登录页。
next(`/login`)
}
}
})
需要注意的一点是,用户没有登录,是需要跳转到登录页面,如果在白名单里面没有登录页或者没有
next()
,页面一直跳转直到内存溢出。
每个项目的验证是否拥有权限不一样,权限判断那一块可以根据自己的实项目需求来进行操作。
写公用组件的时候,怎么提高可配置性
1.
带着开放封闭原则的视角思考
开放原则,是说我们需要将有可能变动的属性,通过
props
接口的方式暴露给组件的调用者。
封闭原则,意思是我们需要将该组件的通用能力及逻辑封装再组件内部,让调用者使用更加方便
2.
组件的可配置性需要结合实际的业务需求具体分析
假设我们要封装一个
Tab
选项卡组件,实际功能交互可参考
组件
| Element
参数
说明
类型
可选值
默认
值
value /
v-model
绑定值,选中选项
卡的
name
string
—
第一
个选
项卡
的
name
type
风格类型
string
card/border-card
—
closable
标签是否可关闭
boolean
—
false
addable
标签是否可增加
boolean
—
false
editable
标签是否同时可增
加和关闭
boolean
—
false
tab-
position
选项卡所在位置
string
top/right/bottom/left
top
stretch
标签的宽度是否自
撑开
boolean
-
false
before-
leave
切换标签之前的钩
子,若返回
false
或者返回
Promise
且被
reject
,则阻
止切换。
Function(activeName,
oldActiveName)
—
—
3.
组件的开放原则实践
上面的表格为
Tab
组件提供的
props
配置接口,它们需要遵循如下特点,可以极大提高可配置性
:
配置项要尽可能多的,覆盖到业务中可能出现的每一种情况。
保证组件在每一项配置缺省状态下都能够正常表现
每一项配置都应该具备合理的默认值。
1.
组件的封闭原则实践
事件名称
说明
回调参数
tab-click
tab
被选中时触发
被选中的标签
tab
实例
tab-remove
点击
tab
移除按钮后触发
被删除的标签的
name
tab-add
点击
tabs
的新增按钮后触发
—
edit
点击
tabs
的新增按钮或
tab
被关闭后触发
(targetName, action)
上面的表格为
Tab
组件所提供的自定义事件,但是事件相关的逻辑实现,已经完全在组件内部封装好了,
组件使用者,只需要按需绑定事件即可对组件的行为做出监听,这些事件也需要遵循如下特点,才能保
证该组件的可配置性:
事件函数相关逻辑,必须在组件内部封装完善
自定义事件函数在触发时,必须能够返回相关的参数
例如
@tab-click
事件会给函数返回,被点击菜单的
index
下标等信息
事件函数本身也需要能够在缺省状态下,保持组件的正常运行。
vue-router
怎么生成动态地址
1.
动态路由配置的应用场景
一般我们在使用
vue-router
进行路由管理的时候,是通过如下方式配置
路由
与
组件
之间的
映射关
系
:
但是在后台管理平台这种类型的项目中,我们需要让拥有不同
角色权限
的用户,访问不同的
菜单
及
路由
,如上述代码所示,部分路由只有管理员才能访问,而另外一部分路由只能
vip
用户才能访
问,所以需要用到
vue-router
提供的
addRoute
方法来动态管理这一部分路由配置。
// router/index.js
配置文件
const router = new VueRouter({
routes:[
{
path:'/login',
component:()=>import ('../views/Login') //
登录路由
},
{
path:'/reg',
component:()=>import ('../views/Reg') //
注册路由
},
{
path:'/admin',
component:()=>import ('../views/Admin') //
这是一个管理员才能访问的路由
},
{
path:'/vip',
component:()=>import ('../views/Vip') //
假设,这是要给
vip
用户才能访问的路
由
},
]
})
2.
本地只配置通用路由
我们为了实现路由的动态配置,需要将上述路由配置进行拆分,本地配置文件中,只保留通用的
路由映射。
3.
后端为每个用户分配一个角色,随登录接口下发给前端
4.
前端登录并动态获取路由配置
前端登录成功后,会得到后端动态下发的,跟自己账号角色相匹配的路由数据,此时可以通过
addRoute
方法,将这些动态获取的路由配置数据包,设置给
router
对象
const router = new VueRouter({
routes:[
{
path:'/login',
component:()=>import ('../views/Login') //
登录路由
},
{
path:'/reg',
component:()=>import ('../views/Reg') //
注册路由
}
]
})
app.get('/login',(req,res)=>{
//
此处需要实现登录相关逻辑
res.send({
username:'
张三丰
',
role:'admin', //
标志当前用户角色
routerList:[ //
此处的路由配置,也可以通过独立接口向前端提供
{
path:'/admin',
component:()=>import ('../views/Admin') //
这是一个管理员才能访问的路
由
},
...
此处可能会有很多其他路由,这些路由数据应该由专门的数据表来存储
]
})
})
// views/Login.vue
登录面板
axios.get('/login',(result)=>{
let {routerList} = result
routerList.forEach((item) => {
this.$router.addRoute(item)
})
})
vue-router
有哪些方法
1.
router.beforeEach
路由守卫
我们可以使用这个方法,按需拦截用户访问某些敏感路由,例如
:
2.
router.push
编程式导航
通过编程式导航,我们可以通过事件的方式触发路由跳转
3.
router.go
、
router.back
、
router.forward
路由的进入与返回
router.go
作用等同于
window.history.go
4.
router.addRoute
动态设置路由映射
添加一条新路由规则。如果该路由规则有
name
,并且已经存在一个与之相同的名字,则会覆盖
它。
router.beforeEach((to,from,next)=>{ //
路由的全局前置守卫
if(to.path.indexOf('/account')==-1){ //
判断用户访问的是不是个人中心
next() //
不是个人中心,直接放行
}else{
if(store.state.my.userInfo){ //
判断登录状态
next() //
如果已经登录,直接放行
}else{
next('/login') //
如果没有登录,则跳至登录页
}
}
})
//
字符串
router.push('home')
//
对象
router.push({ path: 'home' })
//
命名的路由
router.push({ name: 'user', params: { userId: 123 }})
//
带查询参数,变成
/register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})
//
在浏览器记录中前进一步,等同于
history.forward()
router.go(1)
//
后退一步记录,等同于
history.back()
router.go(-1)
//
前进
3
步记录
router.go(3)
谈谈对
MVVM
的理解
1.
什么是
MVVM
不管是
MVC
,
MVP
,或者
MVVM
,都是常见的软件架构设计模式(
Architectural Pattern
),它通过分
离关注点来改进代码的组织方式。不同于设计模式(
Design Pattern
),只是为了解决一类问题而
总结出的抽象方法,一种架构模式往往使用了多种设计模式。
MVVM
,可以拆分为
Model-View-
ViewModel
来理解:
Model
-
数据模型,可以对应到真实开发过程中的
数据包
View
-
视图层,布局和外观,可以对应到真实开发中的
DOM
结构
ViewModel
-
扮演
“View”
和
“Model”
之间的使者,帮忙处理
View
视图层的全部业务逻辑
2.
为什么使用
MVVM
框架
要回答这个问题,我们需要对比一下,在使用
MVVM
框架之前,我们是如何完成前端交互的。
使用前
为了修改某个视图节点中的内容信息,我们需要频繁人为操作
DOM
,效率低下
使用后
当
name
数据发生变化的时候,视图区域的
name
自定触发更新,极大提高开发效率
3.
通过上述案例进一步理解
MVVM
name
数据包可以认为就是那个
Model
div
节点可以认为就是那个
View
而
Vue
提供的语法环境所支持的数据驱动能力,就可以认为是那个
ViewModel
axios.get('/login',(result)=>{ //
通过异步接口获取对应用户的特有路由配置
let {routerList} = result
routerList.forEach((item) => {
this.$router.addRoute(item) //
通过
addRoute
方法依次将路由配置设置给
router
对象
})
})
var
dom
=
document
.
querySelector
(
'div'
);
dom
.
innerHTML
=
'
张三丰
'
;
dom
.
style
.
color
=
'red'
;
<div>{{name}}</div>
data:{
name:'
张三丰
'
}
vue-router
有什么组件
1.
router-link
组件
<router
-
link>
组件支持用户在具有路由功能的应用中
(
点击
)
导航。 通过
to
属性指定目标地
址,默认渲染成带有正确链接的
<a>
标签,可以通过配置
tag
属性生成别的标签
.
。另外,当目
标路由成功激活时,链接元素自动设置一个表示激活的
CSS
类名。
<router
-
link>
比起写死的
<a href="...">
会好一些,理由如下:
无论是
HTML5 history
模式还是
hash
模式,它的表现行为一致,所以,当你要切换路由模
式,或者在
IE9
降级使用
hash
模式,无须作任何变动。
在
HTML5 history
模式下,
router
-
link
会守卫点击事件,让浏览器不再重新加载页面。
当你在
HTML5 history
模式下使用
base
选项之后,所有的
to
属性都不需要写
(
基路径
)
了。
2. router-view
组件
<router
-
view>
组件是一个
functional
组件,渲染路径匹配到的视图组件。
<router
-
view>
渲
染的组件还可以内嵌自己的
<router
-
view>
,根据嵌套路径,渲染嵌套组件。
其他属性
(
非
router-view
使用的属性
)
都直接传给渲染的组件, 很多时候,每个路由的数据都是包
含在路由参数中。
因为它也是个组件,所以可以配合
<transition>
和
<keep
-
alive>
使用。如果两个结合一起
用,要确保在内层使用
<keep
-
alive>
:
vue-router
和
location.href
的用法区别
3. vue-router
使用
pushState
进行路由更新,静态跳转,页面不会重新加载;
4. location.href
会触发浏览器,页面重新加载一次
5. vue-router
使用
diff
算法,实现按需加载,减少
dom
操作
6. vue-router
是路由跳转或同一个页面跳转;
location.href
是不同页面间跳转;
7. vue-router
是异步加载
this.$nextTick(()=>{
获取
url})
;
location.href
是同步加载
vue-cli
怎么自定义组件
1.
组件封装
<transition>
<keep-alive>
<router-view></router-view>
</keep-alive>
</transition>
// HelloWorld.vue
组件
<template>
<div>
自定义组件
</div>
</template>
<script>
export default
{
data
() {
1.
局部注册调用组件
1.
全局注册使用
先在
main.js
中全局注册该组件
然后在需要使用公共组件的业务组件中,调用该组件
return
{
key
:
'value'
}
},
//
组件交互
}
</script>
<style
scoped lang
=
"less"
>
//
组件样式
</style>
// Test.vue
<template>
<div>
<HelloWorld/>
</div>
</template>
<script>
import HelloWorld from './HelloWorld.vue'
export default {
components:{
HelloWorld
}
}
</script>
<style lang="less" scoped>
</style>
import
Vue
from
'vue'
import
App
from
'./App.vue'
//
全局注册
import
HelloWorld
from
'./components/HelloWorld.vue'
Vue
.
component
(
'hello-world'
,
HelloWorld
)
new
Vue
({
render
:
h
=>
h
(
App
),
}).
$mount
(
'#app'
)
// Test.vue
<template>
<div>
<hello-world></hello-world>
</div>
</template>
谈谈对
vue-loader
的理解,实现原理是什么
一
. vue-loader
的作用是什么
1.
首先我们需要达成共识的是,目前浏览器,只能识别普通的
html
、
css
、
javascript
。
2.
但是为了能够方便使用
vue
的组件化开发,需要我们将代码写在
.vue
单文件组件中。
3. .vue
文件,以及其内部的
template
、
style
、
script
区域代码,不能直接交给浏览器去解析,因为它解
析不了。
4.
所以我们需要一个
vue-loader
进行
.vue
单文件组件代码的转换,也就是
.vue
方便开发
------> vue-laoder
协助翻译
----->
浏览器才能展示
二
. vue-loader
工作原理
vue-loader
的工作流程, 简单来说,分为以下几个步骤
:
1.
将一个
.vue
文件
切割成
template
、
script
、
styles
三个部分。
2.
template
部分
通过
compile
生成
render
、
staticRenderFns
。
3.
获取
script
部分
返回的配置项对象
scriptExports
。
4.
styles
部分
,会通过
css-loader
、
vue-style-loader
, 添加到
head
中, 或者通过
css-loader
、
MiniCssExtractPlugin
提取到一个
公共的
css
文件
中。
5.
使用
vue-loader
提供的
normalizeComponent
方法,
合并
scriptExports
、
render
、
staticRenderFns
, 返回
构建
vue
组件需要的配置项对象
- options
, 即
{data, props, methods,
render, staticRenderFns...}
。
vue
实例中的
data
,在生命周期哪里能找到
如果想要了解哪个生命周期中可以找到
vue
实例的
data
,那我们必须了解,
vue
实例初始化的基本流程。
一、
vue
实例的初始化过程
1. new Vue
从
new Vue(options)
开始作为入口,
Vue
只是一个简单的构造函数,内部是这样的:
进入了
_init
函数之后,先初始化了一些属性。
1.
initLifecycle
:初始化一些属性如
$parent
,
$children
。根实例没有
$parent
,
$children
开始是空数组,直到它的
子组件
实例进入到
initLifecycle
时,才会往父组件的
<script>
export default {
}
</script>
<style lang="less" scoped>
</style>
function Vue (options) {
this._init(options)
}
复制代码
$children
里把自身放进去。所以
$children
里的一定是组件的实例。
2.
initEvents
:初始化事件相关的属性,如
_events
等。
3.
initRender
:初始化渲染相关如
$createElement
,并且定义了
$attrs
和
$listeners
为
浅层
响应式属性。具体可以查看
细节
章节。并且还定义了
$slots
、
$scopedSlots
,其中
$slots
是立刻赋值的,但是
$scopedSlots
初始化的时候是一个
emptyObject
,直到组件的
vm._render
过程中才会通过
normalizeScopedSlots
去把真正的
$scopedSlots
整合后挂
到
vm
上。
然后开始第一个生命周期:
2. beforeCreate
被调用完成
beforeCreate
之后
1.
初始化
inject
2.
初始化
初始化
props
初始化
methods
初始化
data
初始化
computed
初始化
watch
3.
初始化
provide
所以在
data
中可以使用
props
上的值,反过来则不行。
然后进入
created
阶段:
3. created
被调用完成
调用
$mount
方法,开始挂载组件到
dom
上。
如果使用了
runtime
-
with
-
compile
版本,则会把你传入的
template
选项,或者
html
文本,通过
一系列的编译生成
render
函数。
编译这个
template
,生成
ast
抽象语法树。
优化这个
ast
,标记静态节点。(渲染过程中不会变的那些节点,优化性能)。
根据
ast
,生成
render
函数。
对应具体的代码就是:
callHook(vm, 'beforeCreate')
复制代码
state
callHook(vm, 'created')
复制代码
const ast = parse(template.trim(), options)
if (options.optimize !== false) {
optimize(ast, options)
}
const code = generate(ast, options)
复制代码
如果是脚手架搭建的项目的话,这一步
vue
-
cli
已经帮你做好了,所以就直接进入
mountComponent
函数。
那么,确保有了
render
函数后,我们就可以往
渲染
的步骤继续进行了
二、结论
通过上面的
vue
实例化的前面几步我们可以知道,在
created
生命周期中,我们就可以对
data
做操作,因为
此时根实例相关的属性都已准备完毕。那在
created
后面执行的生命周期,自然也都可以获取并操作
data
,所以能够操作
data
的常用生命周期就有如下几个
:
created
beforeMount
mounted
beforeUpdate
updated