Vue 最独特的特性之一,是其非侵入性的响应式系统。数据模型仅仅是普通的 JavaScript 对象。对这些对象进行操作时,却能影响对应视图,核心实现就是「响应式系统」。尽管我们在使用 Vue.js 进行开发时不会直接修改「响应式系统」,但是理解它的实现有助于避开一些常见的「坑」或通过原理解决问题。
先了解getter和setter:
getter:
let obj1 = {
firstName:"高",
lastName:"圆圆",
姓名(){
return this.firstName + this.lastName;
}}
console.log("打印一:" + obj1.姓名());
// "打印一: 高圆圆
加了get
let obj2 = {
firstName:"高",
lastName:"圆圆",
get 姓名(){
return this.firstName + this.lastName;
}}
console.log("打印二:" + obj2.姓名); // 这里没有加括号了
//打印二:高圆圆
总结:getter 就是不加括号的函数
setter:
let obj3 = {
firstName:"高",
lastName:"圆圆",
get 姓名(){
return this.firstName + this.lastName;
}
set 姓名(xxx){
this.firstName = xxx[0]
this.lastName = xxx.substring(1)
}}
obj3.姓名 = "刘诗诗"
console.log(`打印三: 姓${obj3.firstName},名${obj3.lastName}`)
//打印三:姓:刘,名:诗诗
总结: setter就是接受一个参数的函数 用 = xxx 这样的形式触发,来改变里面的值
set和get后便是:
Object.defineProperty():
我们使用set和get的时候都是在声明的时候就直接使用的,但是我想声明之后再使用怎么办,这就用到Object.defineProperty()了
Object.defineProperty()第一个参数是告诉它,定义再那个对象上,第二个参数就是要定义个什么东西,然后就能写了
var _xxx = 0
Object.defineProperty(obj3,"xxx",{
get(){
return _xxx
},
set(value){
_xxx = value
}
})
这里需要注意定义的xxx是不存在的所以你用get传是不行的,而且会死循环(因为你在调用xxx的时候就会触发,无限循环),所以需要重新定义一个值用来传。
但是这里有一个问题,我们可以通过直接改变全局变量_xxx来改变里面的值,这不是我们想看到的,那么如何解决这个问题呢?
那就需要一个代理了:
let data1 = proxy({data:{n:0}}) // 括号里面是匿名对象,所以根本无法访问
function proxy({data} /*这里用了解构赋值*/){
conost obj = {}
Object.defineProperty(obj,"n",{
get(){
return data.n
}
set(value){
if(value<0)return
data.n = value
}
})
return obj //这就是代理
}
上面用到解构赋值,本来应该这样写 :
function proxy(options){
const {data} = options //这里也是解构,太基础,不懂建议先去了解
因为上面options只用了一次所以,直接参数哪里直接表示就行了:function proxy({data}){}
}
这样你修改我obj的n我就会去设置data的n,obj就是一个代理,这样你就不能擅自修改我的对象数据,你只能通过obj代理来修改?
错! 我还是能修改
如果你那个匿名函数我用对象给你呢
let myData5 = {n:0}
let data1 = proxy({data:myData5})
那我只要修改myData5 还是能改你的数据,这时候就不能只靠代理了,还需要监听,这样就算你改了也没用
let myData5 = {n:0}
let data = proxy({data:myData5})
function proxy({data} /*这里用了解构赋值*/){
let value = data.n
dalate data.n //这一句可以不写因为下面已经覆盖了原先的n
Object.defineProperty(data,"n",{ //这里挂载的对象就是参数data
get(){
return value
}
set(newvalue){
if(newvalue<0)return
value = newvalue
}
})
//上面这几句,就会监听data
conost obj = {}
Object.defineProperty(obj,"n",{
get(){
return data.n
}
set(value){
if(value<0)return
data.n = value
}
})
return obj //这就是代理
}
上面的方法就是把原先的数据复制一遍,再删掉用新数据来填补,这样你就不能通过对象修改我的数据,即:
let data = proxy({data:myData5})
const vm = new Vue({data:{n:0}})
new Vue()
vue 对 data 做了什么?
当你创建一个实例时
const vm = new Vue({data: myData})
- vue 会让 vm 成为 myData 的代理。
- vue 会对 myData 的所有属性进行监控。
目的
这样做的目的是什么?
- 你可以使用 this 来访问到 vm。 this.n === myData.n。
- 之所以要监控,就是防止 vue 无法得知 myData 的属性变化。
- vue 得知属性变化才可以使用 render(data) 来更新 UI 和渲染页面。
数据响应式
- 响应式即对外界的变化做出的反应的一种形式。
- const vm = new Vue({data:{n: 0}})
- 当修改 vm.n 或 data.n 时,render(data...) 中的 n 就会做出响应的响应。
- 这个联动的过程就是 vue 的 数据响应式。
- vue 目前通过 Object.defineProperty 来实现数据响应式。
如何追踪变化
当你把一个普通的js对象传入Vue实例作为data选项,Vue将遍历此对象的所有的property,并且使用Object.defineProperty把这些property全部转为getter/setter。
Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。
这些getter/setter对用户来说是不可见的,但是内部他们让Vue能够追踪依赖,在property被访问和修改时通知变更。
每个组件实例都对应一个watcher实例,他会在组件渲染过程中把接触的数据property记录为依赖。之后当依赖项的setter触发时,会通知watcher,从而使它关联的组件重新渲染。
在 data 中添加属性
Vue 虽然对 data 中的属性(或对象中的属性)进行监听和代理,但是它却没有办法进行事先的监听和代理。
如果你在初始化 data 之后再添加属性,该如何实现?
一般对象
对于一般的对象来说,可以在 data 中预先把所有可能用到的属性全部写出来,这样并不需要新增属性,只需要改它。
也可以通过其他方法来添加属性。
在了解以上原理后,我们来了解 Vue 提供的一个 API:
Vue.set(object, key, value)
或
this.$set(object, key, value)
作用
- 在 data 中添加新的属性。
- 自动创建为它创建代理和监听(如果没有创建过)。
示例
const Vue = window.Vue
new Vue({
data: {
obj: {
a: 0
}
},
template: `
<div>
{{obj.b}}
<button @click='one'>One</button>
</div>
`,
methods: {
one() {
Vue.set(this.obj, 'b', 1)
// 或 this.$set(this.obj, 'b', 1)
}
}
}).$mount('#app')
数组
因为数组本身的特殊性:数组的长度无法预测(比如所有用户的用户名,存在数组中),你无法使用 undefined 去为每一项占位,或一直使用 Vue.set( ) 方法。
- 你可以使用 push 方法 this.array.push('value'),但其实数组已经被 Vue 包装了新的 push 方法。
- 原理就是声明一个新的类来继承数组。
- 各种在 Vue 实例 中使用的特例方法, 详见数组变异方法,一共有7个API。
- 这些方法 (API) 会自动处理对数组该项的监听和代理,并触发视图更新。
什么才是最重要的 研究思路和方法永远比知识更重要。 你可以在不懂、不看源代码的前提下,去使用和理解一个库或项目或其他。 前提是你已经知道了它的思路和方法。