前言(使用vue2时出现的问题以及背后的思考)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>vue2</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
</head>
<body>
<div id="app">
<button @click="update">点击更改</button>
<button @click="addProperty">添加属性</button>
<button @click="deleteProperty">删除属性</button>
{{list}}<br>
{{obj}}
</div>
<script>
const app = new Vue({
el:'#app',
data(){
return {
list:[
{
name:'张三',
age:20
},
{
name:'李四',
age:30
},
],
obj:{
name:'张三'
}
}
},
methods:{
update(){
//修改其属性,具备响应式
// this.list[0].name = '王五'
//通过this.$set实现其响应式
this.$set(this.list,0,'张三')
/*直接修改无效,Object.defineProperty无法追踪到数组的索引设置(例如使用
arr[index] = value)这种改变 */
//this.list[0] = '王五'
},
addProperty(){
//不具有响应式,Object.defineProperty无法监控对象属性值的新增或删除
this.obj.text = '哈哈'
//具有响应式
this.obj = { age:20 }
// 若想保留响应式需使用this.$set(this.obj,'name','王五')
console.log(this.obj)
},
deleteProperty(){
//delete this.obj.name响应式不生效,
// delete this.obj.name
//需要通过this.$delete(重写方法)删除
this.$delete(this.obj,'name')
}
}
})
/*
出现以上问题的原因是Vue2响应式原理是基于Object.defineProperty实现的,Object.defineProperty无法
检测到对象属性的动态添加。于是作者尤雨溪重写了数组的方法,并加入了Vue.set来解决这个问题。
示例:
data(){
return {
obj:{
name:'李四'
}
}
},
methods:{
update(){
this.$set(this.obj,'age', 20) //向obj添加新属性age
}
}
*/
</script>
</body>
</html>
1.Object.defineProperty (ES5)
api局限性:向对象追加属性不会被劫持,向数组追加同样无法劫持,想要保留响应式需要重写方法。
1.1 对象响应式处理
/**
* 对象响应式
* @param {*} target
* @param {*} key
* @param {*} value
*/
export const defineObjectReactive = (target,key,value)=>{
Object.defineProperty(target,key,{
enumerable:true,
configurable: true,
get(){
return value
},
set(newVal){
if(value === newVal) return
value = newVal
console.log('触发了',newVal)
}
})
}
/**
* 对象每一项转为响应式
* @param {*} target
*/
export const transformToReactive = (target)=>{
Object.keys(target).forEach(item=>{
defineObjectReactive(target,item,target[item])
})
}
1.2 重写数组方法
//重写数组方法
export const defineArrayReactive = (arr) => {
const origin = Array.prototype;
const originMethod = Object.create(origin);
const methods = [
"push",
"pop",
"shift",
"unshift",
"splice",
"sort",
"reverse",
];
methods.forEach((method) => {
const result = (originMethod[method] = function (...args) {
origin[method].call(this, ...args);
console.log(args);
//可以在此处处理数组修改
return result;
});
});
if(Array.isArray(arr)){
//重设此数组的prototype
arr.__proto__ = originMethod
}
};
2.proxy (ES6)
相较于Object.defineProperty的优势可以监听对象和数组追加内容。
const reactiveProxy = (params)=>{
const data = new Proxy(params,{
get(target,value){
return target[value]
},
set(target,props,value){
// //给源对象赋值
target[props] = value
console.log(target)
}
})
return data
}
const data2 = reactiveProxy(obj)
console.log(data2)