index.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>Vue源码</title>
</head>
<body>
<div id="app">
<p>{{name}}</p>
<p e-text="name"></p>
<p>{{age}}</p>
<input type="text" k-model="name" />
<button @click="changeName">click button</button>
<div e-html="buttonStr"></div>
</div>
<script src="./js/e-complie.js"></script>
<script src="./js/e-vue.js"></script>
</body>
<script>
let vm = new EVue({
el: '#app',
data: {
name: 'ESullivan',
age: 18,
buttonStr: '<button>click html btn</button>'
},
created() {
setTimeout(() => {
this.name = '我是create修改的'
}, 2000)
},
methods: {
changeName() {
this.name = 'vuejs,函数改变name'
this.age = 19
}
}
})
</script>
</html>
e-compile.js
class Compile {
constructor(el, vm) {
this.$vm = vm
this.$el = document.querySelector(el)
//获取所有子元素
this.$fragment = this.nodeFragment(this.$el)
this.compileElement(this.$fragment)
this.$el.appendChild(this.$fragment)
}
nodeFragment(el) {
let fragment = document.createDocumentFragment()
console.log('nodeFragment() fragment:', fragment)
let child
while (child = el.firstChild) {
console.log('nodeFragment() child:', child)
fragment.appendChild(child)
}
console.log('nodeFragment() fragment:', fragment)
return fragment
}
compileElement(el) {
let childNodes = el.childNodes
//使伪数组变为数组在forEach循环处理
Array.from(childNodes).forEach(node => {
//获取节点文本内容
let text = node.textContent
//使用简单正则匹配{{}}
let reg = /\{\{(.*)\}\}/;
if (this.isElementNode(node)) {
// 如果是一个标签
this.compile(node)
}
if (this.isTextNode(node) && reg.test(text)) {
// 文本节点 并且包含{{}}
this.compileText(node, RegExp.$1)
// this.compileText(node,)
}
//获取子节点递归调用函数
if (node.childNodes && node.childNodes.length) {
this.compileElement(node)
}
})
}
compileText(node, key) {
console.log('文本替换 并且可以收集依赖', node, key)
this.text(node, this.$vm, key)
}
compile(node) {
let attrs = node.attributes
Array.from(attrs).forEach(attr => {
const attrName = attr.name
const key = attr.value
if (this.isVueDirective(attrName)) {
// 是vue的属性
console.log('vue的变量', attrName)
// 过滤到k-
// k-text
// k-model
// k-html
const dir = attrName.slice(2)
if (this[dir]) {
this[dir](node, this.$vm, key)
}
}
if (this.isVueEvent(attrName)) {
console.log('vue的事件绑定', attrName)
// @click ==> click
let action = attrName.substring(1)
this.eventHander(node, this.$vm, key, action)
// 如果是绑定的事件
}
})
// 标签比较复杂 在这里统一做
}
eventHander(node, vm, key, action) {
const fn = vm.$options.methods && vm.$options.methods[key]
node.addEventListener(action, fn.bind(vm), false)
}
text(node, vm, key) {
this.update(node, vm, key, 'text')
}
html(node, vm, key) {
this.update(node, vm, key, 'html')
}
model(node, vm, key) {
this.update(node, vm, key, 'model')
// 双向绑定 做数据修改
node.addEventListener('input', e => {
const newValue = e.target.value
vm[key] = newValue
})
}
update(node, vm, key, dir) {
const fn = this[dir + "Updater"]
fn && fn(node, vm[key])
// 实际的更新操作
// 自动添加依赖
new Watcher(vm, key, value => {
fn && fn(node, value)
})
}
textUpdater(node, value) {
node.textContent = value
}
htmlUpdater(node, value) {
node.innerHTML = value
}
modelUpdater(node, value) {
node.value = value
}
isVueDirective(name) {
return name.indexOf('k-') == 0
}
isVueEvent(name) {
return name.indexOf('@') == 0
}
isElementNode(node) {
return node.nodeType == 1
}
isTextNode(node) {
return node.nodeType == 3
}
}
e-vue.js
// 依赖收集类
class Dep {
constructor() {
// 数组里的是依赖(监听器)
this.deps = []
}
// 依赖添加到数组中
addDeps(dep) {
this.deps.push(dep)
}
// 循环缓存数组里的依赖循环触发通知更新
notify() {
this.deps.forEach(dep => {
dep.update()
})
}
}
Dep.target = null
class Watcher {
constructor(vm, key, cb) {
this.vm = vm
this.cb = cb
this.key = key
//Dep的target设置为监听器
this.value = this.get()
}
get() {
//收集依赖
Dep.target = this
//读取数据自动收集依赖
let value = this.vm[this.key]
Dep.target = undefined
return value
}
update() {
this.value = this.get()
this.cb.call(this.vm, this.value)
// 实际更新的函数
// console.log('视图想更新~~ 监听器收到')
}
}
class EVue {
constructor(options) {
this.$options = options
this.$data = options.data
//递归为data里的数据添加getter/setter使其变为响应式的。
this.observer(this.$data)
// new Watcher()
// console.log('模拟render,出发name的getter',this,name)
if (options.el) {
this.$compile = new Compile(options.el, this)
}
if (options.created) {
options.created.call(this)
}
//设置$data在this上的依赖
}
observer(data) {
Object.keys(data).forEach(key => {
this.proxyData(key)
this.defineReactive(data, key, data[key])
})
}
defineReactive(obj, key, val) {
// 新建一个依赖
// 每一个key一个依赖收集的小朋友
let dep = new Dep()
Object.defineProperty(obj, key, {
get() {
// 收集依赖 addDep
// console.log('收集依赖')
Dep.target && dep.addDeps(Dep.target)
return val
},
set(newVal) {
val = newVal
// console.log('通知依赖去更新')
dep.notify()
// 通知依赖去更新
// 依赖.notify
}
})
}
proxyData(key) {
Object.defineProperty(this, key, {
get() {
return this.$data[key]
},
set(newVal) {
this.$data[key] = newVal
}
})
}
}
Comments | NOTHING