Vueソース学習:Arrayのデータリスニングについて
18074 ワード
サマリ
Vueの応答式はObjectであることはよく知られています.definePropertyはデータハイジャックを行います.しかし、それはObjectタイプに対して実現できますが、配列なら?set/get方式では無理です.
しかし,Vueの著者らは,Array型のモニタリング:ブロッキングを実現するための方法を使用した.
核心思想.
ブロックを作成することで配列自体のプロトタイプオブジェクトArrayを上書きする.prototype.
ブロッキング
Vueソースパスvue/src/core/observer/arrayを表示します.js.
Vueがいつdata属性をObserverするかについて
Vueのソースコードに詳しい子供靴なら、すぐにVueの入り口ファイルvue/src/core/instance/indexを見つけることができるはずです.js.
this.init()
ソースパス:vue/src/core/instance/init.js.
initState()
ソースパス:vue/src/core/instance/state.js.
この時observeが現れたことに気づきます.
observe
ソースパス:vue/src/core/observer/index.js
ブロッキングを使用するタイミング
Vueの応答システムにはObserveクラスがあります.ソースパス:vue/src/core/observer/index.js.
依存を収集する方法
Vueの中で本当にデータ応答式の処理をしているのはdefineReactive()です.defineReactiveメソッドは、オブジェクトのデータ属性をアクセサ属性に変換することです.すなわち、データ属性にget/setを設定します.
ストレージ配列依存リスト
なぜObserverインスタンスに依存を存在させる必要があるのか.すなわち
まずgetterでObserverインスタンスにアクセスする必要があります
また、前述したブロッキングではObserverインスタンスを使用します.
上記のことを考えます.ob__属性はどこから来ますか?
すべての属性が検出されると1つの__が打たれることを覚えておいてください.ob__のタグ、すなわち応答型データであることを示す.
Arrayに関する注意事項
JavaScriptの制限により、Vueは以下の変動する配列を検出できません.インデックスを使用して直接アイテムを設定する場合、例えばvm.items[indexOfItem] = newValue 配列の長さを変更すると、例えばvm.items.length = newLength
解決方法は公式サイトのドキュメントを参照してください.
Arrayに関する注意事項
転載先:https://juejin.im/post/5cbc92906fb9a068890f3029
Vueの応答式はObjectであることはよく知られています.definePropertyはデータハイジャックを行います.しかし、それはObjectタイプに対して実現できますが、配列なら?set/get方式では無理です.
しかし,Vueの著者らは,Array型のモニタリング:ブロッキングを実現するための方法を使用した.
核心思想.
ブロックを作成することで配列自体のプロトタイプオブジェクトArrayを上書きする.prototype.
ブロッキング
Vueソースパスvue/src/core/observer/arrayを表示します.js.
/**
* Vue
* : Array.prototype。
* Object, Array.prototype 。 。
*/
function def (obj, key, val, enumerable) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
})
}
//
const arrayProto = Array.prototype
//
const arrayMethods = Object.create(arrayProto)
// :
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
methodsToPatch.forEach(function (method) {
//
const original = arrayProto[method]
// ( )
def(arrayMethods, method, function mutator (...args) {
// value
const result = original.apply(this, args)
// value observer() __ob__
const ob = this.__ob__
// , ( Observer)
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
// Observe
if (inserted) ob.observeArray(inserted)
//
ob.dep.notify()
return result
})
})
Vueがいつdata属性をObserverするかについて
Vueのソースコードに詳しい子供靴なら、すぐにVueの入り口ファイルvue/src/core/instance/indexを見つけることができるはずです.js.
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
initMixin(Vue)
// $props, $data
// Vue : vm.$watch,vm.$set,vm.$delete
stateMixin(Vue)
// Vue : vm.$on, vm.$once ,vm.$off , vm.$emit
eventsMixin(Vue)
// Vue : vm.$forceUpdate, vm.destroy, _update
lifecycleMixin(Vue)
// Vue : vm.$nextTick, _render,
renderMixin(Vue)
export default Vue
this.init()
ソースパス:vue/src/core/instance/init.js.
export function initMixin (Vue: Class) {
Vue.prototype._init = function (options?: Object) {
//
const vm: Component = this
// a uid
//
vm._uid = uid++
let startTag, endTag
/* istanbul ignore if */
// , Vue performance.mark API 。
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
startTag = `vue-perf-start:${vm._uid}`
endTag = `vue-perf-end:${vm._uid}`
//
mark(startTag)
}
// a flag to avoid this being observed
// Vue
vm._isVue = true
// merge options
// optionsMerge $options
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
//
initLifecycle(vm)
//
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
// State
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
vm._name = formatComponentName(vm, false)
mark(endTag)
measure(`vue ${vm._name} init`, startTag, endTag)
}
//
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
initState()
ソースパス:vue/src/core/instance/state.js.
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
この時observeが現れたことに気づきます.
observe
ソースパス:vue/src/core/observer/index.js
export function observe (value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
// value Observe ,
ob = value.__ob__
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
// , Observer
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
ブロッキングを使用するタイミング
Vueの応答システムにはObserveクラスがあります.ソースパス:vue/src/core/observer/index.js.
// can we use __proto__?
export const hasProto = '__proto__' in {}
const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
function protoAugment (target, src: Object) {
/* eslint-disable no-proto */
target.__proto__ = src
/* eslint-enable no-proto */
}
function copyAugment (target: Object, src: Object, keys: Array) {
// target: Observe
// src:
// keys: const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
// keys:
// const methodsToPatch = [
// 'push',
// 'pop',
// 'shift',
// 'unshift',
// 'splice',
// 'sort',
// 'reverse'
// ]
for (let i = 0, l = keys.length; i < l; i++) {
const key = keys[i]
def(target, key, src[key])
}
}
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
constructor (value: any) {
this.value = value
//
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
//
if (Array.isArray(value)) {
if (hasProto) {
// __proto__ ( , ):
protoAugment(value, arrayMethods)
} else {
// ( )
// ,
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
this.walk(value)
}
}
/**
* Walk through all properties 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])
}
}
/**
* , Observer()
*/
observeArray (items: Array) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
依存を収集する方法
Vueの中で本当にデータ応答式の処理をしているのはdefineReactive()です.defineReactiveメソッドは、オブジェクトのデータ属性をアクセサ属性に変換することです.すなわち、データ属性にget/setを設定します.
function dependArray (value: Array) {
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)
}
}
}
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
// dep
// dep
// Dep 。
const dep = new Dep()
//
const property = Object.getOwnPropertyDescriptor(obj, key)
// : Object.defineProperty 。
if (property && property.configurable === false) {
return
}
// , get set
// Object.defineProperty setter/getter
// set get , setter/getter
const getter = property && property.get
const setter = property && property.set
//
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
// , __ob__
// Vue.set Vue.delete 。
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
// getter , ,
// getter val
const value = getter ? getter.call(obj) : val
// Dep.target Watch
if (Dep.target) {
// dep
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
// dependArray
dependArray(value)
}
}
}
// 。
return value
},
set: function reactiveSetter (newVal) {
//
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
// , NaN
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
// setter,
if (setter) {
setter.call(obj, newVal)
} else {
//
val = newVal
}
// , ,
// ,
childOb = !shallow && observe(newVal)
// dep watcher
dep.notify()
}
})
}
ストレージ配列依存リスト
なぜObserverインスタンスに依存を存在させる必要があるのか.すなわち
export class Observer {
constructor (value: any) {
...
this.dep = new Dep()
}
}
まずgetterでObserverインスタンスにアクセスする必要があります
//
let childOb = !shallow && observe(val)
...
if (childOb) {
// Observer dep depend()
childOb.dep.depend()
if (Array.isArray(value)) {
// dependArray
dependArray(value)
}
}
また、前述したブロッキングではObserverインスタンスを使用します.
methodsToPatch.forEach(function (method) {
...
// this
// __ob__ ?
const ob = this.__ob__
...
// Observe
if (inserted) ob.observeArray(inserted)
//
ob.dep.notify()
...
})
上記のことを考えます.ob__属性はどこから来ますか?
export class Observer {
constructor () {
...
this.dep = new Dep()
// vue __ob__ , Observer
// __ob__ Observer
// __ob__ dep
def(value, '__ob__', this)
...
}
}
すべての属性が検出されると1つの__が打たれることを覚えておいてください.ob__のタグ、すなわち応答型データであることを示す.
Arrayに関する注意事項
JavaScriptの制限により、Vueは以下の変動する配列を検出できません.
解決方法は公式サイトのドキュメントを参照してください.
Arrayに関する注意事項
転載先:https://juejin.im/post/5cbc92906fb9a068890f3029