前置知识:getter和setter
- 例一
let obj={
姓:'张',
名:'三',
姓名(){return this.姓 +this.名}
}
console.log('需求一:'+obj.姓名()) //调用姓名属性需要加括号执行这个函数
//结果:需求一:张三
- 例二 getter
let obj={
姓:'张',
名:'三',
get 姓名(){return this.姓 +this.名}
}
console.log('需求二:'+obj.姓名) //调用时不用加上括号,也能执行
结果 需求二:张三
- 例三 setter
let obj = {
姓: '张',
名: '三',
set 姓名(xxx) {
this.姓 = xxx[0],
this.名 = xxx.substring(1)
}
}
obj.姓名 = '李四' //接收新的参数,修改原有的属性
console.log(`需求三: 姓:${obj.姓} 名:${obj.名}`)
// 结果 需求三: 姓:李 名:四
Object.defineProperty实现若干需求
基本原理
- 场景:obj对象已经声明,在对象中添加虚拟属性
let obj = {
姓: '张',
名: '三'
}
var _xxx=0 // 由于xxx属性不存在,需要引入一个变量_xxx用于存放传入的值
Object.defineProperty(obj, 'xxx', { //obj对象里面添加一个虚拟属性xxx,里面包含了get和set
get() {
return _xxx
},
set(value) {
_xxx=value
}
})
需求一
空对象设置属性值
let data2={}
Object.defineProperty(data2,'n',{value:0}) //空对象里面新增一个新的虚拟属性n,值为0
console.log(`需求一:${data2.n}`) //结果为 0
需求二
data2.n的值不能小于0,data2.n=1有效,data2.n=-1无效
let data2 = {};
data2._n = 0; //——n用于存放n的值
Object.defineProperty(data2, "n", {
get() { //自动调用虚拟属性n
return this._n;
},
set(value) {
if (value < 0) {
console.log("传入的值不能小于0");
} else {
this._n = value;
}
}
});
data2.n = -1;
console.log(`${data2.n}`); //错误,提示传入的值错误,值依然是0
data2.n = 0;
console.log(`${data2.n}`); // 结果为0
data2.n = 1;
console.log(`${data2.n}`); //结果为1
需求三 使用代理
let data3 = proxy({ data: { n: 0 } }); //data3为proxy函数,接收一个对象
function proxy({ data }) { //解构赋值 {data}= { data: { n: 0 } }
data为{n:0}
const obj = {}; //使用代理
Object.defineProperty(obj, "n", {
get() {
return data.n;
},
set(value) {
if (value < 0) return;
data.n = value;
}
});
return obj;
}
data3.n=XX //修改的是obj的n
//使用对象obj作为代理,不能直接修改对象{data:{n:0}}
- 此方法的漏洞:
let myData={n:0}
let data=proxy({data:mydata})
//可以直接修改 myData.n来修改对象
需求四:拦截数据的篡改
let myData={n:0} //引入媒介,试图间接篡改对象
let data5=proxy({data:myData})
function proxy({data}){
let value=data.n //存n的初始值
delete n //此条代码可省略,下一行设置n虚拟属性,同名会删除n
//本可以通过myData对参数对象进行修改 ,但是当传入proxy函数中时,被修改的n直接被删除了
Object.defineProperty(data,'n',{
get(){
return value
},
set(newValue){
if(newValue<0)return
value=newValue
}
})
//以下代码为obj代理相同代码,略
//代码
略
略
略
.
//代码
}
data5.n=xxxx //此时的n不是myData的n,而是虚拟属性n
小结:以上代码let data5=proxy({data:myData})与 Vue框架中const vm=new Vue({data:myData})的思想类似,进而引出Vue实例对于数据的处理
Vue处理数据的机制
- Vue会让vm成为myData的代理
- vm会对myData的所有属性进行监控
- 当myData的属性发生变化,vm就会调用render(data),更新Ul
- 同理,Vue对methods,computed也会进行类似的处理
小结
什么是数据响应式
- 如果修改数据(如修改vm.n或者直接修改data.n),vue实例会监听这个变化,Ul中涉及数据的部分就会响应这种变化(通过Object.defineProperty())
特殊情况
情况一:data 中的对象key声明问题
问题一:data中未放入数据
- data中没有放入数据
- 视图标签中引用了n
- Vue警告,n未定义,在调用render的时候引用了n
//以下为Vue实例中的内容
data:{ }
template:`<div>{{n}}<div>`
问题二(消除Vue的警告)
- data中放入obj对象obj,包含a属性
- 此时不会有警告,因为只会监听data{}这一层
- 视图标签中引用obj.b
- 由于Vue只在data{}中设置值的时候监听,后续声明obj.b的时候无法监听,导致obj.b不会出现在视图中
//以下为Vue实例中的内容
data:{
obj:{a:0}
},
template:`<div>{{obj.b}}
<button @click='setB'>setb</button>
<div>`,
methods:{
setB(){
this.obj.b=1
}
}
解决方法
- 方法一:在放入data时就声明好属性
data:{obj:{a:0,b:1}) - 方法二:## Vue.set() / this.$set()
- 作用:新增key
- 自动创建代理和监听(如果没有创建过)
- 触发UI更新(但不会立刻更新即异步更新)
//以下为Vue实例中的内容
data:{
obj:{a:0}
},
template:`<div>{{obj.b}}
<button @click='setB'>setb</button>
<div>`,
methods:{
setB(){
Vue.set(this.obj,'b',1) //新增key b ,创建代理和监听
this.$set(this.obj,'b',1) //与上条方法同理
}
}
情况二:data中有数组-关于数组变异
-
数组中添加d,失败
-
使用set方法存在局限性,因为数组的长度是无法预测的
- 与传入对象同理,新的key:4 未提前声明,vue无法监听
new Vue({
data: {
array:['a','b','c']
},
template: `
<div>
{{array}}
<button @click='setD'>set D</button>
</div>
`,
methods: {
setD() {
this.array[3]='d'
}
}
}).$mount('#app')
- 使用Vue的push方法,与array.push不同
- Vue的push方法对每个新增的key进行监听
```js
new Vue({
data: {
array:['a','b','c']
},
template: `
<div>
{{array}}
<button @click='setD'>set D</button>
</div>
`,
methods: {
setD() {
this.array.push("d") //这里的push与原生js中的array.push不同
}
}
}).$mount('#app')
- Vue中的push大致原理
//说明:以下代码不代表Vue源码,仅作为原理的大致解释
class VueArray extends Array {
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
}
}
}
小结
- Vue实例中,数据(data:{})中的对象新增属性(key),Vue无法直接进行监听和代理。应提前将所需的属性(key)写入到data的对象中,或者通过Vue.set/this.$set方法创建监听和代理
- 如果data中的对象是数组,在后续的操作中新增key,也可用Vue.set方法,但是数组的长度不好把控,存在局限性。因此优先使用Vue提供的7个API对数组进行操作](url),这7个方法会自动处理监听和代理更新UI
- 详情参考文档:数组变更方法