/**
* author:Echonessy
* des:
* date:2020.07.24
* target: Vue
* 1.Vue
* 把data中的成员注入到Vue实例中,并且把data中的成员转成getter/setter
* 2.Observer
* 能够对数据对象的所有属性进行监听,如果变动可拿到最新值并通知Dep(发布者-目标)
* 3.Watcher
* 定义观察者,定义update()函数,当数据发生变动,更新视图
* 4.Dep
* 添加观察者,当数据发生变化的时候,通知所有的观察者,执行观察者的update()函数
* 5.Compiler
* 负责编译模板,解析指令/差值表达式,负责页面的首次渲染,当数据变化后更新视图
* */
/**
* 1.Vue
* 把data中的成员注入到Vue实例中,并且把data中的成员转成getter/setter
* 功能:
* 1.负责接受初始化的参数(选项)
* 2.负责吧data中的属性注入到Vue实例,转换成getter/setter
* 3.负责调用Observer监听data中所有属性的变化
* 4.负责调用compiler解析指令/差值表达式
* 结构:
* +$options :记录所有参数配置
* +$el :记录绑定的DOM Element
* +$data :记录响应式数据
* ---------------------
* -_proxyData() 私有成员,把data中的属性,转换成getter/setter注入到Vue实例中
* */
class Vue {
constructor(options) {
// 1.通过属性保存选项的数据
this.$options = options || Object.create(null);
// data 必须是一个函数,为了防止与内部变量冲突
if(typeof options.data !== 'function'){
throw ('data must be a function')
return
}
this.$data = options.data() || Object.create(null);
this.$el = typeof options.el === 'string' ? document.querySelector(options.el):options.el;
// 2.把data中的成员转换成getter/setter注入到Vue实例中
this._proxyData(this.$data);
// 3.调用Observer对象,监听数据的变化
new Observer(this.$data)
// 4.调用Compiler对象,解析指令和差值表达式
new Compiler(this)
}
// 私有成员,把data中的属性,转换成getter/setter注入到Vue实例中
_proxyData(data){
// 1.遍历data中的所有属性,
if(!data || typeof data !== 'object') return;
Object.keys(data).forEach(key =>{
Object.defineProperty(this,key,{
configurable:true,
enumerable:true,
get() {
return data[key]
},
set(nv) {
if(data[key] == nv) return;
data[key] = nv;
}
})
})
}
}
/**
* 2.Observer 核心
* 数据响应式处理
* 功能:
* 1.负责编译模板,解析指令/差值表达式,
* 2.负责页面的首次渲染
* 3.当数据变化后更新视图
* 结构:
* +go(data)
* 负责遍历对象属性,对象拦截,只针对对象数据进行响应式处理,
* +proxyData(data)
* 数据代理
* 负责通过Object.defineProperty进行对象劫持,通过递归进行深度对象监听,
* 针对新赋值属性值,如果是对象,同样进行数据拦截
* */
//监听data
class Observer {
constructor(data) {
this.go(data)
}
go(data){
if(typeof data !== 'object'){
return
}
Object.keys(data).forEach(key =>{
this.proxyData(data,key,data[key])
})
}
proxyData(data,key,value){
this.go(value);
let that = this;
//收集依赖,发送通知
let dep = new Dep();
Object.defineProperty(data,key,{
configurable:true,
enumerable:true,
get() {
// console.log('getter -> ' + value)
Dep.target && dep.addSub(Dep.target)
return value;
},
set(nv) {
if(value == nv) return;
console.log('数据变化'+value+'-->'+nv+',发送通知')
value = nv;
that.go(nv);
dep.notify(key,nv)
// 数据变化,发送通知
}
})
}
}
/**
* 3.Compiler 核心
* 编译、更新视图
* 功能:
* 1.负责编译模板,解析指令和差值表达式
* 2.负责页面的首次加载
* 3.当数据变化时,更新视图
*
* 结构:
* +el
* Vue构造函数的options.el ,DOM对象
* +vm
* Vue实例
* ----------------------------------------------
* +compile(el)
* 用于遍历DOM对象所有节点,如果是文本节点,解析差值表达式。如果是元素节点,解析指令。
* +compileText(node)
* 解析差值表达式
* +compileElement(node)
* 解析元素指令
* +isDirective(node)
* 判断是否是指令
* +isTextNode(node)
* 判断是否是文本节点
* +isElementNode(node)
* 判断是否是元素节点
* +update(node,key,attrName)
* 更新视图,执行指令,根据 attrName+Update 执行对应方法
* +textUpdate(node,key,attrName)
* 更新文本,执行指令v-text
* +modelUpdate(node,key,attrName)
* 更新表单value,执行指令v-model
*
* nodeType:12种节点类型
* 1 Element 代表元素
* 2 Attr 代表属性
* 3 Text 代表元素或属性中的文本内容。
* 4 CDATASection 代表文档中的 CDATA 部分(不会由解析器解析的文本)。
* 5 EntityReference 代表实体引用。
* 6 Entity 代表实体。
* 7 ProcessingInstruction 代表处理指令。
* 8 Comment 代表注释。
* 9 Document 代表整个文档(DOM 树的根节点)。
* 10 DocumentType 向为文档定义的实体提供接口
* 11 DocumentFragment 代表轻量级的 Document 对象,能够容纳文档的某个部分
* 12 Notation 代表 DTD 中声明的符号。
* */
class Compiler {
constructor(vm) {
this.vm = vm;
this.el = vm.$el;
this.compile(this.el)
}
//编译模板,处理文本节点和元素节点
compile(el){
let childNodes = el.childNodes; // 所有节点,属于伪数组需要通过Array.from()转换成真实数组
Array.from(childNodes).forEach(node =>{
if(this.isTextNode(node)){
// 处理文本节点
this.compileText(node)
} else if(this.isElementNode(node)){
// 处理元素节点
this.compileElement(node)
}
// 判断node节点,是否有子节点,如果有,递归深度遍历
if(node.childNodes && node.childNodes.length) {
this.compile(node)
}
})
}
//编译元素节点,处理指令
compileElement(node){
// v-text v-html
// 1.遍历所有的属性节点
// 2.判断是否是指令
Array.from(node.attributes).forEach(attr=>{
let attrName = attr.name;
if(this.isDirective(attrName)){
attrName = attrName.substr(2);
let key = attr.value;
// 如果当前元素含有指令,则需要首次渲染指令对应的内容
this.update(node,key,attrName)
}
})
}
update(node,key,attrName){
let updateFn = this[attrName+'Update'];
updateFn && updateFn.call(this,node,this.vm[key],key);
}
// 处理v-for 指令
forUpdate(node,value,key){
let reg = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/;
let list = this.vm[key.match(reg)[2]];
list.forEach(item =>{
// console.log(item)
})
// console.log(list)
}
// 处理v-text 指令
textUpdate(node,value,key){
node.textContent = value;
new Watcher(this.vm,key,(k,nv) =>{
console.log('创建Watcher ,当数据改变更新视图' + nv)
node.textContent = nv;
})
}
//编译文本节点,处理差值表达式
compileText(node){
// {{name}}
// . 匹配除换行符 \n 之外的任何单字符。要匹配 . ,请使用 \.
// \ 将下一个字符标记为或特殊字符、或原义字符、或向后引用、或八进制转义符。例如, 'n' 匹配字符 'n'。'\n' 匹配换行符。序列 '\\' 匹配 "\",而 '\(' 则匹配 "("。
// ? 匹配前面的子表达式零次或一次,或指明一个非贪婪限定符。要匹配 ? 字符,请使用 \?。
// + 匹配前面的子表达式一次或多次。要匹配 + 字符,请使用 \+。
let reg = /\{\{(.+?)\}\}/; // 匹配单个的{{key1}}
let value = node.textContent;
if(reg.test(value)){
let key = RegExp.$1.trim();
node.textContent = value.replace(reg,this.vm[key]);
new Watcher(this.vm,key,(k,nv) =>{
console.log('创建Watcher ,当数据改变更新视图' + nv)
node.textContent = this.vm[key];
})
}
}
// 处理v-model 指令
modelUpdate(node,value,key){
node.value = value;
new Watcher(this.vm,key,(k,nv) =>{
console.log('创建Watcher ,当数据改变更新视图' + nv)
node.value = nv;
})
//设置双向绑定事件
node.addEventListener('input',e => this.vm[key] = node.value)
}
// 判断元素是否是指令
isDirective(attrName){
//判断属性是否是v-开头
return attrName.startsWith('v-');
}
//判断是否是文本节点
isTextNode(node){
return node.nodeType === 3;
}
//判断是否是元素节点
isElementNode(node){
return node.nodeType === 1;
}
}
/**
* 4.Dep 核心 dependence
* 目标(发布者)
* 功能:
* 1.收集依赖,添加观察者
* 2.通知所有观察者
*
* 结构:
* +subs 数组:存储所有的观察者
* ---------------------------------
* +addSub():添加观察者
* +notify():当事件发生时,调用所有的观察者的update()方法
* */
class Dep {
constructor() {
// 记录所有的(观察者/订阅者)
this.subs = new Array(0);
}
addSub(sub){
// 每一个观察者都必须包含一个update方法
if(sub && sub.update) this.subs.push(sub);
}
notify(key,nv){
this.subs.forEach(sub =>sub.update(key,nv))
}
}
/**
* 4.Watcher 核心
* 观察者 ->update():当事件发生时,具体要做的事情
* 功能:
* 1.当数据变化触发依赖,dep通知所有的Watcher实例更新视图
* 2.自身实例化的时候往dep对象中添加自己
*
* 结构:
* +vm Vue 实例
* +key data中的属性名称
* +cb 回调函数 负责更新视图
* +oldValue 记录数据变化之前的值
* ------------------------------------
* +update() 当数据发生变化的时候,更新视图
* */
// 订阅者-观察者
class Watcher {
constructor(vm,key,cb) {
this.vm = vm;
this.key = key;
this.cb = cb;
// 把Watcher对象记录到Dep类的静态属性target
// 触发get方法,在get中会调用addSub
Dep.target = this;
// 当获取vm[key]的时候会执行getter
this.oldValue = vm[key];
// 当Watcher 添加到subs之后,我们要对Dep进行静态属性的重置
Dep.target = null;
}
update(key,nv){
if(nv == this.oldValue) return;
this.cb(key,nv)
this.oldValue = nv;
}
}