components 三种写法
阴影中的写法和options的写法是一样的,只是阴影中的 data 必须用函数
message前面不加 冒号,双引号里面的 n 就是字符串;
加了冒号后,后面的双引号里面的内容就是 JS 代码
getter 和 setter
// setter 和 getter
let obj0 = {
姓:'高',
名:'圆圆',
age:18
};
//需求一,得到姓名
let obj1 = {
姓:'高',
名:'圆圆',
姓名(){
return this.姓 + this.名;
},
age:18
}
console.log('需求一' + obj1.姓名());
let obj2 = {
姓:'高',
名:'圆圆',
get 姓名(){
return this.姓 + this.名;
},
age:18
}
console.log('需求二' + obj2.姓名)
//总结:getter 就是这样用的,不加括号的函数,仅此而已。
//需求三:姓名可以被写
let obj3 = {
姓:'高',
名:'圆圆',
get 姓名(){
return this.姓 + this.名
},
set 姓名(xx){
this.姓 = xx[0]
this.名 = xx.slice(1)
},
age:18
}
obj3.姓名 = '高媛媛'
console.log(`需求三:姓 ${obj3.姓},名 ${obj3.名}`)
// 总结: setter 就是这样用的,用 =xx 触发 set 函数。
Object.defineProperty、代理、监听
let data0 = {
n:0
}
//需求一:用 Object.defineProperty 定义 n
let data1 = {}
Object.defineProperty(data1,'n',{
value:0
})
console.log(`需求一:${data1.n}`)
// 需求二:n 不能小于 0
// 即 data2.n = -1 应该无效,但 data.n = 1 有效
let data2 = {}
data2._n = 0 // _n 用来偷偷存储 n 的值
Object.defineProperty(data2,'n',{
get(){
return this._n
},
set(value){
if(value < 0) return
this._n = value
}
})
console.log(`需求二:${data2.n}`)
data2.n = -1
console.log(`需求二:${data2.n} 设置为 -1 失败`)
data2.n = 1
console.log(`需求二:${data2.n} 设置为 1 成功`)
// 那如果对方直接用 data2._n 呢?
// 需求三:使用代理
let data3 = proxy({
data:{
n:0
}
})
// proxy 圆括号里是匿名对象,无法访问
function proxy({data}){
// {data}解构赋值,相当于
// function proxy(options){const {data} = options},意思是从 options 中拿到 data ,但是 options 只用了一遍,所以就省略了。
const obj = {}
//这里的 'n' 写死了,理论上应该遍历 data 的所有的 Key ,这里做了简化
Object.defineProperty(obj,'n',{
get(){
return data.n
},
set(value){
if(value < 0) return
data.n = value
}
})
return obj //obj 就是代理
}
// data3 就是 obj
console.log(`需求三:${data3.n}`)
data3.n = -1
console.log(`需求三:${data3.n},设置为 -1 失败`)
data3.n = 1
console.log(`需求三:${data3.n},设置为 1 成功`)
// 需求四
let myData = {n:0}
let data4 = proxy({data:myData}) //proxy 圆括号里是匿名对象,无法访问
// data4 就是 obj
// 直接改 myData ,绕过代理
console.log(`需求四:${data4.n}`)
myData.n = -1
console.log(`需求四:${data4.n},设置为 -1 成功了`)
// 需求五,就算用户擅自修改 myData ,也要拦截它
let myData5 = {n:0}
let data5 = proxy({data:myData5})
function proxy2({data}){
let value = data.n
Object.defineProperty(data,'n',{
get(){
return value
},
set(value){
if(newValue < 0 ) return
value = newValue
}
})
//就加了上面几句,这几句话会监听 data
const obj = {}
Object.defineProperty(obj,'n',{
get(){
return data.n
},
set(value){
if(value < 0) return
data.n = value
}
})
return obj // obj 就是代理
}
console.log(`需求五:${data5.n}`)
myData5.n = -1
console.log(`需求五:${data5.n},设置为 -1 失败了`)
myData5.n = 1
console.log(`需求五:${data5.n},设置为 1 成功了`)
// let data5 = proxy({data:myData5})
// let vm = new Vue({data: myData}) 类似
代理
1、代理是一种设计模式,对 myData 对象的属性读写,全权由另一个对象 vm 负责;
2、那么 vm 就是 myData 的代理(类似于房东租房),比如 myData.n 不用,偏要用 vm.n 来操作 myData.n;
3、vm = new Vue({data:myData}):
- 会让 vm 成为 myData 的代理;
- 会对 myData 的所有属性进行监控;
- 为什么要监控:防止 myData 的属性变了,vm 不知道。一旦 vm 知道属性变了,就可以调用 render(data) 。
- UI = render(data)
vue2 的数据响应式
- const vm = new Vue({data:{n:0}});
- 如果用户修改 vm.n ,那么 UI 中的 n 就会响应;
- vue2 通过 Object.defineProperty 来实现数据响应式;
响应式网页
- 改变窗口大小,网页内容会做出响应,就是响应式网页;
- Smashing Magazine
Object.defineProperty 的问题
- Object.defineProperty(obj,'n',{...}),必须要有一个 'n',才能监听和代理 obj.n ;
- 如果开发者没有给 n ,Vue 会给出一个警告:
new Vue({
data: {},
template: `
<div>{{n}}</div>
`
}).$mount("#app");
- Vue 只会检查第一层属性
new Vue({
data: {
obj: {
a: 0 // obj.a 会被 Vue 监听 & 代理
}
},
template: `
<div>
{{obj.b}}
<button @click="setB">set b</button>
</div>
`,
methods: {
setB() {
this.obj.b = 1; //请问,页面中会显示 1 吗?不会,因为 Vue 没办法监听一开始不存在的属性 obj.b
}
}
}).$mount("#app");
- 解决方法 1、把所有的 key 都申明号,后面不在加属性就行;
2、使用 Vue.set 或者 this.$set
Vue.set 和 this.$set
- 作用 1、新增 Key;
2、自动创建代理和监听(如果没有创建过);
3、触发 UI 更新(但并不会立刻更新)
this.$set(this.object,'m',100)
- data 中有数组 1、问题:没法提前声明所有 key,数组的长度可以一直增加,下标就是 Key ,没办法提前把数组的 Key 都声明出来,Vue 也不能检测新增了下标,每次该数组都用 Vue.set 或者 this.$set 太麻烦;
2、解决方法:Vue 篡改了数组的 API ,变异方法
变异方法
- ES6 写法
class VueArray extends Array{
push(...args){
const oldLength = this.length //this 就是当前数组
super.push(...args)
console.log('你 push 了 ')
for(let i = oldLength; i< this.length; i++){
Vue.set(this,i,this[i])
//将每个新增的 key 都告诉 Vue
}
}
}
- ES5 写法-原型
const VueArrayPrototype = {
push: function(){
console.log('你 push 了')
return Array.prototype.push.apply(this,arguments)
}
}
VueArrayPrototype.__proto__ = Array.prototype
const array = Object.create(VueArrayPrototype)
array.push(1)
总结
对象中新增的 key
1、Vue 没有办法事先监听和代理;
2、要使用 set 来新增 key ,创建监听和代理,更新 UI ;
3、最好提前把属性都写出来,不要新增 key ;
4、但数组做不到‘不新增 key’.
数组中新增的 Key
1、可以用 set 来新增 key ,更新 UI ;
2、7个变异方法会自动处理监听和代理,并更新 UI ;
3、数组新增 key 最好通过7个API ;
小结
我们在声明Vue实例对象的时候,会初始化data,通过哈希表的形式接收数据,在初始化data中的数据后,vue会通过Object.defineProperty()方法来为key添加getter和setter方法实现data中数据的读写操作。以达到监听的目的,data中的每个数据都会进行遍历,然后使用这个方法来实现添加监听,在添加监听后,防止用户直接修改data中的内容,vue又让其实例对象成为data对象的代理,至此,vue实现了对data中数据的监听和代理操作。
但是如果data数据中有对象或者数组类型的数据时,我们无法实现它的增删操作,因为Object.defineProperty()方法只会对已经存在的key进行操作,所以vue为我们提供了vue.set()/this.$set()方法,它接受对象,要增加的key,以及value值,从而使我们可以增加key。但是这样又比较麻烦,因此我们在声明对象数据的时候,尽量提前声明所有key值,但是在对数组操作的时候,我们是无法直接这样添加key值的,所以vue又为我们提供了数组的变异方法,分别有push(),pop(),shift(),unshift(),splice(),sort(),reverse()七种变异方法,他们通过原型链/继承的方式,在实现基本数组增删操作的同时,又实现了vue对其数据的监听和代理,类似于内置了set方法。