Vue 基础
概念
- vue.js 是一套构建用户界面的渐进式框架。
- vue.js 是自底向上增量开发设计的
- vue.js 是一个构建数据驱动的 web 界面的库。
- vue 核心库只关注视图层。
- vue.js 是一个MVVM库。
问题
-
什么是渐进式框架?
- 框架是分层的,每层都有不同功能,可以根据需求选择,一步一步,不用一下把所有东西都用上
-
什么是自底向上增量开发?
- 由基础程序逐渐扩大规模,升级功能
-
什么是数据驱动?
- 所谓数据驱动,是指视图是由数据驱动生成的,我们对视图的修改,不会直接操作 DOM,而是通过修改数据。
响应式数据原理
vue2.x 响应原理
参考:Vue 源码阅读-依赖收集原理 - SHERlocked93
-
通过数据劫持配合发布-订阅的设计模式利用 Object.defineProperty将 data 中的 property 转为 getter/setter(通过 observer)。这些 getter 和 setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。【每个组件实例都对应一个watcher实例,它会在组件渲染过程中把接触过的 property 记录为依赖(通过 dep 进行依赖收集),之后依赖项的 setter 触发时会通知 watcher 重新计算,从而使它关联的组件重新渲染】。
-
数据劫持:指在访问或者修改对象的某个属性时,被一段代码拦截这个行为,进行额外的操作或者修改返回结果。
-
发布-订阅模式:定义了一种一对多的依赖关系,发生改变时,所有依赖它的对象都会得到通知。
-
在 google 的时候看到的一个新问题,观察者和发布-订阅模式有什么区别?
- 发布-订阅与观察者模式,在广义上时是一个意思。
- 观察者模式,观察者需要直接订阅目标,在目标发出事件后,直接接收事件做出响应。
- 发布订阅模式,中间存在一个信息中介,发布不知道有没有人订阅及订阅的人是谁,解耦能力更强
-
Vue3.x 响应式数据原理
- 使用Proxy取代 Object.defineProperty()实现数据劫持
vue3出来还没了解,这段后续待更
对比
- object.defineProperty 只能劫持对象的属性,需要遍历对象的每个属性。
- object.defineProperty 新增需要重新遍历对象,再对新增对象进行劫持
- proxy 直接遍历对象。
- proxy 可以直接监听数组的变化.
- proxy 性能高,支持 13 种拦截方式
特性
- 轻量级的框架
- 双向数据绑定
- 指令
- 插件化
MVVM 框架
Model view viewmodel
- model 代表数据模型,可以在 model 中定义数据修改和操作的业务逻辑。
- view 代表 UI 组件,它负责将数据模型转换成 UI 展现出来。
- viewmodel 同步 view 和 model。
- view 和 model 通过 viewmodel 进行交互,交互是双向的。
- view 的变化会同步到 model 中,model 数据的变化会立即反映到 view 上。
使用 MVVM 模式的优点
- 低耦合: 视图可以独立于模型变化和修改,一个 viewmodel 可以绑定在不同的视图上,当视图变化的时候模型可以不变,模型变化时视图也可以不变。
- 可重用性:可以把一些视图的逻辑放在 viewmodel 中,让很多视图复用这段逻辑。
- 独立开发
- 可测试性
MVC
-
MVC(model-view-controller)
-
MVC 强调职责分离
-
所有通信都是单向的
- view 将指令传送到 controller
- controller 完成业务逻辑要求 model 改变状态
- model 将新的数据传递给 view
-
思考二者区别
- MVVM 通过数据来显示视图,解决了 MVC 中大量 DOM 操作的问题,
Vue 实例
创建 vue 实例
当一个 vue 实例被创建时,它的 data 对象的所有 property 加入到 vue 响应式系统中,当 property 值发生变化时,视图也会产生响应
var vm = new Vue({
<!--选项-->
})
举例
<div id="app">
{{message}}
</div>
<script>
var vm = new Vue({
el: "#app",
data: {
message: 'hello vue!'
}
})
</script>
el
提供一个页面上已存在的 DOM 元素作为 Vue 实例的挂载目标。
- 实例挂载后,可以通过 vm.$el 访问
data
vue 将 data 的 property 转换为 getter/setter,让 data 的 property 能够响应数据变化。
-
对于已经存在的实例,不允许动态添加根级别的响应式 property
- 无法检测property的添加或移除。
- 因为 Vue 在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式。
- 可以通过 Vue.set()方法添加响应式 property。
-
可以通过 vm.$data 访问原始数据对象
-
通过 vm.a 可以获取 data 的 a 属性
-
vm.a = vm.$data.a
问题
<div id="app">
<span class="span-a">
{{obj.a}}
</span>
<span class="span-b">
{{obj.b}}
</span>
</div>
- 问最终 span-a 和 span-b 分别展示什么字符串?
var app = new Vue({
el: "#app",
data: {
obj: {
a: 'a'
}
}
})
app.obj.a = 'a2'
答:span-a 中显示 a2,span-b 不显示(因为 obj.b 在实例被创建时不存在)
只有当实例被创建时就已经存在于 data 中的 property 才是响应式的
- 问最终 span-a 和 span-b 分别展示什么字符串?
var app = new Vue({
el: "#app",
data: {
obj: {
a: 'a'
}
}
})
app.obj.b = 'b'
答:span-a 中显示 a,span-b 中不显示(因为 obj.b 在实例被创建时不存在,对 b 的改动不会触发任何视图的更新)
解决方法:
- 方法一 通过 Vue.set(object, propertyName, value)方法向嵌套对象添加响应式 property
Vue.set(app.obj, 'b', 'b')
- 方法二 通过 vm.$set()添加响应式 property
var app = new Vue({
el: "#app",
data: {
obj: {
a: 'a'
}
},
methods: {
add: function(){
this.$set(this.obj,'b','b')
}
}
})
app.obj.a = 'a2'
- 方法三 利用 Object.assign({},this.obj)
var app = new Vue({
el: "#app",
data: {
obj: {
a: 'a'
}
},
methods: {
add: function(){
this.obj = Object.assign({},this.obj,{b:'b'})
}
}
})
- 问最终 span-a 和 span-b 分别展示什么字符串?
var app = new Vue({
el: "#app",
data: {
obj: {
a: 'a'
}
}
})
app.obj.a = 'a2'
app.obj.b = 'b'
答案:span-a 中显示 a2,span-b 中显示 b
Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更
-
优点
- 提高性能,在本轮数据更新后再去异步更新视图,若不采用异步更新,则每次更新都会重新渲染
- 页面显示结果是什么
<div id="app">
{{foo}}
<button @click="foo = 'baz'">点击一下</button>
</div>
<script>
var obj = {
foo: 'bar'
}
Object.freeze(obj)
var app = new Vue({
el: '#app',
data: obj
})
</script>
答:bar
Object.freeze()会阻止修改现有的 property,响应系统无法在追踪变化
- Vue 不能检测数组和对象变化(对象见第 2 题)
- 利用索引直接设置一个数组项
- 通过 Vue.set(vm.items, indexOfItem, newValue)方法
- vm.$set()
- 通过 vm.splice()方法增删改
- 修改数组的长度
- 通过 vm.splice()方法_
<div id="app">
{{items}}
</div>
<script>
var app = new Vue({
el: '#app',
data: {
items: ['a','b','c']
}
})
// 下面两种方法不响应
// app.items[1] = 'z';
// app.items.length = 2;
Vue.set(app.items,'1','z') //[ "a", "z", "c" ]
app.items.splice(3,1,'y') //[ "a", "z", "c", "y" ]
methods: {
add: function(){
this.$set(this.items,'4','d')
}
}
app.items.splice(2)//[ "a", "z" ]修改数组长度,只能往短了修改
</script>
- 删除属性值
- 方法一 this.$delete(object,property)
var app = new Vue({
el: "#app",
data: {
obj: {
a: 'a',
b: 'b'
}
},
methods: {
del: function(){
this.$delete(this.obj,'a')
}
}
})
- 方法二 Vue.delete()
Vue.delete(app.obj,'a')
- 方法三 Vue.set(object,property)
Vue.set(app.obj,'b')
常用的部分全局 API
Vue.set(target,propertyName/index,value)
- target {Object | Array}
- propertyName/index {String | number}
//对象
Vue.set(app.obj,b,'b')
//数组
Vue.set(app.items,'1'.'c')
Vue.delete(target,propertyName/index)
Vue.deletet(app.obj,'b')
Vue.delete(app.items,'1')
Vue.filter(id,[definition])
- id {string}
- [definition] {Function}
生命周期
从对象的创建、使用到消亡就是对象的生命周期
vue 生命周期的阶段
图来自 vue.js 官方文档
-
总体
- 初始化
- 运行中
- 销毁
-
详细
- 创建
- 初始化
- 编译
- 挂载 DOM
- 渲染-更新-渲染
- 销毁
生命周期钩子函数
生命周期钩子的 this 上下文指向调用它的 Vue 实例。
- beforeCreate
在实例初始化之后,data 和 watcher 等事件配置前调用。
- created
在实例创建完成后被立即调用,当前阶段完成了数据观测 data,property,methods,computed,watcher,但是更改数据不会触发 update 函数。挂载还没开始,$el 不可用。
- beforeMount
发生在挂载之前,相关的 render 函数首次被调用,将 template 编译到 render 函数中。虚拟 DOM 创建完毕。
- mounted
挂载到实例后调用,可以进行 DOM 操作
- beforeUpdate
数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前
- updated
由于数据更改导致虚拟 DOM 重新渲染和打补丁,避免在此期间更改数据可能会导致无限渲染
- beforeDestroy
实例销毁之前调用,实例仍然可能被调用
- destroyed
实例销毁后调用,对应 Vue 实例所有指令都被解绑,监听器被移除。(清除计时器,解除事件绑定等)
keep-alive 拥有独立钩子函数
- activated
被 keep-alive 缓存的组件激活时调用。
- deactivated
被 keep-alive 缓存的组件停用时调用。
模板语法
插值
文本
“Mustache”语法
<div>
{{message}}
</div>
js 表达式
只支持单个表达式,不支持流程控制和多行表达式
{{number + 1}}
{{ ok? 'yes' : 'no'}}
{{ message.split('').reverse().join('') }}
-----------------------------
<!--这是语句不是表达式-->
{{var a = 1}}
<!--流程控制语句不会生效-->
{{ if(){return message}}}
过滤器
{{data | filter1 | filter2}}
{{ data | formatDate(66,99)}}中的两个参数对应的是过滤器中的第二个和第三个参数
filters: {
formatDate: function(value,a,b){}
}
<div id="app">
{{date}}
{{date|formatDate}}
</div>
<script>
var plusDate = function (value) {
return value < 10 ? '0' + value : value;
}
var app = new Vue({
el: '#app',
data: {
date: new Date()
},
filters: {
formatDate: function (value) {
var date = new Date(value);
var week = plusDate(date.getDay())
var year = date.getFullYear()
var month = plusDate(date.getMonth() + 1)
var day = plusDate(date.getDate())
var hour = plusDate(date.getHours())
var minute = plusDate(date.getMinutes())
var second = plusDate(date.getSeconds())
return week + '--' + year + '--' + month + '--' + day + '--' + hour + '--' + minute + '--' + second;
}
},
mounted: function () {
var _this = this;
this.timer = setInterval(function () {
_this.date = new Date()
}, 1000)
},
// 在实例销毁前清除计时器,否则可能会造成业务逻辑混乱或者页面卡死
beforeDestroy: function () {
if (this.timer) {
clearInterval(this.timer)
}
}
})
</script>
指令
带有 v-前缀的特殊 attribute。
- 指令 attribute 的预期值是单个 JavaScript 的表达式(v-for 除外)
- 指令的职责:当表达式的值改变时,将响应地作用于 DOM,快速实现 DOM 操作,渲染
v-text
和{{Mustache}}作用一样
<span v-text='messsage'></span>
<!--作用相同-->
<span>
{{message}}
</span>
v-html
解析 html,按普通 Html 插入不会作为 Vue 模板进行编译
注意:在网站动态渲染任意 html,容易导致 XSS 攻击,永不用在用户提交的内容上。
<div id="app">
<span v-html='html'></span>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
html: '<spanstyle="color:red;"></pan>'
}
})
</script>
v-bind
动态绑定一个或多个 attribute,或一个组件 prop 到表达式,缩写:
<div id='app'>
<div v-bind:class="className">
</div>
<style>
.red {
background: red;
height: 10px;
}
</style>
<script>
var app = new Vue({
el: '#app',
data: {
className: 'red'
}
})
</script>
v-on
绑定监听事件,缩写@
- 在普通元素上只能监听原生 DOM 事件
- 在自定义元素组件上,可以监听子组件触发的自定义事件
<div id='app'>
<button v-on:click="count">{{countNum}}</button>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
countNum: 0
},
methods: {
count: function () {
this.countNum = this.countNum + 1
}
}
})
</script>
动态参数
举例:
<a :[attributeName]='url'></a>
<div class='app'>
<a :href='url'>1234</a>
</div>
<script>
new Value({
el: '.app',
data: {
url: 'https://baidu.com'
}
})
</script>