Vue 源码(一):响应式原理
栏目: JavaScript · 发布时间: 5年前
内容简介:本文只做简单介绍,结合代码食用更佳:效果预览:github pagesdefineProperty 让我们可以劫持某个属性的 getter 和 setter,举个例子:
本文只做简单介绍,结合代码食用更佳: github/vue-learn-source-code
效果预览:github pages
Object.defineProperty
defineProperty 让我们可以劫持某个属性的 getter 和 setter,举个例子:
var person = { firstName: 'meimei', lastName: 'han' }; Object.defineProperty(person, 'fullName', { get() { return this.lastName + ' ' + this.firstName; }, set(val) { let arr = val.split(' '); this.lastName = arr[0]; this.firstName = arr[1]; } }); 复制代码
劫持 fullName 后,改变 firstName 或 lastName 会更新 fullName,反之亦然
目标
本文的目标是仿造 vue 实现改变数据后更新 dom,让以下代码能够 work:
<div id="app"> <p>firstName: {{firstName}}</p> <p>lastName: {{lastName}}</p> <p>fullName: {{fullName}}</p> </div> <script src="./vue.js"></script> <script> let vm = new Vue({ el: '#app', data() { return { firstName: 'meimei', lastName: 'han' }; }, computed: { fullName: { get: function() { return this.lastName + ' ' + this.firstName; }, set: function(val) { let arr = val.split(' '); this.lastName = arr[0]; this.firstName = arr[1]; } } } }); </script> 复制代码
观察者模式
我们要做的是数据变化后去更新 dom,观察者模式很适合
数据只需要收集依赖,当数据变化通知依赖更新即可,先建一个类描述这件事:
class Dep { constructor() { this.subs=[] } addSub(item) { this.subs.push(item); } notify() { this.subs.forEach(item => { item.update(); }); } } 复制代码
再细想一下,dom 依赖 data,则在获取 dom 的过程中需要用到 data 的 get,在 data get 时收集依赖即可,set data 时执行 dom 的 update
get data 时需要记录依赖 data 的数据,给 class Dep 增加一个属性 target 作为记录工具,结合 defineProperty 实现如下:
Dep.target = undefined; function defineReactive(obj, key) { let dep = new Dep(); let val = obj[key]; Object.defineProperty(obj, key, { get: function() { if (Dep.target) { // get 中收集依赖 dep.addSub(Dep.target); } return val; }, set: function(value) { val = value; // set 中触发更新 dep.notify(); } }) } 复制代码
工具已备齐,接下来就是遍历 data 的属性,用 defineReactive 走一遍
data, computed, dom 的依赖关系
解析 dom 会用到 data 和 computed,computed 的 get 会用到 data
- 遍历 data
function initData(vm) { let data = vm.$options.data; data = typeof data === 'function' ? data() : data; Object.keys(data).forEach(key => { defineReactive(data, key); }); // 把 data 的属性代理到 vm 实例 proxy(data, vm); } 复制代码
- 遍历 computed
function initComputed(vm) { let computed = vm.$options.computed; let defaultSetter = function(key) { console.error(this, ' has no setter for ', key) } Object.keys(computed).forEach(key => { let getter = typeof computed[key] === 'function' ? computed[key] : computed[key].get; let setter = typeof computed[key] === 'function' ? defaultSetter.bind(computed) : computed[key].set; Object.defineProperty(computed, key, { get: getter.bind(vm), set: setter.bind(vm) }) }) // 把 computed 的属性代理到 vm 实例 proxy(computed, vm); } 复制代码
- 解析 dom
function mount(vm) { let update = compile(vm); let watcher = new Watcher(update); // 把 target 标为 dom Dep.target = watcher; update(); Dep.target = undefined; } function compile(vm) { let el = vm.$options.el; el = document.querySelector(el); vm.$el = el; let innerHTML = el.innerHTML; let getter = function() { return innerHTML.replace(/{{(.*?)}}/g, function() { // 这里用到了 data computed 的 get,收集了依赖 return vm[arguments[1]] }); }; let update = function() { let iHTML = getter(); el.innerHTML = iHTML; } return update; } 复制代码
多说一句
在收集依赖时,我们给 Dep 这个 class 增加一个属性 target,在 vue 中还结合了 targetStack。这种收集方式稍微管理不慎就可能存在 bug,在另一篇文章有提过: 熟悉 Vue ?你能解释这个死循环吗? 。
为自己的填坑喝彩~
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- flask 源码解析6:响应
- 从源码解析vue的响应式原理-响应式的整体流程
- vue响应式系统源码解析
- Vue 源码解读-数据响应系统
- 从源码解析vue的响应式原理
- Vue源码分析系列五: 响应式原理
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Apache源代码全景分析第1卷
2009-5 / 88.00元
《Apache源代码全景分析第1卷:体系结构与核心模块》是“Apache源代码全景分析”的第1卷。书中详细介绍了Apache的基础体系结构和核心模块的实现机制,包括配置文件、模块化结构、多任务并发,以及网络连接和请求读取,其中多任务并发体系结构是《Apache源代码全景分析第1卷:体系结构与核心模块》分析的重点,讨论了Prefork、Worker及WinNT三种MPM。《Apache源代码全景分析......一起来看看 《Apache源代码全景分析第1卷》 这本书的介绍吧!