package.json
{
"name": "vue-app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "webpack --mode development",
"build": "webpack --mode production",
"start": "webpack serve"
},
"author": "",
"license": "ISC",
"devDependencies": {
"clean-webpack-plugin": "^3.0.0",
"css-loader": "^5.0.1",
"html-webpack-plugin": "^5.0.0-alpha.14",
"less": "^3.12.2",
"less-loader": "^7.1.0",
"style-loader": "^2.0.0",
"vue": "^2.6.12",
"webpack-cli": "^4.2.0",
"webpack-dev-server": "^3.11.0",
"webpack-hot-middleware": "^2.25.0"
},
"dependencies": {}
}
webpack.config.js
const { resolve } = require("path");
const webpack = require("webpack");
const { CleanWebpackPlugin } = require("clean-webpack-plugin")
const HtmlWebpackPlugin = require(‘html-webpack-plugin‘);
module.exports = {
entry: [
// 由于 index.html 等html文件
// 不在依赖跟踪里面所以不会进行 HMR
"./src/index.js",
],
output: {
filename: "[name]-bundle-[fullhash:10].js",
path: resolve(__dirname, "dist")
},
module: {
rules: [
{
test: /\.css$/,
use: [
"style-loader",
"css-loader"
],
},
{
test: /\.less$/,
use: [
"style-loader",
"css-loader",
// 需要安装 less 和 less-loader
"less-loader"
]
}
]
},
plugins: [
注意,这里的插件是顺序执行的
new webpack.progressplugin(),
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: resolve(__dirname, "src", "static", "index.html")
}),
// HMR modules
// new webpack.HotModuleReplacementPlugin(),
// new webpack.NoEmitOnErrorsPlugin()
],
mode: "development",
// webpack-dev-server
devServer: {
host: "localhost",
port: 5001,
// recompile [JS module] when js file modified
hot: true,
// refresh page when recompile [JS module]
inline: true,
open: true,
compress: true,
watchContentBase: true,
contentBase: resolve(__dirname, "dist"),
// watchOptions: {
// poll: true
// }
}
}
src/index.js
import "./index.less"
console.log("init index.js")
import ‘./mvue/MVue‘
// if (module.hot) {
// module.hot.accept()
// }
src/index.less
#app {
display: flex;
justify-content: center;
align-items: center;
height: 500px;
flex-direction: column;
font-size: 30px;;
}
.hidden {
display: block;
border: 1px solid black;
}
src/static/index.html
<!DOCTYPE html>
<head>
<!-- <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script> -->
<!-- <script src="../MVue.js"></script> -->
</head>
<body>
<div id="app">
{{person.name}}---{{person.age}}
<div>普通的 div 分隔新</div>
<!-- <div v-text="person.age">{{person.name}}</div> -->
<div v-text="msg"></div>
<div v-html="htmlStr"></div>
<input type="text" v-model="person.name"/>
<!-- v-for -->
<!-- { hidden: isActive } -->
<div v-bind:class="isActive">box shown</div>
<div :class="isActive">box shown</div>
<button v-on:click="onClickButton">测试事件命令</button>
<button @click="onClickButton">测试 @</button>
<!-- isActive=!isActive -->
</div>
</body>
</html>
<script defer>
// defer 对内联无效
// 等待 外部 MVue 加载完成
document.addEventListener(‘DOMContentLoaded‘,function(){
const vm = new MVue({
el: "#app",
data: function () {
return {
msg: "v text 测试",
person: {
name: "markaa",
age: 80
},
htmlStr: "<h2>测试 v-html</h2>",
isActive: "hidden"
}
},
methods: {
onClickButton(e) {
this.$data.msg += "1"
console.log(this.$data.msg);
}
}
});
window.vm = vm;
});
</script>
src/mvue/MVue.js
const {Observer, Watcher, Dep} = require("./Observer");
class MVue {
constructor(options) {
this.$options = options;
this.$data = options.data;
if (typeof this.$data == ‘function‘) {
this.$data = this.$data();
}
this.$el = options.el;
new Observer(this.$data);
new Compiler(this.$el, this);
}
}
class Compiler {
constructor(el, vm) {
this.vm = vm;
const node = this.isElementNode(el) ? el : document.querySelector(el);
const fragments = this.node2Fragment(node);
this.Compile(fragments);
node.appendChild(fragments);
}
getValue(expr, data) {
return expr.split(".").reduce((value, field) => {
return value[field];
}, data);
}
setValue(expr, newValue, data) {
let findObj = data;
const fields = expr.split(".");
for(let i = 0 ; i < fields.length - 1; i++){
findObj = findObj[fields[i]];
}
findObj[fields[fields.length - 1]] = newValue;
// return expr.split(".").reduce((value, field) => {
// return value[field];
// }, data);
}
compute() {
// 目前只能支持 变量绑定,不支持 js 表达式
return {
text: (expr, eventName) => {
const viewData = this.getValue(expr, this.vm.$data);
return viewData;
},
html: (expr, eventName) => {
const viewData = this.getValue(expr, this.vm.$data);
return viewData;
},
model: (expr, eventName) => {
const viewData = this.getValue(expr, this.vm.$data);
return viewData;
},
bind: (expr, eventName) => {
const viewData = this.getValue(expr, this.vm.$data);
return viewData;
},
on: (expr, eventName) => {
return this.vm.$options.methods[expr];
},
textExpr: (expr) => {
return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
return this.getValue(args[1], this.vm.$data);
});
}
};
}
updaters() {
return {
text: (node, value) => {
node.textContent = value;
},
html: (node, value) => {
node.innerHTML = value
},
model: (node, value) => {
node.value = value;
},
bind: (node, value, eventName) => {
node.setAttribute(eventName, value);
},
on: (node, value, eventName) => {
node.addEventListener(eventName, value.bind(this.vm));
}
};
// 目前只能支持 变量绑定,不支持 js 表达式
}
Compile(fragments) {
// 对 frag 里面的元素进行编译,根据类型
fragments.childNodes.forEach(node => {
if (node.nodeType == 1) {
// 元素节点
// 查找是否拥有指令、
const attributes = node.attributes;
[...attributes].forEach(attr => {
// 检测如果是 v- 指令,就使用指定的编译方法
const { name, value } = attr;
let directive, directiveName, eventName, viewData;
// 判断分支里面仅仅做 获取 事件名 和 属性值 的功能,细节功能在下一级别
if (this.isDirective(name)) {
[, directive] = name.split("v-");
// 可能拥有事件名称,例如 v-on:click
[directiveName, eventName] = directive.split(":");
// 不能统一在这里面使用 getValue,因为属性值不仅仅是 data 数据绑定,还有 on 的 来自与 methods 里面的函数名字或者的 js 表达式
// 统一传到 updater 里面 分别 处理 viewData = this.getValue(value, this.vm.$data);
console.log(directiveName, eventName, value);
// 在这里创建 观察者, 获取 监听的 expr 和 更新回调函数
// 由于在 构造函数里面将 watcher 挂到 target,所以不会被垃圾回收
new Watcher(this.vm.$data, node, value, (newValue) => {
this.updaters()[directiveName](node, newValue, eventName);
});
// 在这步,会调用在 observer 里面劫持定义的 get 方法。
viewData = this.compute()[directiveName](value, eventName);
this.updaters()[directiveName](node, viewData, eventName);
this.clearDirective(node, name);
// 如果是输入类型控件,需要进行双向绑定
if (directiveName == "model") {
node.addEventListener("input", (event) => {
const newValue = event.target.value;
this.setValue(value, newValue, this.vm.$data);
});
}
} else if (this.isAtDirective(name)) {
[, eventName] = name.split("@");
// 可能拥有事件名称,例如 v-on:click
directiveName = "on";
// console.log(directiveName, eventName, value);
viewData = this.compute()[directiveName](value, eventName);
this.updaters()[directiveName](node, viewData, eventName);
this.clearDirective(node, name);
} else if (this.isBindDirective(name)) {
[, eventName] = name.split(":");
// 可能拥有事件名称,例如 v-on:click
directiveName = "bind";
// console.log(directiveName, eventName, value);
new Watcher(this.vm.$data, node, value, (newValue) => {
this.updaters()[directiveName](node, newValue, eventName);
});
viewData = this.compute()[directiveName](value, eventName);
this.updaters()[directiveName](node, viewData, eventName);
this.clearDirective(node, name);
}
// 是正常属性,不做处理
});
} else if (node.nodeType == 3) {
// 文本节点
let viewData = node.textContent;
// 查找是否包含差值表达式
if (viewData.indexOf("{{") >= 0) {
new Watcher(this.vm.$data, node, viewData, (newValue) => {
// TODO 需要是 textExpr 类型的计算
this.updaters()["text"](node, newValue);
});
viewData = this.compute()["textExpr"](viewData);
this.updaters()["text"](node, viewData);
}
}
});
}
node2Fragment(node) {
const fragments = document.createDocumentFragment();
let firstChild;
while (firstChild = node.firstChild) {
fragments.appendChild(firstChild);
}
return fragments;
}
isElementNode(node) {
return node.nodeType == 1;
}
isDirective(directiveName) {
return directiveName.startsWith("v-");
}
isAtDirective(directiveName) {
return directiveName.startsWith("@");
}
isBindDirective(directiveName) {
return directiveName.startsWith(":");
}
clearDirective(node, attrName) {
node.removeAttribute(attrName);
}
}
window.MVue = MVue
src/mvue/Observer.js
class Observer {
constructor(data) {
this.data = data;
this.observe(this.data);
}
observe(data) {
if (data && typeof data == "object") {
Object.keys(data).forEach(key => {
this.defineReactive(data, key, data[key]);
});
}
}
// 对于 data 来说,如果是非 object 类型的成员,就直接劫持属性
// 否则递归一直到普通值
defineReactive(data, key, value) {
this.observe(value);
// 定义依赖收集器, 然后每次调用 get 的时候(也就是在 compile 的时候,调用 getValue 的时候会调用 get)
// 此时,从 compiler 那里获取 node 和 expr。 即观察者对象
// 对该变量(或者 从 compiler 看来是 expr )的全部依赖 对象
// 闭包引用 dep
const dep = new Dep();
Object.defineProperty(data, key, {
enumerable: true,
configurable: false,
get: () => {
if (Dep.target) {
dep.addWatcher(Dep.target);
}
console.log(dep);
return value;
},
// 这里面必须要使用 箭头函数,从而使得 this 指向 Observer.否则是 data
set: (newValue) => {
// 如果用户赋值了一个 object 变量,就需要对新值进行监听
this.observe(newValue);
if (newValue != value) {
value = newValue;
}
dep.notify();
}
});
}
}
class Watcher {
constructor(data, node, expr, callback) {
this.data = data;
this.node = node;
this.expr = expr;
this.callback = callback;
this.oldValue = this.getOldValue();
}
getValue(expr, data) {
if (expr.indexOf("{{") > -1) {
return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
return this.getValue(args[1], this.data);
});
} else {
return expr.split(".").reduce((value, field) => {
return value[field];
}, data);
}
}
getOldValue() {
// 将当前的 watcher 绑到 Dep 下面。
Dep.target = this;
// 取值,会触发劫持的 get 方法
let value = this.getValue(this.expr, this.data);
Dep.target = null;
return value;
}
update() {
let value = this.getValue(this.expr, this.data);
this.callback(value);
}
}
class Dep {
constructor(node, expr) {
this.watcher = [];
// this.oldValue = this.getOldValue();
}
// getOldValue() {
// return
// }
addWatcher(watcher) {
this.watcher.push(watcher);
}
notify() {
this.watcher.forEach(w => {
w.update();
});
}
}
module.exports = {
Observer,
Watcher,
Dep
};
原文:https://www.cnblogs.com/yumingle/p/14121768.html