一、项目环境搭建
项目环境和之前的一样,由webpack和webpack-dev-server搭建而成。
{
"name": "study-snabbdom",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "webpack-dev-server"
},
"author": "",
"license": "ISC",
"dependencies": {
},
"devDependencies": {
"webpack": "^5.11.0",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0"
}
}
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
publicPath: 'xuni',
filename: 'bundle.js'
},
devServer: {
port: 8080,
contentBase: 'www'
}
};
二、Vue类的创建
import Vue from './Vue.js';
window.Vue = Vue;
import Compile from "./Compile.js";
import observe from './observe.js';
import Watcher from './Watcher.js';
export default class Vue {
constructor(options) {
this.$options = options || {};
this._data = options.data || undefined;
observe(this._data);
this._initData();
this._initWatch();
new Compile(options.el, this);
}
_initData() {
var self = this;
Object.keys(this._data).forEach(key => {
Object.defineProperty(self, key, {
get: () => {
return self._data[key];
},
set: (newVal) => {
self._data[key] = newVal;
}
});
});
}
_initWatch() {
var self = this;
var watch = this.$options.watch;
Object.keys(watch).forEach(key => {
new Watcher(self, key, watch[key]);
});
}
};
三、Fragment的生成
import Watcher from './Watcher.js';
export default class Compile {
constructor(el, vue) {
this.$vue = vue;
this.$el = document.querySelector(el);
if (this.$el) {
let $fragment = this.node2Fragment(this.$el);
this.compile($fragment);
this.$el.appendChild($fragment);
}
}
node2Fragment(el) {
var fragment = document.createDocumentFragment();
var child;
while (child = el.firstChild) {
fragment.appendChild(child);
}
return fragment;
}
compile(el) {
var childNodes = el.childNodes;
var self = this;
var reg = /\{\{(.*)\}\}/;
childNodes.forEach(node => {
var text = node.textContent;
(text);
if (node.nodeType == 1) {
self.compileElement(node);
} else if (node.nodeType == 3 && reg.test(text)) {
let name = text.match(reg)[1];
self.compileText(node, name);
}
});
}
compileElement(node) {
var nodeAttrs = node.attributes;
var self = this;
[].slice.call(nodeAttrs).forEach(attr => {
var attrName = attr.name;
var value = attr.value;
var dir = attrName.substring(2);
if (attrName.indexOf('v-') == 0) {
if (dir == 'model') {
new Watcher(self.$vue, value, value => {
node.value = value;
});
var v = self.getVueVal(self.$vue, value);
node.value = v;
node.addEventListener('input', e => {
var newVal = e.target.value;
self.setVueVal(self.$vue, value, newVal);
v = newVal;
});
} else if (dir == 'if') {
}
}
});
}
compileText(node, name) {
node.textContent = this.getVueVal(this.$vue, name);
new Watcher(this.$vue, name, value => {
node.textContent = value;
});
}
getVueVal(vue, exp) {
var val = vue;
exp = exp.split('.');
exp.forEach(k => {
val = val[k];
});
return val;
}
setVueVal(vue, exp, value) {
var val = vue;
exp = exp.split('.');
exp.forEach((k, i) => {
if (i < exp.length - 1) {
val = val[k];
} else {
val[k] = value;
}
});
}
}
四、初始数据的响应式和watch
把之前学的手写响应式代码拿过来
import Observer from './Observer.js';
export default function (value) {
if (typeof value != 'object') return;
var ob;
if (typeof value.__ob__ !== 'undefined') {
ob = value.__ob__;
} else {
ob = new Observer(value);
}
return ob;
}
import { def } from './utils.js';
import defineReactive from './defineReactive.js';
import { arrayMethods } from './array.js';
import observe from './observe.js';
import Dep from './Dep.js';
export default class Observer {
constructor(value) {
this.dep = new Dep();
def(value, '__ob__', this, false);
if (Array.isArray(value)) {
Object.setPrototypeOf(value, arrayMethods);
this.observeArray(value);
} else {
this.walk(value);
}
}
walk(value) {
for (let k in value) {
defineReactive(value, k);
}
}
observeArray(arr) {
for (let i = 0, l = arr.length; i < l; i++) {
observe(arr[i]);
}
}
};
export const def = function (obj, key, value, enumerable) {
Object.defineProperty(obj, key, {
value,
enumerable,
writable: true,
configurable: true
});
};
import observe from './observe.js';
import Dep from './Dep.js';
export default function defineReactive(data, key, val) {
const dep = new Dep();
if (arguments.length == 2) {
val = data[key];
}
let childOb = observe(val);
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get() {
if (Dep.target) {
dep.depend();
if (childOb) {
childOb.dep.depend();
}
}
return val;
},
set(newValue) {
console.log('你试图改变' + key + '属性', newValue);
if (val === newValue) {
return;
}
val = newValue;
childOb = observe(newValue);
dep.notify();
}
});
};
import { def } from './utils.js';
const arrayPrototype = Array.prototype;
export const arrayMethods = Object.create(arrayPrototype);
const methodsNeedChange = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
];
methodsNeedChange.forEach(methodName => {
const original = arrayPrototype[methodName];
def(arrayMethods, methodName, function () {
const result = original.apply(this, arguments);
const args = [...arguments];
const ob = this.__ob__;
let inserted = [];
switch (methodName) {
case 'push':
case 'unshift':
inserted = args;
break;
case 'splice':
inserted = args.slice(2);
break;
}
if (inserted) {
ob.observeArray(inserted);
}
ob.dep.notify();
return result;
}, false);
});
var uid = 0;
export default class Dep {
constructor() {
this.id = uid++;
this.subs = [];
}
addSub(sub) {
this.subs.push(sub);
}
depend() {
if (Dep.target) {
this.addSub(Dep.target);
}
}
notify() {
const subs = this.subs.slice();
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update();
}
}
};
import Dep from "./Dep";
var uid = 0;
export default class Watcher {
constructor(target, expression, callback) {
console.log('我是Watcher类的构造器');
this.id = uid++;
this.target = target;
this.getter = parsePath(expression);
this.callback = callback;
this.value = this.get();
}
update() {
this.run();
}
get() {
Dep.target = this;
const obj = this.target;
var value;
try {
value = this.getter(obj);
} finally {
Dep.target = null;
}
return value;
}
run() {
this.getAndInvoke(this.callback);
}
getAndInvoke(cb) {
const value = this.get();
if (value !== this.value || typeof value == 'object') {
const oldValue = this.value;
this.value = value;
cb.call(this.target, value, oldValue);
}
}
};
function parsePath(str) {
var segments = str.split('.');
return (obj) => {
for (let i = 0; i < segments.length; i++) {
if (!obj) return;
obj = obj[segments[i]]
}
return obj;
};
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
你好{{b.m.n}}
<br />
<input type="text" v-model="b.m.n">
</div>
<button onclick="add()">按我加1</button>
<script src="/xuni/bundle.js"></script>
<script>
var vm = new Vue({
el: '#app',
data: {
a: 10,
b: {
m: {
n: 7
}
}
},
watch: {
a() {
console.log('a改变啦');
}
},
created() {
},
update() {
}
});
console.log(vm)
function add() {
vm.b.m.n++;
}
</script>
</body>
</html>