手写简易版 Vue 2.0.2
简易版 Vue 2.0.2, 增加功能
增加 $mount(el)
,作用:
- 获取宿主
updateComponet
- 创建
Watcher
Watcher
改造
- 能够传入
updateComponet
- 和
dep
对应关系1:n
测试代码
js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app"></div>
<script src="./kvue.js"></script>
<script>
const app = new Kvue({
el: '#app',
data: {
counter: 1,
},
render(h) {
// const d1 = document.createElement('div')
// d1.id = 'app'
// const p1 = document.createElement('p')
// p1.textContent = this.counter
// d1.appendChild(p1)
// return d1
// 使用 h 函数
return h('div', {id: 'app', title: this.counter}, [
h('p', null, this.counter + '')
])
}
})
setInterval(() => {
app.counter++
}, 1000)
</script>
</body>
</html>
升级版的简易版 Vue 2.0
js
/**
增加 `$mount(el)` ,作用:
- 获取宿主
- `updateComponet`
- 创建 `Watcher`
`Watcher` 改造
- 能够传入 `updateComponet`
- 和 `dep` 对应关系 `1:n`
*/
/**
* 1、对 data 选项进行响应式处理
* 2、编辑模板
* 3、指令和事件
* 4、依赖收集
*/
class Kvue {
constructor(options) {
this.$options = options;
this.$data = options.data;
// 响应式
observe(this.$data);
// 代理下
proxy(this);
// compile 编辑模板
// new Compile(options.el, this)
if (options.el) {
this.$mount(options.el);
}
}
$mount(el) {
// 获取宿主
this.$el = document.querySelector(el);
// 创建 updateComponent
const updateComponent = () => {
// 获取渲染函数
const { render } = this.$options;
const vnode = render.call(this, this.$createElement);
// vnode 变成 dom
this._update(vnode);
// const parent = this.$el.parentElement
// parent.insertBefore(el, this.$el.nextSibling)
// parent.removeChild(this.$el)
// this.$el = el
};
// 创建根组件对应的 Watcher
new Watcher(this, updateComponent);
}
// h 函数
$createElement(tag, props, children) {
return { tag, props, children };
}
_update(vnode) {
const prevVnode = this._vnode;
if (!prevVnode) {
// init
this.__patch__(this.$el, vnode);
} else {
// update
this.__patch__(prevVnode, vnode);
}
}
__patch__(oldVnode, vnode) {
// dom
if (oldVnode.nodeType) {
const parent = oldVnode.parentElement;
const refElm = oldVnode.nextSibling;
const el = this.createElm(vnode);
parent.insertBefore(el, refElm);
parent.removeChild(oldVnode);
// 保存当前 vnode
this._vnode = vnode;
} else {
// update
// 获取 el
const el = (vnode.el = oldVnode.el);
//相同节点
if (oldVnode.tag === vnode.tag) {
// props 新旧对比
const oldProps = oldVnode.props || {};
const newProps = vnode.props || {};
// 属性更新
for (const key in oldProps) {
const oldValue = oldProps[key];
const newValue = newProps[key];
if (oldValue !== newValue) {
el.setAttribute(key, newValue);
}
}
// 属性删除
for (const key in oldProps) {
if (!(key in newProps)) {
el.removeAttribute(key);
}
}
// children
const oldCh = oldVnode.children;
const newCh = vnode.children;
if (typeof newCh === 'string') {
if (typeof oldCh === 'string') {
// 新旧文本都存在且不同
if (oldCh !== newCh) {
el.textContent = newCh;
}
} else {
// 旧的没有文本
el.textContent = newCh;
}
} else {
// children
if (typeof oldCh === 'string') {
el.innerHTML = '';
newCh.forEach((child) => this.createElm(child));
} else {
// updateChildren
this.updateChildren(el, oldCh, newCh);
}
}
}
}
}
createElm(vnode) {
const el = document.createElement(vnode.tag);
// props
if (vnode.props) {
for (const key in vnode.props) {
const value = vnode.props[key];
el.setAttribute(key, value);
}
}
// children
if (vnode.children) {
if (typeof vnode.children === 'string') {
// text
el.textContent = vnode.children;
} else {
// 递归
vnode.children.forEach((n) => {
const child = this.createElm(n);
el.appendChild(child);
});
}
}
vnode.el = el;
return el;
}
updateChildren(parentElm, oldCh, newCh) {
const len = Math.min(oldCh.length, newCh.length);
for (let i = 0; i < len; i++) {
this.__patch__(oldCh[i], newCh[i]);
}
if (newCh.length > oldCh.length) {
// add
newCh.slice(len).forEach((child) => {
const el = this.createElm(child);
parentElm.appendChild(el);
});
} else if (newCh.length < oldCh.length) {
// remove
oldCh.slice(len).forEach((child) => {
const el = this.createElm(child);
parentElm.removeChild(el);
});
}
}
}
// 数据响应式
function defineReactive(obj, key, val) {
// 递归下,兼容 obj[key] = {a:10}
observe(obj[key]);
// 创建 Key、Dep、Watcher 的依赖
const dep = new Dep();
Object.defineProperty(obj, key, {
get() {
console.log('get', key);
// 依赖收集
Dep.target && dep.addDep(Dep.target);
return val;
},
set(newVal) {
if (newVal !== val) {
console.log('set', key);
// 如果 newVal 是对象,做响应式处理,比如 obj.key = {a:10}
observe(newVal);
val = newVal;
// watcher 更新
// watchers.forEach(w => w.update())
dep.notify();
}
},
});
}
// 遍历 obj, 对所有属性做响应式
function observe(obj) {
if (typeof obj !== 'object' || obj == null) {
return;
}
new Observer(obj);
}
// 代理,实现的目的 vm.$data.counter 可以通过 vm.counter 拿到
function proxy(vm) {
Object.keys(vm.$data).forEach((key) => {
Object.defineProperty(vm, key, {
get() {
return vm.$data[key];
},
set(v) {
vm.$data[key] = v;
},
});
});
}
// 根据不同类型做响应式处理
class Observer {
constructor(value) {
this.value = value;
if (Array.isArray(value)) {
// todo
} else {
this.walk(value);
}
}
walk(obj) {
Object.keys(obj).forEach((key) => {
defineReactive(obj, key, obj[key]);
});
}
}
// 解析模板,1、处理插值,2、处理指令和事件,3、以上两者的初始化和更新
class Compile {
constructor(el, vm) {
this.$vm = vm;
this.$el = document.querySelector(el);
if (this.$el) {
this.compile(this.$el);
}
}
compile(el) {
// 遍历 el 子节点,判断类型做相应的处理
const childNodes = el.childNodes;
childNodes.forEach((node) => {
if (node.nodeType === 1) {
// 元素
// console.log('元素', node.nodeName)
// 处理 指令和事件
const attrs = node.attributes;
Array.from(attrs).forEach((attr) => {
// k-xxx="abc"
const attrName = attr.name;
const exp = attr.value;
if (attrName.startsWith('k-')) {
const dir = attrName.substring(2);
this[dir] && this[dir](node, exp);
}
});
} else if (this.isInter(node)) {
// 文本 插值表达式
// console.log('插值', node.textContent)
this.compileText(node);
}
// 递归下
if (node.childNodes) {
this.compile(node);
}
});
}
// 高级函数执行 dir
update(node, exp, dir) {
// 1、初始化
const fn = this[dir + 'Updater'];
fn && fn(node, this.$vm[exp]);
// 2、更新操作
new Watcher(this.$vm, exp, function (val) {
fn && fn(node, val);
});
}
// 对应的 k-text 的指令
text(node, exp) {
// node.textContent = this.$vm[exp]
this.update(node, exp, 'text');
}
textUpdater(node, value) {
node.textContent = value;
}
// 编译 {{xxx}}文本
compileText(node) {
// node.textContent = this.$vm[RegExp.$1]
this.update(node, RegExp.$1, 'text');
}
// 对应的 k-html 的指令
html(node, exp) {
// node.innerHTML = this.$vm[exp]
this.update(node, exp, 'html');
}
htmlUpdater(node, value) {
node.innerHTML = value;
}
// 是否插值表达式
isInter(node) {
return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent);
}
}
// 监督器:负责依赖更新,有多少个变量就有多少个 watcher
// const watchers = []
class Watcher {
constructor(vm, fn) {
this.vm = vm;
// this.key = key
// this.updateFn = updateFn
this.getter = fn;
// watchers.push(this)
// Dep.target = this
// 相当于调用了一次 key 的 get 方法进行依赖收齐
// this.vm[this.key]
// Dep.target = null
this.get();
}
get() {
Dep.target = this;
this.getter.call(this.vm);
Dep.target = null;
}
// 被 Dep 调用的
update() {
// 执行实际的更新操作
// this.updateFn.call(this.vm, this.vm[this.key])
this.get();
}
}
// 实现 Dep,有多少个 key 就有多少个 dep
class Dep {
constructor() {
// this.deps = []
this.deps = new Set();
}
addDep(dep) {
// 这里的 dep 其实就是 watcher
// this.deps.push(dep)
this.deps.add(dep);
}
notify() {
// 这里的 dep 其实就是 watcher
this.deps.forEach((dep) => dep.update());
}
}