Vue.js 是一个提供了 MVVM(Model-View-ViewModel ) 风格的双向数据绑定、数据层和视图层通过DOM监听和Data绑定的方式,实现View 和 Model的一致性。
view层和model层之间通过 ViewModel也就是Vue实例绑定在一起实现数据驱动,免去了频繁更新Dom的操作。MVVM实现的原理是用defineProperty方法进行数据劫持(拦截处理数据),即拦截目标属性定义给目标对象(要操作的对象,为这个对象定义属性),并给目标属性一定的特性。再通过发布订阅的方式对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者,执行更新DOM的操作。
{{msg}}
但是这个方法不完善,因为当data中包含多属性是就无法进行获取或者设置,所以当data中还有对象的时候就应该遍历data
const data ={
name: "zhangsan",
age: 18,
}
//模拟vue对象
let vm = {}
function handleData(data) {
//遍历data中的属性
Object.keys(data).forEach((key) => {
//对每个属性进行数据劫持
// 参数:对象 属性 对象增加的描述
Object.defineProperty(vm, key,// key就是data里的动态属性
{
get() {
console.log('get', data[key]);
},
set(newValue) {
console.log('set', newValue);
if (newValue === data[key]) {
return
}
data[key] = newValue
document.querySelector('#app').textContent = data[key]
}
})
})
}
handleData(data);
如果data中包含多属性,对象中还有多属性就要循环递归遍历,实现起来就很麻烦。 Vue3引入proxy代理监听变化之后就可以简单高效的拦截数据。
Vue核心源码数据驱动部分如下
{{msg}}
{{msg}}
class Vue{
constructor(options){
this.$options = options
this._data = options.data
this.$el = typeof options.el === 'string'?document.querySelector(options.el):options.el
this._proxyData(this._data) //调用劫持数据,注入到vue实例上
//执行 响应式处理
new Observer(this._data)
// 模板编译 解析指令 初始化dom
new Compiler(this)
}
// 代理数据 数据劫持
_proxyData(data){
// 遍历key
Object.keys(data).forEach(key=>{
// 挂载 defineProperty()
Object.defineProperty(this,key,{
get(){
return data[key]
},
set(nValue){
if(data[key]===nValue){
return
}
data[key]=nValue
}
})
})
}
}
class Observer{ //操作数据
constructor(data){
this.walk(data)
}
// 遍历
walk(data){
console.log(typeof data);
if(!data || typeof data !== 'object'){
console.log('lidata',data);
return
}
Object.keys(data).forEach(key=>{
console.log(key);
this.defineReative(data,key,data[key])
})
}
// 定义响应式数据
defineReative(data, key,value) {
let publisher = new Publisher()
let that = this
//走一遍walk方法 判断是不是对象
// this.walk(data[key]) //是个死循环 占内存溢出
this.walk(value)
console.log('reay-walk');
Object.defineProperty(data, key, {
get() {
console.log('get---');
// 收集依赖,添加观察者
Publisher.target && publisher.addSub(Publisher.target)
return value
},
set(nValue) {
if (value === nValue) {
return
}
value = nValue;
// 赋新值是个对象
// this.walk(nValue) this指向data
that.walk(nValue)
console.log('set publisher notify');
// 通知依赖
// 发送通知 ->观察者通过update()更新到模板dom中
publisher.notify()
}
})
}
}
// 处理模板相关操作 需要dom:el data
class Compiler{
constructor(vm){
this.el = vm.$el
this.data = vm._data
this.compile(this.el)
}
// 编译
compile(el){ //处理el
let childNodes = el.childNodes //是个伪数组
Array.from(childNodes).forEach(node=>{//伪数组转换真数组
// 代码分割
if(this.isTextNode(node)){//文本处理
this.compileText(node)
}else if(this.isElementNode(node)){//元素处理
this.compileElement(node)
}
if(node.childNodes){
this.compile(node) //递归
}
})
}
// 编译文本 {{}}处理插值表达式
compileText(node){
let value = node.textContent
let reg = /\{\{(.+?)\}\}/
if(reg.test(value)){
// 获取插值表达式的变量名
let k = RegExp.$1.trim() //第一个分组的内容()中
// 替换
node.textContent = value.replace(reg,this.data[k])
// 通知订阅数据变化 ,调用updata方法,调用回调函数,执行dom操作
new Watcher(this.data,k,(newValue)=>{
node.textContent = newValue
})
}
}
// 编译元素 v-
compileElement(node){
// 获取多个属性 伪数组
let attributes = node.attributes
//转换成真数组 遍历所有属性
Array.from(attributes).forEach(attr=>{
// v-: v-text -> text v- 获取vue指令
// console.log(attr);
let attrName = attr.name //属性名
// 是否是指令 v-
if(this.isDirective(attrName)){
// 获取属性名中 v-后面的部分
attrName = attrName.substr(2)
// 获取属性值 -data(key)
let key = attr.value
// text - 映射方法 不同指令的处理方式,也就是不同函数
this.update(node,attrName,key)
}
})
}
update(node,attrname,key){
// if(attrname=='text'){
// this.textUpdate(node,attrname,key)
// }
let fn = this[attrname + 'Update']
fn && fn.call(this,node,attrname,key)
}
textUpdate(node,attrname,key){
node.textContent = this.data[key]
new Watcher(this.data,key,(newValue)=>{
node.textContent = newValue
})
}
modelUpdate(node,attrname,key){
node.value = this.data[key]
new Watcher(this.data,key,(newValue)=>{
node.textContent = newValue
})
node.addEventListener("input",()=>{
this.data[key] = node.value
})
}
// 判断是什么节点
isTextNode(node){
return node.nodeType === 3 //1是元素 2是属性 3是文本
}
isElementNode(node){
return node.nodeType === 1 //1是元素 2是属性 3是文本
}
isAttrNode(node){
return node.nodeType === 2 //1是元素 2是属性 3是文本
}
isDirective(attrName){
return attrName.startsWith("v-")
}
}
class Watcher{ //定义订阅者类
constructor(data,key,cb){
this.data = data
this.key = key
this.cb = cb
Publisher.target = this
this.oldValue = data[key]
}
update(){ //获取发布者发布通知的信息
console.log('watcher update');
let newValue = this.data[this.key]
if(this.oldValue === newValue){
return
}
// 调用模板更新dom
this.cb(newValue)
}
}
class Publisher{ //定义发布者类
constructor(){
this.subs =[]
}
addSub(sub){ //添加订阅者
if(sub && sub.update){
this.subs.push(sub)
}
}
notify(){ //通知订阅者
this.subs.forEach(w=>{
w.update()//约定必须有updata放啊
})
}
}