Vue2的双向数据绑定技术点
1. 数组的reduce()方法
- 应用场景一:如何实现数组的累加?
const arr = [3,7,8,9,0,123];
// function 1
let total = 0;
arr.forEach((item)=>{
total += item;
})
// anymore ?
reduce语法: arr.reduce(function(prev,nowValue,currentIndex,Array),initial)
解析:
- 对于数组arr中的每一项,调用一次function。
- 可以通过initial给function指定初始值(第一个元素执行function的prev),如果没有指定initial,则会从第二项开始执行function,此时的prev的值为第一个元素。
侧重点:滚雪球
// function 2
let total = arr.reduce((prev,value)=>{
return prev+value;
})
- 应用场景2:链式获取对象的属性值。
//如何通过reduce获取到Pikaqiu的最喜欢颜色?
const Pikaqiu = {
name : '皮卡丘',
info :{
favourite:{
color:'yellow',
},
},
}
看到这里,可能有小伙伴一头雾水,但是当我们拥有一个对象属性的数组,那么就可以使用reduce方法进行滚雪球式地获取属性了。
//传入根节点(对象)作为初始值
const attrs = ['info','favourite','color'];
let ans = attrs.reduce((newObj,key)=>{
return newObj[key];
},Pikaqiu)
console.log(ans);//yellow
或许你会说,这不需要我们提前手动写好我们对象的一层层属性吗?实际上没啥用。
但是,当你再去使用Vue的模板字符串的时候,思路就此打开…
- 假设我们在HTML里面使用了模板字符串
<div>{{info.favourite.color}}</div>
- 通过
{{}}
我们获取到了一个字符串info.favourite.color
。
大声告诉我, ~把大象装进冰箱需要几步? ~如何将上面的字符串转成我们需要的数组?
let attrStr = 'info.favourite.color';
let attrs = attrStr.split('.');
//箭头函数更短小精悍写法:省略花括号和return
let ans2 = attrs.reduce((newObj,key)=> newObj[key],Pikaqiu);
console.log(ans2);// yellow
2. 发布订阅模式
-
Dep:收集依赖/收集订阅
-
要求:
- 首先,有个数组专门来存放所有的订阅信息。
- 其次,还要提供一个向数组中追加订阅信息的方法。
- 然后,还要提供一个循环,循环触发数组中的每个订阅信息。
-
属性:
- subs:存放所有订阅者的信息
- addSub:添加订阅者
- notify:发布通知
-
Watcher:订阅者的类,负责订阅一些事件。
- callback:存储回调函数
- update:触发回调函数
下面我们创建最基本的Dep和Watcher对象
class Dep{
constructor(){
this.subs = [];//存放所有订阅者信息
}
addSub(watcher){
this.subs.push(watcher);
}
//通知所有订阅者
notify(){
//...根据订阅者的更新方法进行编辑
this.subs.forEach((watcher)=> watcher.update());
}
}
//订阅者
class Watcher{
constructor(callbackFunction){
this.callback = callbackFunction;
}
update(){
this.callback();
}
}
接着我们构造几个订阅者,实现一个最简单的发布订阅。
- 我们把训练师小智(xiaozhi)当成发布者,宝可梦当作订阅者。
- 每当小智需要与别人战斗的时候,就通知(
notify
)所有自己收服的宝可梦(或者说每一个订阅了这个Dep的watcher
)。 - 此时,所有的宝可梦都被唤醒来干活了(
update
)。
const w1 = new Watcher(()=>{
console.log("我是1号订阅者皮卡丘");
})
const w2 = new Watcher(()=>{
console.log("我是2号订阅者喷火龙");
})
const xiaozhi = new Dep();//创建
xiaozhi.addSub(w1);//收服皮卡丘
xiaozhi.addSub(w2);//收服喷火龙
xiaozhi.notify();//通知所有宝可梦干活
-
理解Vue:
- 只要我们为Vue中的data数据重新赋值了,这个赋值的动作,会被Vue监听到。(具体怎么监听到的,需要通过
defineProperty()
进行数据劫持) - vue只需要把数据的变化通知到每一个订阅者(
notify
) - 订阅者(DOM元素)根据最新的数据,更新 自己的内容即可。
- 只要我们为Vue中的data数据重新赋值了,这个赋值的动作,会被Vue监听到。(具体怎么监听到的,需要通过
3. Object.defineProperty()
vue2的数据代理的原理,使用该方法进行数据劫持。 Vue3使用Proxy代理。
//class Person(){...}
//person = new Person();
//_data 代理 data
Object.defineProperty(person,"_data",{
value:18,
enumerable:true,//控制属性是否可以被枚举
writable:true,//控制属性是否可以被修改
configurable:true,//控制属性是否可以被删除
//通过set()劫持赋值操作
set:function(value){
//在此方法中,我们可以在此调用更新DOM的方法
data = value;
},
//通过get()劫持取值操作
get:function(){
return data;
}
})
class Vue{
constructor(options){
this.$data = options.data;
//调用数据劫持的方法
Observe(this.$data);
//属性代理,直接找vm要属性值,那么就可以返回$data的数据给它。便利使用者。
Object.keys(this.$data).forEach(key=>{
Object.defineProperty(this,key,{
enumerable:true,
configurable:true,
get(){
return this.$data[key]
},
set(newValue){
this.$data[key] = newValue;
},
})
})
}
}
//定义一个数据劫持方法
function Observe(obj){
// 我们需要考虑递归的情况,即属性也是一个对象的情况
// 递归终止条件
if(!obj || typeof obj !== 'object') return ;
//通过Object.keys(obj)获取到当前obj上的每一个属性。
console.log(Object.keys(obj));
Object.keys(obj).forEach(key=>{
//当我们拿到一个子节点的时候
let value = obj[key];
Observe(value);//进入子节点
//为当前的key所对应的属性,添加getter和setter
Object.defineProperty(obj,'key',{
enumerable:true,
configurable:true,
get(){
console.log(`有人获取了${key}的值`)
return value;
},
set(newVal){
value = newVal;
Observe(value);//深度更新里面的内容,假如我直接将字符串name修改为一个嵌套的对象。
}
})
})
}
注意:
-
对象的数据劫持与数组的不同。
上述的数据劫持仅仅针对对象的数据拦截。而没有对数组进行数据劫持。Vue2中对数组的数据代理实现完全不同于对象的。而是通过修改数组的变异方法来实现的。
-
通过$data来代理实际data里面的数据。