Vue 最独特的特性之一,是应系其非侵入性的响应式系统。那么什么是统原响应式原理? 数据模型仅仅是普通的JavaScript对象,而当我们修改数据时,理搭视图会进行更新,浅析避免了繁琐的应系DOM操作,提高开发效率。统原简言之,理搭在改变数据的浅析时候,视图会跟着更新。应系 了解概念之后,统原那么它是理搭怎么实现的呢? 其实是利用Object.defineProperty()中的getter 和setter方法和设计模式中的观察者模式。 那么,浅析我们先来看下Object.defineProperty()。应系MDN中它是统原这样解释它的:Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。站群服务器 简单介绍Object.defineProperty()之后,接着就是了解观察者模式,看到它,你可能会想起发布-订阅模式。其实它们的本质是相同的,但是也存在一定的区别。 我们不妨先来看下发布-订阅模式。 发布-订阅者模式里面包含了三个模块,发布者,订阅者和统一调度中心。这里统一调度中心相当于报刊办事大厅。发布者相当与某个杂志负责人,他来中心这注册一个的杂志,而订阅者相当于用户,我在中心订阅了这分杂志。每当发布者发布了一期杂志,办事大厅就会通知订阅者来拿新杂志。发布-订阅者模式由统一调度中心调用,因此发布者和订阅者不需要知道对方的存在。 下面,我们将通过一个实现Vue自定义事件的例子来更进一步了解发布-订阅模式。云服务器提供商 这种自定义事件广泛应用于Vue同级组件传值。 接下来,我们来介绍观察者模式。 观察者模式是由目标调度,比如当事件触发时,目标就会调用观察者的方法,所以观察者模式的订阅者(观察者)与发布者(目标)之间存在依赖。 下图是区分两种模式。 为什么要实现一个Vue迷你版本,目的就是加深对Vue响应式原理以及其中一些API的理解。首先我们先来分析Vue2.x 响应式原理的整体结构。 如下图所示: 我们接下来,将根据这幅图片描述的流程来实现一款迷你版Vue。Vue2.x采用了Virtual DOM,但是因为这里只需要实现一个迷你版,所以我们这里做了简化,我们这里就是直接操作DOM。 下面,我们来看下我是如何搭建一款Vue mini的。 页面结构如下,服务器租用我们可以先引入Vue2.x完整版本,看下实现效果。 经过测试,Vue2.x完整版搭载的页面显示如下。我们将使用Vue迷你版本同样实现以下页面效果。 我们将根据整体结构图和页面结构来搭建这个Vue迷你版本,我们姑且将这个版本叫做vuemini.js。 通过整体结构图我们发现,一共有Vue、Observer、Compiler、Dep、Watcher这几个构造函数。我们首先创建这几个构造函数,这里不使用class类来定义是因为Vue源码大部分也使用构造函数,另外,相对也好拓展。 以上这几个构造函数就实现了我们所说的迷你版本,将它们整合成一个文件vuemini.js。在上面所提示的页面引入,查看效果。 另外,我在data中绑定了一个html属性,值为一个<div>{ { msg}}</div>,与之前完整版相比,图中的v-html下方的maomin文本也被渲染出来。 下面,我们将看下尤大开发的迷你版本,这个版本引入了Virtual DOM,但是主要是针对响应式式原理的,可以根据尤大的迷你版本与上面的版本作个比较,可以看下有哪些相似之处。 【编辑推荐】Vue2.x响应式原理怎么实现的浅析?
实现Vue2.x迷你版本
第一步
第二步
Vue
// 实例。 function Vue(options) { this.$options = options || { }; this._data = typeof options.data === function ? options.data() : options.data || { }; this.$el = typeof options.el === string ? document.querySelector(options.el) : options.el; // 负责把data中的属性注入到Vue实例,转换成getter/setter this._proxyData(this._data); this.initMethods(this, options.methods || { }) // 负责调用observer监听data中所有属性的变化 new Observer(this._data); // 负责调用compiler解析指令/插值表达式 new Compiler(this); } // 将data中的属性挂载到this上 Vue.prototype._proxyData = function (data) { Object.keys(data).forEach(key => { Object.defineProperty(this, key, { configurable: true, enumerable: true, get() { return data[key] }, set(newVal) { if (newVal === data[key]) { return } data[key] = newVal; } }) }) } function noop(a, b, c) { } function polyfillBind(fn, ctx) { function boundFn(a) { var l = arguments.length; return l ? l > 1 ? fn.apply(ctx, arguments) : fn.call(ctx, a) : fn.call(ctx) } boundFn._length = fn.length; return boundFn } function nativeBind(fn, ctx) { return fn.bind(ctx) } const bind = Function.prototype.bind ? nativeBind : polyfillBind; // 初始化methods属性 Vue.prototype.initMethods = function (vm, methods) { for (var key in methods) { { if (typeof methods[key] !== function) { warn( "Method \"" + key + "\" has type \"" + (typeof methods[key]) + "\" in the component definition. " + "Did you reference the function correctly?", vm ); } } vm[key] = typeof methods[key] !== function ? noop : bind(methods[key], vm); } } Observer
// 数据劫持。 // 负责把data(_data)选项中的属性转换成响应式数据。 function Observer(data) { this.walk(data); } Observer.prototype.walk = function (data) { if (!data || typeof data !== object) { return } Object.keys(data).forEach(key => { this.defineReactive(data, key, data[key]); }) } Observer.prototype.defineReactive = function (obj, key, val) { let that = this; // 负责收集依赖 let dep = new Dep(); // 如果val是对象,把val内部的属性转换成响应式数据 this.walk(val); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get() { // 收集依赖 Dep.target && dep.addSub(Dep.target) return val }, set(newVal) { if (newVal === val) { return } val = newVal; // data内属性重新赋值后,使其转化为响应式数据。 that.walk(newVal); // 发送通知 dep.notify(); } }) } Compiler
// 编译模板,解析指令/插值表达式 // 负责页面的首次渲染 // 当数据变化时重新渲染视图 function Compiler(vm) { this.el = vm.$el; this.vm = vm; // 立即编译模板 this.compile(this.el); } // 编译模板,处理文本节点和元素节点 Compiler.prototype.compile = function (el) { let childNodes = el.childNodes; Array.from(childNodes).forEach(node => { // 处理文本节点 if (this.isTextNode(node)) { this.compileText(node); } // 处理元素节点 else if (this.isElementNode(node)) { this.compileElement(node); } // 判断node节点,是否有子节点,如果有子节点,要递归调用compile方法 if (node.childNodes && node.childNodes.length) { this.compile(node); } }) } // 编译文本节点,处理插值表达式 Compiler.prototype.compileText = function (node) { // console.dir(node); let reg = /\{ \{ (.+?)\}\}/; let value = node.textContent; if (reg.test(value)) { let key = RegExp.$1.trim(); if (this.vm.hasOwnProperty(key)) { node.textContent = value.replace(reg, typeof this.vm[key] === object ? JSON.stringify(this.vm[key]) : this.vm[key]); // 创建watcher对象,当数据改变更新视图 new Watcher(this.vm, key, (newVal) => { node.textContent = newVal; }) } else { const str = `this.vm.${ key}`; node.textContent = value.replace(reg, eval(str)); // 创建watcher对象,当数据改变更新视图 new Watcher(this.vm, key, () => { const strw = `this.vm.${ key}`; node.textContent = value.replace(reg, eval(strw)); }) } } } // 判断节点是否是文本节点 Compiler.prototype.isTextNode = function (node) { return node.nodeType === 3; } // 判断节点是否是元素节点 Compiler.prototype.isElementNode = function (node) { return node.nodeType === 1; } // 编译元素节点,处理指令 Compiler.prototype.compileElement = function (node) { // console.log(node.attributes); // 遍历所有的属性节点 Array.from(node.attributes).forEach(attr => { let attrName = attr.name; // console.log(attrName); // 判断是否是指令 if (this.isDirective(attrName)) { // 判断:如v-on:click let eventName; if (attrName.indexOf(:) !== -1) { const strArr = attrName.substr(2).split(:); attrName = strArr[0]; eventName = strArr[1]; } else if (attrName.indexOf(@) !== -1) { eventName = attrName.substr(1); attrName = on; } else { attrName = attrName.substr(2); } let key = attr.value; this.update(node, key, attrName, eventName); } }) } // 判断元素属性是否是指令 Compiler.prototype.isDirective = function (attrName) { return attrName.startsWith(v-) || attrName.startsWith(@); } // 指令辅助函数 Compiler.prototype.update = function (node, key, attrName, eventName) { let updateFn = this[attrName + Updater]; updateFn && updateFn.call(this, node, this.vm[key], key, eventName); } // 处理v-text指令 Compiler.prototype.textUpdater = function (node, value, key) { node.textContent = value; new Watcher(this.vm, key, (newVal) => { node.textContent = newVal; }) } // 处理v-html指令 Compiler.prototype.htmlUpdater = function (node, value, key) { node.insertAdjacentHTML(beforeend, value); new Watcher(this.vm, key, (newVal) => { node.insertAdjacentHTML(beforeend, newVal); }) } // 处理v-show指令 Compiler.prototype.showUpdater = function (node, value, key) { !value ? node.style.display = none : node.style.display = block new Watcher(this.vm, key, (newVal) => { !newVal ? node.style.display = none : node.style.display = block; }) } // 处理v-if指令 Compiler.prototype.ifUpdater = function (node, value, key) { const nodew = node; const nodep = node.parentNode; if (!value) { node.parentNode.removeChild(node) } new Watcher(this.vm, key, (newVal) => { console.log(newVal); !newVal ? nodep.removeChild(node) : nodep.appendChild(nodew); }) } // 处理v-on指令 Compiler.prototype.onUpdater = function (node, value, key, eventName) { if (eventName) { const handler = this.vm.$options.methods[key].bind(this.vm); node.addEventListener(eventName, handler); } } // 处理v-model指令 Compiler.prototype.modelUpdater = function (node, value, key) { node.value = value; new Watcher(this.vm, key, (newVal) => { node.value = newVal; }) // 双向绑定,视图变化更新数据 node.addEventListener(input, () => { this.vm[key] = node.value; }) } Dep
// 发布者。 // 收集依赖,添加所有的观察者(watcher)。通知所有的观察者。 function Dep() { // 存储所有的观察者watcher this.subs = []; } // 添加观察者 Dep.prototype.addSub = function (sub) { if (sub && sub.update) { this.subs.push(sub); } } // 发送通知 Dep.prototype.notify = function () { this.subs.forEach(sub => { sub.update(); }) } Watcher
function Watcher(vm, key, cb) { this.vm = vm; this.key = key; this.cb = cb; // 把当前watcher对象记录到Dep类的静态属性target Dep.target = this; if (vm.hasOwnProperty(key)) { this.oldVal = vm[key]; } else { const str = `vm.${ key}`; this.oldVal = eval(str); } Dep.target = null; } // 当数据发生变化的时候更新视图 Watcher.prototype.update = function () { let newVal; if (this.vm.hasOwnProperty(this.key)) { newVal = this.vm[this.key]; } else { const str = `this.vm.${ this.key}`; newVal = eval(str); } this.cb(newVal); } 尤大开发的Vue2.x迷你版本