HTML部分的代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<input type="text" name="" id="" v-model="school.name">
<div>{{school.name}}</div>
<div>{{school.age}}</div>
<div v-html="message"></div>
<!-- 如果数据不变化视图不用刷新 -->
<!-- 我们在内部对{{}} 进行匹配 -->
{{getNewName}}
<ul>
<li>1</li>
</ul>
<button v-on:click="change">点击</button>
</div>
<!-- <script src="./node_modules/vue/dist/vue.js"></script> -->
<script src="./MVVM.js"></script>
<script>
let vm = new Vue({
el: "#app",
data: {
school: {
name: 'hao',
age: 20
},
message: '<h1>welcome</h1>'
},
methods: {
change() {
this.school.name = 'ggg'
}
},
computed: {
getNewName() {
return this.school.name + 'verygood'
}
}
})
</script>
</body>
</html>
JS部分的代码主要是面向对象编程
// 观察者模式(发布订阅)
class Dep {
constructor() {
this.subs = [] // 存放所有的watcher
}
addSub (watcher) { // 添加watcher方法
this.subs.push(watcher)
}
// 发布
notify () {
this.subs.forEach(watcher => {
watcher.update()
})
}
}
class Watcher {
constructor(vm, expr, cb) {
this.vm = vm
this.expr = expr
this.cb = cb
// 默认先存放一个老值
this.oldValue = this.get()
}
get () {
Dep.target = this // 先把自己放在this上
// 取值,把这个观察者和数据关联起来
let value = compileUtil.getValue(this.vm, this.expr)
Dep.target = null ///
return value
}
update () { // 更新操作 数据变化后 会调用观察者的update方法
let newVal = compileUtil.getValue(this.vm, this.expr)
if (newVal !== this.oldValue) {
this.cb(newVal)
}
}
}
// vm.$watch(vm, 'school.name', (newVal) => {
// })
class Observer { // 实现数据劫持
constructor (data) {
this.observer(data)
}
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 () {
// 创建watcher会取到对应的内容, 并且把watcher放在全局
Dep.target && dep.addSub(Dep.target)
return value
},
set: (newVal) => {
if (newVal != value) {
this.observer(newVal)
value = newVal
dep.notify()
}
}
})
}
}
// 创建一个基类
class Compiler {
constructor(el, vm) {
// 判断el属性是不是元素(还有字符串或者直接document.getElement 出来的)
this.el = this.isElementNode(el) ? el : document.querySelector(el)
this.vm = vm
// 把当前节点中的元素获取到,方在内存里
let fragment = this.node2fragment(this.el)
console.log(fragment)
// 把节点中的内容进行替换
// 用数据编译模板
this.compile(fragment)
// 把内容塞到页面中
this.el.appendChild(fragment)
}
// 判断是不是指令
isDirective (attrName) {
return attrName.startsWith('v-')
}
// 编译元素的
compileElement (node) {
let attributes = node.attributes
console.log(attributes, 'attr')
const arr = [...attributes]
arr.forEach(attr => { // type
// console.log(attr, 9999)
let {name, value: expr} = attr
if (this.isDirective(name)) {
let [,directive] = name.split('-')
let [directiveName, eventName] = directive.split(':')
// compileUtil[directive](node,expr,this.vm)
compileUtil[directiveName](node,expr,this.vm, eventName)
console.log(node)
}
})
}
// 编译文本的
compileText(node) { // 判断当前文本节点中内容是否包含{{}}
let content = node.textContent
if (/\{\{(.+?)\}\}/.test(content)) {
console.log(content) // 找到所有文本
compileUtil['text'](node, content, this.vm)
}
}
// 核心的编译方法
compile (node) { // 编译内存中的节点
let childNodes = node.childNodes
// console.log(childNodes) // 只拿出第一层的
childNodes.forEach(child => {
if (this.isElementNode(child)) {
// console.log('elemement', child)
// 所以写两个方法编译元素 和编译文本的
this.compileElement(child)
// 如果是元素的话需要把自己穿进去,再去遍历子节点
this.compile(child)
} else {
// console.log('text', child)
this.compileText(child)
}
})
}
node2fragment (node) { // 把节点移动到内存中
let fragment = document.createDocumentFragment()
let firstChild
while (firstChild = node.firstChild) {
// appendChild 具有移动性,可以加入后清除这个节点,或者说转移这个节点
fragment.appendChild(firstChild)
}
return fragment
}
isElementNode(node) {
// 判断是不是元素节点
return node.nodeType === 1
}
}
compileUtil = {
getValue (vm, expr) { // 根据表达式取到对应的数据
return expr.split('.').reduce((data, current) => {
return data[current]
},vm.$data)
},
setValue (vm, expr, value) { // vm.$data school.name
return expr.split('.').reduce((data, current, index, arr) => {
if (index === arr.length - 1) {
return data[current] = value
}
return data[current]
},vm.$data)
},
// 解析v-model 的指令
model (node, expr, vm) { // node是节点 expr是表达式 VM是当前实例
// 给输入框value赋值
let fn = this.updater['modelUpdater']
new Watcher(vm, expr, (newVal)=> { // 给输入框加一个观察者,如果稍后数据更新了会触发此方法,然后把新值赋给输入框
fn(node,newVal)
})
node.addEventListener('input', (e) => {
let value = e.target.value
this.setValue(vm, expr, value)
})
let value = this.getValue(vm, expr) // 通过这个取值
fn(node, value)
},
html (node, expr, vm) {
let fn = this.updater['htmlUpdater']
new Watcher(vm, expr, (newVal)=> {
fn(node,newVal)
})
let value = this.getValue(vm, expr) // 通过这个取值
fn(node, value)
},
getContentValue (vm, expr) {
// 遍历 表达式,将内容重新替换成一个完整的内容然后返回
return expr.replace(/\{\{(.+?)\}\}/g, (...args) =>{
return this.getValue(vm, args[1])
})
},
on (node,expr,vm, eventName) {
node.addEventListener(eventName, (e) => {
vm[expr].call(vm, e)
})
},
text (node, expr, vm) { // expr {{a}}
let fn = this.updater['textUpdater']
let content = expr.replace(/\{\{(.+?)\}\}/g, (...args) =>{
// 给表达式中的{{}} 每一个都加观察者
new Watcher(vm, args[1], (newVal)=> {
fn(node,this.getContentValue(vm, expr)) // 返回一个全部的字符串
})
return this.getValue(vm, args[1])
})
fn(node, content)
},
updater: {
modelUpdater (node, value) { // xss 攻击
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.defineeProperty来定义
new Observer(this.$data)
// 把数据火气操作 VM上的取值操作,都代理成vm.$data这个操作
console.log(this.$data)
for (let key in computed) {
Object.defineProperty(this.$data, key, {
get: () => {
return computed[key].call(this)
}
})
}
for(let key in methods) {
Object.defineProperty(this,key, {
get() {
return methods[key]
}
})
}
this.proxyVm(this.$data)
new Compiler(this.$el,this)
}
}
proxyVm (data) {
for(let key in data) {
Object.defineProperty(this, key, {
get () {
return data[key] // 可以从vm取到当前对应的内容代理一层就好
},
set (newVal) {
data[key] = newVal
}
})
}
}
}