記事ディレクトリ
1 はじめに
私たちは知っています: データ駆動型ビューの重要なポイントは、データが変更されたことをどのように知るかです. データがいつ変更されたかを知っている限り、問題は解決されます.データの変更。
データがいつ読み取られたのか、いつデータが書き換えられたのかを知ることは実際には難しくありません.データがいつ変更されたのかを簡単に知る方法を
JS
提供します.Object.defineProperty
2.オブジェクトデータを「観察可能」にする
データのすべての読み取りと書き込みは、私たちが見ることができる、つまりいつデータが読み取られたのか、いつデータが書き換えられたのかを知ることができます.これをデータの「観測可能」と呼びます.
Object.defineProperty
データを「観測可能」にするには、冒頭で述べた方法が必要ですが、この記事では、この方法を使用してデータを「観測可能」にします。
まず、データ オブジェクトを定義しますcar
。
let car = {
'brand':'BMW',
'price':3000
}
これを定義したcar
ブランドはbrand
でBMW
、価格price
は 3000 です。car.brand
これで、と を介して、この対応するプロパティ値をcar.price
直接読み書きできるようになりました。car
ただし、car
このプロパティがいつ読み取られたり、変更されたりするかはわかりません。car
では、属性が変更されたことを率先して知らせるにはどうすればよいでしょうか。
次に、Object.defineProperty()
上記の例を次のように書き直します。
let car = {
}
let val = 3000
Object.defineProperty(car, 'price', {
enumerable: true,
configurable: true,
get(){
console.log('price属性被读取了')
return val
},
set(newVal){
console.log('price属性被修改了')
val = newVal
}
})
プロパティはObject.defineProperty()
メソッドによって定義され、このプロパティの読み取りと書き込みはそれぞれ使用され、傍受されます。プロパティが読み取られるか書き込まれるたびに、 と がトリガーされます。以下に示すように:car
price
get()
set()
get()
set()
car
属性の読み取りおよび書き込みステータスをアクティブに通知できることがわかります。これは、このcar
データ オブジェクトが既に「観察可能」であることも意味します。
car
オブザーバブルのすべてのプロパティを作成するには、次のコードを記述します。
// 源码位置:src/core/observer/index.js
/**
* Observer类会通过递归的方式把一个对象的所有属性都转化成可观测对象
*/
export class Observer {
constructor (value) {
this.value = value
// 给value新增一个__ob__属性,值为该value的Observer实例
// 相当于为value打上标记,表示它已经被转化成响应式了,避免重复操作
def(value,'__ob__',this)
if (Array.isArray(value)) {
// 当value为数组时的逻辑
// ...
} else {
this.walk(value)
}
}
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
}
/**
* 使一个对象转化成可观测对象
* @param { Object } obj 对象
* @param { String } key 对象的key
* @param { Any } val 对象的某个key的值
*/
function defineReactive (obj,key,val) {
// 如果只传了obj和key,那么val = obj[key]
if (arguments.length === 2) {
val = obj[key]
}
if(typeof val === 'object'){
new Observer(val)
}
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get(){
console.log(`${
key}属性被读取了`);
return val;
},
set(newVal){
if(val === newVal){
return
}
console.log(`${
key}属性被修改了`);
val = newVal;
}
})
}
上記のコードでは、法線をオブザーバブルにobserver
変換するために使用されるクラスを定義しています。object
object
value
新しい属性を追加します。__ob__
値はthisvalue
のインスタンスですObserver
。この操作は、マークを付けることと同じでvalue
、レスポンシブ型に変換されたことを示し、操作の繰り返しを回避します。
次に、データのタイプを決定します。object
データのタイプのみがwalk
、各属性が変換されたフォームを呼び出してgetter/setter
変更を検出します。defineReactive
最後に、渡されたプロパティ値がまだ one の場合にinを再帰的にサブプロパティにobject
使用して、変更を検出するためにすべてのプロパティ (サブプロパティを含む)new observer(val)
を変換できるようにします。つまり、 1 つを に渡す限り、これは観察可能で応答性が高くなります。obj
getter/seter
object
observer
object
object
observer
クラスはソース コードにありますsrc/core/observer/index.js
。
したがって、次のように定義できますcar
。
let car = new Observer({
'brand':'BMW',
'price':3000
})
このように、car
の両方のプロパティが観測可能です。
3.依存コレクション
3.1 依存コレクションとは
前の章では、データを観察可能にするという最初のステップを踏みましたobject
。観測可能になった後、データがいつ変更されたかを知ることができるため、データが変更されたときにビューに通知して更新することができます。ここでまた問題が発生します。ビューが非常に大きいため、誰に変更を通知する必要がありますか? データの一部が変更された後にビュー全体を更新するのは明らかに不合理です。この時点で、ビューでこのデータを使用する人は誰でも誰でも更新すると間違いなく考えるでしょう。右!そうです、それだけです。
ビューでこのデータを使用する人は、それを使用する人を更新します. 別の言い方をすると: 「このデータを使用する人」を「このデータに依存する人」と呼び、各データの依存関係配列を作成します (データはこのデータに依存する人 (つまり、このデータを使用する人) は誰でもこの依存関係配列に入れ、このデータが変更されると、対応する依存関係配列に移動します。彼ら:「依存しているデータが変更されました。更新する必要があります!」. このプロセスは依存コレクションです。
3.2 依存関係をいつ収集するか? 依存関係の更新はいつ通知されますか?
依存関係の収集とは何かを理解した後、いつ依存関係を収集する必要がありますか? 依存関係の更新はいつ通知されますか?
実際、この質問は前のセクションで回答済みですが、このデータを使用する人は誰でも、データが変更されたときに通知されると述べました。いわゆる誰がこのデータを使用したかは、実際には誰がこのデータを取得したかであり、観測可能なデータが取得されるとgetter
属性がトリガーされるため、getter
この依存関係を で収集できます。同様に、このデータが変更されると、setter
プロパティがトリガーされるため、setter
依存関係の更新を で通知できます。
要約すると、1 つの文は次のとおりです。ゲッターで依存関係を収集し、セッターで依存関係の更新を通知します。
3.3 依存関係を収集する場所
依存関係の収集とは何か、収集して通知するタイミングを理解したら、依存関係をどこで収集する必要がありますか?
セクション 3.1 で説明したように、データごとに依存関係配列を作成し、依存関係配列に入れられたデータに依存する人は誰でも作成します。依存関係を格納するために配列のみを使用する場合、関数が少し不足しているように見え、コードが結合されすぎています。依存関係配列の機能を拡張する必要があります. より良いアプローチは、データごとに依存関係マネージャーを構築して、このデータのすべての依存関係を管理することです. OK、ここで、依存関係マネージャーDep
クラスが作成されました。コードは次のとおりです。
// 源码位置:src/core/observer/dep.js
export default class Dep {
constructor () {
this.subs = []
}
addSub (sub) {
this.subs.push(sub)
}
// 删除一个依赖
removeSub (sub) {
remove(this.subs, sub)
}
// 添加一个依赖
depend () {
if (window.target) {
this.addSub(window.target)
}
}
// 通知所有依赖更新
notify () {
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
/**
* Remove an item from an array
*/
export function remove (arr, item) {
if (arr.length) {
const index = arr.indexOf(item)
if (index > -1) {
return arr.splice(index, 1)
}
}
}
上記の依存関係マネージャーDep
クラスでは、最初にsubs
依存関係を格納する配列を初期化し、依存関係に対する追加、削除、通知、およびその他の操作を行ういくつかのインスタンス メソッドを定義しました。
依存関係マネージャーを配置すると、getter で依存関係を収集し、setter で依存関係の更新を通知できます。コードは次のとおりです。
function defineReactive (obj,key,val) {
if (arguments.length === 2) {
val = obj[key]
}
if(typeof val === 'object'){
new Observer(val)
}
const dep = new Dep() //实例化一个依赖管理器,生成一个依赖管理数组dep
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get(){
dep.depend() // 在getter中收集依赖
return val;
},
set(newVal){
if(val === newVal){
return
}
val = newVal;
dep.notify() // 在setter中通知依赖更新
}
})
}
上記のコードでは、メソッドgetter
を呼び出してdep.depend()
依存関係を収集し、メソッドをsetter
呼び出してすべての依存関係の更新を通知しました。dep.notify()
4.誰に依存するか
前の章を通じて、依存関係とは何かを理解しましたか? 依存関係はいつ収集されますか? 収集された依存関係はどこに保存されますか? では、私たちが収集する依存関係は誰でしょうか?
「誰がこのデータを利用するかは依存している」と言い続けてきましたが、これは口語レベルでしかないので、この「誰」をコードでどのように記述すればよいでしょうか。
実際、Vue
と呼ばれるクラスは にも実装されておりWatcher
、Watcher
クラスのインスタンスは前述の「誰」です。つまり、データを使用する人は誰でも依存しており、Watcher
その人のインスタンスを作成します。将来データが変更された場合、依存関係の更新を直接通知するのではなく、Watch
依存関係の対応するインスタンスに通知し、Watcher
インスタンスは実際のビューに通知します。
Watcher
クラスの具体的な実装は次のとおりです。
export default class Watcher {
constructor (vm,expOrFn,cb) {
this.vm = vm;
this.cb = cb;
this.getter = parsePath(expOrFn)
this.value = this.get()
}
get () {
window.target = this;
const vm = this.vm
let value = this.getter.call(vm, vm)
window.target = undefined;
return value
}
update () {
const oldValue = this.value
this.value = this.get()
this.cb.call(this.vm, this.value, oldValue)
}
}
/**
* Parse simple path.
* 把一个形如'data.a.b.c'的字符串路径所表示的值,从真实的data对象中取出来
* 例如:
* data = {a:{b:{c:2}}}
* parsePath('a.b.c')(data) // 2
*/
const bailRE = /[^\w.$]/
export function parsePath (path) {
if (bailRE.test(path)) {
return
}
const segments = path.split('.')
return function (obj) {
for (let i = 0; i < segments.length; i++) {
if (!obj) return
obj = obj[segments[i]]
}
return obj
}
}
Watcher
データを使用する人は誰でも依存関係であり、その人のインスタンスを作成しますWatcher
. インスタンスの作成プロセス中に、このデータに対応する依存関係マネージャーに自分自身を自動的に追加します. 将来、このWatcher
インスタンスはこの依存関係を表します. . データが変更されると、Watcher
インスタンスに通知し、Watcher
インスタンスは実際の依存関係を通知します。
では、Watcher
インスタンスの作成プロセス中に、このデータに対応する依存関係マネージャーにどのように追加するのでしょうか?
Watcher
クラスのコード実装ロジックを分析しましょう。
Watcher
クラスをインスタンス化するとき、そのコンストラクターが最初に実行されます。- インスタンス メソッドはコンストラクターで呼び出されます
this.get()
。 - このメソッドでは
get()
、まずwindow.target = this
インスタンス自体をグローバルで一意のオブジェクトwindow.target
に割り当て、次にlet value = this.getter.call(vm, vm)
依存データを取得します. 依存データを取得する目的は、上記のデータをトリガーすることですgetter
. 上で述べたように、依存関係を収集するためにgetter
呼び出してdep.depend()
、dep.depend()
取得します.window.target
マウントの値を依存関係配列に格納し、get()
メソッドの最後でwindow.target
解放します。 - When the data changes, the data will be trigger
setter
, and the methodsetter
is called in the method.メソッドでは、すべての依存関係 (つまり、ウォッチャー インスタンス) が走査され、依存するメソッド (つまり、インスタンス メソッド)が実行されます。クラス、およびデータの変更がメソッドで呼び出されます ビューを更新するコールバック関数を更新します。dep.notify()
dep.notify()
update()
Watcher
update()
update()
簡単な要約は次のとおりです。Watcher
まず、グローバルに一意の指定された位置 ( window.target
) に自分自身を設定し、次にデータを読み取ります。データが読み込まれるため、このデータがトリガーされますgetter
。次に、 in はgetter
グローバルに一意な場所から現在読み取られているデータを読み取りWatcher
、これを in にwatcher
収集しますDep
。データが収集されると、データが変更されたときにDep
それぞれに通知が送信されますWatcher
。このようにして、Watcher
データの変更を積極的にサブスクライブできます。理解を容易にするために、以下に示すように、その関係のフローチャートを描きました。
Object
以上で、データの検出、依存関係の収集、依存関係の更新などのすべての操作が完全に完了します。
5.弱点
この方法でデータの可観測性をObject.defineProperty
実現しましたが、この方法ではデータの値と設定値しか観測できず、データに新しいペアを追加したり、既存のペアを削除したりすると、観測できませんでした。データに値を追加または削除すると、依存関係を通知できず、ビューを駆動してレスポンシブ更新を実行できません。object
object
object
key/value
key/value
object
もちろん、Vue
これにも気付きました.この問題を解決するために、とVue
の2つのグローバルAPIが追加されました.この2つのAPIの実装原理については、グローバルAPIを学ぶときに後述します.Vue.set
Vue.delete
6. まとめ
まず、メソッドによってデータの可観測性をObject.defineProperty
実現し、クラスをカプセル化して、データ内のすべての属性 (サブ属性を含む) を簡単に形式に変換して変化を検出できるようにしました。object
Observer
object
getter/seter
次に、依存関係のコレクションとは何かを学びました。また、getter
で依存関係を収集し、setter
で依存関係の更新を通知し、Dep
収集した依存関係を格納するために依存関係マネージャーをカプセル化する方法を知っています。
最後に、依存関係ごとにインスタンスを作成しますWatcher
. データが変更されると、Watcher
インスタンスに通知され、Watcher
インスタンスが実際の更新操作を実行します.
全体のプロセスはおおよそ次のとおりです。
Data
変更は、フォームにobserver
変換することによって追跡されます。getter/setter
- 外の世界が
Watcher
データを読み取ると、それがトリガーされgetter
、Watcher
依存関係に追加されます。 - データが変更されると、依存関係 (つまり、ウォッチャー)
setter
に通知を送信するようにトリガーされます。依存コレクションとは何ですか? また、で依存関係を収集し、で依存関係の更新を通知し、収集した依存関係を格納するために依存関係マネージャーをカプセル化する方法を知っています。Dep
getter
setter
Dep
最後に、依存関係ごとにインスタンスを作成しますWatcher
. データが変更されると、Watcher
インスタンスに通知され、Watcher
インスタンスが実際の更新操作を実行します.
全体のプロセスはおおよそ次のとおりです。
Data
変更は、フォームにobserver
変換することによって追跡されます。getter/setter
- 外の世界が
Watcher
データを読み取ると、それがトリガーされgetter
、Watcher
依存関係に追加されます。 - データが変更されると、依存関係 (つまり、ウォッチャー)
setter
に通知を送信するようにトリガーされます。Dep
Watcher
通知を受け取った後、外界に通知を送信します. 変更通知が外界に到達した後、ビューの更新をトリガーするか、ユーザーのコールバック関数がトリガーされる場合があります.