vue.js 源代码学习笔记 ----- observe
参考 vue 2.2.6版本
/* @flow */
//引入订阅者模式
import Dep from './dep'
import { arrayMethods } from './array'
import {
def,
isObject,
isPlainObject,
hasProto,
hasOwn,
warn,
isServerRendering
} from '../util/index' const arrayKeys = Object.getOwnPropertyNames(arrayMethods) /**
* By default, when a reactive property is set, the new value is
* also converted to become reactive. However when passing down props,
* we don't want to force conversion because the value may be a nested value
* under a frozen data structure. Converting it would defeat the optimization. 默认新的值也会被definePropty, 但是有些情况是不希望被转换的, 比如 frozen的属性, 实际不需要监听, 如果强制转换, 会破坏做的性能优化, 所以这里监听做成可配置
*/
export const observerState = {
shouldConvert: true,
isSettingProps: false
} /**
* Observer class that are attached to each observed
* object. Once attached, the observer converts target
* object's property keys into getter/setters that
* collect dependencies and dispatches updates. 每个监听对象在Observer里面被监听, 对象属性进入 getter setter方法后, 会收集依赖项 和 触发更新也的回调方法
*/
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that has this object as root $data constructor (value: any) {
this.value = value
// 监听的是 obj = {a:{}}, 这里的观察是整个对象 obj, 而不是某个属性的变化 /*
举个列子 data: { a:{b:{c:1}} } var w1 = this.$watch('a.b.c', fn1); 初始化的时候, 会把这个data生成 6 个dep, 其中三个是这个方法生成的, 另外三个是下面那个defineReact方法生成的
前三个对应的值分别是 {a:{b:{c:1}}} {b:{c:1}} {c:1}
后三个对应的值分别是 data.a a.b b.c 上面的 w1 这个watcher收集依赖的时候, 会有5个依赖, 分别是 data.a || {b:{c:1}} || a.b || {c:1} || b.c 对应的dep
在wathc a.b.c 的时候就会依次去取值 data.a => a.b => b.c, 收集这个三个属性依赖就可以了, 至于为什么要收集前三个对象依赖,
还没有搞清楚, 可能在一些地方会在这三个对象ob中挂载一些东西, 以便发布一些东西.
*/
this.dep = new Dep()
this.vmCount = 0 //value增加一个 属性 __ob__ , 值就是 observer
def(value, '__ob__', this)
if (Array.isArray(value)) { //有__proto__的浏览器才能监控数组的push pop 方法, 如果没有, 则不能监控
const augment = hasProto
? protoAugment
: copyAugment
augment(value, arrayMethods, arrayKeys)
//监控数组的每一项
this.observeArray(value)
} else {
this.walk(value)
}
} /**
* Walk through each property and convert them into
* getter/setters. This method should only be called when
* value type is Object.
循环每一个对象属性, 逐个监控
*/
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]])
}
} /**
* Observe a list of Array items.
监控数组每个元素, 如果元素也是数组, 又走数组监控的分支
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
} // helpers /**
* Augment an target Object or Array by intercepting
* the prototype chain using __proto__
利用 __proto__ 截断原有的原型链
相当于
arrA.__proto__ = arrayMethods
这样的话 , 用户使用 arrA.push('haha');
就会沿着 __proto__找到 arrayMethods 属性 __proto__ 的push方法, 因为这个方法被检测了, 所以会触发set方法, 最终触发 notify()
*/
function protoAugment (target, src: Object) {
/* eslint-disable no-proto */
target.__proto__ = src
/* eslint-enable no-proto */
} /**
* Augment an target Object or Array by defining
* hidden properties.
*通过定义隐藏属性来加强一个目标对象 或者 数组
/* istanbul ignore next */
function copyAugment (target: Object, src: Object, keys: Array<string>) {
for (let i = 0, l = keys.length; i < l; i++) {
const key = keys[i]
def(target, key, src[key])
}
} /**
* Attempt to create an observer instance for a value,
* returns the new observer if successfully observed,
* or the existing observer if the value already has one.
如果监控的值已经存在, 返回这个订阅者
*/
export function observe (value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value)) {
return
}
let ob: Observer | void //如果这个值有__ob__, 直接返回
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
//如果不是服务器 而且 能被监控, 而且是一个数组或者对象, 而且这个对象不是vue实例
observerState.shouldConvert &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
//根据值来创建ob
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
} /**
* Define a reactive property on an Object.
为属性添加监控主方法
*/
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: Function
) { //而这个dep是 observe下的某一个属性, 比如监听的是 obj = {a:{}}, 这里的dep是obj的属性 a
const dep = new Dep()
//获取Object的get set函数
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
} // cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
//如果val也是一个对象, 而且监控成功, 返回 一个observe实例
let childOb = observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const 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) {
const 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()
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = observe(newVal)
dep.notify()
}
})
} /**
* Set a property on an object. Adds the new property and
* triggers change notification if the property doesn't
* already exist.
对象添加一个属性, 如果新的属性, 触发通知, 数组的$set 方法 其实就是通过splice方法触发的页面变化
*/
export function set (target: Array<any> | Object, key: any, val: any): any {
if (Array.isArray(target) && typeof key === 'number') {
target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
}
if (hasOwn(target, key)) {
target[key] = val
return val
}
const ob = (target : any).__ob__
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
)
return val
}
if (!ob) {
target[key] = val
return val
}
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
} /**
* Delete a property and trigger change if necessary.
Vue.$remove 方法触发页面变化的来源
*/
export function del (target: Array<any> | Object, key: any) {
//判断如果是数组
if (Array.isArray(target) && typeof key === 'number') {
target.splice(key, 1)
return
}
const ob = (target : any).__ob__
//避免删除vue实例
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid deleting properties on a Vue instance or its root $data ' +
'- just set it to null.'
)
return
}
if (!hasOwn(target, key)) {
return
}
//如果是对象
delete target[key]
if (!ob) {
return
}
//触发通知
ob.dep.notify()
}
/**
* Collect dependencies on array elements when the array is touched, since
* we cannot intercept array element access like property getters.
*/
function dependArray (value: Array<any>) {
for (let e, i = 0, l = value.length; i < l; i++) {
e = value[i]
e && e.__ob__ && e.__ob__.dep.depend()
if (Array.isArray(e)) {
dependArray(e)
}
}
}
vue.js 源代码学习笔记 ----- observe的更多相关文章
- vue.js 源代码学习笔记 ----- 工具方法 env
/* @flow */ /* globals MutationObserver */ import { noop } from 'shared/util' // can we use __proto_ ...
- vue.js 源代码学习笔记 ----- html-parse.js
/** * Not type-checking this file because it's mostly vendor code. */ /*! * HTML Parser By John Resi ...
- vue.js 源代码学习笔记 ----- instance state
/* @flow */ import Dep from '../observer/dep' import Watcher from '../observer/watcher' import { set ...
- vue.js 源代码学习笔记 ----- 工具方法 lang
/* @flow */ // Object.freeze 使得这个对象不能增加属性, 修改属性, 这样就保证了这个对象在任何时候都是空的 export const emptyObject = Obje ...
- vue.js 源代码学习笔记 ----- helpers.js
/* @flow */ import { parseFilters } from './parser/filter-parser' export function baseWarn (msg: str ...
- vue.js 源代码学习笔记 ----- instance render
/* @flow */ import { warn, nextTick, toNumber, _toString, looseEqual, emptyObject, handleError, loos ...
- vue.js 源代码学习笔记 ----- instance event
/* @flow */ import { updateListeners } from '../vdom/helpers/index' import { toArray, tip, hyphenate ...
- vue.js 源代码学习笔记 ----- instance init
/* @flow */ import config from '../config' import { initProxy } from './proxy' import { initState } ...
- vue.js 源代码学习笔记 ----- instance index
import { initMixin } from './init' import { stateMixin } from './state' import { renderMixin } from ...
随机推荐
- 前端工程师在实现支付功能的时候能做些什么(V客学院技术分享)?
现在最流行的两种支付微信支付和支付宝支付,在日常开发的过程中肯定离不开支付功能的开发,有很多人第一次接触时会有些措手不及. 一.业务逻辑 (电商平台为例子) 支付大部分用在电商平台,各种打赏,游戏充值 ...
- Spring七大框架
Spring Core:最基础部分,提供IOC和依赖注入.基础概念是BeanFactory,提供对Factory模式的经典实现,这样来消除对程序性单例模式的需要,并真正地允许你从程序逻辑中分离出依赖关 ...
- nginx之proxy、cache、upstream模块学习
nginx之proxy反向代理模块: location ^~ /proxy_path/ { root "/www/html"; 这里没必要配置 index index.html; ...
- Java学习笔记-方法引用
方法引用(Method Reference) 上一篇中记录了Lambda表达式,其可以创建匿名方法.当Lambda表达式只是调用一个存在的方法时,可以采用方法引用(JDK8具有的特性).如下: pub ...
- iOS开发进阶 - 基于PhotoKit的图片选择器
移动端访问不佳,请访问我的个人博客 很早之前就用OC把代码写完了并用在项目中了,一直没时间整理,现在用swift重写一份,并且更加详细的来了解这个Photos框架,下面是我集合苹果官方文档和其他大神的 ...
- windows环境下mysql的解压安装以及备份和还原
系统环境为server2012 1.下载mysql解压版,解压安装包到指定目录 2.在以上目录中,复制一份my-default.ini文件,重命名为my.ini,进行如下修改(按照需要): [mysq ...
- HTTP-API-DESIGN 怎样设计一个合理的 HTTP API (二)
接上篇 HTTP-API-DESIGN 怎样设计一个合理的 HTTP API (一) 整个 ppt 可以去这里下载. 这一篇主要从服务端应该如何返回合理的返回值的角度,讨论如何设计一个合理的 HTTP ...
- bugfree登录后报错PHP Fatal error: Call-time pass-by-reference has been removed in
详细报错信息[Tue Apr 25 06:49:07.556316 2017] [:error] [pid 21799] [client *.*.*.*:55813] PHP Fatal error: ...
- 解决ubuntu在当前位置打开终端功能
ubuntu右键在当前位置打开终端 ubuntu增加右键命令: 在终端中打开 软件中心: 搜索nautilus-open-terminal安装 命令行: sudo apt-ge ...
- WPF圆角透明无边框窗体
<Window x:Class="ImportData.MainWindow" xmlns="http://schemas.microsoft.com/winfx/ ...