Vue源码学习(一)———数据双向绑定 Observer
2018-01-27 14:06
796 查看
从最简单的案例,来学习Vue.js源码。
(一)为何可以直接使用 Vue?
此写法,即兼容了 cmd ,也兼容了ES6,也将Vue对象挂载到window对象。
(二)Vue双向绑定的实现原理
Vue 采用数据劫持,通过Object.defineProperty的getter和setter,结合观察者模式来实现数据绑定。
与此相关的对象有四个:
Observer(数据监听器):对数据对象的所有属性进行监听,如果 Data Change ===通知==> Watcher;
Watcher(订阅者): 连接 Observer、Compile 的桥梁。
Dep(消息订阅器):收集 Watcher,数据变动触发 notify 函数,再调用 Watcher.update()
Compile:(Directive 指令解析器):对元素节点进行扫描和解析,替换、绑定相应回调函数。
其实这里我有一个疑惑: 为什么Dep 要收集 Watcher?
看源码的时候我重点关注上面 Observer、Watcher、Dep.
进入Vue构造函数,实例化对象,进入 Vue.prototype._init() 函数。
(1) Vue.prototype._init() 函数
2-13行代码,通过整合传入的参数,赋值vm. $options属性中,以便方便的取出。
如果我们使用了 Vuex或者VueRouter,也会将其方法属性挂载在 $options属性中。
(2)initProxy()
为vm新增了一个 _renderProxy 代码属性。
vm. _renderProxy 会在 Vue.prototype._render() 中如下使用。
vnode = render.call(vm._renderProxy, vm.$createElement);
(三) Observer 的初始化
(1) initData()函数
initData完成了对model元素Data数据格式化、元素代理初始化、监听初始化。
(2)observe(data,true)
这个函数会返回一个Observer() 实例对象, 如果model还未添加监听属性,则添加。
observe()函数的关键点在于 ob=new Observer(value) 。
(3)Observer 对象
Observer初始化时,会将当前Observer自身引用挂载在 model 中,
同时循环为每个model属性重写get/set 方法,这样就实现了数据劫持。
(4)defineReactive$$1()
这个函数主要是对Object某个属性值设置了数据劫持,也就是通过重写对象属性中的 get/set 方法,
一旦改变,就会立刻触发相关函数。
但是我有一点疑惑的是:model值改变,会按照 hasHandler ==> proxySetter ==> reactiveSetter 这样一种顺序,
直至最后 触发 Dep.notify() 为什么?
<body> <div id='app'> <input type="text" v-model="message">---{{message}} </div> </body> <script src='./vue.js'></script> <script> var app = new Vue({ el: '#app', data: { message: 'Hello Vue!' } }); </script>
(一)为何可以直接使用 Vue?
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global.Vue = factory()); }(this, (function () { 'use strict'; function Vue$3(options){ this._init(options); } return Vue$3;
})));
此写法,即兼容了 cmd ,也兼容了ES6,也将Vue对象挂载到window对象。
(二)Vue双向绑定的实现原理
Vue 采用数据劫持,通过Object.defineProperty的getter和setter,结合观察者模式来实现数据绑定。
与此相关的对象有四个:
Observer(数据监听器):对数据对象的所有属性进行监听,如果 Data Change ===通知==> Watcher;
Watcher(订阅者): 连接 Observer、Compile 的桥梁。
Dep(消息订阅器):收集 Watcher,数据变动触发 notify 函数,再调用 Watcher.update()
Compile:(Directive 指令解析器):对元素节点进行扫描和解析,替换、绑定相应回调函数。
其实这里我有一个疑惑: 为什么Dep 要收集 Watcher?
看源码的时候我重点关注上面 Observer、Watcher、Dep.
进入Vue构造函数,实例化对象,进入 Vue.prototype._init() 函数。
(1) Vue.prototype._init() 函数
Vue.prototype._init = function (options) { var vm = this; // a uid vm._uid = uid$1++; vm._isVue = true; //代表一个Vue实例,不是组件,不用被监听 if (options && options._isComponent) { initInternalComponent(vm, options);//注入的参数中是组件处理 } else { //实例内部初始化,返回 vm.constructor.options ,如果含有 super,内部也会调用 mergeOptions() //extend(Vue.options,XXX)以及 initGlobalAPI() 中扩展了options 所有实例基础参数 var tempOptions = resolveConstructorOptions(vm.constructor); vm.$options = mergeOptions(tempOptions, options || {}, vm); } //完成后,vm 拥有了 _renderProxy 对象属性 initProxy(vm); //为什么要保存自身引用呢? vm._self = vm; //一步一步往 vm 添加属性,方法 initLifecycle(vm); // initEvents(vm);//父子组件通信工作 initRender(vm); callHook(vm, 'beforeCreate'); initInjections(vm); // resolve injections before data/props initState(vm);//完成 watch observe 等初始化 initProvide(vm); // resolve provide after data/props callHook(vm, 'created'); //Vue 传入的参数中有el属性,进行挂载,启动 if (vm.$options.el) { vm.$mount(vm.$options.el); } }; }
2-13行代码,通过整合传入的参数,赋值vm. $options属性中,以便方便的取出。
如果我们使用了 Vuex或者VueRouter,也会将其方法属性挂载在 $options属性中。
(2)initProxy()
为vm新增了一个 _renderProxy 代码属性。
initProxy = function initProxy(vm) { if (hasProxy) { //是否支持 es6 的代理 var options = vm.$options; var handlers = options.render && options.render._withStripped ? getHandler : hasHandler; vm._renderProxy = new Proxy(vm, handlers); } else { vm._renderProxy = vm; } }; var hasHandler = { has: function has(target, key) { var has = key in target; //判断key是否跟内置全局变量冲突 var isAllowed = allowedGlobals(key) || key.charAt(0) === '_'; if (!has && !isAllowed) { warnNonPresent(target, key); } return has || !isAllowed } }; var getHandler = { get: function get(target, key) { if (typeof key === 'string' && !(key in target)) { warnNonPresent(target, key); } return target[key] } };
vm. _renderProxy 会在 Vue.prototype._render() 中如下使用。
vnode = render.call(vm._renderProxy, vm.$createElement);
(三) Observer 的初始化
(1) initData()函数
initData完成了对model元素Data数据格式化、元素代理初始化、监听初始化。
function initData(vm) { var data = vm.$options.data; /*获取自定义 data 数据,这里为什么使用闭包,而不是直接获取 options.data*/ data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {}; var keys = Object.keys(data); var props = vm.$options.props; var methods = vm.$options.methods; var i = keys.length; while (i--) { //循环为每个 data属性加入代理 var key = keys[i]; if (props && hasOwn(props, key)) { } else if (!isReserved(key)) { //如果key以 $ _ 开头,不作处理,是Vue的关键字 //复制创建新的属性。直接挂在在vm 下,且自定义了 get/set 方法, //其真实的值在 vm._data 下 proxy(vm, "_data", key); } } //处理完成后,vm 以及 vm._data 都含有用户定义的model数据 //监听 observe(data, true /* asRootData */); }
(2)observe(data,true)
这个函数会返回一个Observer() 实例对象, 如果model还未添加监听属性,则添加。
function observe(value, asRootData) { if (!isObject(value)) { return } var ob; if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { //有 __ob__ 属性,即已经被监听 ob = value.__ob__; } else if ( //是否应该被监听 是否是服务器渲染 必须为数组或对象 是否可扩展 是否实例才有的属性 observerState.shouldConvert && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue) {//生成一个观察者 ob = new Observer(value); } //根数据计数 属性 ++ if (asRootData && ob) { ob.vmCount++; } //ob 包含了 dep,value,__ob__属性引用自身,自定义 get\set 方法, //计数vmCount,原型方法haiyou observeArray,监听对象的walk方法 return ob }
observe()函数的关键点在于 ob=new Observer(value) 。
(3)Observer 对象
Observer初始化时,会将当前Observer自身引用挂载在 model 中,
同时循环为每个model属性重写get/set 方法,这样就实现了数据劫持。
/** * 创建Dep对象实例 * 将自身this添加到value的ob属性上 */ var Observer = function Observer(value) { this.value = value; this.dep = new Dep(); //Dep 构造函数: uid--id,subs[] --依赖收集 this.vmCount = 0; def(value, '__ob__', this);//为model属性添加 __ob__属性 if (Array.isArray(value)) { //递归调用 Observer(value) 最后仍然走 walk() var augment = hasProto ? protoAugment : copyAugment; augment(value, arrayMethods, arrayKeys);//原型扩展 this.observeArray(value); } else { this.walk(value); } }; //Walk 为每个属性对象添加get/set Observer.prototype.walk = function walk(obj) { var keys = Object.keys(obj); for (var i = 0; i < keys.length; i++) { defineReactive$$1(obj, keys[i], obj[keys[i]]); } };
(4)defineReactive$$1()
这个函数主要是对Object某个属性值设置了数据劫持,也就是通过重写对象属性中的 get/set 方法,
一旦改变,就会立刻触发相关函数。
/** * 对象属性被劫持 通过调用Object.defineProperty 给data的每个属性添加 getter setter方法, * 当data某个属性被访问时,调用getter方法,判断当 Dep.target 不为空时调用 dep.denpend 和 childObj.dep.denpend方法 * 当改变data的属性时,调用setter方法,这时调用 dep.notify方法进行通知 * */ function defineReactive$$1(obj, key, val, customSetter, shallow) { var dep = new Dep();//依赖管理 var property = Object.getOwnPropertyDescriptor(obj, key);//返回键描述信息 if (property && property.configurable === false) { //不可以修改直接返回 return } var getter = property && property.get; var setter = property && property.set; 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; //获取当前值,是前一个值 if (newVal === value || (newVal !== newVal && value !== value)) { //值没有发生变化,不再做任何处理 return } /* eslint-enable no-self-compare */ if ("development" !== 'production' && customSetter) { customSetter(); } if (setter) { setter.call(obj, newVal);//调用默认setter方法或将新值赋给当前值 } else { val = newVal; } childOb = !shallow && observe(newVal); dep.notify();//赋值后通知依赖变化 } }); }
但是我有一点疑惑的是:model值改变,会按照 hasHandler ==> proxySetter ==> reactiveSetter 这样一种顺序,
直至最后 触发 Dep.notify() 为什么?
相关文章推荐
- Vue.js学习 Item4 -- 数据双向绑定
- Vue学习之源码分析--从Vue.js源码角度再看数据绑定(三)
- 我的angularjs源码学习之旅3——脏检测与数据双向绑定
- Vue.js学习 Item4 -- 数据双向绑定
- Vue.js第一天学习笔记(数据的双向绑定、常用指令)
- Vue 2.0学习笔记:实现组件数据的双向绑定
- 从源码角度学习vue数据绑定
- 学习vue实现双向绑定【附源码下载地址】
- vue源码解析之--数据双向绑定
- Vue.js第一天学习笔记(数据的双向绑定、常用指令)
- 【学习笔记】Vue中实现双向数据绑定的原理
- 通过源码分析Vue的双向数据绑定详解
- vue学习相关知识体系及数据双向绑定的理解
- 双向数据绑定---AngularJS的基本原理学习
- vue.js动态数据绑定学习
- Vue.js双向数据绑定实现
- 模拟实现vue数据双向绑定
- vue实现数据双向绑定原理剖析
- Vue 双向数据绑定原理分析
- angular和vue双向数据绑定