标签:fun v-on 就是 赋值 字符 enum 内容 源码 判断
Vue 数据独立在 data 里面,视图在 template 中dom 操作被封装
dom来修改视图,例如 ducument.getElementById(‘xx‘).innerHTML="xxx"Model 数据 → View 视图 → Controller 控制器MVVM不算是一种创新ViewModel 是一种创新ViewModel 是真正结合前端应用场景的实现MVVM - Model View ViewModel,数据,视图,视图模型Vue 的对应:view 对应 template,vm 对应 new Vue({…}),model 对应 dataview 可以通过事件绑定的方式影响 model,model 可以通过数据绑定的形式影响到view,viewModel是把 model 和 view 连起来的连接器MVVM 框架的三大要素
Vue 如何监听到 data 的每个属性变化Vue 的模板如何被解析,指令如何处理Vue 的模板如何被渲染成 html,渲染过程是怎样的data 属性之后,Vue 立刻监听到,立刻渲染页面data 属性被代理到 vm 上JavaScript 对象,做属性修改,我们监听不到,所以需要用到 Object.definePropertyhtml 来显示vnode,解决了模板中的逻辑(v-if, v-for)问题code.render,将code打印出来,就是生成的render函数html:vm._cvm._c 和 snabbdom 中的 h 函数的实现很像,都是传入标签,属性,子元素作为参数Vue.js 的 vdom 实现借鉴了 snabbdomupdateComponent 中实现了 vdom 的 patchupdateComponentdata 中每次修改属性,都会执行 updateComponent
render 函数
updateComponent,执行 vm._render()render 函数,会访问到 data 中的值,访问时会被响应式的 get 方法监听到updateComponent,会走到 vdom 的 patch 方法patch 将 vnode 渲染成 dom,初次渲染完成get,而不是直接监听 set ?
data 中有很多属性,有些被用到,有些可能不被用到getget 中的属性,set 的时候我们也无需关心data 属性变化,触发 re-render
set 监听到set 中执行 updateComponentupdateComponent 重新执行 vm._render()vnode 和 prevVnode,通过 patch 进行对比html 中
index.html
这是最终的测试代码,我们自己实现的 Vue 在 XVue.js 和 compile.js两个文件中,加起来大概200行代码左右,主要包括功能如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title></title>
</head>
<body>
<div id="app">
{{test}}
<div v-text="test"></div>
<p>
<input type="text" v-model="test" />
</p>
<p v-html="html"></p>
<p>
<button @click="onClick">按钮</button>
</p>
</div>
<script src="./compile.js"></script>
<script src="./XVue.js"></script>
<script>
const o = new XVue({
el: '#app',
data: {
test: '123',
foo: { bar: 'bar' },
html: '<button>html test</button>'
},
methods: {
onClick() {
alert('按钮点击了')
}
}
})
console.log(o.$data.test) //123
o.$data.test = 'hello, Xvue!'
console.log(o.$data.test) //hello, Xvue!
</script>
</body>
</html>
Mini Vue 的组成部分:
XVue.js
class XVue {
constructor(options) {
this.$data = options.data;
this.observe(this.$data);
// 执行编译
new Compile(options.el, this);
}
observe(value) {
if (!value || typeof value !== 'object') {
return;
}
Object.keys(value).forEach(key => {
this.defineReactive(value, key, value[key]);
// 为vue的data做属性代理
this.proxyData(key);
});
}
defineReactive(obj, key, val) {
// 递归查找嵌套属性
this.observe(val);
// 创建Dep
const dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
// 收集依赖
Dep.target && dep.addDep(Dep.target);
// console.log(dep.deps);
return val;
},
set(newVal) {
if (newVal === val) {
return;
}
val = newVal;
dep.notify();
},
});
}
proxyData(key) {
Object.defineProperty(this, key, {
get() {
return this.$data[key];
},
set(newVal) {
this.$data[key] = newVal;
},
});
}
}
// 依赖管理器:负责将视图中所有依赖收集管理,包括依赖添加和通知
class Dep {
constructor() {
// deps里面存放的是Watcher的实例
this.deps = [];
}
addDep(dep) {
this.deps.push(dep);
}
// 通知所有watcher执行更新
notify() {
this.deps.forEach(dep => {
dep.update();
});
}
}
// Watcher: 具体的更新执行者
class Watcher {
constructor(vm, key, cb) {
this.vm = vm;
this.key = key;
this.cb = cb;
// 将来 new 一个监听器时,将当前 Watcher 实例附加到 Dep.target
// 将来通过 Dep.target 就能拿到当时创建的 Watcher 实例
Dep.target = this;
// 读取操作,主动触发 get,当前 Watcher 实例被添加到依赖管理器中
this.vm[this.key];
// 清空操作,避免不必要的重复添加(再次触发 get 就不需要再添加 watcher 了)
Dep.target = null;
}
update() {
// console.log('from Watcher update: 视图更新啦!!!');
// 通知页面做更新
this.cb.call(this.vm, this.vm[this.key]);
}
}
compile.js
// 扫描模板中所有依赖(指令、插值、绑定、事件等)创建更新函数和watcher
class Compile {
// el是宿主元素或其选择器
// vm当前Vue实例
constructor(el, vm) {
this.$el = document.querySelector(el);
this.$vm = vm;
if (this.$el) {
// 将dom节点转换为Fragment提高执行效率
this.$fragment = this.node2Fragment(this.$el);
// 执行编译,编译完成以后所有的依赖已经替换成真正的值
this.compile(this.$fragment);
// 将生成的结果追加至宿主元素
this.$el.appendChild(this.$fragment);
}
}
node2Fragment(el) {
// 创建一个新的Fragment
const fragment = document.createDocumentFragment();
let child;
// 将原生节点移动至fragment
while ((child = el.firstChild)) {
// appendChild 是移动操作,移动一个节点,child 就会少一个,最终结束循环
fragment.appendChild(child);
}
return fragment;
}
// 编译指定片段
compile(el) {
let childNodes = el.childNodes;
Array.from(childNodes).forEach(node => {
// 判断node类型,做相应处理
if (this.isElementNode(node)) {
// 元素节点要识别v-xx或@xx
this.compileElement(node);
} else if (
this.isTextNode(node) &&
/\{\{(.*)\}\}/.test(node.textContent)
) {
// 文本节点,只关心{{msg}}格式
this.compileText(node, RegExp.$1); // RegExp.$1匹配{{}}之中的内容
}
// 遍历可能存在的子节点
if (node.childNodes && node.childNodes.length) {
this.compile(node);
}
});
}
compileElement(node) {
// console.log('编译元素节点');
// <div v-text="test" @click="onClick"></div>
const attrs = node.attributes;
Array.from(attrs).forEach(attr => {
const attrName = attr.name; // 获取属性名 v-text
const exp = attr.value; // 获取属性值 test
if (this.isDirective(attrName)) {
// 指令
const dir = attrName.substr(2); // text
this[dir] && this[dir](node, this.$vm, exp);
} else if (this.isEventDirective(attrName)) {
// 事件
const dir = attrName.substr(1); // click
this.eventHandler(node, this.$vm, exp, dir);
}
});
}
compileText(node, exp) {
// console.log('编译文本节点');
this.text(node, this.$vm, exp);
}
isElementNode(node) {
return node.nodeType == 1; //元素节点
}
isTextNode(node) {
return node.nodeType == 3; //元素节点
}
isDirective(attr) {
return attr.indexOf('v-') == 0;
}
isEventDirective(dir) {
return dir.indexOf('@') == 0;
}
// 文本更新
text(node, vm, exp) {
this.update(node, vm, exp, 'text');
}
// 处理html
html(node, vm, exp) {
this.update(node, vm, exp, 'html');
}
// 双向绑定
model(node, vm, exp) {
this.update(node, vm, exp, 'model');
let val = vm.exp;
// 双绑还要处理视图对模型的更新
node.addEventListener('input', e => {
vm[exp] = e.target.value; // 这里相当于执行了 set
});
}
// 更新
// 能够触发这个 update 方法的时机有两个:1-编译器初始化视图时触发;2-Watcher更新视图时触发
update(node, vm, exp, dir) {
let updaterFn = this[dir + 'Updater'];
updaterFn && updaterFn(node, vm[exp]); // 立即执行更新;这里的 vm[exp] 相当于执行了 get
new Watcher(vm, exp, function (value) {
// 每次创建 Watcher 实例,都会传入一个回调函数,使函数和 Watcher 实例之间形成一对一的挂钩关系
// 将来数据发生变化时, Watcher 就能知道它更新的时候要执行哪个函数
updaterFn && updaterFn(node, value);
});
}
textUpdater(node, value) {
node.textContent = value;
}
htmlUpdater(node, value) {
node.innerHTML = value;
}
modelUpdater(node, value) {
node.value = value;
}
eventHandler(node, vm, exp, dir) {
let fn = vm.$options.methods && vm.$options.methods[exp];
if (dir && fn) {
node.addEventListener(dir, fn.bind(vm), false);
}
}
}标签:fun v-on 就是 赋值 字符 enum 内容 源码 判断
原文地址:https://www.cnblogs.com/dora-zc/p/11111813.html