まず写真を見て、全体的なプロセスと何をすべきかを理解してください 初期化新しい Vue が初期化されると、コンポーネントのプロパティとデータが初期化されます。この記事では主に応答性について紹介するため、他の部分については詳しく説明しません。ソースコードを見てみましょう。 ソースコードアドレス: src/core/instance/init.js - 行 15 エクスポート関数 initMixin (Vue: Class<Component>) { // プロトタイプに_initメソッドを追加します Vue.prototype._init = function (options?: Object) { ... vm._self = vm initLifecycle(vm) // インスタンスのプロパティとデータを初期化します: $parent、$children、$refs、$root、_watcher... など。 initEvents(vm) // イベントを初期化します: $on、$off、$emit、$once initRender(vm) // レンダリングを初期化: render、mixin callHook(vm, 'beforeCreate') // ライフサイクルフック関数を呼び出す initInjections(vm) // injectを初期化する initState(vm) // コンポーネントデータを初期化する: props、data、methods、watch、computed initProvide(vm) // provideを初期化する callHook(vm, 'created') // ライフサイクル フック関数を呼び出します... } } ここでは初期化のために多くのメソッドが呼び出され、それぞれが異なる処理を実行します。応答性は主にコンポーネント内のデータ プロパティとデータを参照します。この部分は initState() メソッド内にあるため、このメソッドのソース コードを開いて確認してみましょう。 初期化状態()ソースコードアドレス: src/core/instance/state.js - 行 49 エクスポート関数 initState (vm: コンポーネント) { vm._watchers = [] 定数opts = vm.$options // プロパティを初期化する if (opts.props) initProps(vm, opts.props) // 初期化メソッド if (opts.methods) initMethods(vm, opts.methods) // データを初期化する if (opts.data) { 初期化データ(vm) } それ以外 { // データがない場合、デフォルト値は空のオブジェクトとなり、observe(vm._data = {}, true /* asRootData */) となります。 } // 計算結果を初期化する if (opts.computed) initComputed(vm, opts.computed) // ウォッチを初期化する opts.watch の場合、opts.watch は nativeWatch と等しくなります。 initWatch(vm, opts.watch) } } ここでも、一連の初期化メソッドが呼び出されます。早速、レスポンシブデータに関連するもの、つまりinitProps()、initData()、observe()を取り上げます。 初期化プロパティ()ソースコードアドレス: src/core/instance/state.js - 行 65 ここで行われる主な作業は次のとおりです。
関数 initProps (vm: コンポーネント、propsOptions: オブジェクト) { // 親コンポーネントは子コンポーネントにプロパティを渡します 定数 propsData = vm.$options.propsData || {} // 変換後の最終プロパティ 定数プロパティ = vm._props = {} //props のキーを保存します。props の値が空の場合でも、キーはそこに含まれます。const keys = vm.$options._propKeys = [] 定数isRoot = !vm.$parent // 非ルートインスタンスのプロパティを変換する ルートの場合 監視の切り替え(false) } for (propsOptions 内の定数キー) { keys.push(キー) // プロパティのタイプ、デフォルトの属性などをチェックします。const value = validateProp(key, propsOptions, propsData, vm) // 非本番環境の場合 if (process.env.NODE_ENV !== 'production') { const hyphenatedKey = hyphenate(キー) if (isReservedAttribute(ハイフン付きキー) || config.isReservedAttr(ハイフン付きキー)) { 警告(`hyphenatedKey は予約済みのプロパティであり、コンポーネント プロパティとして使用できません`) } // props をレスポンシブに設定する defineReactive(props, key, value, () => { // ユーザーがプロパティを変更した場合に警告する if (!isRoot && !isUpdatingChildComponent) { 警告(`propsを直接変更しないでください`) } }) } それ以外 { // props をレスポンシブに設定する defineReactive(props, key, value) } // デフォルトの vm にないプロパティをインスタンスにプロキシします // これにより、vm._props.xx に vm.xx 経由でアクセスできるようになります if (!(key in vm)) { プロキシ(vm、`_props`、キー) } } 監視の切り替え(true) } 初期化データ()ソースコードアドレス: src/core/instance/state.js - 行 113 ここで行われる主な作業は次のとおりです。
関数 initData (vm: コンポーネント) { // 現在のインスタンスのデータを取得する データ = vm.$options.data とします // データの型を決定する data = vm._data = typeof data === 'function' ? getData(データ、vm) : データ || {} if (!isPlainObject(データ)) { データ = {} process.env.NODE_ENV !== 'production' && warn(`データ関数はオブジェクトを返す必要があります`) } // 現在のインスタンスのデータ属性名セットを取得します。const keys = Object.keys(data) // 現在のインスタンスのプロパティを取得します 定数プロパティ = vm.$options.props // 現在のインスタンスのメソッドオブジェクトを取得します。const methods = vm.$options.methods i = キーの長さとする (i--) { 定数キー = キー[i] // 非本番環境では、methods 内のメソッドが props 内に存在するかどうかを判定します if (process.env.NODE_ENV !== 'production') { if (メソッド && hasOwn(メソッド、キー)) { 警告(`メソッド メソッドは繰り返し宣言できません`) } } // 非本番環境では、データ内の属性が props 内に存在するかどうかを判定します。if (props && hasOwn(props, key)) { process.env.NODE_ENV !== 'production' && warn(`プロパティを繰り返し宣言することはできません`) } そうでなければ (!isReserved(key)) { // 名前が同じでない場合は、vm にプロキシします // vm._data.xx は vm.xx を介して proxy(vm, `_data`, key) にアクセスできます } } // データをリッスンする 観察(データ、true /* asRootData */) } 観察する()ソースコードアドレス: src/core/observer/index.js - 行 110 このメソッドは主にデータにリスナーを追加するために使用されます。 ここで行われる主な作業は次のとおりです。
エクスポート関数 observe (値: any、asRootData: ?boolean): Observer | void { // 'object' 型または vnode 型でない場合は、直接戻ります if (!isObject(value) || value instanceof VNode) { 戻る } ob: オブザーバー | void // キャッシュされたオブジェクトを使用する if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { ob = 値.__ob__ } それ以外の場合 ( 観察すべき && !isServerRendering() && (Array.isArray(値) || isPlainObject(値)) && Object.isExtensible(値) && !値._isVue ){ // リスナーを作成する ob = new Observer(value) } if (asRootData && ob) { ob.vmCount++ } 戻り値 } オブザーバーソースコードアドレス: src/core/observer/index.js - 行 37 これは通常のデータを観測可能なデータに変換するクラスです。 ここで行われる主な作業は次のとおりです。
エクスポートクラスObserver { 値: 任意; dep: 削除; vmCount: number; // ルートオブジェクトコンストラクタ上のvmの数(値: 任意){ this.value = 値 this.dep = 新しい Dep() this.vmCount = 0 // value に __ob__ 属性を追加します。この属性の値は value の Observe インスタンスです。 // これは、応答可能になったことを示します。目的は、オブジェクトをトラバースするときに直接スキップして、繰り返し操作を回避することです。 def(value, '__ob__', this) // 型判定 if (Array.isArray(value)) { // 配列に__proty__があるかどうかを判定する (hasProto) の場合 { // ある場合は、配列メソッド protoAugment(value, arrayMethods) を書き換えます。 } それ以外 { // そうでない場合は、def を通じてプロパティ値を定義します。つまり、Object.defineProperty copyAugment(value, arrayMethods, arrayKeys) } this.observeArray(値) } それ以外 { this.walk(値) } } // オブジェクト型の場合 walk (obj: Object) { 定数キー = Object.keys(obj) // オブジェクトのすべてのプロパティを走査し、レスポンシブなオブジェクトに変換します。双方向バインディングを実現するために、ゲッターとセッターを動的に追加します for (let i = 0; i < keys.length; i++) { 定義Reactive(obj, キー[i]) } } //ObserveArray (items: Array<any>) { // 配列を走査し、各要素を監視します for (let i = 0, l = items.length; i < l; i++) { 観察(アイテム[i]) } } } リアクティブを定義する()ソースコードアドレス: src/core/observer/index.js - 行 135 このメソッドの目的は、レスポンシブオブジェクトを定義することです ここで行われる主な作業は次のとおりです。
エクスポート関数defineReactive( obj: オブジェクト、 キー: 文字列、 値: 任意、 customSetter?: ?関数、 浅い?: ブール値 ){ // depインスタンスを作成する const dep = new Dep() // オブジェクトのプロパティ記述子を取得します。const property = Object.getOwnPropertyDescriptor(obj, key) if (プロパティ && property.configurable === false) { 戻る } // カスタムゲッターとセッターを取得する const ゲッター = プロパティ && プロパティ.get const セッター = プロパティ && プロパティ.set if ((!getter || setter) && arguments.length === 2) { val = obj[キー] } // val がオブジェクトの場合は、再帰的に observe します // observe を再帰的に呼び出すと、オブジェクト構造がどれだけ深くネストされていても、応答性の高いオブジェクトになることが保証されます let childOb = !shallow && observe(val) //オブジェクトプロパティのゲッターとセッターをインターセプトする Object.defineProperty(obj, キー, { 列挙可能: true、 設定可能: true、 //インターセプトゲッター、値が取得されたときに関数がトリガーされます get: function reactiveGetter () { 定数値 = getter ? getter.call(obj) : val // 依存関係の収集を実行します // レンダリングウォッチャーを初期化するときに、双方向バインディングを必要とするオブジェクトにアクセスし、それによって get 関数をトリガーします if (Dep.target) { 依存() if (childOb) { childOb.dep.depend() Array.isArray(値)の場合{ 依存配列(値) } } } 戻り値 }, //インターセプトセッター、値が変わると関数がトリガーされる set: function reactiveSetter (newVal) { 定数値 = getter ? getter.call(obj) : val // 変更が発生したかどうかを判定します if (newVal === value || (newVal !== newVal && value !== value)) { 戻る } process.env.NODE_ENV !== 'production' && customSetter の場合 { カスタムセッター() } // セッターなしのアクセサプロパティ if (getter && !setter) return if (セッター) { セッター呼び出し(obj, newVal) } それ以外 { val = 新しい値 } // 新しい値がオブジェクトの場合は、再帰的に観察します。childOb = !shallow && observe(newVal) // 更新をディスパッチする dep.notify() } }) } 前述の通り、依存関係の収集は dep.depend を通じて行われます。Dep は getter 依存関係収集全体の中核であると言えます。 依存関係の収集依存関係収集の核となるのはDepであり、Watcherと切り離すことはできません。 デップソースコードアドレス: src/core/observer/dep.js これは実際にWatcherを管理するクラスです まず、依存関係、つまりオブザーバーを格納するためのサブ配列を初期化します。このデータに依存するものはすべてこの配列に格納され、追加、削除、更新の通知などを行ういくつかのメソッドを定義します。 さらに、静的属性ターゲットがあり、これはグローバル ウォッチャーです。つまり、同時に存在できるグローバル ウォッチャーは 1 つだけであることを意味します。 uid = 0 とする デフォルトクラス Dep をエクスポートします { 静的ターゲット: ?Watcher; id: 番号; サブ: Array<Watcher>; コンストラクタ(){ this.id = uid++ this.subs = [] } // オブザーバーを追加する addSub (sub: Watcher) { this.subs.push(サブ) } // オブザーバーを削除します。removeSub (sub: Watcher) { 削除(this.subs, sub) } 依存する() { if (依存ターゲット) { //Watcher の addDep 関数を呼び出す Dep.target.addDep(this) } } // 更新を配布する(次のセクションで説明) 通知(){ ... } } // 一度に使用されるオブザーバーは 1 つだけなので、オブザーバーを割り当てます。Dep.target = null 定数ターゲットスタック = [] エクスポート関数pushTarget(ターゲット:?Watcher){ ターゲットスタックをプッシュします。 目標到達目標 = 目標 } エクスポート関数popTarget(){ ターゲットスタックのポップ() 依存関係ターゲット = targetStack[targetStack.length - 1] } ウォッチャーソースコードアドレス: src/core/observer/watcher.js Watcher もクラスであり、オブザーバー (サブスクライバー) とも呼ばれます。ここで行われる作業は非常に複雑で、レンダリングとコンパイルも接続します。 まずソース コードを見てから、依存関係の収集プロセス全体を見てみましょう。 uid = 0 とする デフォルトクラスWatcherをエクスポートします{ ... コンストラクタ( vm: コンポーネント、 expOrFn: 文字列 | 関数、 cb: 機能、 オプション?: ?オブジェクト、 isRenderWatcher?: ブール値 ){ this.vm = vm if (isRenderWatcher) { vm._watcher = これ } vm._watchers.push(これ) // Watcherインスタンスが保持するDepインスタンスの配列 this.deps = [] this.newDeps = [] this.depIds = 新しいSet() this.newDepIds = 新しいSet() this.value = this.lazy ? 未定義 : this.get() if (typeof expOrFn === 'function') { this.getter = expOrFn } それ以外 { this.getter = parsePath(expOrFn) } } 得る () // この関数はウォッチャーをキャッシュするために使用されます // コンポーネントにはネストされたコンポーネントが含まれているため、親コンポーネントのウォッチャーを復元する必要があります pushTarget(これ) 値を与える 定数 vm = this.vm 試す { // コールバック関数、つまり upcateComponent を呼び出して、双方向バインディングを必要とするオブジェクトを評価し、依存関係コレクションをトリガーします。 value = this.getter.call(vm, vm) } キャッチ (e) { ... ついに // ディープモニタリング if (this.deep) { トラバース(値) } // ウォッチャーを復元する ポップターゲット() // 不要な依存関係をクリーンアップします this.cleanupDeps() } 戻り値 } // 依存関係の収集時にaddDep (dep: Dep)を呼び出す{ 定数 id = dep.id if (!this.newDepIds.has(id)) { this.newDepIds.add(id) this.newDeps.push(dep) if (!this.depIds.has(id)) { // 現在のウォッチャーを配列にプッシュします dep.addSub(this) } } } // 不要な依存関係をクリーンアップする(以下) クリーンアップDeps() { ... } // 更新がディスパッチされたときに呼び出されます (以下) アップデート () { ... } // ウォッチャーコールバックを実行する run () { ... } 依存する() { i = this.deps.length とします (i--) { this.deps[i].depend() } } } 補充: 独自のコンポーネントに記述されたウォッチが、新しい値と古い値の 2 つのパラメータを自動的に取得できるのはなぜですか? つまり、watcher.run() でコールバックが実行され、新しい値と古い値が渡されます。 なぜ 2 つの Dep インスタンス配列を初期化するのですか? Vueはデータ駆動型なので、データが変更されるたびに再レンダリングされます。つまり、vm.render()メソッドが再実行され、ゲッターが再度トリガーされます。そのため、新しく追加されたDepインスタンス配列newDepsと最後に追加されたインスタンス配列depsの2つの配列を使用してそれを表現します。 依存関係収集プロセス初めてレンダリングしてマウントするときには、このようなロジックがあります mountComponent ソースコード アドレス: src/core/instance/lifecycle.js - 行 141 エクスポート関数 mountComponent (...): コンポーネント { //ライフサイクルフック関数 callHook(vm, 'beforeMount') を呼び出す コンポーネントを更新します 更新コンポーネント = () => { // _update を呼び出して、render によって返された仮想 DOM を実際の DOM にパッチ (つまり、Diff) します。これが最初のレンダリングです。vm._update(vm._render(), hydrating) } // 現在のコンポーネントインスタンスにオブザーバーを設定し、updateComponent 関数によって取得されたデータを監視します。以下は導入部です。new Watcher(vm, updateComponent, noop, { // 更新がトリガーされると、更新の前にbefore()が呼び出されます{ // DOM がマウントされているかどうかを判定します。つまり、最初のレンダリングとアンマウント時には実行されません。if (vm._isMounted && !vm._isDestroyed) { //ライフサイクルフック関数 callHook(vm, 'beforeUpdate') を呼び出す } } }, true /* isRenderWatcher */) // 古いvnodeがないので、これが最初のレンダリングであることを示します。if (vm.$vnode == null) { vm._isMounted = true //ライフサイクルフック関数 callHook(vm, 'mounted') を呼び出す } 戻り値 } 依存関係のコレクション:
サブスクリプションを削除するサブスクリプションを削除するには、 cleanupDeps() メソッドを呼び出します。たとえば、テンプレートに v-if がある場合、条件を満たすテンプレート a 内の依存関係を収集します。条件が変化するとテンプレート b が表示され、テンプレート a は非表示になります。この時点で、依存関係を削除する必要があります ここで行われる主な作業は次のとおりです。
// 不要な依存関係をクリーンアップする cleanupDeps () { i = this.deps.length とします (i--) { 定数 dep = this.deps[i] (!this.newDepIds.has(dep.id))の場合{ dep.removeSub(これ) } } tmp = this.depIds とします this.depIds = this.newDepIds this.newDepIds = tmp this.newDepIds.clear() tmp = this.deps this.deps = this.newDeps this.newDeps = tmp this.newDeps.length = 0 } アップデートを配布する通知()セッターがトリガーされると、dep.notify() が呼び出され、すべてのサブスクライバーに更新を配布するように通知します。 通知(){ const subs = this.subs.slice() process.env.NODE_ENV !== 'production' && !config.async の場合 { // 非同期でない場合は、正しいトリガーを確実に実行するためにソートが必要です。subs.sort((a, b) => a.id - b.id) } // すべてのウォッチャーインスタンス配列を走査します for (let i = 0, l = subs.length; i < l; i++) { // 更新をトリガーする subs[i].update() } } アップデート()更新がトリガーされたときに呼び出されます アップデート () { if (this.lazy) { this.dirty = true } それ以外の場合は (this.sync) { この.run() } それ以外 { //コンポーネントデータの更新はここに行われます queueWatcher(this) } } キューウォッチャー()ソースコードアドレス: src/core/observer/scheduler.js - 行 164 これはキューであり、Vue が更新を配布する際の最適化ポイントでもあります。つまり、データが変更されるたびにウォッチャー コールバックがトリガーされるのではなく、これらのウォッチャーがすべてキューに追加され、nextTick の後に実行されます。 これは次のセクションの flushSchedulerQueue() のロジックと重複するため、一緒に理解する必要があります。 主なタスクは次のとおりです。
エクスポート関数 queueWatcher (watcher: Watcher) { // ウォッチャーIDを取得する 定数 id = ウォッチャー.id // 現在のIDのウォッチャーがプッシュされているかどうかを確認します if (has[id] == null) { has[id] = true if (!フラッシュ) { // まずここに入力します queue.push(watcher) } それ以外 { // 以下のflushSchedulerQueueを実行すると、新しく配布された更新があればここに入り、新しいウォッチャーを挿入します。以下は導入です。let i = queue.length - 1 i > インデックス && queue[i].id > watcher.id の間 { 私 - } キュー.splice(i + 1, 0, ウォッチャー) } // 最初にここに入ります if (!waiting) { 待機 = 真 process.env.NODE_ENV !== 'production' && !config.async の場合 { スケジューラキューをフラッシュする() 戻る } // 各更新ディスパッチによってレンダリングが発生するため、すべてのウォッチャーを nextTick に配置し、nextTick(flushSchedulerQueue) を呼び出します。 } } } スケジューラキューをフラッシュする()ソースコードアドレス: src/core/observer/scheduler.js - 行 71 ここで行われる主な作業は次のとおりです。
関数flushSchedulerQueue() { 現在のフラッシュタイムスタンプ = getNow() フラッシング = true ウォッチャー、ID // ID でソートします。次の条件が適用されます // 1. コンポーネントの更新は親から子の順序で行う必要があります。作成プロセスも最初に親、次に子であるためです // 2. コンポーネントに記述するウォッチャーはレンダリング ウォッチャーよりも優先されます // 3. 親コンポーネントのウォッチャーの実行中にコンポーネントが破棄された場合、このウォッチャーをスキップします キュー.ソート((a, b) => a.id - b.id) // キューの長さはトラバース中に変わる可能性があるため、キューの長さをキャッシュしないでください。 for (index = 0; index < queue.length; index++) { ウォッチャー = キュー[インデックス] if (watcher.before) { //beforeUpdateライフサイクルフック関数watcher.before()を実行する } id = ウォッチャーid has[id] = null // コンポーネントに記述したウォッチのコールバック関数を実行し、コンポーネントをレンダリングします watcher.run() // ループ更新をチェックして停止します。たとえば、ウォッチャープロセス中にオブジェクトが再割り当てされると、無限ループが発生します。if (process.env.NODE_ENV !== 'production' && has[id] != null) { 円形[id] = (円形[id] || 0) + 1 (円形[id] > MAX_UPDATE_COUNT)の場合{ 警告(`無限ループ`) 壊す } } } // 状態をリセットする前に、キューのコピーを保持します const activatedQueue = activatedChildren.slice() const updatedQueue = queue.slice() スケジューラ状態をリセットする() // コンポーネントアクティベーションフックをアクティブ化する アクティブ化されたフックを呼び出します(アクティブ化されたキュー) // コンポーネント更新フックの呼び出しが更新されました 更新されたフックを呼び出します(更新されたキュー) } 更新されました()最後に、更新します。ライフサイクルフック関数である updated は皆さんもよくご存知でしょう。 callUpdatedHooks()が上記で呼び出されると、ここに入り、更新された処理を実行します。 関数callUpdatedHooks(キュー){ i = キューの長さとする (i--) { const ウォッチャー = キュー[i] 定数 vm = ウォッチャー.vm if (vm._watcher === ウォッチャー && vm._isMounted && !vm._isDestroyed) { callHook(vm, '更新') } } } ここまでで、Vue2 のレスポンシブ原則プロセスのソースコードは基本的に分析されました。次に、上記のプロセスの欠点を紹介します。 定義プロパティの欠陥と解決策Object.definePropertyを使用してレスポンシブオブジェクトを実装することにはまだいくつかの問題がある。
そしてこれらの問題に対して、Vue2にも対応する解決策がある。 Vue.set()オブジェクトに新しいレスポンシブプロパティを追加する場合は、グローバルAPIであるVue.set()メソッドを使用できます。 ソースコードアドレス: src/core/observer/index.js - 行 201 set メソッドは次の 3 つのパラメータを受け入れます。
ここで行われる主な作業は次のとおりです。
エクスポート関数セット (ターゲット: 配列<any> | オブジェクト、キー: any、値: any): any { (process.env.NODE_ENV !== 'production' && の場合 (isUndef(ターゲット) || isPrimitive(ターゲット)) ){ 警告(`未定義、null、またはプリミティブ値にリアクティブ プロパティを設定できません: ${(target: any)}`) } // 配列であり、添え字が有効である場合 if (Array.isArray(target) && isValidArrayIndex(key)) { ターゲットの長さ = Math.max(ターゲットの長さ、キー) // 直接置換するには splice を使用します。ここでの splice はネイティブではないため、検出される可能性があることに注意してください。詳細については、以下の target.splice(key, 1, val) を参照してください。 戻り値 } // これはオブジェクトであることを意味します // キーがターゲットに存在する場合は、直接割り当てられ、監視することもできます if (key in target && !(key in Object.prototype)) { ターゲット[キー] = 値 戻り値 } // target.__ob__ を取得します。 const ob = (ターゲット: 任意).__ob__ target._isVue の場合、ob と ob.vmCount は次のように記述します。 process.env.NODE_ENV !== 'production' && 警告( 'Vue インスタンスまたはそのルート $data にリアクティブ プロパティを追加しないでください ' + 「実行時 - データ オプションで事前に宣言します。」 ) 戻り値 } // Observerで導入されたように、この属性が欠落している場合は、レスポンシブオブジェクトではないことを意味します if (!ob) { ターゲット[キー] = 値 戻り値 } // 次に、新しく追加された属性をレスポンシブにします。defineReactive(ob.value, key, val) // 手動で更新をディスパッチする ob.dep.notify() 戻り値 } 配列メソッドのオーバーライドソースコードアドレス: src/core/observer/array.js ここで行われる主な作業は次のとおりです。
// 配列のプロトタイプを取得します。const arrayProto = Array.prototype // 配列プロトタイプを継承するオブジェクトを作成します。 export const arrayMethods = Object.create(arrayProto) // 元の配列のメソッドリストを変更します const methodsToPatch = [ '押す'、 'ポップ'、 'シフト'、 'シフト解除'、 'スプライス'、 '選別'、 '逆行する' ] // 配列イベントを書き換えるmethodsToPatch.forEach(function (method) { //元のイベントを保存する const original = arrayProto[method] // レスポンシブオブジェクトを作成する def(arrayMethods, method, function mutator (...args) { const 結果 = original.apply(this, args) 定数ob = this.__ob__ 挿入させる スイッチ(メソッド){ ケース 'プッシュ': ケース 'unshift': 挿入された = 引数 壊す ケース 'スプライス': 挿入 = args.slice(2) 壊す } if (挿入済み) ob.observeArray(挿入済み) // 更新を配布する ob.dep.notify() // 必要な処理が完了したら、元のイベントを実行して結果を返します }) }) 要約するこれで、Vue のレスポンシブ原則のソースコード分析の解釈に関するこの記事は終了です。Vue のレスポンシブ原則のソースコードに関するより関連性の高いコンテンツについては、123WORDPRESS.COM の以前の記事を検索するか、次の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM を応援していただければ幸いです。 以下もご興味があるかもしれません:
|
<<: docker CMD/ENTRYPOINT が sh スクリプトを実行する問題の解決策: not found/run.sh:
>>: HTMLは実際にはいくつかの重要なタグを学ぶアプリケーションです
1. CSSを使用するコードをコピーコードは次のとおりです。スタイル="display:n...
WeChat アプレットの軌跡再生では、主に線描画操作にポリラインを使用し、車の移動操作にマーカーを...
違い: 1. InnoDB はトランザクションをサポートしていますが、MyISAM はサポートしてい...
新しいプロジェクトは基本的に終了しました。フロントエンドとバックエンドを分離して統合を完了したのは初...
問題の説明html <iframe id="h5Content" src=...
データのエクスポートエラーを報告する 「secure_file_priv」のような変数を表示します。...
簡単な説明適切な読者: モバイル開発sqlite3 データを mysql に移行する場合、多くの構文...
フロントエンド開発者がよく遭遇する問題は、ユーザーに情報を提示するためのポップアップ ウィンドウを作...
Alibaba Cloud yum コマンドでのデフォルトの MySQL バージョンは 5.17**...
目次MVCCとはMVCC 実装MVCC はファントム リードを解決しますか? MVCCとはMVCC ...
1. 概要mysql-monitor MYSQL 監視ツール、最適化ツール、1 つの Java Sp...
最近はコース設計を実現するために、フロントエンドも少しやっています。今日はいくつかの機能を実現するた...
目次1. 背景2. 操作手順3. Dockerをインストールする4. 主なサービス構成5. サービス...
1. xshell を使用して仮想マシンに接続するか、仮想マシンに直接コマンドを入力します。以下はx...
//文法: @media mediatype and | not | only (メディア機能) ...