1.前端框架的根本意义前端
1.1 前端框架的好处vue
最开始学习前端框架的时候(我第一个框架是 React)并不理解框架能带来什么,只是由于你们都在用框架,最实际的一个用途就是全部企业几乎都在用框架,不用框架就 out 了.前端框架
随着使用的深刻我逐渐理解到框架的好处:数据结构
1.2 前端框架的根本意义架构
简单来讲,前端框架的根本意义是解决了UI 与状态同步问题。app
在 Vue 中咱们若是要在todos
中添加一条,只须要app4.todos.push({ text: '新项目' })
,这时因为 Vue 内置的响应式系统会自动帮咱们进行 UI 与状态的同步工做.框架
<div id="app-4"> <ol> <li v-for="todo in todos"> {{ todo.text }} </li> </ol> </div>
var app4 = new Vue({ el: '#app-4', data: { todos: [ { text: '学习 JavaScript' }, { text: '学习 Vue' }, { text: '整个牛项目' } ] } }) //在此我向你们推荐一个前端全栈开发交流圈:619586920 突破技术瓶颈,提高思惟能力
若是咱们用 JQuery 或者 JS 进行操做,免不了一大堆li.appendChild
、document.createElement
等 DOM 操做,咱们须要一长串 DOM 操做保证状态与 UI 的同步,其中一个环节出错就会致使 BUG,手动操做的缺点以下:异步
不论是 vue 的数据劫持、Angular 的脏检测仍是 React 的组件级 reRender都是帮助咱们解决 ui 与状态同步问题的利器。函数
这也解释了Backbone做为前端框架鼻祖在以后落寞的缘由,Backbone只是引入了 MVC 的思想,并无解决 View 与 Modal 同步的问题,相比于现代的三大框架直接操做 Modal 就能够同步 UI 的特性, Backbone 仍然与 JQuery 绑定,在 View 里操做 Dom来达到同步 UI 的目的,这显然是不符合现代前端框架设计要求的。组件化
UI 在 MVVM 中指的是 View,状态在 MVVM 中指的是 Modal,而保证 View 和 Modal 同步的是 View-Modal。
Vue 经过一个响应式系统保证了View 与 Modal的同步,因为要兼容IE,Vue 选择了 Object.defineProperty
做为响应式系统的实现,可是若是不考虑 IE 用户的话,Object.defineProperty
并非一个好的选择target=https%3A%2F%2Fjuejin.im%2Fpost%2F5acd0c8a6fb9a028da7cdfaf)。
咱们将用 Proxy 实现一个响应式系统。
2.1 发布订阅中心
一个响应式系统离不开发布订阅模式,由于咱们须要一个 Dep保存订阅者,并在 Observer 发生变化时通知保存在 Dep 中的订阅者,让订阅者得知变化并更新视图,这样才能保证视图与状态的同步。
/** * [subs description] 订阅器,储存订阅者,通知订阅者 * @type {Map} */ export default class Dep { constructor() { // 咱们用 hash 储存订阅者 this.subs = new Map(); } // 添加订阅者 addSub(key, sub) { // 取出键为 key 的订阅者 const currentSub = this.subs.get(key); // 若是能取出说明有相同的 key 的订阅者已经存在,直接添加 if (currentSub) { currentSub.add(sub); } else { // 用 Set 数据结构储存,保证惟一值 this.subs.set(key, new Set([sub])); } } // 通知 notify(key) { // 触发键为 key 的订阅者们 if (this.subs.get(key)) { this.subs.get(key).forEach(sub => { sub.update(); }); } } }
咱们在订阅器 Dep
中实现了一个notify
方法来通知相应的订阅这们,然而notify
方法到底何时被触发呢?
固然是当状态发生变化时,即 MVVM 中的 Modal 变化时触发通知,然而Dep
显然没法得知 Modal 是否发生了变化,所以咱们须要建立一个监听者Observer
来监听 Modal, 当 Modal 发生变化的时候咱们就执行通知操做。
vue 基于Object.defineProperty
来实现了监听者,咱们用 Proxy 来实现监听者.
与Object.defineProperty
监听属性不一样, Proxy 能够监听(实际是代理)整个对象,所以就不须要遍历对象的属性依次监听了,可是若是对象的属性依然是个对象,那么 Proxy 也没法监听,因此咱们实现了一个observify
进行递归监听便可。
/** * [Observer description] 监听器,监听对象,触发后通知订阅 * @param {[type]} obj [description] 须要被监听的对象 */ const Observer = obj => { const dep = new Dep(); return new Proxy(obj, { get: function(target, key, receiver) { // 若是订阅者存在,直接添加订阅 if (Dep.target) { dep.addSub(key, Dep.target); } return Reflect.get(target, key, receiver); }, set: function(target, key, value, receiver) { // 若是对象值没有变,那么不触发下面的操做直接返回 if (Reflect.get(receiver, key) === value) { return; } const res = Reflect.set(target, key, observify(value), receiver); // 当值被触发更改的时候,触发 Dep 的通知方法 dep.notify(key); return res; }, }); }; /** * 将对象转为监听对象 * @param {*} obj 要监听的对象 */ export default function observify(obj) { if (!isObject(obj)) { return obj; } // 深度监听 Object.keys(obj).forEach(key => { obj[key] = observify(obj[key]); }); return Observer(obj); }
咱们目前已经解决了两个问题,一个是如何得知 Modal 发生了改变(利用监听者 Observer 监听 Modal 对象),一个是如何收集订阅者并通知其变化(利用订阅器收集订阅者,并用notify通知订阅者)。
咱们目前还差一个订阅者(Watcher)
// 订阅者 export default class Watcher { constructor(vm, exp, cb) { this.vm = vm; // vm 是 vue 的实例 this.exp = exp; // 被订阅的数据 this.cb = cb; // 触发更新后的回调 this.value = this.get(); // 获取老数据 } get() { const exp = this.exp; let value; Dep.target = this; if (typeof exp === 'function') { value = exp.call(this.vm); } else if (typeof exp === 'string') { value = this.vm[exp]; } Dep.target = null; return value; } // 将订阅者放入待更新队列等待批量更新 update() { pushQueue(this); } // 触发真正的更新操做 run() { const val = this.get(); // 获取新数据 this.cb.call(this.vm, val, this.value); this.value = val; } }
咱们在上一节中实现了订阅者( Watcher),可是其中的update
方法是将订阅者放入了一个待更新的队列中,而不是直接触发,缘由以下:
所以这个队列须要作的是异步且去重,所以咱们用 Set
做为数据结构储存 Watcher 来去重,同时用Promise
模拟异步更新。
// 建立异步更新队列 let queue = new Set() // 用Promise模拟nextTick function nextTick(cb) { Promise.resolve().then(cb) } // 执行刷新队列 function flushQueue(args) { queue.forEach(watcher => { watcher.run() }) // 清空 queue = new Set() } // 添加到队列 export default function pushQueue(watcher) { queue.add(watcher) // 下一个循环调用 nextTick(flushQueue) }
咱们梳理一下流程, 一个响应式系统是如何作到 UI(View)与状态(Modal)同步的?
咱们首先须要监听 Modal, 本文中咱们用 Proxy 来监听了 Modal 对象,所以在 Modal 对象被修改的时候咱们的 Observer 就能够得知。
咱们得知Modal发生变化后如何通知 View 呢?要知道,一个 Modal 的改变可能触发多个 UI 的更新,好比一个用户的用户名改变了,它的我的信息组件、通知组件等等组件中的用户名都须要改变,对于这种状况咱们很容易想到利用发布订阅模式来解决,咱们须要一个订阅器(Dep)来储存订阅者(Watcher),当监听到 Modal 改变时,咱们只须要通知相关的订阅者进行更新便可。
那么订阅者来自哪里呢?其实每个组件实例对应着一个订阅者(正由于一个组件实例对应一个订阅者,才能利用 Dep 通知到相应组件,否则乱套了,通知订阅者就至关于间接通知了组件)。
当订阅者得知了具体变化后它会进行相应的更新,将更新体如今 UI(View)上,至此UI 与 Modal 的同步完成了。
响应式系统虽然是 Vue 的核心概念,可是一个响应式系统并不够.
响应式系统虽然得知了数据值的变化,可是当值不能完整映射 UI 时,咱们依然须要进行组件级别的 reRender,这种状况并不高效,所以 Vue 在2.0版本引入了虚拟 DOM, 虚拟 DOM进行进一步的 diff 操做能够进行细粒度更高的操做,能够保证 reReander 的下限(保证不那么慢)。
除此以外为了方便开发者,vue 内置了众多的指令,所以咱们还须要一个 vue 模板解析器.