HTMl代码结构
<div id="wrap">
<p v-html="test"></p>
<input type="text" v-model="form">
<div>
多层对象渲染
<div>{{aaa.bb}}</div>
</div>
<div>计算属性: <span>{{newform}}</span></div>
<button @click="add">
++++
</button>
<button @click="sub">
---
</button>
单层对象渲染{{form}}
<div>函数渲染{{fnForm()}}</div>
</div>
js调用代码
new Vue({
el: '#wrap',
data: {
form: 0,
test: '<strong>我是粗体</strong>',
aaa: {
bb: '123'
}
},
computed: {
newform() {
return this.form * 2;
}
},
methods: {
add() {
this.form++;
},
fnForm() {
return this.form;
},
sub() {
this.form--;
}
},
created() {
console.log(this, 'vue');
}
});
vue结构
class Vue{
constructor(){}
observer(){}
compile(){}
dealComputed(){}
render(){}
}
class Watcher{
constructor(){}
update(){}
}
- Watcher 渲染视图的依赖 (局部更新)
- Vue constructor 构造函数主要是数据的初始化
- observer 劫持监听所有数据
- compile 编译dom
- dealComputed computed处理
- render 解析{{}}
vue 初始化
class Vue {
constructor(options = {}) {
this.$el = document.querySelector(options.el);
this.data = options.data;
this.callerName = '';
// 依赖收集器: 存储依赖的回调函数
this.caller = {};
this.methods = options.methods;
// 依赖收集器: 存储依赖的渲染函数
this.watcherTask = {};
// 计算属性
this.observer(this.data);
// 异步任务集合
this.taskList = new Set([]);
this.timeId = 0;
this.dealComputed(options.computed);
this.compile(this.$el); // 解析dom
// 监听的任务队列
options.created.bind(this)();
}
observer 劫持监听
observer(data) {
let that = this;
Object.keys(data).forEach(key => {
let value = data[key];
this.watcherTask[key] = new Set([]);
Object.defineProperty(this, key, {
configurable: false,
enumerable: true,
get() {
if (that.callerName) {
that.addCallback(key);
}
return value;
},
set(newValue) {
if (newValue !== value) {
value = newValue;
if (that.caller[key]) {
that.caller[key].forEach(name => {
// 放到任务列表中,避免无用的重复执行
/**
* 这里为什么要写name进去呢, 就是为了callback执行的时候 可以从wather函数中找到 对应dom的渲染函数
* 思路是 computer里的某一个函数所依赖的data的值一旦发生改变
* 在setter函数里重新调用computer函数, 去更新值
* 更新完后 再去wather里找到指定dom的渲染函数, 渲染到页面
*/
that.taskList.add({
type: name,
fn: that[name]
});
});
}
that.toExecTask(key);
}
}
});
});
}
compile 编译dom
简单实现了一下 双向绑定 v-model v-html @click
compile(el) {
let nodes = el.childNodes;
for (let i = 0; i < nodes.length; i++) {
let node = nodes[i];
// 区分文本节点和元素节点
if (node.nodeType === 3) {
/**
* 如果是文本节点 则直接取出 调用render函数渲染
* 取出文本节点的内容
*/
let text = node.textContent.trim();
if (!text) {
continue;
}
this.render(node, 'textContent');
}
else if (node.nodeType === 1) {
/**
* 元素节点 检测节点内是否还有嵌套的子节点
* 如果有 就递归再去执行
*/
if (node.childNodes.length > 0) {
this.compile(node);
}
// 编译v-model
let vmFlag = node.hasAttribute('v-model');
if (vmFlag && (node.tagName === 'INPUT' || node.tagName === 'TEXTAREA')) {
// 编辑时候input 自执行函数
node.addEventListener('input', (() => {
// 获取v-model 绑定的key
let key = node.getAttribute('v-model');
node.removeAttribute('v-model');
// 渲染视图 并添加到监听的任务队列
this.watcherTask[key].add(new Watcher(node, this, key, 'value'));
return () => {
this.data[key] = node.value;
// console.log(this.data[key]);
}
})());
}
// 编译v-html
if (node.hasAttribute('v-html')) {
let key = node.getAttribute('v-html');
node.removeAttribute('v-html');
this.watcherTask[key].add(new Watcher(node, this, key, 'innerHTML'));
}
// 编译@click
if (node.hasAttribute('@click')) {
let key = node.getAttribute('@click');
node.removeAttribute('@click');
node.addEventListener('click', () => {
if (this.methods[key]) {
this.methods[key].bind(this)();
}
});
}
this.render(node, 'innerHTML');
}
}
}
render 函数 解析{{}}
render(node, type) {
let reg = /\{\{(.*?)\}\}/g;
// 取出文本
let txt = node.textContent;
let flag = reg.test(txt);
if (flag) {
node.textContent = txt.replace(reg, ($1, $2) => {
// 是否是函数
let tpl = null;
if ($2.includes('(') || $2.includes(')')) {
//函数
// 未完成 - 函数欠缺收集依赖(回头补上)
let key = $2.replace(/[(|)]/g, '');
return this.methods[key] ? this.methods[key].bind(this)() : '';
} else {
// data
let tpl = this.watcherTask[$2] = this.watcherTask[$2] || new Set([]);
tpl.add(new Watcher(node, this, $2, type));
}
console.log($2);
// 处理对象 例如 {{data.a}}
let valArr = $2.split('.');
// 如果是 {{}}里是对象嵌套的值
if (valArr.length > 1) {
let v = null;
valArr.forEach(key => {
v = !v ? this[key] : v[key];
});
return v;
}
// 如果是 {{}}里 不是对象嵌套的值
return this[$2];
});
}
}
execTask函数 执行 taskList里收集的依赖函数更新数据 然后执行watcherTask 队列里的 渲染函数 更新视图
execTask(key) {
this.taskList.forEach(item => {
item.fn.bind(this)();
if (item.type) {
let key = item.type.replace(/_computed_/g, '');
// 渲染该dom computed
this.watcherTask[key].forEach(task => {
task.update();
});
}
});
this.taskList = new Set([]);
console.log(this.watcherTask, 'key', key);
// 渲染该dom
this.watcherTask[key].forEach(task => {
task.update();
});
}
computed 计算属性
dealComputed(computed) {
Object.keys(computed).forEach(key => {
// 回调函数名
let computedCallbackName = '_computed_' + key;
// 回调函数值
let fn = (() => {
this[key] = computed[key].bind(this)();
});
this[computedCallbackName] = fn;
// 读取值之前设置callerName
this.callerName = computedCallbackName;
fn();
});
}
简单实现了 nextTick, 并且对 多次修改data下的值 进行依赖合并调用
例如: this.a+ 1
this.a+ 1
this.a+ 1
如上接连三次修改this.a的值 这样就会导致setter函数被触发三次, 重复去执行其依赖操作, 所以每次调用依赖队列 都将其放到 异步队列中操作
// 向特定字段下加入依赖它的回调函数
addCallback(key) {
if (!this.caller[key]) {
this.caller[key] = new Set([]);
}
this.caller[key].add(this.callerName);
}
$nextTick(cb) {
this.timeId = setTimeout(cb.bind(this), 0);
}
toExecTask(key) {
if (!this.timeId) {
this.$nextTick(() => {
this.timeId = 0;
this.execTask(key);
});
}
}
Watcher 渲染函数
class Watcher {
constructor(el, vm, value, type) {
this.el = el;
this.vm = vm;
this.value = value;
this.type = type;
this.update();
}
update() {
this.el[this.type] = this.vm[this.value];
}
}
以下是源代码 vue.js 直接在index.html 中引入就好 <script src="./vuea.js"></script>
// 更细视图操作
class Watcher {
constructor(el, vm, value, type) {
this.el = el;
this.vm = vm;
this.value = value;
this.type = type;
this.update();
}
update() {
this.el[this.type] = this.vm[this.value];
}
}
class Vue {
constructor(options = {}) {
this.$el = document.querySelector(options.el);
this.data = options.data;
this.callerName = '';
// 依赖收集器: 存储依赖的回调函数
this.caller = {};
this.methods = options.methods;
this.watcherTask = {};
// 初始化劫持监听的所有数据
// 计算属性
this.observer(this.data);
// 异步任务集合
this.taskList = new Set([]);
this.timeId = 0;
this.dealComputed(options.computed);
this.compile(this.$el); // 解析dom
// 监听的任务队列
options.created.bind(this)();
}
// 向特定字段下加入依赖它的回调函数
addCallback(key) {
if (!this.caller[key]) {
this.caller[key] = new Set([]);
}
this.caller[key].add(this.callerName);
}
observer(data) {
let that = this;
Object.keys(data).forEach(key => {
let value = data[key];
this.watcherTask[key] = new Set([]);
Object.defineProperty(this, key, {
configurable: false,
enumerable: true,
get() {
if (that.callerName) {
that.addCallback(key);
}
return value;
},
set(newValue) {
if (newValue !== value) {
value = newValue;
if (that.caller[key]) {
that.caller[key].forEach(name => {
// 放到任务列表中,避免无用的重复执行
/**
* 这里为什么要写name进去呢, 就是为了callback执行的时候 可以从wather函数中找到 对应dom的渲染函数
* 思路是 computer里的某一个函数所依赖的data的值一旦发生改变
* 在setter函数里重新调用computer函数, 去更新值
* 更新完后 再去wather里找到指定dom的渲染函数, 渲染到页面
*/
that.taskList.add({
type: name,
fn: that[name]
});
});
}
that.toExecTask(key);
}
}
});
});
}
// 编译
compile(el) {
let nodes = el.childNodes;
for (let i = 0; i < nodes.length; i++) {
let node = nodes[i];
// 区分文本节点和元素节点
if (node.nodeType === 3) {
/**
* 如果是文本节点 则直接取出 调用render函数渲染
* 取出文本节点的内容
*/
let text = node.textContent.trim();
if (!text) {
continue;
}
this.render(node, 'textContent');
}
else if (node.nodeType === 1) {
/**
* 元素节点 检测节点内是否还有嵌套的子节点
* 如果有 就递归再去执行
*/
if (node.childNodes.length > 0) {
this.compile(node);
}
// 编译v-model
let vmFlag = node.hasAttribute('v-model');
if (vmFlag && (node.tagName === 'INPUT' || node.tagName === 'TEXTAREA')) {
// 编辑时候input 自执行函数
node.addEventListener('input', (() => {
// 获取v-model 绑定的key
let key = node.getAttribute('v-model');
node.removeAttribute('v-model');
// 渲染视图 并添加到监听的任务队列
this.watcherTask[key].add(new Watcher(node, this, key, 'value'));
return () => {
this.data[key] = node.value;
// console.log(this.data[key]);
}
})());
}
// 编译v-html
if (node.hasAttribute('v-html')) {
let key = node.getAttribute('v-html');
node.removeAttribute('v-html');
this.watcherTask[key].add(new Watcher(node, this, key, 'innerHTML'));
}
// 编译@click
if (node.hasAttribute('@click')) {
let key = node.getAttribute('@click');
node.removeAttribute('@click');
node.addEventListener('click', () => {
if (this.methods[key]) {
this.methods[key].bind(this)();
}
});
}
this.render(node, 'innerHTML');
}
}
}
// 计算属性
dealComputed(computed) {
Object.keys(computed).forEach(key => {
// 回调函数名
let computedCallbackName = '_computed_' + key;
// 回调函数值
let fn = (() => {
this[key] = computed[key].bind(this)();
});
this[computedCallbackName] = fn;
// 读取值之前设置callerName
this.callerName = computedCallbackName;
fn();
});
}
// 解析双括号
render(node, type) {
let reg = /\{\{(.*?)\}\}/g;
// 取出文本
let txt = node.textContent;
let flag = reg.test(txt);
if (flag) {
node.textContent = txt.replace(reg, ($1, $2) => {
// 是否是函数
let tpl = null;
if ($2.includes('(') || $2.includes(')')) {
//函数
// 欠缺收集依赖
let key = $2.replace(/[(|)]/g, '');
return this.methods[key] ? this.methods[key].bind(this)() : '';
} else {
// data
let tpl = this.watcherTask[$2] = this.watcherTask[$2] || new Set([]);
tpl.add(new Watcher(node, this, $2, type));
}
console.log($2);
// 处理对象 例如 {{data.a}}
let valArr = $2.split('.');
// 如果是 {{}}里是对象嵌套的值
if (valArr.length > 1) {
let v = null;
valArr.forEach(key => {
v = !v ? this[key] : v[key];
});
return v;
}
// 如果是 {{}}里 不是对象嵌套的值
return this[$2];
});
}
}
// 执行并清空任务队列
execTask(key) {
this.taskList.forEach(item => {
item.fn.bind(this)();
if (item.type) {
let key = item.type.replace(/_computed_/g, '');
// 渲染该dom computed
this.watcherTask[key].forEach(task => {
task.update();
});
}
});
this.taskList = new Set([]);
console.log(this.watcherTask, 'key', key);
// 渲染该dom
this.watcherTask[key].forEach(task => {
task.update();
});
}
$nextTick(cb) {
this.timeId = setTimeout(cb.bind(this), 0);
}
toExecTask(key) {
if (!this.timeId) {
this.$nextTick(() => {
this.timeId = 0;
this.execTask(key);
});
}
}
}
网友评论