[Vue2 ソースコード] レスポンシブ原則
記事ディレクトリ
Vue.js は基本的に MVVM (Model–View–ViewModel) アーキテクチャ パターンに従い、データ モデルは単なる通常の JavaScript オブジェクトです。それらを変更すると、ビューが更新されます。この記事では、Vue 応答システムの基礎となる詳細について説明します。
Vue响应式
核となる設計思想は、
Vue
インスタンスを作成するとき、オプションのプロパティをvue
走査し、プロパティの追加をハイジャックしてデータを読み取り (依存関係の収集に使用され、更新のディスパッチに使用されます)、内部で依存関係を追跡し、プロパティがアクセスおよび変更されたときに変更を通知します。data
Object.defineProperty
getter
setter
getter
setter
各コンポーネント インスタンスには対応するインスタンスがあり、コンポーネント (依存関係コレクションとインスタンス)watcher
のレンダリング中に依存関係のすべてのデータ属性が記録され、依存関係が変更されると、メソッドは依存関係にあるインスタンスに通知します。再計算 (更新のディスパッチ) を行い、関連するコンポーネントを再レンダリングします。computed watcher
user watcher
setter
data
watcher
全体的なプロセス
フロントエンドMVVM
フレームワークとしてのVue
基本的な考え方はと同じでAngular
、その核となるのは、データが変更されるとページが自動的に更新されるため、煩雑な操作から解放され、ビジネス ロジックの処理に集中できるということです。React
DOM
DOM
これはVue
双方向のデータ バインディング (応答原則とも呼ばれます) です。データの双方向バインディングは、Vue
最もユニークな機能の 1 つです。ここでは、公式フローチャートを使用して、Vue
レスポンシブ システムのプロセス全体を簡単に説明します。
ではVue
、各コンポーネント インスタンスには対応するwatcher
インスタンス オブジェクトがあり、コンポーネントのレンダリング中にプロパティを依存関係として記録し、依存関係が呼び出されるときに再計算setter
を通知することwatcher
で、関連するコンポーネントが更新されます。
これは典型的な観察者のパターンです。
応答性における重要な役割
Vue データ双方向バインディングの実装ロジックには、3 つの重要な役割があります。
Observer
: その機能は、依存関係の収集と更新のディスパッチのためにオブジェクトのプロパティにgetter
と を追加することです。setter
Dep
: 現在の応答オブジェクトの依存関係を収集するために使用されます. サブオブジェクトを含む各応答オブジェクトにはインスタンスがありますDep
(内部にインスタンス配列subs
がありますWatcher
). データが変更されると、dep.notify()
それぞれに通知されますwatcher
。Watcher
: オブザーバー オブジェクト、インスタンスは 、 、 の 3 つのタイプに分けられます渲染 watcher (render watcher)
。计算属性 watcher (computed watcher)
侦听器 watcher(user watcher)
変更の検出に関する考慮事項
Object.defineProperty
Vue 2.0 では、実装に基づいた応答性の高いシステムです (このメソッドは ES5 ではシム化できない機能であるため、Vue は IE8 以前のバージョンのブラウザーをサポートしません) Proxy/Reflect
。
1. JavaScript の制限により、Object.defineProperty() API は配列の長さの変化を監視したり、配列やオブジェクトの新しい変更を検出したりできません。
2. Vue は、配列インデックスを介して配列項目を直接変更する操作を検出できません。これが Object.defineProperty() API の理由ではありませんが、Yuda 氏は、パフォーマンスの消費はそれがもたらすユーザー エクスペリエンスに直接比例しないと考えています。配列の項目が 1000 または 10000 個など大きい場合があるため、配列の応答検出ではパフォーマンスが大幅に消費されます。
応答原理
応答性の基本原則は、オプションのデータを Vue のコンストラクターで処理することです。つまり、Vue インスタンスを初期化するときに、データ、プロパティ、その他のオブジェクトの各属性が Object.defineProperty によって一度定義され、データが設定されると、対応するビューを変更するためのいくつかの操作が実行されます。
データ観察
Object.defineProperty に基づいて配列とオブジェクトのハイジャックを実現します。
\src\observe\index.js
import {
newArrayProto } from "./array"
class Observe {
constructor (data) {
//Object.defineReactive只能劫持已经存在的属性(vue俩民回为此单独写一些api)
//data.__ob__ = this //这里的this指的是Observe,把这个实例附到了ob上,还可以用于检测是否被劫持过
Object.defineProperty(data,'__ob__',{
value:this,
enumerable:false //将__ob__编程不可枚举,这样循环的时候就无法获取到,不会进入死循环
})
if(Array.isArray(data)) {
data.__proto__ = newArrayProto
this.observeArray(data) //如果数组中放置的是对象,也可以被监控到
//这里我们可以重写数组中的方法,7个变异方法,是可以修改到数组本身的
}else {
this.walk(data)
}
}
walk (data) {
//循环对象,对属性依次劫持
//“重新定义”属性 性能差
Object.keys(data).forEach(key => defineReactive(data, key, data[key]))
}
observeArray(data) {
data.forEach(item=> observe(item))
}
}
export function defineReactive (target, key, value) {
//闭包
observe(value) //对所有的对象都进行属性劫持 深度劫持
Object.defineProperty(target, key, {
get () {
//取值的时候会执行get
console.log(key,"key");
return value
},
set (newValue) {
//修改的时候会执行set
if (newValue === value) return
observe(newValue)
value = newValue
}
})
}
export function observe (data) {
if (typeof data !== 'object' || data == null) {
//只对对象进行劫持
return
}
if(data.__ob__ instanceof Observe) {
//如果存在data.__ob__就说明这个被代理过了
return data.__ob__
}
//如果一个对象被劫持过了,那就不需要再被劫持了
//要判断一个对象是否被劫持过,可以增添一个实例,用实例来判断是否被劫持过
return new Observe(data)
}
配列 7 の変更メソッドを書き換える
7 つのメソッドとは、プッシュ、ポップ、シフト、シフト解除、ソート、リバース、スプライスを指します。(この 7 つで元の配列が変更されます) 実装アイデア: スライス指向プログラミング! !!
Array.prototype のメソッドを直接オーバーライドするのではなく、プロトタイプ チェーンの継承と関数ハイジャックによる移行です。
Object.create(Array.prototype) を使用して新しいオブジェクト newArrayProto を生成します。オブジェクトのプロトはArray.prototype を指し、次にオーバーライドされたメソッドで配列のプロトが新しいオブジェクト newArrayProto を指すようにします。これにより、newArrayProto と Array.prototype がすべて一致するようになります。配列のプロトタイプチェーン上。
ああ。プロト=== newArrayProto;newArrayProto。プロト=== 配列.プロトタイプ
次に、Array.prototype.push.call を使用して、オーバーライドされたメソッド内の元のメソッドを呼び出し、新しく追加されたデータをハイジャックします。
\src\observe\array.js
let oldArrayProto = Array.prototype //获取数组的原型
export let newArrayProto = Object.create(oldArrayProto)
let methods = [ //通过遍历寻找到所有变异方法
'push',
'pop',
'shift',
'reverse',
'sout',
'splice'
] //concat slice不会改变原数组
methods.forEach(method => {
newArrayProto[method] = function (...args) {
//这里重写了数组的方法
const result = oldArrayProto[method].call(this,...args) //再内部调用原来的方法,函数的劫持,切片编程
//我们需要对新增的数据进行劫持
let inserted
let ob = this.__ob__
switch (method) {
case 'push':
case 'unshift':
inserted = args
break;
case 'splice' : //arr.splice(0,1,{a:1},{b:2})
inserted = args
break
}
console.log("xinzeng ");
if(inserted) {
//对新增的内容再次进行观测
ob.observeArray(inserted)
}
return result
}
})
__ob__ 属性を追加
これは厄介で賢いプロパティです。このインスタンスを Observer クラス内のリアクティブ データに追加します。これは、すべてのレスポンシブデータに識別子を追加することに相当し、オブザーバーインスタンス上のメソッドをレスポンシブデータ上で取得できます。
class Observe {
constructor (data) {
//Object.defineReactive只能劫持已经存在的属性(vue俩民回为此单独写一些api)
//data.__ob__ = this //这里的this指的是Observe,把这个实例附到了ob上,还可以用于检测是否被劫持过
Object.defineProperty(data,'__ob__',{
value:this,
enumerable:false //将__ob__编程不可枚举,这样循环的时候就无法获取到,不会进入死循环
})
if(Array.isArray(data)) {
data.__proto__ = newArrayProto
this.observeArray(data) //如果数组中放置的是对象,也可以被监控到
//这里我们可以重写数组中的方法,7个变异方法,是可以修改到数组本身的
}else {
this.walk(data)
}
}
walk (data) {
//循环对象,对属性依次劫持
//“重新定义”属性 性能差
Object.keys(data).forEach(key => defineReactive(data, key, data[key]))
}
observeArray(data) {
data.forEach(item=> observe(item))
}
}
__ob__ には 2 つの主な用途があります。
1. オブジェクトが乗っ取られた場合、再度乗っ取る必要はありません。オブジェクトが乗っ取られたかどうかを判断するには、obを使用して判断できます。
export function observe (data) {
if (typeof data !== 'object' || data == null) {
//只对对象进行劫持
return
}
if (data.__ob__ instanceof Observe) {
//如果存在data.__ob__就说明这个被代理过了
return data.__ob__
}
//如果一个对象被劫持过了,那就不需要再被劫持了
//要判断一个对象是否被劫持过,可以增添一个实例,用实例来判断是否被劫持过
return new Observe(data)
}
2. 配列の 7 つの突然変異メソッドを書き直しました。そのうちのプッシュ、アンシフト、およびスプライス メソッドは、配列に新しいメンバーを追加します。この時点で、新しく追加されたメンバーを再度監視する必要があり、Observer インスタンスのobserveArray メソッドをobを通じて呼び出すことができます。