<!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>
<style>
</style>
<div id="app" style="width:500px;height:500px;border:1px solid red;position:relative">
<h1>{{msg}}</h1>
<div class="pge">我的年龄 最大是</div>
<h1>{{age.max}}</h1>
<h2>{{hello}}</h2>
<button @click="beBiggerAge">修改最大年龄</button>
<input type="text" v-model="age.max">
</div>
</body>
</html>
复制代码
class Vue {
constructor(options) {
this.$options = options;
this._init();
}
_init() {
this.$data = this.initData();
this.$methods = this.$options.methods;
this.$computed = this.$options.computed;
this.$watch = this.$options.watch;
new Observer(this.$data)
this.proxyData(this.$data);
this.proxyData(this.$methods);
this._watch();
this.$options.created.apply(this)
if (this.el) this.$options.$mount(this.el)
this.$options.mounted.apply(this)
}
_watch() {
Object.keys(this.$watch).forEach(key => {
new Watcher(key, this, (newValue, oldValue) => {
this.$watch[key].call(this, newValue, oldValue)
})
})
}
//代理this 使得可以直接访问 this.data this.method
proxyData(proxy) {
Object.keys(proxy).forEach(key => {
Object.defineProperty(this, key, {
get() {
return proxy[key]
},
set(newValue) {
if (newValue !== proxy[key]) proxy[key] = newValue
}
})
})
}
//初始化 data
initData() {
const type = typeof this.$options.data;
return type === 'function' ? this.$options.data() : this.$options.data
}
//挂载 element
$mount(el) {
if (typeof el === 'string') this.$el = document.querySelector(el)
else if (el.nodeType === 1) this.$el = el
else throw new Error('节点错误')
new Compiler(this.$el, this)
}
}
class Compiler {
constructor(el, vm) {
this.$vm = vm
this.$el = el;
let fragment = this.vNodeFragment(el)
this.compilerFragment(fragment);
this.$el.appendChild(fragment)
}
vNodeFragment(el) {
let fragment = document.createDocumentFragment(); //创建文档碎片
while (el.firstChild) fragment.appendChild(el.firstChild);
return fragment
}
compilerFragment(fragment) {
const childNodes = fragment.childNodes;
childNodes.forEach(node => {
if (node.nodeType === 1) {
this.compileElement(node);
this.compilerFragment(node);
} else {
this.compileText(node)
}
})
}
compileText(node) {
let reg = /\{\{(.+?)\}\}/g;
let text = node.textContent;
if (reg.test(text)) {
let variable = CompilerUtils.getTextVariable(text);
const isComputed = this.$vm.$computed[variable];
//编辑 computed 属性
if (isComputed) {
CompilerUtils.computed(variable, node, this.$vm)
// console.log(isComputed, variable)
} else {
CompilerUtils.text(variable, node, this.$vm)
}
}
}
compileElement(node) {
const attrs = [...node.attributes];
attrs.forEach(attr => {
const name = attr.name
if (name.includes('v-')) {
//处理指令
const value = attr.value;
const [, type] = name.split('v-');
CompilerUtils[type](value, node, this.$vm)
} else if (name.includes('@')) {
//处理事件
const [, event] = name.split('@')
const method = attr.value;
// console.log(method, event, node)
CompilerUtils['addEvent'](method, event, node, this.$vm)
}
})
}
}
//计算属性
class ComputedWathcer {
constructor(variable, vm, cb) {
this.variable = variable;
this.vm = vm;
this.cb = cb;
this.value = this.getValue();
}
getValue() {
Dep.target = this;
let value = this.vm.$computed[this.variable].call(this.vm);
Dep.target = null;
return value;
}
update() {
let newValue = this.vm.$computed[this.variable].call(this.vm);
if (newValue !== this.value) this.cb(newValue, this.value)
}
}
//模板编译工具
const CompilerUtils = {
addEvent(method, event, node, vm) {
node.addEventListener(event, (...args) => {
vm[method].apply(vm, args)
})
},
textUpdater(node, value) {
node.textContent = value
},
computed(variable, node, vm) {
let fn = this.textUpdater;
let computedIns = new ComputedWathcer(variable, vm, (nv, ov) => {
fn && fn(node, nv)
})
fn && fn(node, computedIns.value)
},
text(variable, node, vm) {
let fn = this.textUpdater;
let value = this.getValue(variable, vm);
new Watcher(variable, vm, (newValue) => {
fn && fn(node, newValue)
})
fn && fn(node, value)
},
getTextVariable(variable) {
let reg = /\{\{(.+?)\}\}/g;
let res = variable.replace(reg, ($0, $1) => $1)
return res;
},
getValue(variable, vm) {
return variable.split('.').reduce((prev, next) => {
return prev[next]
}, vm.$data)
},
setValue(variable, vm, newValue) {
const keys = variable.split('.')
const len = keys.length;
keys.reduce((prev, next, index) => {
if (index === len - 1) prev[next] = newValue
return prev[next]
}, vm.$data)
},
inputUpdater(node, value) {
node.value = value;
},
model(variable, node, vm) {
const value = this.getValue(variable, vm);
const fn = this.inputUpdater
fn && fn(node, value)
node.addEventListener('input', (event) => {
const newValue = event.target.value
if (newValue !== value) this.setValue(variable, vm, newValue)
})
new Watcher(variable, vm, (newValue) => {
fn && fn(node, newValue)
})
}
}
//劫持数据 双向绑定
class Observer {
constructor(data) {
this.observe(data)
}
observe(data) {
if (!data || typeof data !== 'object') return;
Object.keys(data).forEach(key => {
this.defineReactive(key, data[key], data);
if (typeof data[key] === 'object') this.observe(data[key])
})
}
defineReactive(key, value, data) {
let dep = new Dep();
let _this = this;
Object.defineProperty(data, key, {
get() {
Dep.target && dep.addSub(Dep.target)
return value
},
set(newValue) {
if (newValue !== value) {
value = newValue;
_this.observe(newValue);
dep.notify();
}
}
})
}
}
class Dep {
constructor() {
this.subs = []
}
addSub(wathcer) {
this.subs.push(wathcer)
}
notify() {
this.subs.forEach(w => w.update())
}
}
class Watcher {
constructor(variable, vm, cb) {
this.variable = variable
this.vm = vm
this.cb = cb
this.value = this.get();
}
get() {
Dep.target = this;
const value = CompilerUtils.getValue(this.variable, this.vm);
Dep.target = null;
return value
}
update() {
let newValue = CompilerUtils.getValue(this.variable, this.vm);
let oldValue = this.value;
if (newValue !== oldValue) this.cb(newValue, oldValue)
}
}
new Vue({
data() {
return {
msg: '第一次测试',
name: 'mike',
age: {
max: 100,
min: 10
}
}
},
watch: {
msg(newV, oldV) {
console.log(newV, oldV)
}
},
computed: {
hello() {
return `你好我是mike, 今年${this.age.max * 2}岁。`;
}
},
methods: {
beBiggerAge() {
this.msg = '我可以活到 200 岁了';
// this.age.max = 200;
}
},
created() {
this.msg = 'created 已创建';
console.log('created')
},
mounted() {
console.log('mounted')
}
}).$mount('#app');
复制代码
可以直接测试 欢迎指正,后期会加上注释.
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END