vue_源码 原理 剖析
相关基础知识点
// 可以让 任意函数/方法 成功临时指定成对象的方法进行调用 - call/apply
// 1. 根据伪数组生成 真数组
const lis = document.getElementsByTagName("li");
const arr = [].slice.call(lis);
const relArr = Array.from(lis); // ES6语法
// 2. node.nodeType // 得到节点类型
// 3. 给对象添加属性,指定 属性描述符:存取描述符 get(),set() - 数据描述符
Object.defineProperty(ojb, propertyName, {
get(){return this.firstNAme+"-"+this.lastNAme},
set(newVAlue){const names=newValue.split("-");this.firstNAme=names[0];this.lastNAme=names[1]},
})
// 4. Object.keys(obj); // 得到对象自身可枚举属性组成的数组
// 5. obj.hasOwnProperty("name") // 判断 obj 对象自身属性是否包含 name
// 6. DocumentFragment 文档碎片(高效更新多个节点)
内存中用来存储多个节点对象 的容器,不与任何 页面/页面节点 对应
思路: 先将节点复制到内存中,在内存中修改完后,一次性添加到页面中
// 1. 创建一个内存中 节点容器
const fragment = document.createDocumentFragment();
// 2. 取出 div 中所有节点,转移到 fragment 中
const div = document.getElementById("demo");
let child; // newPos = appendChild(child) 会将 child 从原来位置取下来,放到 newPos 的最后位置
while(child=div.firstChild){fragment.appendChild(child)}
// 3. 在内存中遍历修改
const nodes = fragment.children[0].childNodes;
Array.prototype.slice.call(nodes).forEach(node=>{
if(node.nodeType === 1){
node.textContent = "丘魔的兵"
}
})
// 4. 一次性添加到页面
div.appendChild(fragment);
在 git 上,有个开源项目,剖析了 vue 原理
- 数据代理
为达到简化编码的目的,
内部实现的关键点: 通过 Object.defineProperty(vm, key, {}) 实现
对于 data 中状态的数据,通过 ViewModel 实例 来进行 代理读/代理写 操作
---------------------------------------------------------------
function Vue(configObj){
var me = this;
// 1. 保存 配置对象 到 ViewModel
this.$configObj = configObj;
// 2. 保存 data 对象到 ViewModel 和 变量 data 上
var data = this._data = this.$configObj.data;
// 3. 遍历 data 中所有属性, 实现数据代理
Object.keys(data).forEach(function (key) {
me._proxy(key);
});
// 实现数据绑定
observe(data, this);
// 实现模版解析
this.$compile = new Compile(options.el || document.body, this)
}
Vue.prototype = {
//
_proxy: function(key){
var vm = this;
// 给 vm 添加指定属性,使用 属性描述符 (设置 getter/setter)
Object.defineProperty(vm, key, {
configurable: false,
enumerable: true,
get: function proxyGetter(){ // 代理读
return vm._data[key]
},
set: function proxySetter(newValue){ // 代理写
vm._data[key] = newValue
}
})
},
$watch: function (key, cb, options) {
new Watcher(this, key, cb);
}
}
- 模板解析
- 模板表达式
- 指令
- 事件指令
- 一般指令
this.$compile = new Compile(options.el || document.body, this)
function Compile(el, vm) {
this.$vm = vm;
this.$el = this.isElementNode(el) ? el : document.querySelector(el); if (this.$el) { // 整体流程:
this.$fragment = this.node2Fragment(this.$el); // 1. 将 目标元素节点 拷贝到 DocumentFragment
this.init(); // 2. 内存中编译 DocumentFragment
this.$el.appendChild(this.$fragment); // 3. 将 DocumentFragment 追加到目标元素节点
}
} Compile.prototype = {
node2Fragment: function(el) {
var fragment = document.createDocumentFragment(),
child; // 将原生节点拷贝到fragment
while (child = el.firstChild) {
fragment.appendChild(child);
} return fragment;
}, init: function() {
this.compileElement(this.$fragment); // 传入待处理节点,进行编译处理
}, compileElement: function(el) {
var childNodes = el.childNodes,
me = this; [].slice.call(childNodes).forEach(function(node) {
var text = node.textContent; // 换行
var reg = /\{\{(.*)\}\}/; // 匹配 {{模板表达式}} if (me.isElementNode(node)) {
me.compile(node);
} else if (me.isTextNode(node) && reg.test(text)) { // 如果是 {{模板表达式}}
// 则 解析 成node.textContent = 模板表达式,不管匹配成功几个表达式,总是只生效第一个
me.compileText(node, RegExp.$1);
}if (node.childNodes && node.childNodes.length) { // 如果还有子节点,则递归编译
me.compileElement(node);
}
});
}, compile: function(node) {
var nodeAttrs = node.attributes,
me = this; [].slice.call(nodeAttrs).forEach(function(attr) {
var attrName = attr.name;
if (me.isDirective(attrName)) {
var exp = attr.value; // 得到表达式 即 回调函数名
var dir = attrName.substring(2); // 得到指令
// 事件指令
if (me.isEventDirective(dir)) {
compileUtil.eventHandler(node, me.$vm, exp, dir);
// 普通指令
} else {
compileUtil[dir] && compileUtil[dir](node, me.$vm, exp);
} node.removeAttribute(attrName);
}
});
}, compileText: function(node, exp) {
compileUtil.text(node, this.$vm, exp);
}, isDirective: function(attr) {
return attr.indexOf('v-') == 0;
}, isEventDirective: function(dir) {
return dir.indexOf('on') === 0;
}, isElementNode: function(node) {
return node.nodeType == 1;
}, isTextNode: function(node) {
return node.nodeType == 3;
}
}; // 用于编译的方法 - 指令处理集合
var compileUtil = {
text: function(node, vm, exp) { // 编译 v-text 或者 {{模板表达式}}
this.bind(node, vm, exp, 'text');
}, html: function(node, vm, exp) { // v-html
this.bind(node, vm, exp, 'html');
}, model: function(node, vm, exp) { // v-model
this.bind(node, vm, exp, 'model'); var me = this,
val = this._getVMVal(vm, exp);
node.addEventListener('input', function(e) {
var newValue = e.target.value;
if (val === newValue) {
return;
} me._setVMVal(vm, exp, newValue);
val = newValue;
});
}, class: function(node, vm, exp) { // v-bind:class
this.bind(node, vm, exp, 'class');
}, bind: function(node, vm, exp, dir) {
var updaterFn = updater[dir + 'Updater']; // 根据指令名 得到一个用于更新节点的更新函数 updaterFn && updaterFn(node, this._getVMVal(vm, exp)); // 得到指定表达式对应的值
// 解析每个表达式后 - 为表达式创建对应的 watcher 对象(事件表达式除外)
new Watcher(vm, exp, function(value, oldValue) {
updaterFn && updaterFn(node, value, oldValue); // 更新节点
});
}, // 事件处理
eventHandler: function(node, vm, exp, dir) {
var eventType = dir.split(':')[1],
fn = vm.$options.methods && vm.$options.methods[exp]; if (eventType && fn) {
node.addEventListener(eventType, fn.bind(vm), false);
}
}, _getVMVal: function(vm, exp) {
var val = vm._data;
exp = exp.split('.');
exp.forEach(function(k) {
val = val[k];
});
return val;
}, _setVMVal: function(vm, exp, value) {
var val = vm._data;
exp = exp.split('.');
exp.forEach(function(k, i) {
// 非最后一个key,更新val的值
if (i < exp.length - 1) {
val = val[k];
} else {
val[k] = value;
}
});
}
}; // 更新节点的方法集
var updater = {
textUpdater: function(node, value) { // v-text 或者 {{模板表达式}}
node.textContent = typeof value == 'undefined' ? '' : value;
}, htmlUpdater: function(node, value) { // v-html
node.innerHTML = typeof value == 'undefined' ? '' : value;
}, classUpdater: function(node, value, oldValue) { // v-bind:class
var className = node.className;
className = className.replace(oldValue, '').replace(/\s$/, ''); var space = className && String(value) ? ' ' : ''; node.className = className + space + value;
}, modelUpdater: function(node, value, oldValue) { // v-model
node.value = typeof value == 'undefined' ? '' : value;
}
};
- 数据绑定 -------- 双向数据绑定
使用 数据劫持技术 实现数据绑定
思想: 通过 defineProperty() 来监视 data 中所有状态属性(任意层次)的变化,一旦变化了,界面会实时更新
Observer
用来对 data 中数据进行监视 的构造函数
为每个 data 数据设置 getter 和 setter ,并配置 dep 对象
Compile
用来解析模板页面对象的构造函数
利用 compile 对象解析模板页面
每解析一个表达式都会创建一个 watcher 对象,并建立 watcher 和 dep 的关系
1 个 watcher 对应 n>=1 个 dep -------- 假设表达式是 2 层表达式 mom.son 时,当前 watcher 就对应 2 个 dep
1 个 dep 对应 n>=0 个 watcher -------- 没有一个表达式用到这个属性; 只有一个表达式用到这个属性; 多个表达式用到这个属性
每个 dep 都有唯一的 id
subs 包含 n 个对应 watcher 的数组 ---- subcribes 的简写
function Observer(data) {
this.data = data;
this.walk(data);
} Observer.prototype = {
walk: function(data) {
var me = this;
Object.keys(data).forEach(function(key) {
me.convert(key, data[key]);
});
},
convert: function(key, val) { // 给每个对象设置 响应式属性
this.defineReactive(this.data, key, val);
}, defineReactive: function(data, key, val) { // 对某个属性进行劫持
var dep = new Dep(); // 在每个属性进行劫持前创建一个对应的 dep 对象
var childObj = observe(val); // 递归实现属性下所有层次属性的劫持 Object.defineProperty(data, key, {
enumerable: true, // 可枚举
configurable: false, // 不能再define
get: function() {
if (Dep.target) {
dep.depend();
}
return val;
},
set: function(newVal) {
if (newVal === val) {
return;
}
val = newVal; childObj = observe(newVal); // 新的值是object的话,进行监听 dep.notify();// 通知订阅者
}
});
}
}; function observe(value, vm) {
if (!value || typeof value !== 'object') {
return;
} return new Observer(value);
}; var uid = 0; function Dep() {
this.id = uid++;
this.subs = [];
} Dep.prototype = {
addSub: function(sub) {
this.subs.push(sub);
}, depend: function() {
Dep.target.addDep(this);
}, removeSub: function(sub) {
var index = this.subs.indexOf(sub);
if (index != -1) {
this.subs.splice(index, 1);
}
}, notify: function() {
this.subs.forEach(function(sub) {
sub.update();
});
}
}; Dep.target = null;
5
5
5
55
5
5
vue_源码 原理 剖析的更多相关文章
- ArrayList源码深度剖析,从最基本的扩容原理,到魔幻的迭代器和fast-fail机制,你想要的这都有!!!
ArrayList源码深度剖析 本篇文章主要跟大家分析一下ArrayList的源代码.阅读本文你首先得对ArrayList有一些基本的了解,至少使用过它.如果你对ArrayList的一些基本使用还不太 ...
- Redis 源码简洁剖析 03 - Dict Hash 基础
Redis Hash 源码 Redis Hash 数据结构 Redis rehash 原理 为什么要 rehash? Redis dict 数据结构 Redis rehash 过程 什么时候触发 re ...
- HashMap源码深度剖析,手把手带你分析每一行代码,包会!!!
HashMap源码深度剖析,手把手带你分析每一行代码! 在前面的两篇文章哈希表的原理和200行代码带你写自己的HashMap(如果你阅读这篇文章感觉有点困难,可以先阅读这两篇文章)当中我们仔细谈到了哈 ...
- ArrayDeque(JDK双端队列)源码深度剖析
ArrayDeque(JDK双端队列)源码深度剖析 前言 在本篇文章当中主要跟大家介绍JDK给我们提供的一种用数组实现的双端队列,在之前的文章LinkedList源码剖析当中我们已经介绍了一种双端队列 ...
- JDK数组阻塞队列源码深入剖析
JDK数组阻塞队列源码深入剖析 前言 在前面一篇文章从零开始自己动手写阻塞队列当中我们仔细介绍了阻塞队列提供给我们的功能,以及他的实现原理,并且基于谈到的内容我们自己实现了一个低配版的数组阻塞队列.在 ...
- Contiki源码+原理+功能+编程+移植+驱动+网络(转)
源:Contiki源码+原理+功能+编程+移植+驱动+网络 请链接:http://www.rimelink.com/nd.jsp? id=31&_np=105_315 假设您对于用Contik ...
- libevent 源码深度剖析十三
libevent 源码深度剖析十三 —— libevent 信号处理注意点 前面讲到了 libevent 实现多线程的方法,然而在多线程的环境中注册信号事件,还是有一些情况需要小心处理,那就是不能在多 ...
- libevent源码深度剖析十二
libevent源码深度剖析十二 ——让libevent支持多线程 张亮 Libevent本身不是多线程安全的,在多核的时代,如何能充分利用CPU的能力呢,这一节来说说如何在多线程环境中使用libev ...
- libevent源码深度剖析十一
libevent源码深度剖析十一 ——时间管理 张亮 为了支持定时器,Libevent必须和系统时间打交道,这一部分的内容也比较简单,主要涉及到时间的加减辅助函数.时间缓存.时间校正和定时器堆的时间值 ...
随机推荐
- Linux shell脚本启动 停止 重启jar包
最近做的微服务jar包想弄在持续集成中自动化部署,所以首先得有一个操作jar包的脚本 只需将jar文件的路径替换到APP_NAME的值就可以了,其他不用改 注意:window编辑的shell文件,通过 ...
- 语义化标签和jQuery选择器
关于语义化标签 https://blog.csdn.net/nongweiyilady/article/details/53885433 更详细的语义化标签:https://www.cnblogs.c ...
- axd文件
ashx与axd作用相同,ashx一般在本项目内,axd在其它dll中.axd扩展名的必须要在web.config中的<httpHandlers>中进行注册,而ashx直接在项目中当成as ...
- [再寄小读者之数学篇](2014-06-20 求极限-L'Hospital 法则的应用)
设 $f\in C[0,+\infty)$, $a$ 为实数, 且存在有限极限 $$\bex \vlm{x}\sez{f(x)+a\int_0^x f(t)\rd t}. \eex$$ 证明; $f( ...
- [物理学与PDEs]第2章习题11 Lagrange 形式的一维理想流体力学方程组在强间断线上的间断连接条件
对由第 10 题给出的 Lagrange 形式的一维理想流体力学方程组, 给出解在强间断线上应满足的间断连接条件 (假设体积力 $F\equiv 0$). 解答: $$\beex \bea \sez{ ...
- Jenkins 子业务日志拆分分析方法
需求 Jenkins日志打印内容很长,或者并发编译导致,日志内容不容易查看. 对于具体业务失败, 开发者希望看到具体业务自身的日志内容. 解法 tee 命令能够保证, shell命令执行的内容,即往控 ...
- Coursera, Big Data 3, Integration and Processing (week 5)
Week 5, Big Data Analytics using Spark Programing in Spark Spark Core: Programming in Spark us ...
- svn 支持中文显示
https://blog.csdn.net/chentengkui/article/details/77543498 https://blog.csdn.net/bugall/article/deta ...
- 连接远程MySQL数据库项目启动时,不报错但是卡住不继续启动的,
连接远程MySQL数据库项目启动时,不报错但是卡住不继续启动的, 2018-03-12 17:08:52.532DEBUG[localhost-startStop-1]o.s.beans.factor ...
- Python 入门基础15 --shutil、shelve、log常用模块2、项目结构
今日内容: 一.常用模块 2019.04.10 更新 1.time:时间 2.calendar:日历 3.datatime:可以运算的时间 4.sys:系统 5.os:操作系统 6.os.path:系 ...