index.html:
<html lang="en">
<head>
head>
<body>
<div id="app">
<p>{{msg}}p>
<p>{{car.brand}}p>
<p v-text="msg">p>
<p v-text="car.color">p>
<p v-html="msg">p>
<p v-html="car.color">p>
<input type="text" v-model="msg">
<button v-on:click="clickFn">点我button>
div>
<script src="./src/watcher.js">script>
<script src="./src/observe.js">script>
<script src="./src/compile.js">script>
<script src="./src/vue.js">script>
body>
html>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: 'hello wrold',
car: {
brand: '宝马',
color: 'blue'
}
},
methods: {
clickFn () {
// 在vue的methods中this应该指向当前实例
this.msg = '嘿嘿'
this.car.brand = '奔驰'
}
}
})
script>
compiler.js:
/**
* 专门负责解析模板内容
*/
class Compile {
constructor (el, vm) {
// el: new vue传递的选择器
this.el = typeof el === "string" ? document.querySelector(el) : el
// vm: new的vue实例
this.vm = vm
// 编译模式
if (this.el) {
// 1. 把el中所有的子节点都放入到内存中, fragment
let fragment = this.node2fragment(this.el)
// 2. 在内存中编译fragment
this.compile(fragment)
// 3. 把fragment一次性的添加到页面
this.el.appendChild(fragment)
}
}
/**
* 核心方法
*/
node2fragment (node) {
let fragment = document.createDocumentFragment()
// 把el中所有的子节点挨个添加到文档碎片中
let childNodes = node.childNodes
this.toArray(childNodes).forEach(node => {
// 把所有的子节点都添加到fragment中
fragment.appendChild(node)
})
return fragment
}
/**
* 编译文档碎片(内存中)
* @param {*} fragment
*/
compile(fragment) {
let childNodes = fragment.childNodes
this.toArray(childNodes).forEach(node => {
// 编译子节点
if(this.isElementNode(node)) {
// 如果是元素,需要解析指令
this.compileElement(node)
}
if (this.isTextNode(node)) {
// 如果是文本节点,需要解析插值表达式
this.compileText(node)
}
// 如果当前节点还有子节点,需要递归的解析
if (node.childNodes && node.childNodes.length > 0) {
this.compile(node)
}
})
}
// 解析html标签
compileElement(node) {
// 1. 获取到当前节点下所有的属性
let attributes = node.attributes
this.toArray(attributes).forEach(attr => {
// 2. 解析vue的指令(所有以v-开头的属性)
let attrName = attr.name
if(this.isDirective(attrName)) {
let type = attrName.slice(2)
let expr = attr.value
// 解析v-on指令
if (this.isEventDirective(type)) {
CompileUtil["eventHandler"](node, this.vm, type, expr)
} else {
CompileUtil[type] && CompileUtil[type](node, this.vm, expr)
}
}
})
}
// 解析文本节点
compileText (node) {
CompileUtil.mustache(node, this.vm)
}
/**
* 工具方法
*/
toArray (likeArray) {
return [].slice.call(likeArray)
}
isElementNode(node) {
// nodeType: 节点类型 1:元素节点 3: 文本节点
return node.nodeType === 1
}
isTextNode(node) {
return node.nodeType === 3
}
isDirective(attrName) {
return attrName.startsWith("v-")
}
isEventDirective(type) {
return type.split(":")[0] === "on"
}
}
let CompileUtil = {
mustache (node, vm) {
let txt = node.textContent
let reg = /\{\{(.+)\}\}/
if (reg.test(txt)) {
let expr = RegExp.$1
node.textContent = txt.replace(reg, this.getVMValue(vm, expr))
new Watcher(vm, expr, newValue => {
node.textContent = txt.replace(reg, newValue)
})
}
},
// 处理v-text指令
text(node, vm, expr) {
node.textContent = this.getVMValue(vm, expr)
// 通过watcher对象,监听expr的数据变化,一旦发生变化,执行回调函数
new Watcher(vm, expr, (newValue, oldValue) => {
node.textContent = newValue
})
},
// 处理v-html指令
html(node, vm, expr) {
node.innerHTML = this.getVMValue(vm, expr)
new Watcher (vm, expr, newValue => {
node.innerHTML = newValue
})
},
// 处理v-model指令
model(node, vm, expr) {
let self = this
node.value = this.getVMValue(vm, expr)
// 实现双向数据绑定,给node注册input事件,当当前元素value值发生改变,修改对应的数据
node.addEventListener('input', function () {
self.setVMValue(vm, expr, this.value)
})
new Watcher(vm, expr, newValue => {
node.value = newValue
})
},
eventHandler(node, vm, type, expr) {
// 给当前元素注册事件即可
let eventType = type.split(":")[1]
let fn = vm.$methods && vm.$methods[expr]
if (eventType && fn) {
node.addEventListener(eventType, fn.bind(vm))
}
},
// 这个方法用于获取VM中的数据
getVMValue(vm, expr) {
// 获取到data中的数据
let data = vm.$data
expr.split(".").forEach(key => {
data = data[key]
})
return data
},
setVMValue(vm, expr, value) {
let data = vm.$data
let arr = expr.split(".")
arr.forEach((key, index) => {
// 如果index是最后一个
if(index < arr.length - 1) {
data = data[key]
} else {
data[key] = value
}
})
}
}
observe.js:
/**
* observer用于给data中所有的数据添加getter和setter
* 方便我们在获取或者设置data中数据的时候,实现我们的逻辑
*/
class Observer {
constructor(data) {
this.data = data
this.walk(data)
}
/**
* 核心方法
* 遍历data中所有的数据,都添加上getter和setter
*/
walk(data) {
if (!data || typeof data != "object") {
return
}
Object.keys(data).forEach(key => {
// 给data对象的key设置getter和setter
this.defineReactive(data, key, data[key])
// 如果data[key]是一个复杂的类型,递归的walk
this.walk(data[key])
})
}
// 定义响应式的数据(数据劫持)
// data中的每一个数据都应该维护有一个dep对象
// dep保存了所有的订阅了改数据的订阅者
defineReactive(obj, key, value) {
let that = this
let dep = new Dep()
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
// 如果Dep.target中有watcher对象,存储到订阅者数组中
Dep.target && dep.addSub(Dep.target)
return value
},
set(newValue) {
if (value === newValue) {
return
}
value = newValue
// 如果newValue是一个对象,也应该对它进行劫持
that.walk(newValue)
// 发布通知, 让所有的订阅者更新内容
dep.notify()
}
})
}
}
watcher.js :
/**
* watcher模块负责把compile模块与observe模块关联起来
*/
class Watcher {
/**
* vm: 当前vue实例
* expr: data中数字的名字
* 一旦数据发生改变,需要调用cb
*/
constructor(vm, expr, cb) {
this.vm = vm
this.expr = expr
this.cb = cb
// this表示的就是新创建的watcher对象
// 存储到Dep.target属性上
Dep.target = this
// 需要把expr的旧值给储存起来
this.oldValue = this.getVMValue(vm, expr)
// 清空Dep.target方便下一次使用
Dep.target = null
}
// 对外暴露的一个方法,这个方法用于更新页面
update () {
let oldValue = this.oldValue
let newValue = this.getVMValue(this.vm, this.expr)
if (oldValue != newValue) {
this.cb(newValue, oldValue)
}
}
// 用于获取vm中的数据
getVMValue(vm, expr) {
// 获得到data中的数据
let data = vm.$data
expr.split(".").forEach(key => {
data = data[key]
})
return data
}
}
/**
* dep对象用于管理所有的订阅者和通知这些订阅者
*/
class Dep{
constructor () {
// 用于管理订阅者
this.subs = []
}
// 添加订阅者
addSub(watcher) {
this.subs.push(watcher)
}
// 通知
notify () {
// 遍历所有的订阅者, 调用watcher的update方法
this.subs.forEach(sub => {
sub.update()
})
}
}