vue的基本原理解读
vue底层原理关系图
vue运行流程
- 在new Vue() 之后,vue会调用进行初始化, 初始化生命周期、事件、props、methods、data、computed、watch等等。核心是通过Object.defineProperty设置getter、setter, 用来实现响应式和依赖收集。
- $mount用于挂载组件
- 编译模块分为三个阶段
- parse(解析)
- 使用正则解析template中的vue指令和变量等等, 形成树AST
- optimize(优化)
- 标记静态节点,用作后面优化,做diff的时候直接省略
- generate(生成)
- 把第一步生成的AST转化为渲染函数render function
- parse(解析)
- 响应式
- 初始化的时候通过defineProperty进行绑定,设置通知的机制。当编译生成的渲染函数被实际渲染时,会触发getter进行依赖收集,在数据变化时会触发setter进行更新(见下面代码)。
- 什么是虚拟DOM
- 简单来说虚拟DOM就是用JS对象来模拟DOM结构,数据修改时,我们先修改虚拟DOM中的数据,然后用数组做diff,最后汇总所有的diff,然后再更新真实的DOM。js执行比dom快很多,当diff算法后,实现最少的真实dom更新,因此提高了速率。
// vdom { tag: 'div', props: { name: 'test', style: 'color:red', onClick: 'try' } children:[ { tag: 'a', text: 'click me' } ] }
```
<div name=”test” style=”color:red” @click=”try”> click me </div> ```
- 简单来说虚拟DOM就是用JS对象来模拟DOM结构,数据修改时,我们先修改虚拟DOM中的数据,然后用数组做diff,最后汇总所有的diff,然后再更新真实的DOM。js执行比dom快很多,当diff算法后,实现最少的真实dom更新,因此提高了速率。
- 更新视图
- 数据修改触发setter,然后监听器会通知所有修改,对比两个dom树, 得到改变的地方,就是patch。然后修改这部分差异。
实现vue.js响应式(Observe vue2.0)
// kvue.js=>Object.defineProperty
class KVue {
constructor(options) {
this.$data = options.data; // 保存data选项
this.observe(this.$data); // 执行响应式
this.$options = options; // 保存options
// test
// new Watcher();
// console.log('模拟compile', this.$data.test);
// 解析(编译)并保存编译
this.$compile = new Compile(options.el, this);
}
observe(value){
if (!value || typeof value !== 'object' ) { // 中午只是简单判断,还可能是函数等等
return;
}
// 遍历data选项
Object.keys(value).forEach(key => {
// 为每一个key定义响应式=>将属性定义响应式到data对象上
this.defineReactive(value, key, value[key]);
// 为vue的data做属性代理
this.proxyData(key);
})
}
defineReactive(obj, key, val) {
this.observe(val); // 递归查找嵌套属性
// 创建Dep
const dep = new Dep();
// 为data对象定义属性:三个参数(data对象,属性,描述符/配置)
Object.defineProperty(obj, key, {
enumerable: true, // 可枚举
configurable: true, // 可修改或删除
get() {
// 触发get时收集依赖项 Dep.target,即监听器实例watcher
Dep.target && dep.addDep(Dep.target);
console.log(dep.deps);
return val;
},
set(newVal) {
if (newVal === val) {
return;
}
val = newVal;
// console.log('数据发生变化!');
// 通知所有watcher更新视图
dep.notify();
}
})
}
// 将$data里面的数据拓宽到vue实例的根上vm
proxyData(key) {
Object.defineProperty(this, key, {
get(){
return this.$data[key];
},
set(newVal){
this.$data[key] = newVal;
},
});
}
}
// 依赖管理器:负责将视图中所有依赖收集管理,包括依赖添加和通知
class Dep {
constructor() {
this.deps = []; // deps里面存放的是Watcher的实例
}
addDep(dep) {
this.deps.push(dep);// 添加的是每一个watcher
}
// 通知所有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 = this;
this.vm[this.key];
Dep.target = null;
}
// 更新
update() {
// console.log('视图更新啦!');
this.cb.call(this.vm, this.vm[this.key]);
}
}
实现编译解析过程(Compile)
// compile.js
// 扫描模板中所有依赖创建更新函数和watcher
class Compile {
// el是宿主元素或其选择器
// vm当前Vue实例
constructor(el, vm) {
this.$vm = vm;
this.$el = document.querySelector(el); // 默认选择器
if (this.$el) {
// 将dom节点转换为Fragment提高执行效率
this.$fragment = this.node2Fragment(this.$el);
// 执行编译
this.compile(this.$fragment);
// 将生成的结果追加至宿主元素
this.$el.appendChild(this.$fragment);
}
}
// 将当前的html dom节点转化为代码块(fragment)
node2Fragment(el) {
// 创建一个新的Fragment
const fragment = document.createDocumentFragment();
let child;
// 将原生节点拷贝至fragment
while ((child = el.firstChild)) {
// appendChild是移动操作
fragment.appendChild(child);
}
return fragment;
}
// 编译指定片段
compile(el) {
let childNodes = el.childNodes;
// 将类数组转化为数组
Array.from(childNodes).forEach(node => {
// 判断node类型,做相应处理
if (this.isElementNode(node)) {
// 元素节点要识别k-xx或@xx
this.compileElement(node);
} else if (
this.isTextNode(node) &&
/\{\{(.*)\}\}/.test(node.textContent)
) {
// 文本节点,只关心格式
this.compileText(node, RegExp.$1); // RegExp.$1匹配内容
}
// 遍历可能存在的子节点
if (node.childNodes && node.childNodes.length) {
// 递归
this.compile(node);
}
});
}
// 编译元素节点
compileElement(node) {
// console.log("编译元素节点");
// <div k-text="test" @click="onClick">
const attrs = node.attributes;
Array.from(attrs).forEach(attr => {
// 规定指令 k-text="test" @click="onClick"
const attrName = attr.name; // 属性名k-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("k-") == 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");
// 正常是取不到的, 使用proxyData才能取到
let val = vm.exp;
// 双绑还要处理视图对模型的更新
node.addEventListener("input", e => {
vm[exp] = e.target.value;
// val = e.target.value;
});
}
// 更新
update(node, vm, exp, dir) {
let updaterFn = this[dir + "Updater"];
updaterFn && updaterFn(node, vm[exp]); // 执行更新,get
new Watcher(vm, exp, function(value) {
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);
}
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<body>
<div id="app">
<p k-text="test"></p>
<p k-html="html"></p>
<p>
<input type="text" k-model="test">
</p>
<p>
<button @click="onClick">按钮</button>
</p>
</div>
<script src="kvue.js"></script>
<script src="compile.js"></script>
<script>
const o = new KVue({
el: "#app",
data: {
test: "allala",
foo: { bar: "bar" },
html: '<button>adfadsf</button>'
},
methods: {
onClick() {
alert('blabla')
}
},
});
console.log(o.$data.test);
o.$data.test = "hello,kvue!";
console.log(o.$data.test);
console.log(o.$data.foo.bar);
o.$data.foo.bar = "hello,kvue!";
console.log(o.$data.foo.bar);
</script>
<!-- <div id="app">
<p>你好,<span id="name"></span></p>
</div>
<script>
var obj = {};
Object.defineProperty(obj, 'name', {
get: function () {
return document.getElementById('name').innerHTML;
},
set: function (inner) {
document.getElementById('name').innerHTML = inner;
}
})
console.log(obj.name);
obj.name = '乔峰';
console.log(obj.name);
</script> -->
</body>
</html>
总结
vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的。利用了 Object.defineProperty() 这个方法重新定义了对象获取属性值(get)和设置属性值(set)。
vue react angularjs jquery的区别
JQuery与另外几者最大的区别是,JQuery是事件驱动,其他三者是数据驱动。
JQuery业务逻辑和UI更改该混在一起, UI里面还参杂这交互逻辑,让本来混乱的逻辑更加混乱。
Angular使用双向数据绑定,React用于单数据流,Vue支持两者(双向数据绑定和单向数据流)。React是一种开发理念,组件化,分治的管理,数据与view的一体化。它只有一个中心,发出状态,渲染view,对于虚拟dom它并没有提高渲染页面的性能,它提供更多的是利用jsx便捷生成dom元素,利用组件概念进行分治管理页面每个部分(例如 header section footer slider)。