Vue原理——之数据劫持
先看一下vue的用法,两个参数(el的id,data)
创建一个类,
数据劫持:defineProperty或者proxy',给data里的数据添加get、set才能及时获取数据改变
使用Proxy:
只要data数据具备数据劫持属性,就不再操纵dom也能改变视图
更深一点
//ES6实现 class Vue { constructor(options) { //缓存配置项 this.$el = options.el || document.body; this.$options = options; const data = this.$data = options.data; //劫持数据 this.hijackData(data);
//编译模版
this.compile(this.$el); }
数据劫持Observer
hijackData(data) { if (!data || typeof data !== 'object') { //不存在或者不为对象,返回(递归退出条件) return; } //拿到data中所有可枚举的属性 Object.keys(data).forEach(key => { //给每个属性创建观察者 new Observer(data, key); //递归遍历所有层次 this.hijackData(data[key]); }); } } //观察者实现 class Observer { constructor(data, key) { //对数据进行观察 this.observe(data, key, data[key]); } observe(data, key, val) { //data中每一个数据对应一个Dep容器,存放所有依赖于该数据的依赖项 const dep = new Dep(); Object.defineProperty(data, key, { enumerable: true, //可枚举 configurable: false,//不能再配置 get() { if (Dep.target) {//Dep.target存放具体的依赖,在编译阶段检测到依赖后被赋值 dep.addDep(Dep.target); //依赖收集 } return val; }, set(newVal) { if (newVal === val) { return; } val = newVal; dep.notify(); //当数据发生变化时,通知所有的依赖进行更新显示 } }); } }
Dep依赖收集
//Dep容器,data中的每个数据会对应一个,用来收集并存储依赖 class Dep { constructor() { this.deps = []; //所有的依赖将存放在该数组中 } //收集依赖 addDep(dep) { this.deps.push(dep); } //通知更新 notify() { this.deps.forEach(dep => { dep.updata(); }); } }
模板编译 compile
compile(el) { //找到vue管理的区域 this.$el = this.isElementNode(el) ? el : document.querySelector(el); if (this.$el) { //将this.$el中的子节点转移到内存中,document --> 内存 this.$fragment = this.node2Fragment(this.$el); //编译 this.compileElements(this.$fragment); //内存 --> document this.$el.appendChild(this.$fragment); } } //将文档节点转移到fragment(内存)中 得到虚拟dom node2Fragment(node) { let child = null;
//createdocumentfragment()方法创建了一虚拟的节点对象,节点对象包含所有属性和方法。 let fragment = document.createDocumentFragment(); while (child = node.firstChild) { //节点有且只有一个父节点, //所以是转移,不是复制,不会出现两份, //相当于是一个水缸的水舀到另一个水缸里,fragemnt满了,this.$el空了 fragment.appendChild(child); } return fragment; } compileElements(vNode) { let text = ''; //正则表达式,用于匹配大括号表达式 {{}} const reg = /\{\{(.*)\}\}/; //转为真数组并遍历所有节点 Array.from(vNode.childNodes).forEach(node => { text = node.textContent; if (this.isElementNode(node) { //元素节点,解析所有的指令属性 let exp = ''; //表达式 let dir = ''; //指令 let attrName = ''; let attrs = node.attributes; //取出所有属性,转为数组并遍历 Array.from(attrs).forEach(attr => { exp = attr.value; attrName = attr.name; // 普通指令v-text,v-html,v-model等 if (this.isDirective(attrName) { dir = attrName.substring(2); this.update(node, exp, dir); node.removeAttribute(attrName); //事件指令@click等 } else if (this.isEvent(attrName) { dir = attrName.substring(1); this.eventHandler(node, exp, dir); node.removeAttribute(attrName); } }); } else if (this.isTextNode(node) && reg.test(text)) { //文本节点,大括号表达式 this.update(node, RegExp.$1.trim(), 'text'); } //递归遍历所有层次的节点 if (node.hasChildNodes()) { this.compileElements(node); } }); } //事件处理器 eventHandler(node, exp, eType) { const cb = this.$options.methods && this.$options.methods[exp]; cb && node.addEventListener(eType, cb.bind(this)); } //更新视图,依赖的统一入口,每个引用过data中数据的依赖都会进来这里 update(node, exp, dir) { //拿到相对应的更新函数 const fn = this[dir+'Updater']; //初始化更新显示,这里需要指定调用的实例this,即vue实例 fn && fn.call(this, node, exp); //变化更新显示,下一节内容 //。。。。 } textUpdater(vm, node, exp) { node.textContent = vm[exp]; } htmlUpdater(vm, node, exp) { node.innerHTML = vm[exp]; } modelUpdater(vm, node, exp) { node.value = vm[exp]; //添加input事件监听 //v-model双向数据绑定的其中一个方向,即:视图 --> 内存 node.addEventListener('input', e => { vm[exp] = e.target.value; }); } //判断是否为元素节点 isElementNode(node) { return (node.nodeType === 1); } //判断是否为文本节点 isTextNode(node) { return (node.nodeType === 3); }
更新显示
//更新显示实现 class Watcher { constructor(vm, exp, cb) { this.cb = cb; Dep.target = this; //把Watcher赋值给Dep.target vm[exp]; //调用get Dep.target = null; } update() { //dep.notify()通知更新最终入口 //内存 --> 视图,也是双向数据绑定中的另一方向 this.cb(); //更新显示 } }
//更新视图,依赖的统一入口,每个引用过data中数据的依赖都会进来这里 update(node, exp, dir) { //拿到相对应的更新函数 const fn = this[dir+'Updater']; //初始化更新显示,这里需要指定调用的实例this,即vue实例 fn && fn.call(this, node, exp); //变化更新显示,下一节内容 new Watcher(this, exp, () => { fn && fn.call(this, node, exp); }); }
原文链接:https://blog.csdn.net/huolinianyu/article/details/100190032