js代码块
// 订阅
class Dep {
constructor() {
this.subs = []
}
addSub(watcher) { // 订阅 //添加watcher
this.subs.push(watcher)
}
notify() { //发布
this.subs.forEach(watcher => watcher.updater())
}
updater() {
}
}
// 观察者(发布订阅)
class Watcher {
constructor(vm, expr, cb) {
this.vm = vm;
this.expr = expr;
this.cb = cb;
// 默认存放老值
this.oldValue = this.get()
}
get() {
Dep.target = this;
let value = CompileUtil.getVal(this.vm, this.expr)
Dep.target = null;
return value
}
updater() {
let newValue = CompileUtil.getVal(this.vm, this.expr)
if (newValue != this.oldValue) {
this.cb(newValue)
}
}
}
// 数据劫持
class Observer {
constructor(data) {
this.Observer(data)
}
// 数据劫持功能,给每一个对象都添加get 和set
Observer(data) {
if (data && typeof data == 'object') {
// 循环对象
for (let key in data) {
this.defineReactive(data, key, data[key]);
}
}
}
defineReactive(obj, key, value) {
this.Observer(value)
let dep = new Dep() //每一个属性都有发布和订阅的功能
Object.defineProperty(obj, key, {
get() {
//
Dep.target && dep.addSub(Dep.target)
return value
},
set: (newValue) => {
if (newValue != value) {
this.Observer(newValue)
value = newValue
dep.notify()
}
}
})
}
};
// 编译
class Compiler {
// 元素 和实例
constructor(el, vm) {
// 1. 判断el属性 是否是一个元素
this.el = this.isElementNode(el) ? el : document.querySelector(el);
this.vm = vm
// 2.放入内存中
let fragment = this.node2fragment(this.el)
// 把节点中的内容替换
// 3.编译模板 用数据编译
this.compile(fragment)
//把内容放到页面中
this.el.appendChild(fragment)
}
isDirective(attrName) { //是否包含v-model
return attrName.startsWith('v-')
}
compileElement(node) {//编译元素 v-model
let attributes = node.attributes; //类数组
[...attributes].forEach(attr => {
let { name, value: expr } = attr// 注释 : attr => type='text v-model='school.name'
if (this.isDirective(name)) {
console.log(node);
let [, directive] = name.split('-')
let [directiveName, eventName] = directive.split(':')
// 调用不通指令处理数据
CompileUtil[directiveName](node, expr, this.vm, eventName)
}
})
}
compileText(node) {//编译文本 {{}} 文本节点是否包含{{}}
node.textContent
let content = node.textContent
if (/\{\{(.+?)\}\}/.test(content)) { //文本节点
CompileUtil['text'](node, content, this.vm)
console.log('[ conternt ] >', content)
}
}
compile(node) { //编译内存中的dom节点
let childNodes = node.childNodes //每一个节点
console.log('[ childNodes] >',)
let childNodesArr = [...childNodes]
childNodesArr.forEach(child => {
if (this.isElementNode(child)) {
this.compileElement(child)
// 如果是元素 把自己传进入 再去遍历 子节点
this.compile(child)
} else {
this.compileText(child)
}
})
}
isElementNode(node) { //是不是元素节点
return node.nodeType == 1
}
node2fragment(node) {//把节点放到内存中
// 注释:node.firstChild 就是每一个节点
//2.1 创建文档碎片(放入内存中)
let fragment = document.createDocumentFragment()
let firstChild; //
while (firstChild = node.firstChild) {
// 2.2当前节点放入内存中
fragment.append(firstChild)
}
return fragment
}
}
CompileUtil = {
// 根据表达式取到对应的数据
getVal(vm, expr) {// vm.$data 'schllo.name'
return expr.split('.').reduce((data, cuurent) => {
return data[cuurent]
}, vm.$data)
},
setVal(vm, expr, value) { //expr => school.name value =>鸿蒙
// 给vm.$data 中的school中的name赋值
console.log(vm, expr, value);
expr.split('.').reduce((data, cuurent, index, arr) => { //上一个,当前项 索引,数组
// 最后一项赋值
if (index == arr.length - 1) {
return data[cuurent] = value;
}
return data[cuurent]
}, vm.$data)
},
model(node, expr, vm) {
// v-
// node 节点 expr 表达式 vm 实例 school.name
// 输入框赋予value属性 node.value = xx
let fn = this.updater['modelUpdater']
new Watcher(vm, expr, (newVal) => { //输入框添加观察者 数据更新触发用 给输入框赋值
fn(node, newVal)
})
node.addEventListener('input', (e) => {
let value = e.target.value //输入的内容
this.setVal(vm, expr, value)
})
let value = this.getVal(vm, expr)
fn(node, value)
},
html(node, expr, vm) { // v-html ='message'
let fn = this.updater['htmlUpdater']
new Watcher(vm, expr, (newVal) => { //输入框添加观察者 数据更新触发用 给输入框赋值
fn(node, newVal)
})
let value = this.getVal(vm, expr)
fn(node, value)
},
getContentValue(vm, expr) {
return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
return this.getVal(vm, args[1])
})
},
on(node, expr, vm, eventName) { //expr= change v-on:click=change
node.addEventListener(eventName, (e) => {
vm[expr].call(vm, e)
})
},
text(node, expr, vm) { //expr => {{a}} {{b}}
let fn = this.updater['textUpdater']
let content = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
new Watcher(vm, args[1], (newVal) => { //输入框添加观察者 数据更新触发用 给输入框赋值
let content = this.getContentValue(vm, expr) //全的字符串
fn(node, content)
})
return this.getVal(vm, args[1])
}) //取出{{}} 中的值
fn(node, content)
},
bind() { },
updater: {
// 把数据插入节点
modelUpdater(node, value) {
node.value = value
},
htmlUpdater(node, value) {
node.innerHTML = value
},
// 处理文本节点
textUpdater(node, value) {
node.textContent = value
},
}
}
// 基类
class Vue {
constructor(options) {
this.$el = options.el;
this.$data = options.data;
let computed = options.computed
let methods = options.methods
// 根元素 编译模板
if (this.$el) {
// Object.defineProperty 转换数据格式达到劫持的效果
new Observer(this.$data);
//computed的缓存
for (let key in computed) { //数据变化就更新 新增 watcher
Object.defineProperty(this.$data, key, {
get: () => {
return computed[key].call(this)
}
})
}
// 设置methos 方法
for (let key in methods) { //数据变化就更新 新增 watcher
Object.defineProperty(this, key, {
get: () => {
return methods[key]
}
})
}
// vm 上的取值操作 代理到vm.$data
this.proxyVm(this.$data)
// 编译
new Compiler(this.$el, this)
}
}
proxyVm(data) { // 通过vm 取对应的内容
for (let key in data) {
Object.defineProperty(this, key, {
get() {
return data[key]
},
set(newval) {
data[key] = newval
}
})
}
}
}
//
// 逻辑
// 1. 获取页面元素 防止页面多次回流和重绘 把元素放入内容中
// 2.当前节点中的元素放在内存中
// 3.编译内存中的模板(元素 or 模板)
// 3.数据劫持 let a ={name:1} Object.defineProperty('a',name,{set(),get()}) 取值的时候调用get,赋值的时候调用set
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<input type="text" v-model="school.name">
<div>{{school.name}}</div>
<div>{{school.age}}</div>
<ul>
<li>11</li>
<li>11</li>
</ul>
{{getNewName}}
<button v-on:click="change">更新</button>
<div v-html="message"></div>
</div>
<script src ='./MVVM.js'></script>
<script>
let vm =new Vue({
el:'#app',
data:{
school: {
name:'鸿蒙',
age:10
},
message:'<h1>hellow</h1>'
},
computed:{
getNewName(){
return this.school.name +'蝴蝶云'
}
},
methods:{
change(){
this.school.name ='鸿蒙蝴蝶云'
}
}
})
</script>
</body>
</html>