vue源码——浅析响应式原理
又到了学习源码的时间??。
我们都知道vue是一个mvvm框架,数据与视图双向绑定,所有入门vue的同学,实现的第一个demo应该都是??
<div id="app"> <span>{{ msg }}</span></div><script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script><script> var vm = new Vue({ el: '#app', data: { msg: 'hello, world!' } }) // 此时浏览器打印 hello,world! vm.msg = 'change msg' // 增加这句之后, 浏览器打印'change msg'</script>这种数据响应式绑定是如何实现的呢? 稍有经验的同学知道,是发布订阅的设计模式实现的,还知道实现这个模式的关键代码是Object.defineProperty()。再往下问,可能有人就不知道了。 不知道咱们就学,学无止境好伐~??。开始正文 ??
准备工作
- 理解vue响应式原理
- 理解发布订阅设计模式
核心代码
- Observer类, 代码注释中的序号我会逐个解释,假如解释不对,请指正!??
/** * Observer class that is attached to each observed * object. Once attached, the observer converts the target * object's property keys into getter/setters that * collect dependencies and dispatch updates. */var Observer = function Observer (value) { this.value = value; this.dep = new Dep(); // 1. 依赖对象 this.vmCount = 0; def(value, '__ob__', this); // 2.def方法 if (Array.isArray(value)) { if (hasProto) { // 3. hasProto变量 protoAugment(value, arrayMethods); // 4.arrayMethods变量,5. protoAugment方法 } else { copyAugment(value, arrayMethods, arrayKeys); //6.copyAugment方法 } this.observeArray(value); // 7.observeArray方法 } else { this.walk(value); // 8.walk方法 }};首先我们看一下代码开始的官方解释,我了解的意思是:
当目标对象被追踪,观察者这个方法会将目标对象的属性key转换为getter和setter方法,用来收集依赖和捕获升级
一个截图你就明白了~

我在data中随意写了一个对象,打印出来之后可以看到,这个对象中多了get xxx set xxx这种方法。这些方法就是观察者给目标对象增加的
- 依次解释注释部分
- Dep() 依赖类
var uid = 0;/** * A dep is an observable that can have multiple * directives subscribing to it. */var Dep = function Dep () { this.id = uid++; this.subs = [];};官方解释我觉得非常晦涩,我了解的意思就是 dep对象上有多个方法。这个对象的作用是为了去检测数组的变化,由于Array类型的变量没有getter setter方法,只能通过
__ob__属性中的dep来收集依赖捕获升级。
- def方法
/** * Define a property. */function def (obj, key, val, enumerable) { Object.defineProperty(obj, key, { value: val, enumerable: !!enumerable, writable: true, configurable: true });}对原生
Object.defineProperty进行了封装
hasProto
var hasProto = '__proto__' in {};判断一个空对象中能否有
__proto__属性。这段代码我最初以为是判断当前环境,但是我尝试了在node环境下打印空对象,发现里面也有__proto__属性。在网上查了也没有结果,假如你知道的话,请麻烦在评论区帮我解惑!
arrayMethods
var arrayProto = Array.prototype;var arrayMethods = Object.create(arrayProto);
arrayMethods是Array的子类。这样做是为了获取Array的方法,比方push,slice等等所有方法。
protoAugment()
/** * Augment a target Object or Array by intercepting * the prototype chain using __proto__ */function protoAugment (target, src) { /* eslint-disable no-proto */ target.__proto__ = src; /* eslint-enable no-proto */}将
arrayMethods赋值给目标对象。__proto__这个属性是所有对象都有的,是浏览器实现的,方便我们查看原型链,MDN上建议这个属性作为可读属性,最好不要直接使用。
copyAugment
/** * Augment a target Object or Array by defining * hidden properties. *//* istanbul ignore next */function copyAugment (target, src, keys) { for (var i = 0, l = keys.length; i < l; i++) { var key = keys[i]; def(target, key, src[key]); }}这段代码是把
arrayMethods中的方法依次增加到目标对象中
observeArray()
/** * Observe a list of Array items. */Observer.prototype.observeArray = function observeArray (items) { for (var i = 0, l = items.length; i < l; i++) { observe(items[i]); }};假如目标是数组对象,遍历这个数组,给每个对象注册观察者对象(也就是watcher)。
walk()
/** * Walk through all properties and convert them into * getter/setters. This method should only be called when * value type is Object. */Observer.prototype.walk = function walk (obj) { var keys = Object.keys(obj); for (var i = 0; i < keys.length; i++) { defineReactive$$1(obj, keys[i]); }};假如目标是纯对象, 就给其中的每个属性增加getter/setter方法
defineReactive$$1()
/** * Define a reactive property on an Object. */function defineReactive$$1 ( obj, key, val, customSetter, shallow) { var dep = new Dep(); var property = Object.getOwnPropertyDescriptor(obj, key); if (property && property.configurable === false) { return } // cater for pre-defined getter/setters var getter = property && property.get; var setter = property && property.set; if ((!getter || setter) && arguments.length === 2) { val = obj[key]; } var childOb = !shallow && observe(val); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { var value = getter ? getter.call(obj) : val; if (Dep.target) { dep.depend(); if (childOb) { childOb.dep.depend(); if (Array.isArray(value)) { dependArray(value); } } } return value }, set: function reactiveSetter (newVal) { var value = getter ? getter.call(obj) : val; /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter(); } // #7981: for accessor properties without setter if (getter && !setter) { return } if (setter) { setter.call(obj, newVal); } else { val = newVal; } childOb = !shallow && observe(newVal); dep.notify(); // 通知watcher改变, 响应式原理 } });}走到了这一步,才是真正实现了响应式。核心是
dep.notify()
整体解析
有一部分代码我没有贴出来,假如感兴趣可以去vue源码中查看。
我了解的vue响应式的思路大致是
- 首先给目标加上
__ob__属性,其值是目标本身的值以及dep依赖对象和vmcount - 判断目标能否为数组,由于数组变化是无法检测到的,所以特例一个情况。
- 假如目标是数组的话,先把数组中会改变原数组的方法取出来,赋给目标,假如目标触发了这些方法,说明原数组改变了,这样能侧面反应出数据能否改变。源码中不仅仅是如此,还给数组中的每个值注册了watcher,假如这些值改变了,也会通知watcher
- 假如目标是对象,给目标绑定getter/setter。对象的值改变会触发
notify(),通知watcher改变,引起视图改变
总结
目前写下这篇博客,仅仅是我在阅读源码之后写的,其中一定有很多了解不正确的地方,后续在网上学习之后,我会改正。其实我觉得vue实现响应式最关键的是Dep对象, 其中的notify()通知watcher,才实现了响应式,但是因为我对Dep对象的了解不深,所以暂时没有写下相关的代码,继续学习,等我深刻了解之后再回来补充??
路漫漫其修远兮,吾将上下而求索。共勉。
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » vue源码——浅析响应式原理