const vm = new vue(options) //核心代码
我们需要知道,
options里面是什么。
vm实例有什么属性,共有属性__proto__(vue构造函数的prototype)。
vue构造函数的prototype有什么属性,以及构造函数本身有什么属性。
写在前面
我们上篇博客介绍了options里面有什么,vue的核心在于main.js文件里面的new vue,它根据接受的选项对象,把视图和数据和事件结合一起渲染到屏幕上
options.data
import Vue from "vue";
Vue.config.productionTip = false;
let mydata = {
n : 0
}
new Vue({
data: mydata,
template: `
<div>
{{n}}
</div>
`,
methods: {
add(){
mydata.n +=1
}
}
}).$mount("#app");我们可以在new vue 外面对mydata进行更新,vue会自动把数据渲染到页面上。
在一般情况下,我们不会有mydata对象,直接在data上写出n属性即可,add函数里用this代替mydata,因为vue实例代理了data里的属性,变成了vm.n,this就是指vm,vm也代理了add函数,变成了vm.add
ES6 getter和setter
let obj1 = {
firstName: "gao",
name(){
return this.firstName
}
}
//我们 obj1.name() 调用它let obj1 = {
firstName: "gao",
get name(){
return this.firstName
}
}
//我们 obj1.name 调用它这样我们就去掉了方法调用的括号获取了obj1的firstName属性,这就是getter语法,用于获取一个值
let obj1 = {
firstName: "gao",
get name(){
return this.firstName
}
set name(xxx){
this.firstName = xxx[0]
}
}
//obj1.name = "高园园"这样我们就提供setter设置了obj1的firstName属性,setter语法没有把参数放在函数括号里,而是直接赋值
-
getter用于获取一个虚拟属性的值
-
setter用于赋与虚拟属性值
-
getter和setter创建了一个虚拟属性,虚拟属性不能在其他地方调用,并不存在一个name属性但确实对它可以读和写
通常这个虚拟属性和该对象的其他属性挂钩,你读的是虚拟属性实际上是其他属性的结合,你写的是虚拟属性实际是对其他属性的赋值
我们在对象定义的时候就把get xxx和 set xxx属性定义好了,我们定义完对象后没有办法再加get xxx和 set xxx属性
-
所以Object.defineProperty出现了,在一个对象再定义完之后,想添加新的虚拟属性
======================================================
Object.defineProperty(obj, prop, descriptor)参数
objpropdescriptor返回值
被传递给函数的对象。
除了上面那个代码,还需要创建一个变量_xxx来放虚拟属性,和上面分析的一样虚拟属性和该对象的其他属性挂钩,只不过挂钩的是外部的属性,因为虚拟属性并不存在
let _xxx = 0
Object.defineProperty(data, 'xxx', { get() {
return _xxx;
},
set(value) {
_xxx = value
}
});======================================================
Vue对data做了些啥
getter和setter属性的代理
let data1 = {}
Object.defineProperty(data1, 'n', {
value : 0
});
// 我们给data1 设置了n属性值为0
let data2 = {}
data2._n=0 //与虚拟属性挂钩
0bject.defineProperty(data2, 'n', {
get(){
return this. _n
},
set(value){
if(value < 0) return this._ n = value
})
//data2.n = -1 是无效的- get给虚拟属性
n赋值为this._n。直接data2.n就可以拿到 - set要求必须给虚拟属性一个值,如果这个值小于0就没啥,大于0就把这个值给
this._n。使用data2.n = value - 这个时候有问题,_n 可以直接赋值为负数导致我们的代码无效了,我们能不能不在对象上面暴露与虚拟属性挂钩的属性?
======================================================
let data3 = proxy({ data:{n:0} }) //匿名对象
function proxy({data}){ //析构
const obj = {}
0bject.defineProperty(obj, 'n', {
get(){
return data.n
},
set(value){
if(value < 0) return
data.n = value
})
return obj
} - obj就是代理,之后不管你对obj.n做什么,它都会对data.n做一遍,obj代理了匿名对象的data属性,析构赋值是要点
- data3就是obj,代理的n就是数据的n,
- data3.n 进行读写,实际上是对obj进行读写,只暴露了代理对象没有办法接触到真实对象(匿名对象里的data属性),obj的虚拟属性是n和匿名对象的data属性挂钩,obj又等于data3
- data3.n就是 obj的虚拟属性n
======================================================
如果我们一定要给匿名对象里的data属性名字呢?
let myData5 = {n:0}
let data5 = proxy({ data:myData5 } ) //匿名对象
//这个时候修改myData5.n = -1会导致代码失效function proxy2({data}){ let value = data.n Object.defineProperty(data, 'n', { //把mydata5的n变成虚拟的,和value挂钩 get(){ return value }, set(newValue){ if(newValue<0)return value = newValue } }) // data.n本来存在,接着继续get和set 属性n,虚拟属性就会覆盖原来的属性 const obj = {} Object.defineProperty(obj, 'n', { get(){ return data.n }, set(value){ if(value<0)return data.n = value } }) return obj }- 监听mydata(data)中的n:先把data的n偷来当原始值,再从data中删掉n,再给data一个虚拟的n。这个虚拟属性n的值就是之前偷来的n的值,如果给虚拟属性n一个大于0的值就把该值给之前偷来的n值。这样data中的n有任何改变我们都知道看得见。只有满足我们的条件我们才会允许data中的n改变。
- 所以虽然刚开始myData5(data)被声明在前面了,但是只要一对他执行
proxy2函数,就把它的n偷来了,还把他在原来的位置删掉了,我们给还了它一个虚假的n,虚假的n被我们用get set监控着。它要是在外面想改变自己的虚假的n,必须经过我们的同意满足我们的条件才行。 - obj(data5)就是代理 ,obj(data5)的n就是data(mydata)的n
======================================================
vm = new Vue({data: myData}) 和 let data5 = proxy({ data:myData5 } )
这两个看起来是不是很相似呢
首先,会马上对myData的所有属性进行监控,也就是对myData进行改造
-
防止myData的属性在外面变了,vm不知道,知道属性变了就可以调用render(data)呀!
UI = render(data) -
做法: 先把myData的属性n偷来变成我的value,在他的原位置删掉,我给你再补偿一个虚假的n,这个虚拟的n一直被我用get(value) set(value)控制,你在外面修改n,其实是我给你的虚拟的n,
-
你对data的任何修改,我Vue必须知道,不然我怎么对数据去渲染
其次,会让vm成为改造后的myData的代理(proxy)
- 所以vm就是data的代理,vm的n就是myData的(虚假的)n;对vm的n赋值,就相当于对myData的(虚假的)n赋值;完全不直接操作myData的你,而是操作vm的n
- 而且this就是vm。所以this.n === vm.n === myData.假n
- mydata传入之前和传入之后是不一样的
- 现在就有两种方式修改数据,操作myData和操作vm

代理逻辑:vm=函数调用,函数接受实际数据对象,创建一个空对象,在空对象创建虚拟属性,虚拟属性与实际数据挂钩,函数调用返回这个对象,vm就代理了data上的属性
vue对method也做了同样的处理,
代理的目的就是客户不可以随意的写属性,可以对客户写的属性做出操作,还有我们可以直接通过实例来修改数据,把真实数据上的属性移到实例上面
监听的目的就是把原来的数据篡改成虚拟属性,使得客户对数据的读写都是虚拟属性,都会被监听
监听和代理分别是对客户对原数据对象和对实例操作的限制
======================================================
Object.defineProperty
- 可以给对象添加属性value
- 可以给对象添加getter / setter
- getter / setter用于对属性的读写进行监控
啥是代理(设计模式)
- 对myData对象的属性读写,全权由另一个对象vm负责
- 那么vm就是myData的代理(类比房东租房)
- 比如myData.n不用,偏要用vm.n来操作myData.n
数据响应式
“响应式”,是指当数据改变后,Vue会通知到使用该数据的代码。例如,视图渲染中使用了数据,数据改变后,视图也会自动更新。
- options.data
- 会被Vue监听
- 会被Vue实例代理
- 每次对data的读写都会被Vue监控
- Vue会在data变化时更新UI(因为UI用到了data,data变UI也变)
定义
1. Vue的data是响应式
const vm = new Vue({data: {n: 0})
我如果修改vm.n(或者data.n),那么因为视图是对数据的渲染,所以视图中的n(UI中的n)也会改变做出反应也就是响应。这就是数据响应式。
2. 如何实现数据响应式
Vue 2通过Object.defineProperty(和get set)实现对数据的监听从而实现数据响应式
Vue的data的局限性
如果数据中有新增加的内容,那该怎么去监听?
监听数据的话,用Object.defineProperty(obj, 'n' ,.})的话,数据中必须要有真的n
new vue({
template:`{{n}}`
data:{}
})- Vue会给出一个警告:属性或者方法n没有定义在实例上,但是却被你在渲染时引用了
但是由于vue只会监测第一层,所以产生以下代码绕过了警告
- vue对于undefined或者null就不会显示
- data中定义了obj,obj中有个a,所以obj和obj.a都被Vue监听&代理了(vm.obj===myData.obj),对a变化了因为视图因为用了a所以也会改变
- 但是并没有声明obj中还有个b,所以没有对obj.b监听&代理。所以此时对obj.b赋值并且放在视图,视图根本没变化,Vue认为根本没这样的存在。

解决办法
- 方法一:那我把数据中每个key一次性都声明好,也就全部监听好了
obj{
a:0,
b:undefined
}- 方法二:使用
Vue.set或者this. $set
setB(){
Vue.set(this.obj,'b',1)
//或者,多了个$是为了防止和data中万一有个set咋办 ,$是实例方法
this.$set(this.obj,'b',1)
}作用
①新增key
②自动创建代理和监听(如果没有创建过)
③触发UI更新(但并不会立刻更新)
数组解决办法

- 方法一:使用
Vue.set或者this. $set,新增key,但是不会创建监听和代理,会更新UI
示例1:数组的长度可以一直增加,下标就是key 你看,你没有办法提前把数组的key都声明出来 Vue也不能检测对你新增了下标 难道每次改数组都要用Vue.set或者this. $set
- 方法二:使用新的数组的API,见官网中「变异方法」章节,这7个API都会被Vue篡改,这7个新API会调用数组原来的API并且自动增加监听和代理,并更新UI

如何篡改的:新增加一层原型,这层原型继承了数组原型,这层原型有七个API,这七个API都会调用之前的数组原型中对应的那个API,push还是按原来的push只是多执行几行代码,之后set该set的东西。
以push为例
class VueArray extends Array{ //新增加一层原型VueArray,继承以前的Array原型
push(...args){ //新原型的push方法
const oldLength = this.length // this就是当前数组
super.push(...args) //会调用上一层原型(原来数组)的push方法
console.log(' push ')
for(let i = oldLength; i<this.length; i++){ //把之前的下标和现在新的下标找出来,中间的不就是新增加的,那就set他们就行了。
Vue.set(this, i, this[i])
// key Vue
} } }
复制代码总结
- 数据的对象中想新增的key,Vue没有办法事先监听和代理,要使用set来新增key,创建监听和代理,更新UI。结论:数据的对象的话提前把属性都写出来,不要新增key
- 但数据的数组做不到不新增key,因为数组总是要加东西的,数组中新增的key,也可用set来新增key,但是不会创建监听和代理,会更新UI;还有一种方法就是尤玉溪篡改了7个API方便你对数组进行增删(改不用监听,查不用监听),这7个API会自动处理监听和代理,并更新UI。因此结论:数据的数组新增key最好通过7个API