-
原理:
Vue.js
是通过数据劫持以及结合发布者-订阅者来实现的数据绑定,数据劫持是利用ES5
的Object.defineProperty(obj, key, val)
来劫持各个属性的的setter
以及getter
,在数据变动时发布消息给订阅者,从而触发相应的回调来更新视图。 -
数据双向绑定的流程图:
数据双向绑定的流程图.png
- 1、实现一个数据监听器
Obverser
,对data
中的数据进行监听,若有变化,通知相应的订阅者。 - 2、实现一个指令解析器
Compile
,对于每个元素上的指令进行解析,根据指令替换数据,更新视图。 - 3、实现一个
Watcher
,用来连接Obverser
和Compile
, 并为每个属性绑定相应的订阅者,当数据发生变化时,执行相应的回调函数,从而更新视图。 - 4、
mvvm
入口函数,整合以上三者,即构造函数new Vue({})
- 1、实现一个数据监听器
-
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>vue双向数据绑定原理剖析</title>
</head>
<body>
<div id="app">
<div>{{name}}</div>
<p v-text="name"></p>
<p>{{age}}</p>
<input type="text" v-model="name" placeholder="enter something" />
<button @click="changeName">changeName</button>
<div v-html="html"></div>
</div>
<script src="./vue.js"></script>
<script src="./compile.js"></script>
<script>
new Vue({
// 挂载视图
el: '#app',
// 定义数据
data: {
name: 'I am name',
age: 12,
html: '<button>我是按钮</button>'
},
created() {
setTimeout(() => {
this.name = '我是name,我变了';
}, 800);
},
methods: {
changeName() {
this.name = '我是name,我又又又又又又又变了';
this.age = 18;
}
}
})
</script>
</body>
</html>
- vue.js
class Vue {
constructor(options) {
this.$options = options;
// 处理 data
this.$data = options.data;
// 添加属性观察对象(实现属性挟持)
this.observe(this.$data);
// 创建模板编译器,来解析视图
new Compile(options.el, this);
if (options.created) {
options.created.call(this);
}
}
observe(obj) {
if (!obj || typeof obj !== 'object') {
return;
}
Object.keys(obj).forEach(key => {
this.defineReactive(obj, key, obj[key]);
this.proxyData(key);
})
}
// 针对当前对象属性的重新定义(挟持)
defineReactive(obj, key, val) {
const dep = new Dep();
Object.defineProperty(obj, key, {
get() {
// 将Dep.target添加到dep中
// 由于需要在闭包内添加watcher,所以通过Dep定义一个全局target属性,暂存watcher, 添加完移除
Dep.target && dep.addDep(Dep.target);
return val;
},
set(newVal) {
if (newVal !== val) {
val = newVal;
// 通知,触发update操作
dep.notify();
}
}
})
this.observe(val);
}
// 给MVVM实例添加一个属性代理的方法,使访问vm的属性代理为访问vm._data的属性
proxyData(key) {
Object.defineProperty(this, key, {
get() {
return this.$data[key];
},
set(newVal) {
this.$data[key] = newVal;
}
})
}
}
// 消息订阅器,维护一个数组,用来收集订阅者,数据变动触发notify,再调用订阅者的update方法
class Dep {
constructor() {
this.deps = [];
}
addDep(dep) {
this.deps.push(dep);
}
notify() {
// 调用订阅者的update方法,通知变化
this.deps.forEach(dep => dep.update());
}
}
// Observer和Compile之间通信的桥梁
class Watcher {
constructor(vm, key, callback) {
this.vm = vm;
this.key = key;
this.callback = callback;
// 将当前订阅者指向自己
Dep.target = this;
// 为了触发属性的getter,从而在dep添加watcher
this.vm[this.key];
// 添加完毕,重置
Dep.target = null;
}
update() {
this.callback.call(this.vm, this.vm[this.key]);
}
}
- compile.js
class Compile {
constructor(el, vm) {
this.$el = document.querySelector(el);
// vm为创建的vue实例
this.$vm = vm;
// 判断视图是否存在
if (this.$el) {
// 提取宿主中模板内容到Fragment(文档片段)标签,dom操作会提高效率
this.$fragment = this.node2Fragment(this.$el);
// 编译模板内容,同时进行依赖收集
this.compileNode(this.$fragment);
this.$el.appendChild(this.$fragment);
}
}
// 核心方法(节省内存)把模板放入内存,等待解析
node2Fragment(el) {
// 创建内存片段
const fragment = document.createDocumentFragment();
let child;
// 模板内容放到内存
// appendChild会把原来的child给移动到新的文档中,当el.firstChild为空时,
// while也会结束 a = undefined => 返回 undefined
while (child = el.firstChild) {
fragment.appendChild(child);
}
return fragment;
}
compileNode(el) {
const childNodes = el.childNodes;
Array.from(childNodes).forEach(node => {
// 判断节点类型
if (node.nodeType === 1) {
// element 节点
this.compileElement(node);
} else if (this.isInterpolation(node)) {
// 插值表达式
this.compileText(node);
}
// 递归子节点
if (node.childNodes && node.childNodes.length) {
this.compileNode(node)
}
})
}
compileElement(node) {
// <div k-model="foo" k-text="test" @click="onClick">
let nodeAttrs = node.attributes;
Array.from(nodeAttrs).forEach(attr => {
const attrName = attr.name;
const attrValue = attr.value;
if (this.isDirective(attrName)) {
const dir = attrName.substring(2);
this[dir] && this[dir](node, attrValue);
}
if (this.isEvent(attrName)) {
const dir = attrName.substring(1);
this.eventHandle(node, attrValue, dir);
}
})
}
// 指令
isDirective(attrName) {
return attrName.includes('v-');
}
// 双向绑定
model(node, key) {
// data -> view
this.update(node, key, 'model');
// view -> data
node.addEventListener('input', event => {
this.$vm.$data[key] = event.target.value;
})
}
text(node, key) {
this.update(node, key, 'text');
}
html(node, key) {
this.update(node, key, 'html');
}
isInterpolation(node) {
// 文本 && {{}}
return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent);
}
compileText(node) {
// RegExp.$1, 缓存最近一次的正则里面的值
this.update(node, RegExp.$1, 'text');
}
update(node, key, dir) {
// 找到更新规则对象的更新方法
let updatrFn = this[dir + 'Updater'];
// 第一次初始化视图
updatrFn && updatrFn(node, this.$vm.$data[key]);
// 每解析一个表达式(非事件指令)都会创建一个对应的watcher对象, 并建立watcher与dep的关系
new Watcher(this.$vm, key, (value) => {
// 一旦属性值有变化,会收到通知执行此更新函数,更新视图
updatrFn && updatrFn(node, value);
})
}
textUpdater(node, value) {
node.textContent = value;
}
htmlUpdater(node, value) {
node.innerHTML = value;
}
modelUpdater(node, value) {
node.value = value;
}
// 事件
isEvent(attrName) {
return attrName.includes('@');
}
// 事件绑定
eventHandle(node, key, dir) {
const fn = this.$vm.$options.methods && this.$vm.$options.methods[key];
if (dir && fn) {
node.addEventListener(dir, () => { fn.call(this.$vm) });
}
}
}
网友评论