1.如何实现数据变了,视图也跟着变?核心是:如何知道对象的属性值被修改了?
2.如何实现一个数据变量,多出视图跟着变?
3.如何保证视图改变,数据也跟这改变?
这个问题也是最简单的,我们只需要给表单监听input事件,通过e.tatget.value实时获取表单值,设置给对象中对应的属性
var obj = {a:1,b:2}
Object.defineProperty(obj,"a",{
相关配置项.....
})
value:设置属性的值
writable:如果是false,则代表属性值不让修改
configurable:如果是false,后续不能再次通过Object.defineProperty()配置,也不能通过delete删除该属性
enumerable:是否可以枚举,
get:拦截获取对象属性的动作
set:拦截设置对象属性值的动作
const obj = {a:1,b:2}
const newObj = {}
function observe(){
Object.keys(obj).forEach(key=>{
Object.definePropertery(newOjb,key,{
get(){
return obj[key]
},
// 通过set获取数据的变化
set(val){
obj[key]=val
// 修改值后可以做相关dom数据的更新
}
})
})
}
observe(obj)
通过上述的案例明白了问题分析中的第一个问题,要想明白第二个问题,就需要知道什么是观察者模式
概念:观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化时,会通知所有的观察者
// 观察者构造函数
function EventCenter(){
this.guanjia = {
}
}
// 添加观察者
EventCenter.Prototype.$on(EventName,cb){
// 如果存在 就往数组中添加一个观察者函数
if(this.guanjia.EventName){
this.guanjia.EventName.push(cb)
}else{
// 不存在就初始化一个数组,并存放
this.guanjia.EventName=[cb]
}
}
// 用来发布事件发
EventCenter.Prototype.$emit(EventName){
// 如果存在 就依次执行数组中全部的监听者函数
if(this.guanjia.EventName){
this.guanjia.EventName.forEach(fn=>fn())
}
// 不存在就不做响应
}
总结:问题分析中的第二个问题迎刃而解
解决了上述全部问题,那么下面实现一个vue(乞丐版)
模仿vue实现一个简单的mvvm,做到数据改变能影响视图,视图变化数据也会改变
案例HTML结构模板如下:
<div id="app">
<h2 v-html="salary"></h2>
<input type="text" v-model="salary">
<p v-html="salary"></p>
<div v-html="bonus"></div>
</div>
JS实现代码分两个部分,一个是观察者一个是mvvm,代码分别如下:
// 首先导入上述观察者模式的代码
function MVVM(options){
const {data,el}=options
// 实例化一个观察者对象
const ec = new EventCenter()
Object.keys(data).forEach(key=>{
Object.defineProperty(this,key,{
set(val){
console.log(`有人设置了${key}属性,值为${val}`)
data[key]=val
// 数据一旦改变,就发布key属性值改变的事件
ec.$emit(key,val)
},
get(){
console.log(`有人读取了${key}属性`)
return data[key]
}
})
})
// 获取传入el对应的节点元素
const rootNode = document.querySelector(el)
// 将伪数组转化真实数组进行遍历
Array.from(rootNode.children).forEach(node=>{
// 判断每个节点有没有v-html属性
if(node.hasAttribute("v-html")){
// 有就给节点初始值,首先获取v-html属性对应的属性值
let key = node.getAttribute("v-html")
// 初始化节点数据
node.innerHTML= this[key]
// 添加对key属性变化的观察者,
ec.$on(key,()=>{
node.innerHTML= this[key]
})
}else if(node.hasAttribute("v-model")){
// 1.显示初始值
let key = node.getAttribute("v-model")
node.value = this[key]
// 2.数据改变,页面也要更改
ec.$on(key,()=>{
node.value= this[key]
})
// 3.用户设置,数据更者改变
// 实现步骤如下:监听表单的input事件,通过事件对象获取value属性,然后设置value值
node.addEventListener("input",(e)=>{
this[key] = e.target.value // 然后就会被set监听到,最终实现数据的双向绑定
})
}
})
}
new MVVM({
el:"#app",
data:{
salary:20000,
bonus:1000
}
})
vue3通过ES6新增的Proxy来实现响应式
var obj = {a:1,b:2}
var newObj = new Proxy(obj,{
// set的三个参数分别是:target:代理对象 props:设置的属性名 value:设置的属性值
set(target,prop,value){
target[prop]=value
},
// get的两个参数分别是:target:代理对象 props:设置的属性名
get(target,prop){
return target[prop]
}
})
1.效率更高一些
Proxy可以处理一类动作
Object.defineProperty只能处理某个具体属性名,而且需要通过循环遍历属性
2. 能直接监控数组的操作
vue2.0其实不能直接监控数组的操作
vue2.0动态添加属性的时候也不是响应式的
为什么尤大大不监控数组的操作?
因为Object.defineProperty要监听到对象的具体属性,数组的属性又是一堆索引,并没有什么语义化,而且可能数
组的长度非常大,就极容易导致性能过低.所以尤大大没用使用Object.defineProperty来监听数组的操作
而是尤大大通过拦截了数组的7个方法(官网有写哪7个),用户只有调用这7个方法时候,vue2才能感知数组的变化,
才能更新视图
拦截的7个数组方法:
push,pop,shifr,unshift,reserve,splice,sort
vue2在初始化的时候,遍历所有的数据属性,然后通过Object.defineproperty进行数据劫持,在渲染模板的时候,在结和观察者模式,对属性的改变进行监听函数,并且加入到一个数组里面,当某一个属性发现改变时候,就会被set拦截,在set函数中发布属性的改变,这样对这个属性用到的地方都会执行页面的更新