一. vue是什么,vue的核心概念是什么
- vue是一套用于构建用户界面的渐进式JS框架
- 渐进式:允许用户从一个简单的组件写起,按需加载,直到搭建出一个复杂的前端平台。
- vue的核心概念:
- 组件化
- MVVM结构,数据双向绑定
- 响应式,虚拟DOM
- 生命周期
- 一切以数据为核心,数据驱动视图。
二. vue的优点
- 轻量级:vue作为一款轻量级前端框架,大小只有18-21KB,工程搭建简单。
- 高性能:虚拟DOM和响应式避免了不必要的全局渲染,提升了用户体验,使得用户操作更加流畅。
- 易上手:vue的组件化开发思想容易理解,分离式语法对初学者较为友好。
- 插件化:由于vue框架的流行性,目前有许多基于ue的npm扩展包和开发工具(如vuex)。vue可以在一个文件下统一管理所有外部插件的全局使用。插件化提供了很多的开发便捷性。
- 运行速度快:较其他框架就性能而言,vue存在很大的优势。
- 便于测试:组件化的思想便于排查问题所在,能快速追踪到。
- 视图,数据,结构分离:使数据的更改更为简单,不需要进行逻辑代码的修改,只需要操作数据就能完成相关操作。
三. vue.js的安装
- cdn引入
<script src=''></script>
- 下载文件,
<script src='vue.js'></script>
- 通过npm(或者yarn)全局安装:npm install vue@版本 --g/–save
- 注意:可以根据实际需求下载对应的开发环境或者生产环境
四. MVVM
- M:model 数据模型层,从网络请求中获取到的数据。
- V:views 视图层,也就是DOM层,用于向用户展示渲染结果。
- VM:view-model 视图模型层,用于连接view和model,实现两者之间的双向绑定,这样改变model,view会跟着改变,同理改变view,model数据层也会自动更新。
五. 生命周期
- 概念:一个vue的实例从创建、挂载、渲染、更新到销毁的过程
- 创建:
- 创建前beforeCreate:这个时候vue实例还没有初始化,此时挂载元素$el和数据对象data都为空
- 创建后created:这个时候vue实例已经初始化了,但是dom未生成,因此$el为空,data存在,可以通过methods操作data了
- 挂载:
- 挂载前beforeMount:这个时候$el和data都已经有了,模板已经在内存中编辑,但是还没有渲染到页面
- 挂载后mounted:$el已经渲染到页面,用户可以看见
- 更新:
- 更新前beforeUpdate:此时view和model未实现双向绑定
- 更新后updated:此时view和model已经实现了双向绑定
- 销毁:
- 销毁前beforeDestory: 此时vue的实例依然可以使用
- 销毁后destoryed:vue实例已经解除绑定,操作data无效,但是dom结构还依然存在
六. 指令
- v-once
- view层展示的数据只会渲染一次,改变data不会跟着发生改变
- 语法:
<h2 v-once>
{ {message}}</h2>
这里的message只会渲染一次
- v-html
- 将data里面的数据解析成html语言
- 语法:
<h2 v-html='data'></h2>
- v-text
- 渲染data类似于{ {}} mustache 胡须语法
- 语法:
<h2 v-text='message'></h2>
=<h2>
{ {message}}</h2>
(推荐使用{ {}}语法)
- v-pre
- 将后面的内容不做修改,原封不动的渲染出来
- 语法:
<h2 v-pre>
{ {message}}</h2>
- v-clock
- 解决初始化慢导致的页面闪烁问题
- 语法:
<h2 v-clock>
{ {message}}</h2>
- v-bind
- 动态绑定,语法糖为":" v-bind:class<=>:class 动态的绑定一个class类
- 语法:<h2 :class={‘active’:isActive}>{
{message}}
</h2>
- v-on
- 点击事件,语法糖为“@” v-on:click=@click 绑定一个点击事件
- 语法:<h2 @click=‘btn’>{
{message}}
</h2>
- v-if,v-else,v-else-if,v-show
- v-if: 条件判断
- 语法:
<h2 v-if='条件'>
{ {message}}</h2>
条件为真则渲染,条件为假不渲染 - v-if和v-show的区别:
- 当条件为假的时候v-if压根不渲染,v-show会渲染,只是通过display:none的方式将渲染结果隐藏起来
- v-if的渲染成本高于v-show
- 当需要频繁的切换显示与隐藏,建议使用v-show
- v-for
- 循环遍历
- 语法:
- 遍历数组:
<h2 v-for='(book,index) in/of books'>
{ {index}}—{ {book.name}}</h2>
- 遍历对象:
<h2 v-for='(value,key,index) in/of books'>
{ {index}}—{ {key}}—{ {value}}</h2>
- 遍历数组:
- v-model:实现标签数据的双向绑定,本质上是一个语法糖。
<input v-model='test'>`等价于<input :value="test" @input="test = $event.target.value">
七. 组件
- 组件化思想:将一个页面拆成一个个小的部分,每一个小的部分称为一个组件,完成组件内的独立功能,让后将这些组件合并起来,形成完整功能。
- 优点:每个组件之间是相互独立的,便于代码的书写和维护。
- 组件的使用(以单一vue文件为例,实际项目中一般不这么写)
// 1. 创建组件
const cpn = Vue.extend({
// 组件模板
template:`<div>我是子组件</div>`
})
// 2. 注册组件
// (1)全局注册
Vue.component('my-cpn',cpn)
// (2)局部注册
let app=new Vue({
el:'#app',
data:{
message:'你好'
},
// 局部注册
component:{
'my-cpn':cpn
}
})
// 3. 挂载组件
<div id='app'>
<my-cpn></my-cpn>
</div>
- 组件中的数据:
- data(){}:必须是一个函数,因为组件涉及到可能在多个页面引用,为了防止数据之间的相互干扰因此data必须是一个函数,且必须有return
- 父子组件的通信
- 父组件向子组件传递数据
- 在父组件中挂载子组件,并通过v-bind的形式绑定想要传输的数据
- 在子组件中通过props接受数据
- 在子组件中根据需求渲染不同的数据
- 子组件向父组件传递数据
- 在子组件中通过$emit(event,value) 的形式将数据发射出去
- 在父组件中通过v-on:event='methods’来接受数据
- 根据需求在methods中定义该方法
- 父组件向子组件传递数据
- 父子组件之间的访问
- 父组件访问子组件:this. c h i l d r e n 或者 t h i s . children 或者 this. children或者this.refs
- 子组件访问父组件:this.$parent
- this. c h i l d r e n 和 t h i s . children和this. children和this.refs的区别
- this.$children访问到是一个数组类型,需要通过具体的索引值来确定访问到的具体子组件
- this.$refs可以通过绑定一个id来确定访问的子组件
<!--在子组件中绑定id,在父组件中拿到想要的特定的子组件-->
<!-- 通过this.$refs.child1可以拿到具体的子组件 -->
<child_cpn1 ref='child1'></child_cpn1>
<!-- 通过this.$children[0]索引值可以拿到具体的子组件 -->
<child_cpn1></child_cpn1>
<child_cpn2></child_cpn1>
- 非父子组件通信
- 中央事件总线
- 在mian.js入口文件中注册中央事件总线:
Vue.prototype.$bus=new Vue()
- 在需要通信的位置发射该事件:
this.$bus.$emit('事件名',value)
- 在对应位置接收:
this.$bus.$on('发射时候的事件名',callback(value)=>{})
- vuex vue的集中式状态管理机制
- 在mian.js入口文件中注册中央事件总线:
- 发布订阅模式
- 中央事件总线
八. 插槽
- 定义:组件化开发中通过
<slot></slot>
包裹起来的部分称为插槽。 - 作用:在组件化开发中,同一组件可能会被多次复用,而每次仅仅只是内容不同,这时候可以使用插槽占位,然后根据实际需求替换插槽中的内容。
- 分类
(1) 匿名插槽
<!-- 子组件 -->
<div>
<slot></slot>
</div>
<!-- 父组件 -->
<child>
<span>我是插槽插入的内容</span> <!-- 用span标签替换slot插槽 -->
</child>
(2)具名插槽
<!-- 子组件 -->
<div>
<slot name="wangjiajia"></slot>
</div>
<!-- 父组件 -->
<child>
<span slot="wangjiajia">我是插槽插入的内容</span> <!-- 用span标签只替换slot名字为wangjiajia的插槽 -->
</child>
(3)作用域插槽:父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。不过,我们可以在父组件中使用slot-scope 特性从子组件获取数据。
<!-- 子组件 -->
<div>
<slot :data="data"></slot>
</div>
<!-- 父组件 -->
<child>
<span slot-scope="data">我是插槽插入的内容</span>
</child>
九. vue脚手架1. 应用场景:复杂的大型项目
- 应用场景:复杂的大型项目
- 脚手架2:
- 全局安装(建议):npm install vue/cli-init@版本号 -g(init起到一个桥接的作用,便于想继续使用cli2)
- 当前项目下安装:npm install vue/cli-init@版本号 -save
- 初始化项目:vue init webpack project(自己取一个项目名)
- 查看当前vue脚手架的版本:vue -V
- 脚手架3:
- 全局安装(建议):npm install vue/cli@版本号 -g
- 当前项目下安装:npm install vue/cli@版本号 -save
- 初始化项目:vue create project(自己取一个项目名)
- 脚手架2和脚手架3的区别:
- 基于的版本不同:cli2基于webpack3打造的,cli3基于webpack4
- 设计原则不同:cli3的设计原则是0配置,移除配置文件根目录下的build和config
- cli3提供了vue ui命令,提供了可视化配置,更加的方便直观
- 移除static文件夹,新增了public文件夹,并且index.html移到了public文件夹中
十. vue修饰符
- @click.stop 阻止事件继续传播
- @click.prevent 阻止标签默认行为
- @click.capture 使用事件捕获模式,即元素自身触发的事件先在此处处理,然后才交由内部元素进行处理
- @click.self 只当在 event.target 是当前元素自身时触发处理函数
- @click.once 事件将只会触发一次
- @click.passive 告诉浏览器你不想阻止事件的默认行为
十一. 路由
-
原理:
根据url和ui的映射关系,通过改变url达到更新ui的效果(无需刷新页面)
-
实现:
- hash实现:通过hashchange事件监听url的变化,利用location.hash=xxx 来读取属性,本质上就是改变window.location的href属性。
- history实现:
- history.go(0) 刷新当前页面
- history.back() === history.go(-1) 返回上一级
- history.forward === history.go(1) 前进一级
- history.pushState(stateObject,’ ',‘url’) 改变url的path部分,不引起页面刷新
- history.repalceState(stateObject,’ ‘,’ ') 用url代替当前的路由,浏览器不能来回切换,不刷新页面。
- abstract:支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式。
-
vue-router
- 目前流行的三大框架都有自己的路由实现
- angular:ngRouter
- react:ReactRouter
- vue:vue-router
- vue-router的作用:用于访问路径,将路径和组件之间映射起来。
- vue-router的使用
- 安装:npm install vue-router@版本号 --save
- 导入路由对象并安装路由插件(在路由js文件中)
- import Vue from ‘vue’
- import VueRouter from “vue-router”
- Vue.use(VueRouter)
- 创建路由实例,配置映射关系:
const router=new VueRouter({[配置的路由映射,可以提取出来些],mode:'history'})
- 在main.js中挂载路由实例:import router from ‘./router’
- 通过
<router-link>
或者<router-view>
的形式来使用路由<router-link>
内置组件会被渲染成一个a标签<router-view>
会根据当前路径动态的渲染不同的组件
- 目前流行的三大框架都有自己的路由实现
-
路由参数的传递
- params类型:只能通过name引入
- query类型:既能通过name也能通过path引入
-
路由跳转
(1)通过<router-link :to="{}">
(2) 通过this.$router.push({})
<!-- query传参 -->
<router-link :to="
{
path:'/user/'+userId,
query:{name:"beixi",age:18}
}">
</router-link>
<!-- params传参 -->
<router-link :to="
{
name:'/user/'+userId,
params:{name:"beixi",age:18}
}">
</router-link>
// query传参
this.$router.push({
path:'/user/'+userId,
query:{
name:"beixi",age:18}
})
// params传参
this.$router.push({
name:'/user/'+userId,
params:{
name:"beixi",age:18}
})
-
params传参和query传参的区别
params类型:只能通过name引入
query类型:既能通过name也能通过path引入
- query传递
显示参数(url那里)
,params传递不显示参数
,参数在请求体内,params相对于query来说较安全一点。
- query传值页面刷新数据还在,而params传值页面数据消失
- 路由参数的获取不同,query:
this.$route.query.name
,params:this.$route.params.name
-
$router
和$route
的区别$router
:路由操作对象,只写对象,一般用于路由跳转。$route
:路由信息对象,只读对象,一般用于获取路由参数。
十二. computed和methods的区别
- computed:
- 计算属性,里面的
函数会进行缓存,必须要有返回值
,只有在依赖项发生变化的时候会执行
,否则调用的时候直接拿到return 的值,不会再次执行。 - 里面的函数是以
对象的属性形式存在
,在调用的时候直接使用
就行,例如:{ {msg}}。 - 支持get/set操作。一般情况下我们只使用getter属性
- 计算属性,里面的
computed:{
result(){
return this.name
}
}
// 等价于
computed:{
result(){
get:function(){
return this.name
},
set:function(newValue){
this.firstName = newValue
}
}
}
- methods:
- 方法,里面的函数
不会进行缓存
,每次调用都会被执行,不一定要有返回值。
- 里面的方法必须
使用函数调用的形式
,例如:{ {msg()}}
- 方法,里面的函数
- 使用场景:
当某些依赖项不经常发生变化,仅仅需要取到一些返回值的时候,建议使用computed。
当涉及到复杂运算的时候,建设使用computed
十三. computed和watch的区别
- 相同点:都是监听
data里面的变量或者是由父组件传过来的props
,然后执行响应的逻辑。 - 不同点:
- computed里面的方法
必须要有返回值
,watch里面的方法不一定有返回值。
- computed只有当依赖项发生变化的时候才会执行函数,
具有缓存
,函数以对象的形式存在,使用的时候直接调用
;watch在每次监听的值发生变化的时候都会执行回调,不具有缓存。
computed默认第一次加载的时候就开始监听;watch默认第一次加载不做监听,
如果需要第一次加载做监听,添加immediate属性,设置为true(immediate:true)。computed不支持异步,
有异步操作时无法监听数据变化;watch支持异步操作。
- computed里面的属性是
一对一或者多对一(一个属性受到一个或者多个属性影响)
,watch里面的属性是一对多(一个属性影响到多个属性)。
- computed里面的方法
- 使用场景
- 当涉及到复杂的计算场景,
一个属性受到多个属性影响
,如:购物车商品结算,使用computed。 当一个属性影响到多个属性的时候
,如:搜索条件变化,使用watch。
- 当涉及到复杂的计算场景,
十四. watch
- 说明:检测vue实例中数据的变化,执行相应的逻辑,默认第一次加载的时候不监听。
- 键:就是需要监测的那个东西。
- 值:
- 可以是一个执行函数,有两个参数,一个是变化前的一个是变化后的,如果只有一个默认是变化后的。
- 可以是一个函数名,得用单引号包裹。
- 可以是一个对象,这个对象有三个选项:
(1)handler :一个回调函数,监听到变化时应该执行的函数。
(2)deep :boolean值,是否深度监听。(一般监听时是不能监听到对象属性值的变化的,数组的变化可以听到),true表示开启深度监听。
(3)immediate :boolean值,是否立即执行handler函数,true表示立即监听。
// 1.使用watch监听一个值
watch:{
username:function(oldValue,newValue){
console.log(newValue)
}
// 可以简写为
username(oldValue,newValue){
console.log(newValue)
}
}
// 2.使用watch监听一个数组
watch:{
arr(oldValue,newValue){
console.log(newValue)
}
}
// 3.使用watch监听一个对象
watch:{
obj:{
handle(oldValue,newValue){
console.log(newValue)
},
deep: true, // 开启深度监听
immediate: true // 立即监听
}
}
十五. vue双向数据绑定的原理
- vue
双向数据绑定
是通过数据劫持并结合发布-订阅者模式来实现的。
也就是说数据和视图是同步的,数据发生变化,视图跟着变化;视图发生变化,数据也会随之变化。 - 数据劫持:
vue会遍历data中的所有property(属性),并使用Object.defineProperty(obj,属性值)把这些property全部转化为getter/setter。
- 发布-订阅者模式:
每个组件实例都对应一个 watcher 实例,
它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖
。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。
const obj={
name:'xiaoming'
}
Object.defineProperty(obj,'xiaoming',{
get(){
return obj.name
},
set(vakue){
obj.name = value
}
})
4. 检测变化时候的注意事项:由于数组和对象
是引用类型,vue不能检测数组和对象的变化,但是可以通过一些特殊的方法来解决这个问题。
(1)对于对象
:vue无法检测property的添加或移除,由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的。例如:
var vm = new Vue({
data:{
a:1 // 响应式
}
})
vm.b = 2 // 非响应式
// 解决办法:
// 添加属性:通过Vue.set(obj,'属性名','属性值')或者this.$set()的方式
Vue.set(vm.someObject, 'b', 2)
this.$set(vm.someObject, 'b', 2)
// 删除属性:
Vue.delete(vm.someObject, 'b')
this.$delete(vm.someObject, 'b')
// 有时你可能需要为已有对象赋值多个新 property,比如使用 Object.assign() 或 _.extend()。但是,这样添加到对象上的新 property 不会触发更新。在这种情况下,你应该用原对象与要混合进去的对象的 property 一起创建一个新的对象。
Object.assign(this.someObject, {
a: 1, b: 2 }) // 不会触发更新
this.someObject = Object.assign({
}, this.someObject, {
a: 1, b: 2 })
(2)对于数组
:当利用索引直接设置数组中的某一项,或者直接修改数组的长度是不生效。例如:
var vm = new Vue({
data: {
items: ['a', 'b', 'c']
}
})
vm.items[1] = 'x' // 不是响应性的
vm.items.length = 2 // 不是响应性的
// 修改数组中的某一项:
// 解决办法:
// 方法一:this.$set(array,index,newValue)
this.$set(this.items,0,'x')
// 方法二:array.splice(index,1,newValue)
this.items.splice(0,1,'x')
// 修改数组长度:this.items.splice(newLength)
his.items.splice(2)
关于vue的响应式原理,更多内容请参考官网
十六. Object.defineProperty()和Proxy代理的区别
- object.defineProperty()和Proxy()分别是vue2和vue3中用于响应式原理中的方法。
- 语法不同:
// Object.defineProperty()
Object.defineProperty(obj,属性值,{
get(){
return obj.xxx
},
set(value){
obj.xxx = value
}
})
// Proxy():用于创建一个对象的代理,从而实现基本操作的拦截和自定义。
```javascript
const newProxy = new Proxy(target, handler)
// target:要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
// handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。
- Object.defineProperty监听的是
对象的每个属性,需要遍历对象的每个属性,而Proxy监听的是对象本身。
(如果出现嵌套的对象,Proxy 也是要递归进行代理的,但可以做惰性代理,即用到嵌套对象时再创建对应的 Proxy) - Object.defineProperty
无法监听对象的新增属性,无法监听直接通过索引值访问数组
,Proxy可以。
十七. vue中父子组件的生命周期
- 渲染过程:
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子Mounted->父Mounted - 子组件更新:
父beforeUpdate->子beforeUpdate->子Update->父Update - 父组件更新:
父beforeUpdate->父Update - 销毁过程:
父beforeDestory->子beforeDestory->子Destory->父Destory
十八. diff算法
指的是对新旧虚拟节点进行对比,返回一个patch对象,用来存储两个节点不同的地方,最后通过记录的信息,来更新dom节点。
十九.loader和plugin的区别
- loader使得webpack具备了解析非js文件
- plugin用来给webpack扩展功能的,可以加载许多插件
二十. vue中如何进行依赖收集
- vue在初始化实例挂载之后会进行编译,
通过renderFunction生成虚拟DOM。
通过get()和set()函数对数据劫持更新实现响应式原理。
- 每个组件实例都对应一个
watcher 实例
,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖
。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。
- 具体的渲染流程就是
遍历虚拟DOM节点
,将前后不同之处记录在patch对象中
。最后根据patch对象中记录的消息去更新DOM树。
(实时更新
,每次有变化都会记录更新一遍DOM树,react是先把所有的不同收集起来,在统一更改
。)
二十一. 如何理解vue的模板编译原理
- 核心:
将templete语法转成render函数。
- 步骤:
- 将
templete语法转成ast语法树
-parserHTML 对静态语法做标记
(某些节点不改变)重新生成代码
-codeGen,使用with语法包裹字符串
- 将
二十二. mixin的使用场景和原理
- 作用:
抽离公共的业务逻辑
,当组件初始化的时候会调用mergeOptions方法进行合并。如果混入数据和组件本身数据有冲突,采用本身数据为准
。(vue3)中使用自定义hook。 - 缺点:
容易造成命名冲突、数据冲突,数据来源不清晰。
二十三. 为什么在组件中data必须是一个函数
- 在new Vue()
单例模式下
,不存在合并操作,data里面的数据都是独立
的不存在数据污染
情况,因此data用一个对象
就行。 - 在组件中涉及到
组件之间的相互引用
,容易造成数据污染
的可能,data必须是一个函数,这样在数据合并的时候会产生两个空间,能够避免数据污染。
二十四. 请说明nextTick的原理。
- 是一个
微任务。
- nextTick中的回调是在
下次DOM更新循环结束之后执行的延迟回调
。 - 可用于获取更新后的DOM
- vue中的数据更新是异步的,
使用nextTick可以保证用户定义的逻辑在更新之后执行。
二十五. 说说 Vue 中 CSS scoped 的原理
- 作用:
使当前CSS样式只作用于当前组件,避免组件之间样式的相互干扰。
- 原理:通过PostCSS转译实现,
PostCSS给一个组件中的所有dom添加了一个独一无二的动态属性。
给CSS选择器额外添加一个对应的属性选择器来选择该组件中dom。
二十六. 刷新浏览器后,Vuex的数据是否存在?如何解决?
不存在了
,store里面的数据是保存在运行内存中的,当页面刷新的时候,页面会重新加载vue实例,store里面的数据就会被重新赋值
- 解决办法:
- vuex-along 一个插件本质上也是将数据存放到localstorage或者sessionStorage中
在数据存储在localStorage或者sessionStorage中。
二十七. hash路由和history路由
- hash路由
通过hashchange事件监听url的变化
,利用location.hash=xxx 来读取属性,本质上就是改变window.location的href属性。- hash路由在地址栏URL上有#显得不美观,而history路由没有会好看一点。
hash路由的兼容性好一点。
在浏览器进行回车操作,hash路由会加载到地址栏对应的页面,不会进行http请求,改变hash不会重新加载页面,所以这也是单页面应用的必备。
//监听hash路由
window.addEventListener('hashchange', function(){
// 监听hash变化,点击浏览器的前进后退会触发
})
- history路由
history路由是通过调用history.pushState()或者history.replaceState()方法来进行路由跳转。
- history路由在地址栏URL上没有#,显得整洁美观
history路由的兼容性较hash路由差一点
在浏览器进行回车操作,history路由会进行http请求,因此需要后端数据,否则会报错。
- history路由运用了浏览器的历史记录栈,它们提供了对历史记录进行修改的功能,不过在进行修改时,虽然改变了当前的URL,但是浏览器不会马上向后端发送请求。
二十八. vue的页面渲染流程,挂载的过程中发生了什么
- 在实例化的时候调用init()初始化
- 调用$mount()挂载,实际上就是调用mountComponent,除了调用一些生命周期钩子函数,最主要是updateComponent。
- updateComponent会被传入渲染watcher,每当数据变化的时候触发watcher更新就会执行该函数。
- vm._render 创建并返回VNode,vm._update 接受VNode将其转化为真实节点。
二十九. 怎么自定义指令
- 全局注册,第一个参数:指令名称;第二个参数:指令函数
Vue.directive('focus',{
inserted:function(el){
el.focus()
}
})
- 局部注册:在当前组件中注册
directives:{
focus:{
inserted:function(el){
el.focus()
}
}
}
- 使用
<div v-focus />
三十. vuex的辅助函数
state辅助函数为mapState(),actions辅助函数为mapActions(),mutations辅助函数为mapMutations(),getters辅助函数为mapGetters()。
三十一.谈谈你对你vue中keep-alive的理解
- 被keep-alive包裹的组件
具有缓存效果
- 是vue的内置组件,
能在组件切换过程中将状态保留在内存中,防止重复渲染DOM。
- 两个参数:
- include=‘组件名’:指定只有这些组件会被缓存
- exclude=‘组件名’:指定这些组件不会被缓存,剩余组件都会被缓存
三十二. 虚拟DOM一定更快吗
- 什么叫DOM:Document Object Model,
文档对象模型
,是一套用于管理html文档的规则,一个DOM树就是一个html完整文档。 - 作用:
将web网页和编程语言连接起来,通过操作脚本以改变文档的结构、样式和内容。
- 什么叫虚拟DOM
:js对象模拟出来的DOM节点,是一种编程概念,一种模式,他会和真实DOM同步
虚拟DOM可以提升性能但不一定更快。
(点击按钮,加一。直接操作真实DOM更快)真实DOM在直接操作的时候会引起页面的回流或重绘,会造成性能下降,虚拟DOM通过diff算法更新不同虚拟DOM节点,进而达到重新渲染的目的
- 优点:
减少dom操作,提高了性能,更新快
无需手动操作DOM只需要关心view-modal里面的逻辑
- 更方便的跨平台操作,例如服务器渲染
- 缺点:
无法进行极致优化
,对于性能要求极高的应用中,虚拟DOM无法针对性的极致优化首次渲染大量dom时,由于多了一层虚拟dom的计算,速度比innerHtml插入慢
三十三. 谈谈对key的理解
- 概念:
给每一个节点一个唯一的标识
,可以根据key值找到对应的节点 - 作用
尽可能的复用DOM更高效的更新虚拟DOM
- 如果不使用key:vue会尽可能地修改/复用相同类型元素的算法
- 如果使用key:会基于key的变化重新排列元素顺序,并且会移除key不存在的元素
- 注意:如果在dom非常简单的前提下,使用key也不一定就能提高diff算法
- 为什么一般不使用index作为key:如果做了一些删除和添加操作,会使得部分key对应不上,造成一些没有必要的dom更新,渲染效率低下。
三十四. 首屏加载慢的原因
- 什么是首屏加载慢:从用户在浏览器输入网址,到首屏内容渲染完成所用的时间过长。
- 加载慢的原因:
- 网络延迟
- 资源文件体积过大
- 资源是否重复发送请求
- 加载脚本的时候,渲染内容堵塞
- 解决方案
- 减小入口文件体积
- 静态资源本地缓存
- 资源按需加载、异步加载
- 图片资源的压缩懒加载
- CSS代码放在头部,JS代码放在尾部
- 开启GZip压缩
三十五. 为什么vue中的v-if和v-for不建议一起用?
在vue2中v-for的优先级高于v-if。
一起使用的时候每次判断之前都要遍历一边,浪费性能。- 在vue3中v-for的优先级低于v-if。一起使用的时候会直接报错。
- 解决办法:
分开写,将v-if写到外层,
可以用一个templete标签包裹起来。
三十六. v-show 和 v-if 的区别
- 相同点:都是控制元素在页面是否显示
- 不同点:
- v-show本质上是通过
控制css的display:none属性
。即使隐藏了,dom结构依然还在,是一种视觉上的隐藏
,v-if是真正意义上的删除和添加。
- v-show不会触发生命周期钩子,v-if会触发。
- v-show对性能消耗较少,v-if对性能消耗较大,因此
当需要频繁的切换的时候建议使用v-show。
- v-show本质上是通过
三十七. 观察者模
- 在vue初始化的时候通过observe劫持监听属性。
- 定义更新函数和watcher。
- 一般会有多个watcher所以会用一个管家dep来管理多个watcher。
- 当data里面的数据发生变化的时候会找到对应的管家dep,来通知所有的watcher调用更新函数,更新dom。
三十八. 发布订阅者模式
- 触发事件的对象称为发布者
- 接收通知的对象称为订阅者
- 举个例子:js中的事件绑定就是发布订阅模式
三十九. 父组件怎么监听子组件的生命周期
$emit
:在子组件的生命周期钩子里面用$emit触发事件,父组件里用v-on接收
// 子组件
mounted(){
// 监听子组件的挂载完成
this.$emit('mounted')
}
// 父组件监听后执行一些相应的操作
<Child @mounted='doSomething' />
methods:{
doSomething(){
}
}
- @hook
// 子组件
mounted(){
console.log('111')
}
// 父组件监听后执行一些相应的操作
<Child @hook:mounted='doSomething'>
methods:{
doSomething(){
}
}
四十. vue组件通信
父子组件通信
:父组件通过v-bind绑定传递的值,子组件中通过props接收。子组件中通过$emit('函数名',传递的值),父组件中通过v-on接收函数并定义。
// 父组件,以下都是部分代码
<div class="box" @click="clickAction">
我是父组件:{
{
msg}}
</div>
<Child :count="count" @message="message"/>
data(){
return {
msg:'你好,我的世界',
count: 0
}
},
methods:{
// 向子组件传值
clickAction(){
++this.count
},
// 接收子组件传过来的值
message(data){
this.msg = data
}
}
// 子组件
<button @click="clickChild">我是子组件:{
{
count}}</button>
// 通过props接收父组件传过来的值
props: {
count: {
type:Number,
default:0
}
},
data(){
return {
msg: ''
}
},
methods:{
// 通过$emit向父组件传值
clickChild(){
this.$emit('message','这是我在测试父子组件通信')
}
}
2. 祖孙组件之间的通信
:在传递层使用 v-bind="$attrs" v-on="$listeners"
配合 inheritAttrs: false一起使用。
- v-bind="$attrs:
包含了父组件中不被props所识别的特性绑定,如果当前组件没有声明props则会继承父组件的所有绑定的值。
- v-on=“$listeners”:
包含了父作用域中的(不含.native修饰器的)v-on事件监听器
。 - inheritAttrs: false
可以关闭自动挂载到组件根元素上的没有在 props 声明的属性
(参见)
// 子组件
<GrandChildA v-bind="$attrs" v-on="$listeners"/>
// 孙子组件
<div @click="clickGrandSonA">这是孙子组件A:{
{
$attrs }}</div>
methods:{
clickGrandSonA(){
this.$emit("message",'这是测试祖孙组件之间的通信');
}
}
3. 利用provide和inject实现祖孙组件通信
provide:Object | () => Object,是一个对象或者是一个返回对象的函数。
inject:Array | { [key: string]: string | Symbol | Object },是一个字符串数组,或者是一个对象,对象的key是本地的绑定名。
具体用法:
// 父组件:
<div class="box" @click="clickAction">
我是父组件:{
{
msg}} -- {
{
coo }} -- {
{
obj.name }}
</div>
data(){
msg:'你好,我的世界',
count: 0,
coo: "CSS",
obj:{
name:'wangjiajia'
}
},
provide(){
return {
msg:this.msg, // 方式一:直接传值
obj:this.obj, // 方式二:传入一个可以监听的对象
computedCount:()=>this.count, // 方式三:通过computed来计算注入的值
app:this // 方式四:提供祖先组件的实例,缺点是造成传递过去很多没用的东西,冗余。
}
},
methods:{
clickAction(){
++this.count,
this.msg = '世界真美好',
this.obj.name = 'wangtongtong',
this.coo = '通过app引用'
},
}
// 子组件
<GrandChildB /> // 只是一个过渡的作用
// 孙子组件
<div @click="clickGrandSonB">
我是孙子组件B:{
{
msg }} -- {
{
count }} -- {
{
this.app.coo }} --{
{
obj.name }}
</div>
computed:{
count(){
return this.computedCount()
}
},
inject:['msg','computedCount','app','obj'],
methods:{
clickGrandSonB(){
this.msg = '测试祖孙组件传值'
this.obj.name = 'caocongyouren'
this.app.coo = '你礼貌吗!'
}
}
说明:这种方法下要注意,直接传值是不可以实现响应式的,实现数据响应式具体方法有:
* 传入监听对象
* 利用computed计算属性注入
* 传入整个组件实例(缺点是可能传入一些没用的量,造成冗余)
利用中央事件总线实现任意组件之间的通信
- 全局注册:Vue.prototype.$bus = new Bus() / new Vue()
- 发射事件:this. b u s . bus. bus.emit(‘foo’)
- 接收事件:this. b u s . bus. bus.on(‘foo’)
// 父组件
mounted(){
this.$bus.$on('mooValue',this.receive)
},
methods:{
clickAction(){
this.$bus.$emit('dooValue',"Vue")
}
receive(data){
this.moo = data
}
}
// 孙子组件
mounted(){
this.$bus.$on('dooValue',this.receive)
},
methods:{
clickGrandSonB(){
this.$bus.$emit('mooValue','React')
},
receive(data){
this.doo = data
}
}
四十一. vuex
- vuex是什么,为什么要用vuex
vuex是vue官方提供的一种集中式管理vue多组件共享状态数据的插件。
vuex可以帮助我们管理共享状态,实现跨组件通信。
- 适用于较大类型的vue项目
- 安装方式
- 在脚手架创建项目的时候可以选择安装
- 使用npm或者yarn安装
- npm install vuex@next --save
- yarn add vuex@next --save
- 配置
- 创建一个store文件夹,在文件夹下面创建一个index.js文件
- 在main.js文件中引入并挂载到vue实例上。
import Vue from 'vue
import App from './App.vue'
import store from './store'
new Vue({
store,
render:h=>h(App)
}).$mount('#app')
-
核心概念
- State
- Getters
- Mutations
- Actions
- Modules
-
图解关系
-
State:提供唯一的公共数据源,所有共享的数据统一放到store的state进行储存,相似与data。
- 方法一:在标签中直接使用:
<p>{ {$store.state.name}}<p>
- 方法二:通过this调用:
this.$store.state.name
- 方法三:通过辅助函数:
mapState(['name','age','count'])写在当前组件的computed中
。
- 方法一:在标签中直接使用:
-
Mutations:改变数据的唯一方法,接受state作为第一个参数,第二个参数可选
- 方法一:
通过commit触发:this.$store.commit('mutations里面定义的函数名字','可选参数')
- 方法二:
通过辅助函数:mapMutations(['函数名']) => this.函数名('可选参数') => 写在methods中。
- 方法一:
-
Actions:异步操作存放处,本质上还是会触发mutations来改变数据。
- 方法一:
使用dispatch触发:this.$store.dispatch("mutations里面定义的函数名字",'可选参数')
- 方法二:
通过辅助函数:mapActions['函数名'] => this.函数名('可选参数') => 写在methods中。
- 方法一:
-
Getters:对数据进行加工处理形成新的数据,具有缓存
- 方法一:
在当前组件的computed中定义一个函数,在这个函数中直接使用this.$store.getters.'getters中定义的函数名'
- 方法二:
使用辅助函数:mapGetters(['函数名']),写在当前组件的computed中。
注意:辅助函数都可以配置别名
- 方法一:
-
Modules:当遇到大型项目,数据量比较复杂。将每一个状态写在一个文件中,该文件具有自己的state,mutations等,然后在modules中导入,
-
看一个具体的例子熟悉vuex的使用
store.js文件
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
// state: 共享数据,相当于data
state: {
name: 'wangjiajia',
age: 25,
count: 0,
todos: [
{
id: 1, done: true },
{
id: 2, done: false },
{
id: 3, done: false },
]
},
// getters:对数据进行加工处理形成新的数据,具有缓存
getters: {
// 过滤出done为true的项
doneTodos: state => {
return state.todos.filter(item => item.done)
},
// 传入一个id值,从当前数组中找到第一个id等于传入值的项
getTodoById: (state) => (id) => {
return state.todos.find(item => item.id === id)
}
},
// mutations:改变state中数据的唯一办法
mutations: {
// 传入一个值,使得count每次加上这个值
addCount(state,num){
state.count += num
},
// 使得count每次自减1
reduceCount(state){
--state.count
},
['ADDCOUNT'](state){
// 可以配置方法别名
++state.count
}
},
// actions: 异步操作,本质上也是通过触发mutations从而改变state里面的数据
actions: {
asyncAdd(context,data){
setTimeout(()=>{
// 可以调用mutation里面的方法
context.commit('addCount',data)
// 可以调用另一个异步
// context.dispatch('asyncReduce')
},1000)
},
asyncReduce(context){
setTimeout(()=>{
// 可以调用mutation里面的方法
context.commit('reduceCount')
},1000)
},
// modules:复杂业务时候,将每一个状态单独写在一个文件中,最后在这里导入
modules:{
app,
cell
}
},
})
vue文件:
<button @click="clickAdd">点我增加</button>
<div class="direct">直接引用:{
{
$store.state.name }} -- {
{
$store.state.age }} -- {
{
$store.state.count }} -- {
{
doneTodos }} -- {
{
getTodoById(2) }}
</div>
<button @click="clickReduce">点我减少</button>
<div class="direct">通过辅助函数:{
{
name }} -- {
{
age }} -- {
{
count }} -- {
{
doneTodosCount }} -- {
{
doneTodosId }}</div>
<button @click="clickAsync">点我触发一个异步任务</button>
import {
mapActions, mapMutations, mapState,mapGetters } from 'vuex';
export default ({
name:'ChildStore',
computed:{
...mapState(['name','age','count']), // mapState({name:state=>state.name,age:'age'})
// 使用辅助函数
...mapGetters(['doneTodos','getTodoById']), // 可以配置别名:mapGetters({doneCount:'doneTodos'})
doneTodosCount () {
return this.$store.getters.doneTodos
},
doneTodosId () {
return this.$store.getters.getTodoById(2)
}
},
methods:{
// 辅助函数
...mapMutations(['addCount','reduceCount','ADDCOUNT']), // 可以配置别名:mapMutations({increment:'addCount'})
...mapActions(['asyncAdd','asyncReduce']), // 可以配置别名:mapActions({increment:'asyncAdd'})
clickAdd(){
this.$store.commit('addCount',5) // 每次增加5,通过commit
// this.addCount(5) // 通过辅助函数
},
clickReduce(){
// this.$store.commit('reduceCount') // 每次减少1,通过commit
this.reduceCount() // 通过辅助函数
// this.$store.commit('ADDCOUNT')
this.ADDCOUNT() // 方法别名
},
//异步操作
clickAsync(){
// this.$store.dispatch('asyncAdd',5) // 通过dispatch触发一个增加的异步操作
// this.$store.dispatch('asyncReduce') // 通过dispatch触发一个减少的异步操作
this.asyncAdd(5) // 通过辅助函数
// this.asyncReduce() // 通过辅助函数
}
}
})
更过关于vuex的相关内容请大家参考官网