1. vue双向数据绑定原理
vue是采用数据劫持结合发布-订阅
的方式,通过object.defineProperty()
来劫持各个属性的setter
,getter
,在数据发生变动时发布消息给订阅者,触发相应的监听回调来渲染视图
第一步:
需要observer
的数据对象进行递归遍历
,包括子属性对象的属性,都加上setter
,getter
,这样的话,给这个对象的某个值赋值
,就会触发setter
,那么就能监听到数据变化
简单来说就是实现一个数据监听器Observer,它能够对数据对象的所以属性进行监听,如果有变动就可以拿到最新的值并通知订阅者
第二步:
compile
解析模块指令,讲模块中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
简单来说就是实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,替换数据,绑定更新函数
第三步:
Watcher
订阅者时Observer
和Compile
之间的通讯桥梁,主要做的事情是:
1.在自身实例化时往属性订阅器(dep
)里面添加自己
2.自身必须有一个update()
方法
3.待属性变动dep.notice()
通知时,能调用自身的update()
方法,并触发Compile
中的绑定回调
简单来说就是实现一个Watcher,作为Observer和Compile的桥梁,能够收到每个属性变动的通知,绑定相应的回调函数,从而更新视图
第四步:
MVVM
作为数据绑定的入口,整合Observer,Compile和Watcher三者
,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭建起Observer和Compile之间的通信桥梁,达到数据变化->视图更新; 视图交互变化->数据model变更的双向绑定效果
MVVM就是实现Vue双向数据绑定的根本
,MVVM本质上是MVC的改进版,即模型-视图-视图模型
,【模型】
指的是后端传递的数据。【视图】
指的是所看到的页面。【视图模型】
MVVM的核心,他是连接view和model的桥梁
。它有两个方向: 一个是将【模型】转换成【视图】,即后端传递的数据转换成所看到的页面
:实现方式是:数据绑定。第二个是【视图】转化成【模型】,即将所看到的页面转换成后端的数据
:实现方式是:DOM事件监听。这两个方向都实现的话,
我们称之为数据的双向绑定
2.vue虚拟dom,diff算法
虚拟DOM就是为了解决游览器性能问题
而被设计出来的。
就是用对象来模拟dom元素和嵌套关系,指的就是用js对象的形式
,来模拟页面上的DOM嵌套关系
目的:实现页面元素的高效更新,避免大量无谓的计算(若一次操作中有10次更新DOM的动作,虚拟DOM不会立即去操作DOM,而是将10次更新的diff内容保存到本地一个JS对象中,最终将这个JS对象一个姓attch到DOM树上,再进行后续操作
)
作用:用来比较两次DOM结构
web界面由DOM树(树的意思就是数据结构
)来搭建,当其中一部分发生变化时,其实就时对应某个DOM节点发生了变化
diff算法是作为DOM的加速器
,改进优化了页面渲染的基础和性能保障
用js对象表示dom树结构,用这个树建立一个真正的dom树,插入到文档中,状态更新
,重新构造一棵新树,两树比较差异,把差异应用到真的dom树上,更新视图
3. 组件 通讯
1.父传子
父组件是通过props属性给子组件通信的
父组件代码:
<template>
<div>
<h2>{{msg}}</h2>
<Son :faMsg="msg"></Son>
</div>
</template>
<script>
import Son from "./Son";
export default {
name:"HelloWorld",
data(){
return{
msg:"父组件"
}
},
components:{Son}
}
</script>
子组件代码:
<template>
<div>
<h2>{{sonMsg}}</h2>
<p>子组件接收到的内容是:{{faMsg}}</p>
</div>
</template>
<script>
export default {
name:"Son",
data(){
return{
sonMsg:"子组件"
}
},
props:['faMsg'], //接收faMsg值
//还可以这样,上面的不是很严谨
props:{
faMsg:{
type:String,
default:"123"
}
}
}
</script>
在父组件中给子组件标签上绑定一个属性, 属性上挂载需要传递的值
在子组件通过props:[“自定义属性名”]来接收数据
子传父:
(1) 在父组件中给子组件标签绑定一个自定义事件,给这个事件挂载需要调用的方法
(2) 在子组件的方法通过this.$emit(‘自定义事件名’)来调用这个方法
父组件代码:
<template>
<div>
<h2>{{ msg }}</h2>
<p>父组件接手到的内容:{{ username }}</p>
<son psMsg="我是你爸爸" @transfer="getUser"></son>
<!-- 监听子组件触发的transfer事件,然后调用getUser方法 -->
</div>
</template>
<script>
import son from './Son'
export default {
name: 'HelloWorld',
data () {
return {
msg: '父组件',
username:'',
}
},
components:{son},
methods:{
getUser(msg){
this.username= msg
}
}
}
</script>
子组件代码:
<template>
<div class="son">
<p>{{ sonMsg }}</p>
<p>子组件接收到内容:{{ psMsg }}</p>
<!--<input type="text" v-model="user" @change="setUser">-->
<button @click="setUser">传值</button>
</div>
</template>
<script>
export default {
name: "son",
data(){
return {
sonMsg:'子组件',
user:'子传父的内容'
}
},
props:['psMsg'],
methods:{
setUser:function(){
this.$emit('transfer',this.user)//触发transfer方法,this.user 为向父组件传递的数据
}
}
}
</script>
兄弟通信: 比如说vuex 就可以说是一个兄弟通信
4. vuex
首先我们要知道Vuex是一个专为vue.js应用程序开发的状态管理模式,它由五部分组成:
分别是:state,actions,mutations,getters,modules
vuex的五部分分别是:
- state:存储状态
- actions:可以包含异步操作
- getters:类似vue组件中的计算属性,会对state数据进行计算(会被缓存)
- mutations:唯一可以对state数据修改的场所
- modules:模块化管理仓库,每个模块都拥有自己的 state,actions,mutations,getters,modules
Vuex一般用来存储在vue.js中大量出现的数据,比如说token,我们就可以把token存放在Vuex中,当然购物车也是要用到Vuex的.
数据持久化
问题:存储在vuex中的状态,刷新页面会丢失。
为了解决刷新页面数据丢失,才有了数据持久化。
最简单的做法就是利用插件 vuex-persistedState
1.安装
cnpm i vuex-persistedState -S
备注:
-S 是 --save的简写,意思是:把插件安装到dependencies(生成环境依赖)中
-D 是 --save-dev的简写,意思是:把插件安装到devDependencies(开发环境依赖)中
2.使用
import createPersistedState from 'vuex-persistedstate'
const store = new Vuex.Store({
state,
mutations,
actions,
getters,
plugins: [createPersistedState({
storage: sessionStorage,
key: "token"
})]//会自动保存创建的状态。刷新还在
})
参数:
storage:存储方式。(sessionStorage,localStarage) key:定义本地存储中的key
模块化管理数据(modules)
1.什么时候需要用到模块
管理vuex数据
项目庞大,数据信息量特别大的时候,我们可以考虑分模块形式管理数据,比如user模块管理用户信息数据,cart模块管理购物车数据,shop模块管理商品信息数据
5. vue-router(路由原理,路由守卫,路由传参)
路由实现原理:
概念:
通过改变URL,在不重新请求页面的情况下,更新页面视图。
实现方式:
vue-router通过hash
与History interface
两种方式实现前端路由,更新视图但不重新请求页面”是前端路由原理的核心之一,目前在浏览器环境中这一功能的实现主要有两种方式:
1.hash
---- 利用URL中的hash(’#’);
2.利用History interface
在HTML5中的新增方法
6. vue生命周期
简单粗暴:
答: 总共分为8个阶段。创建前/后 , 挂载前/后 , 更新前/后 , 销毁前/后
。
创建前/后: beforeCreated
创建前,vue实例的挂载元素$el
和数据对象data都是undefined,还未初始化。在created阶段/创建后,vue实例的数据对象data有了,$el还没有。
挂载前/后: beforeMount
挂载前,vue实例的el
和data
都初始化了,但是还没有挂载html 到页面上。在Mounted
阶段/挂载后,模板中的 HTML 渲染到 HTML 页面中,此时一般可以做一些 ajax 操作,mounted 只会执行一次。
更新前/后: 当data变化时,会触发beforeUpdate
和updated
方法
销毁前/后: 在destroy
阶段/销毁前,对data的改变不会再触发周期函数,说明此时vue实列已经解除了事件监听和dom绑定,但是dom结构依然存在。destroyed
阶段,组件销毁
7. 自定义指令,自定义过滤器
Vue 自定义指令如何使用
- 自定义指令分为全局自定义指令和局部自定义指令:
- 全局自定义指令:
使用 Vue.directive()来全局注册指令 - 局部自定义指令:
也可以注册局部指令,组件或 Vue 构造函数中接受一个 directives 的选项
钩子函数
自定义指令定义函数提供了几个钩子函数: - bind
只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个在绑定时执行一次的初始化动作 - inserted
被绑定元素插入父节点时调用( 父节点存在即可调用, 不必存在于
document 中) - update
所在组件的 VNode 更新时调用,但是可能发生在其孩子的 VNode 更新之前。指令的值可能发生了改变也可能没有。但是可以通过比较更新前后的值来忽略不必要的模板更新 - componentUpdated
所在组件的 VNode 及其孩子的 VNode 全部更新时调用 - unbind
只调用一次,指令与元素解绑时调用
参数
钩子函数中有两个参数:
- el : 指令所绑定的元素,可以用来直接操作 DOM
- binding : 一个对象,包含以下属性:
name: 指令名,不包括 v- 前缀
value: 指令的绑定值
oldValue: 指令绑定的前一个值(update 和 componentUpdated 钩子中可用。无论值是否改变都可用)
expression: 绑定值的字符串形式
arg: 传给指令的参数
使用场景: 当我们需要对普通的DOM元素进行底层操作时就可以用到自定义指令
全局自定义指令:
//main.js中
// 全局自定义指令
Vue.directive(
'upper-text',{bind: (el, binding)=> {
console.log(el, binding)
el.innerHTML = binding.value.toUpperCase() //转换大写
}
})
局部自定义指令
//组件中
<template>
<div class="wrapper">
<br />
<span>原数据</span>
<p>{{message}}</p>
<br />
<span>全局</span>
<p v-upper-text="message" v-color="'blue'"></p>
<br />
<span>局部</span>
<p v-lower-text="message"></p>
</div>
</template>
<script>
export default {
data() {
return {
message: 'You can start the Internet!'
}
},
computed: {},
//局部自定义指令
directives: {
'lower-text'(el, binding) {
//el:指令属性所在的标签对象
//binding:包含指令相关信息数据的对象
console.log(el, binding)
el.textContent = binding.value.toLowerCase() //转换小写
},
color:{//改变颜色
bind(el,binding){
el.style.color = binding.value
}
}
},
}
</script>
8. 自定义组件
简介
组件系统是Vue.js其中一个重要的概念,它提供了一种抽象,让我们可以使用独立可复用的小组件来构建大型应用,任意类型的应用界面都可以抽象为一个组件树
功能
组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以表现为用 is 特性进行了扩展的原生 HTML 元素。所有的 Vue 组件同时也都是 Vue 的实例,所以可接受相同的选项对象 (除了一些根级特有的选项) 并提供相同的生命周期钩子
组件注册
组件名
组件名应该始终是多个单词的,根组件 App 除外
这样做可以避免跟现有的以及未来的 HTML 元素相冲突,因为所有的 HTML 元素名称都是单个单词的
单文件组件的文件名应该要么始终是单词大写开头 (PascalCase),要么始终是横线连接 (kebab-case)
混用文件命名方式有的时候会导致大小写不敏感的文件系统的问题,这也是横线连接命名同样完全可取的原因
- 使用 kebab-case
当使用 kebab-case (短横线分隔命名) 定义一个组件时,你也必须在引用这个自定义元素时使用 kebab-case,例如
Vue.component('my-component-name', { /* ... */ })
- 使用 PascalCase
当使用 PascalCase (驼峰式命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。也就是说
<my-component-name>
和<MyComponentName>
都是可接受的。注意,尽管如此,直接在 DOM
(即非字符串的模板) 中使用时只有 kebab-case 是有效的
Vue.component('MyComponentName', { /* ... */ })
- 全局注册
以上方法都属于全局注册, 也就是说它们在注册之后可以用在任何新创建的 Vue 根实例 (new Vue) 的模板中, 比如 - HTML
<div id="app">
<component-a></component-a>
<component-b></component-b>
<component-c></component-c>
</div>
- JS
Vue.component('component-a', { /* ... */ })
Vue.component('component-b', { /* ... */ })
Vue.component('component-c', { /* ... */ })
new Vue({ el: '#app' })
在所有子组件中也是如此,也就是说这三个组件在各自内部也都可以相互使用
9. 常用的指令,修饰符
在vue中提供了一些对于页面 + 数据的更为方便的输出,这些操作就叫做指令,指令中封装了一些DOM行为, 结合属性作为一个暗号, 暗号有对应的值,根据不同的值,框架会进行相关DOM操作的绑定
vue中的指令有很多,我们平时做项目常用的有:
v-if
:是动态的9向DOM树种添加或者删除元素;
v-else
:是搭配v-if使用的,它必须紧跟在v-if或者v-else-if后面,否则不起作用
v-show
: 是通过标签的CSS样式display的值是不是none,来控制显示隐藏
10. vue2.0和vue3.0的区别
11. keep-alive
概念:
是Vue的内置组件,当它包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。
keep-alive是一个抽象组件:它自身不会渲染一个DOM元素,也不会出现的父组件链中。
作用:
在组件切换的过程中,把切换出去的组件保留在内存中,防止重复渲染DOM,减少加载事件以及性能消耗,提高用户体验性
原理:
在 created
钩子函数调用时将需要缓存的 VNode 节点保存在 this.cache
中/在 render
(页面渲染) 时,如果VNode
的 name
符合缓存条件(可以用 include
以及 exclude
控制),则会从 this.cache
中取出之前缓存的 VNode
实例进行渲染
VNode: 虚拟DOM,其实就是一个JS对象
参数:
- include - 字符串或者正则表达式。只有匹配的组件会被缓存。
- exclude - 字符串或正则表达式。然后匹配的组件都不会缓存。
- max - 数字。最多可以缓存多少组件实例
对生命周期函数变化:
被包含在keep-alive中的组件,会多出来两个生命周期的钩子函数,activated
和 deactiveated
1.activated
在keep-alive
组件激活时调用
2.deactivated
在keep-alive
组件离开时调用
//正常的生命周期:beforeRouterEnter --> created -->mounted --> updated --> destroyed
//使用keepAlive后生命周期:
//首次进入缓存页面 :beforeRouterEnter --> created -->mounted --> updated --> destroyed
//再次进入缓存页面 :beforeRouteEnter --> activated --> deactivated
//注:
//1、这里的activated非常有用,因为页面被缓存时,created,mounted等生命周期均失效,你若想进行一些操作,那么可以在activated内完成(下面会举个栗子:列表页回到上次浏览位置)
//2、activated keep-alive组件激活时调用,该钩子在服务器端渲染期间不被调用。
//3、deactivated keep-alive组件停用时调用,该钩子在服务端渲染期间不被调用
可以结合Router中的meta,来缓存部分页面
12. 多环境变量
13. 对axios封装,(url统一管理,axios请求拦截,响应拦截,函数封装)
14. element-ui,vant-ui 按需引入
15. sass配置
16. rem , vw/vh 适配
rem适配:
使用vant里面的游览器适配,
Vant 中的样式默认使用 px 作为单位,如果需要使用 rem 单位,推荐使用以下两个工具:
postcss-pxtorem 是一款 postcss 插件,用于将单位转化为 rem
lib-flexible用于设置 rem 基准值
安装 lib-flexible
和 postcss-plugin-px2rem
cnpm i lib-flexible postcss-plugin-px2rem --save-dev
配置rem: mian.js 导入
import 'lib-flexible/flexible'; //rem 就可以生效了
配置px–> rem:创建vue.config.js
//可以在此配置的基础上根据项目需求进行修改。
module.exports = {
css: {
loaderOptions: {
postcss: {
plugins: [
require('postcss-plugin-px2rem')({
rootValue:75, //换算基数, 默认100 ,这样的话把根标签的字体规定为1rem为50px,这样就可以从设计稿上量出多少个px直接在代码中写多上px了。
// unitPrecision: 5, //允许REM单位增长到的十进制数字。
//propWhiteList: [], //默认值是一个空数组,这意味着禁用白名单并启用所有属性。
// propBlackList: [], //黑名单
// exclude: /(page_pc)/i, //默认false,可以(reg)利用正则表达式排除某些文件夹的方法,例如/(node_module)/ 。如果想把前端UI框架内的px也转换成rem,请把此属性设为默认值
exclude: /node_modules/i,
// selectorBlackList: ['van-'], //要忽略并保留为px的选择器,我们一般不转换vantui中的大小
// ignoreIdentifier: false, //(boolean/string)忽略单个属性的方法,启用ignoreidentifier后,replace将自动设置为true。
// replace: true, // (布尔值)替换包含REM的规则,而不是添加回退。
mediaQuery: false, //(布尔值)允许在媒体查询中转换px。
minPixelValue: 3 //设置要替换的最小像素值(3px会被转rem)。 默认 0
}),
]
}
}
},
}