看过Vue源码后不难发现,Vue洋洋洒洒上万行代码,但是核心的代码并不会太多。其中很大一部分是各平台的兼容性代码以及异常的捕获与处理。虽然阅读这些代码对我们编写更严谨的代码有很大的帮助,但是对我们了解Vue的核心原理造成了巨大得阻碍,所以是否可以把这些代码删除,只保留核心代码,实现一个能够正常工作的Vue呢。
export function Vue(options = {}) {
this._init(options)
}
Vue.prototype._init = function(options) {
this.$options = options
this.$el = options.el
this.$data = options.data
this.$methods = options.methods
proxy(this, this.$data)
observer(this.$data)
new Compiler(this);
}
function proxy(target, data) {
Object.keys(data).forEach(key => {
Object.defineProperty(target, key, {
enumerable: true,
configurable: true,
get() {
return data[key]
},
set(newVal) {
if(isSameValue(newVal, data[key])) return;
data[key] = newVal
}
})
})
}
function observer(data) {
new Observer(data)
}
class Observer {
constructor (data) {
this.walk(data)
}
walk(data) {
if(data && typeof data === 'object') {
Object.keys(data).forEach(key => this.defineReactive(data, key, data[key]))
}
}
// 收集data中每个数据
defineReactive(obj, key, value) {
let that = this
// 递归
this.walk(value)
let dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
Dep.target && dep.add(Dep.target);
return value
},
set(newVal) {
if(isSameValue(newVal, value)) return;
value = newVal
// 此部操作,防止新值是对象或者数组,如果是对象或者数组,需要对新值newVal所有元素添加响应式,反之则不需要
// 注意:此时的this执行发生变化,that指当前实例
that.walk(newVal)
dep.notify();
}
})
}
}
// 视图更新
// 数据更新,需要更新视图,需要去观察
class Watcher {
constructor(vm, key, cb) {
this.vm = vm; // 实例
this.key = key;
this.cb = cb;
Dep.target = this;
this.__old = vm[key];
Dep.target = null;
}
update() {
let newVal = this.vm[this.key];
if(!isSameValue(newVal, this.__old)) this.cb(newVal);
}
}
class Dep {
constructor() {
this.watchers = new Set();
}
add(watcher) {
if(watcher && watcher.update) this.watchers.add(watcher)
}
notify() {
this.watchers.forEach(watcher => watcher.update())
}
}
class Compiler {
constructor(vm) {
this.el = vm.$el;
this.vm = vm;
this.methods = vm.$methods;
this.compile(vm.$el);
}
// 这里是递归编译 #app 下面的所有的节点内容;
compile(el) {
let childNodes = el.childNodes;
// 类数组
Array.from(childNodes).forEach(node => {
// 判断如果是文本节点
if(node.nodeType === 3) {
this.compileText(node)
}
// 如果是元素节点
else if(node.nodeType === 1) {
this.compileElement(node)
}
// 如果还有子节点,就递归下去。
if(node.childNodes && node.childNodes.length) this.compile(node);
// ...
})
}
compileText(node) {
// 匹配出来 {{massage}}
let reg = /\{\{(.+?)\}\}/;
let value = node.textContent;
if(reg.test(value)) {
let key = RegExp.$1.trim()
// 开始时赋值。
node.textContent = value.replace(reg, this.vm[key]);
// 添加观察者
new Watcher(this.vm, key, val => {
// 数据改变时的更新
node.textContent = val;
})
}
}
compileElement(node) {
// 简化,只做匹配 v-on 和 v-model 的匹配
if(node.attributes.length) {
Array.from(node.attributes).forEach(attr => {
let attrName = attr.name;
if(attrName.startsWith('v-')) {
// v- 指令匹配成功,可能是 v-on:click 或者 v-model
attrName = attrName.indexOf(':') >-1 ? attrName.substr(5): attrName.substr(2)
let key = attr.value;
//
this.update(node, key, attrName, this.vm[key])
}
})
}
}
update(node, key, attrName, value) {
if(attrName === 'model') {
node.value = value;
new Watcher(this.vm, key, val => node.value = val);
node.addEventListener('input', () => {
this.vm[key] = node.value;
})
} else if (attrName === 'click') {
node.addEventListener(attrName, this.methods[key].bind(this.vm))
}
}
}
function isSameValue(a, b) {
// 考虑NaN情况
return a === b || ((Number.isNaN(a)) && (Number.isNaN(b)))
}