参考 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的更多相关文章

  1. vue.js 源代码学习笔记 ----- 工具方法 env

    /* @flow */ /* globals MutationObserver */ import { noop } from 'shared/util' // can we use __proto_ ...

  2. vue.js 源代码学习笔记 ----- html-parse.js

    /** * Not type-checking this file because it's mostly vendor code. */ /*! * HTML Parser By John Resi ...

  3. vue.js 源代码学习笔记 ----- instance state

    /* @flow */ import Dep from '../observer/dep' import Watcher from '../observer/watcher' import { set ...

  4. vue.js 源代码学习笔记 ----- 工具方法 lang

    /* @flow */ // Object.freeze 使得这个对象不能增加属性, 修改属性, 这样就保证了这个对象在任何时候都是空的 export const emptyObject = Obje ...

  5. vue.js 源代码学习笔记 ----- helpers.js

    /* @flow */ import { parseFilters } from './parser/filter-parser' export function baseWarn (msg: str ...

  6. vue.js 源代码学习笔记 ----- instance render

    /* @flow */ import { warn, nextTick, toNumber, _toString, looseEqual, emptyObject, handleError, loos ...

  7. vue.js 源代码学习笔记 ----- instance event

    /* @flow */ import { updateListeners } from '../vdom/helpers/index' import { toArray, tip, hyphenate ...

  8. vue.js 源代码学习笔记 ----- instance init

    /* @flow */ import config from '../config' import { initProxy } from './proxy' import { initState } ...

  9. vue.js 源代码学习笔记 ----- instance index

    import { initMixin } from './init' import { stateMixin } from './state' import { renderMixin } from ...

随机推荐

  1. Centos编译安装 LAMP (apache-2.4.7 + mysql-5.5.35 + php 5.5.8)+ Redis

    转载地址:http://www.cnblogs.com/whoamme/p/3530056.html 软件源代码包存放位置:/usr/local/src 源码包编译安装位置:/usr/local/软件 ...

  2. Nginx配置location跳转后偶尔出现404

    tv.xxx.com/voice请求时需跳转至:tv.xxx.com/zongyi/zt2015/haoshengyin/index.shtml 目录结构: nginx服务器配置: location ...

  3. No module named _tkinter

    https://www.douban.com/note/524197380/?type=like

  4. bzoj1879: [Sdoi2009]Bill的挑战(codevs2308)(luoguP2167) 状压dp

    唔...懒兔子来写博客了... 点我看题 这题的话...我想了很久但是都不是可行解 刚开始想预处理任意两个串是否可以匹配然后在乱搞,后来发现完全不会写... 然后按照惯例,我会看题解认真的思考... ...

  5. Linux服务器上ftp的搭建和使用

    知识点: 1.  FTP的简介.工作原理 2.在Linux上搭建FTP服务器 参考: 阿里云文档:https://help.aliyun.com/knowledge_detail/60152.html ...

  6. Python学习札记(三) I/O

    参考:输入和输出 I/O 1.print()函数 a.调用print()输出字符串有以下两种方式:(1)print('[字符串]') (2)print("[字符串]") b.调用p ...

  7. 回文树 Palindromic Tree

    回文树 Palindromic Tree 嗯..回文树是个什么东西呢. 回文树(或者说是回文自动机)每个节点代表一个本质不同的回文串. 首先它类似字典树,每个节点有SIGMA个儿子,表示对应的字母. ...

  8. 物料类型AM11没有任务清单类型N定义

    CA01 创建工艺路线时报错信息:“物料类型AM11没有为任务清单类型N定义” (如下图) 处理方法: 配置路径:生产->基本数据->工艺路线->通用数据->定义物料类型分配 ...

  9. hiho 有序01字符串 dp

    题目1 : 有序01字符串 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 对于一个01字符串,你每次可以将一个0修改成1,或者将一个1修改成0.那么,你最少需要修改多少 ...

  10. 解压.zip,.tar.gz文件到指定目录,重命名文件

    1.解压文件到指定目录 /** * 解压文件到指定目录 * zipFile:要解压的文件 * descDir:解压到哪个文件 * */ @SuppressWarnings("rawtypes ...