検索
ホームページウェブフロントエンドVue.jsVue3 の応答メカニズムの詳細な調査

Vue3 の応答メカニズムの詳細な調査

Dec 20, 2022 pm 09:43 PM
フロントエンドvue.jsレスポンシブ

応答性とは何ですか? Vue応答性を実装するにはどうすればよいですか?次の記事では、Vue3 の応答性の原則について詳しく説明します。お役に立てば幸いです。

Vue3 の応答メカニズムの詳細な調査

誰もが Vue フレームワークに精通していると思いますが、Vue に関して言えば、面接官が最初に尋ねる質問の 1 つは、Vue の応答性の原理がどのようになっているのかということだと思います。以前に Vue2 の応答原理について記事を書きましたので、今日は Vue3 の応答原理についてお話します。 [関連する推奨事項: vuejs ビデオ チュートリアル Web フロントエンド開発 ]

1. 応答性とは何ですか?

JavaScript には変数に応答性の概念がありません。コードの実行ロジックはトップダウンです。Vue フレームワークでは、応答性は特別な機能の 1 つです。まず例を見てみましょう

let num = 1;
let double = num * 2;
console.log(double); // 2
num = 2;
console.log(double); // 2

変数 double と変数 num の間の関係が応答していないことがはっきりとわかります。double を計算するロジックを関数にカプセル化すると、変数 num が変化した場合は、この関数を再実行します。その結果、num の変化に応じて double の値も変化します。これは、一般的にレスポンシブと呼ばれるものです。

let num = 1;
// 将计算过程封装成一个函数
let getDouble = (n) => n * 2;
let double = getDouble(num);
console.log(double); // 2

num = 2;
// 重新计算double,这里当然也没有实现响应式,只是说明响应式实现的时候这个函数应该再执行一次
double = getDouble(num);
console.log(double); // 4

実際の開発プロセスは現在の単純な状況よりもはるかに複雑になりますが、関数にカプセル化して実装することができます。問題は、num 変数に応じて double の値をどのように変更するかです。で、再計算はどうなるの?

num 変数の値が毎回変更される場合、getDouble 関数はそれを認識して実行することができ、num 変数の変更に応じて double もそれに応じて変更されます。これは応答性のプロトタイプです。

2. 応答性の原則

Vue で 3 つの応答性の高いソリューション、つまり definePropertyProxy、## を使用しました。 #値セッターdefineProperty API は Vue2 で使用されており、以前の記事で詳しく説明されています。Vue2 の応答性について詳しく知りたい場合は、ここをクリックしてください --->vue 応答性の原則 | vue2 の記事

#defineProperty API

Vue2 のコア部分は、defineProperty データ ハイジャック API です。オブジェクト obj を定義するとき、defineProperty を使用して num Attribute をプロキシし、get num 属性を読み取るときに関数が実行され、num 属性を変更するときに set 関数が実行され、set 関数に double を計算するロジックを記述するだけで、num が変化するたびに double がそれに応じて代入されます。反応が良いです。

let num = 1;
let detDouble = (n) => n * 2;
let obj = {}
let double = getDouble(num)

Object.defineProperty(obj,'num',{
    get() {
        return num;
    }
    set(val){
        num = val;
        double = getDouble(val)
    }
})
console.log(double); // 2
obj.num = 2;
console.log(double); // 4

defineProperty の欠陥

: obj.num プロパティを削除すると、set 関数が実行されなくなるため、Vue2 では $delete 特殊な関数が必要になります。データを削除するには。また、obj オブジェクトに存在しない属性をハイジャックすることはできず、配列の長さ属性の変更も無効です。

Proxy

Proxy という名前だけから、プロキシを意味することがわかります。Proxy の重要な意義は、Vue2 の応答性の欠点を解決することです。

プロキシの使用法:

var proxy = new Proxy(target, handler);

プロキシ オブジェクトの使用法はすべて上記の形式になっています。唯一の違いは、

handler

パラメーターの記述です。このうち、new Proxy()Proxy インスタンスの生成を意味し、target パラメータはインターセプトするターゲット オブジェクトを示し、handler パラメータもオブジェクトであり、インターセプト動作をカスタマイズするために使用されます。

var proxy = new Proxy({}, {
  get: function(target, propKey) {
    return 35;
  }
});

proxy.time // 35
proxy.name // 35
proxy.title // 35
プロキシで 13 種類のカスタマイズされたインターセプトをサポート

  • get(target, propKey, receiver) :拦截对象属性的读取,比如proxy.fooproxy['foo']
  • set(target, propKey, value, receiver) :拦截对象属性的设置,比如proxy.foo = vproxy['foo'] = v,返回一个布尔值。
  • has(target, propKey) :拦截propKey in proxy的操作,返回一个布尔值。
  • deleteProperty(target, propKey) :拦截delete proxy[propKey]的操作,返回一个布尔值。
  • ownKeys(target) :拦截Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy)for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
  • getOwnPropertyDescriptor(target, propKey) :拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
  • defineProperty(target, propKey, propDesc) :拦截Object.defineProperty(proxy, propKey, propDesc)Object.defineProperties(proxy, propDescs),返回一个布尔值。
  • preventExtensions(target) :拦截Object.preventExtensions(proxy),返回一个布尔值。
  • getPrototypeOf(target) :拦截Object.getPrototypeOf(proxy),返回一个对象。
  • isExtensible(target) :拦截Object.isExtensible(proxy),返回一个布尔值。
  • setPrototypeOf(target, proto) :拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
  • apply(target, object, args) :拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)proxy.call(object, ...args)proxy.apply(...)
  • construct(target, args) :拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)

Reflect

在ES6中官方新定义了 Reflect 对象,在ES6之前对象上的所有的方法都是直接挂载在对象这个构造函数的原型身上,而未来对象可能还会有很多方法,如果全部挂载在原型上会显得比较臃肿,而 Reflect 对象就是为了分担 Object的压力。

(1) 将Object对象的一 些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上现阶段,某些方法同时在ObjectReflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,从Reflect对象上可以拿到语言内部的方法。

(2) 修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false

// 老写法
try {
  Object.defineProperty(target, property, attributes);
  // success
} catch (e) {
  // failure
}

// 新写法
if (Reflect.defineProperty(target, property, attributes)) {
  // success
} else {
  // failure
}

(3) 让Object操作都变成函数行为。某些Object操作是命令式,比如name in objdelete obj[name],而Reflect.has(obj, name)Reflect.deleteProperty(obj, name)让它们变成了函数行为。

// 老写法
'assign' in Object // true

// 新写法
Reflect.has(Object, 'assign') // true

(4)Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。

Proxy(target, {
  set: function(target, name, value, receiver) {
    var success = Reflect.set(target, name, value, receiver);
    if (success) {
      console.log('property ' + name + ' on ' + target + ' set to ' + value);
    }
    return success;
  }
});

所以我们在这里会使用到 Proxy 和 Reflect 对象的方与 Proxy 一一对应这一特性,来实现Vue3的响应式原理。

三、Vue3响应式原理的实现

在Vue3中响应式的核心方法是

function reactive (target){
    // 返回一个响应式对象
    return createReactiveObject(target); 
}

根据我们前面所做的铺垫,所以我们会使用 Proxy 代理我们所需要的相应的对象,同时使用 Reflect 对象来映射。所以我们先初步实现一下,再慢慢优化,尽可能全面。

判断是否为对象(方法不唯一,有多种方法)

function isObject(val){
    return typeof val === 'object' && val !== null
}

尽可能采用函数式编程,让每一个函数只做一件事,逻辑更加清晰。

初步实现

function createReactiveObject (target) {
    // 首先由于Proxy所代理的是对象,所以我们需要判断target,若是原始值直接返回
    if(!isObject(target)) {
        return target;
    }
    
    let handler = {
        get(target, key, receiver) {
            let res = Reflect.get(target, key, receiver); // 使用Reflect对象做映射,不修改原对象
            console.log('获取');
            return res;
        },
        set(target, key, value, receiver) {
            let res = Reflect.set(target, key, value, receiver);
            console.log('修改');
            return res
        },
        deleteProperty(target, key) {
            let res = Reflect.deleteProperty(target, key)
            console.log('删除');
            return res;
        }
    }
    let ProxyObj = new Proxy(target,handler); // 被代理过的对象
    return ProxyObj;
}

Vue3 の応答メカニズムの詳細な調査

但是这样会有一个问题,如果我需要代理的对象是深层嵌套的对象呢?我们先看看效果

Vue3 の応答メカニズムの詳細な調査

当我们深层代理时,我们直接修改深层对象中的属性并不会触发 Proxy 对象中的 set 方法,那为什么我们可以修改呢?其实就是直接访问原对象中深层对象的值并修改了,那我们如何优化这个问题呢?

那也需要用到递归操作,判断深层对象是否被代理了,如果没有再执行reactive将内部未被代理的对象代理。

那么我们在 get 方法内部就不能直接将映射之后的 res 返回出去了

解决代理对象内部有嵌套对象

get(target, key, receiver) {
            let res = Reflect.get(target, key, receiver); // 使用Reflect对象做映射,不修改原对象
            console.log('获取');
            // 判断代理之后的对象是否内部含有对象,如果有的话就递归一次
            return isObject(res) ? reactive(res) : res;
        }

解决对象重复代理(多次代理、多层代理)

这样我们就实现了对象的深层代理,并且只有当我们访问到内部嵌套的对象时我们才 会去递归调用reactive ,这样不仅可以实现深层代理,并且节约了性能,但是其实我们还没有彻底完善,我们来看看下面这段代码

let proxy = reactive({name: '寒月十九', message: { like: 'coding' }});
reactive(proxy);
reactive(proxy);
reactive(proxy);

这样是不是合法的,当然是合法的,但是没有必要也没有意义,所以为了避免被代理过的对象,再次被代理,太浪费性能,所以我们需要将被代理的对象打上标记,这样当带被代理过的对象访问到时,直接将被代理过的对象返回,不需要再次代理。

在 Vue3 中,使用了hash表做映射,来记录是否已经被代理了。

// WeakMap-弱引用对象,一旦弱引用对象未被使用,会被垃圾回收机制回收
let toProxy = new WeakMap();  // 存放形式 { 原对象(key): 代理过的对象(value)}
let toRow = new WeakMap();  // 存放形式 { 代理过的对象(key): 原对象(value)}
let ProxyObj = new Proxy(target,handler); // 被代理过的对象
toProxy.set(target,ProxyObj);
toRow.set(ProxyObj.target);
return ProxyObj;
let ByProxy = toProxy.get(target);
// 防止多次代理
if(ByProxy) { // 如果在WeakMap中可以取到值,则说明已经被代理过了,直接返回被代理过的对象
    return ByProxy;
}
// 防止多层代理
if(toRow.get(target)) {
    return target
}
// 为了防止下面这种写法(多层代理)
// let proxy2 = reactive(proxy);
// let proxy3 = reactive(proxy2);
// 其实本质上与下面这种写法没有区别(多次代理)
// reactive(proxy);
// reactive(proxy);
// reactive(proxy);

数组相应问题

let arr = [1 ,2 ,3 ,4];
let proxy = reactive(arr);
proxy.push(5);
// 在set内部其实会干两件事,首先会将5这个值添加到数组下标4的地方,并且会修改length的值

Vue3 の応答メカニズムの詳細な調査

与 Vue2 的数据劫持相比,Vue3 中的 Proxy 可以直接修改数组的长度,但是这样我们需要在 set 方法中判断我们是要在代理对象身上添加属性还是修改属性。

因为更新视图的函数会在set函数中调用,我们向数组中进行操作会触发两次更新视图,所以我们需要做一些优化。

// 判断属性是否原本存在
function hasOwn(target,key) {
    return target.hasOwnProperty(key);
}

set(target, key, value, receiver) {
    let res = Reflect.set(target, key, value, receiver);
    // 判断是新增属性还是修改属性
    let hadKey = hasOwn(target,key);
    let oldValue = target[key];
    if(!hadKey) { // 新增属性
        console.log('新增属性');
    }else if(oldValue !== value){
        console.log('修改属性');
    }
    return res
 },

避免多次更新视图,比如修改的值与原来一致就不更新视图,在上面两个判断条件中添加更新视图的函数,就不会多次更新视图。

完整版代码

function isObject(val) {
  return typeof val === 'object' && val !== null
}

function reactive(target) {
  // 返回一个响应式对象
  return createReactiveObject(target);
}

// 判断属性是否原本存在
function hasOwn(target, key) {
  return target.hasOwnProperty(key);
}

// WeakMap-弱引用对象,一旦弱引用对象未被使用,会被垃圾回收机制回收
let toProxy = new WeakMap();  // 存放形式 { 原对象(key): 代理过的对象(value)}
let toRow = new WeakMap();  // 存放形式 { 代理过的对象(key): 原对象(value)}

function createReactiveObject(target) {
  // 首先由于Proxy所代理的是对象,所以我们需要判断target,若是原始值直接返回
  if (!isObject(target)) {
    return target;
  }

  let ByProxy = toProxy.get(target);
  // 防止多次代理
  if (ByProxy) { // 如果在WeakMap中可以取到值,则说明已经被代理过了,直接返回被代理过的对象
    return ByProxy;
  }
  // 防止多层代理
  if (toRow.get(target)) {
    return target
  }

  let handler = {
    get(target, key, receiver) {
      let res = Reflect.get(target, key, receiver); // 使用Reflect对象做映射,不修改原对象
      console.log('获取');
      return isObject(res) ? reactive(res) : res;
    },
    set(target, key, value, receiver) {
      let res = Reflect.set(target, key, value, receiver);
      // 判断是新增属性还是修改属性
      let hadKey = hasOwn(target, key);
      let oldValue = target[key];
      if (!hadKey) { // 新增属性
        console.log('新增属性');
      } else if (oldValue !== value) {
        console.log('修改属性');
      }
      return res
    },
    deleteProperty(target, key) {
      let res = Reflect.deleteProperty(target, key)
      console.log('删除');
      return res;
    }
  }
  let ProxyObj = new Proxy(target, handler); // 被代理过的对象
  return ProxyObj;
}

// let proxy = reactive({name: '寒月十九'});
// proxy.name = '十九';
// console.log(proxy.name);
// delete proxy.name;
// console.log(proxy.name);

// let proxy = reactive({name: '寒月十九', message: { like: 'coding' }});
// proxy.message.like = 'writing';
// console.log('====================================');
// console.log(proxy.message.like);
// console.log('====================================');

let arr = [1, 2, 3, 4];
let proxy = reactive(arr);
proxy.push(5)

Proxy的缺陷

在IE11以下的浏览器都不兼容,所以如果使用 Vue3 开发一个单页应用的项目,需要考虑到兼容性问题,需要我们做额外的很多操作,才能使得IE11 以下的版本能够兼容。

(学习视频分享:vuejs入门教程编程基础视频

以上がVue3 の応答メカニズムの詳細な調査の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明
この記事は掘金社区で複製されています。侵害がある場合は、admin@php.cn までご連絡ください。
Vue.jsとReact:重要な違​​いを理解するVue.jsとReact:重要な違​​いを理解するApr 10, 2025 am 09:26 AM

Vue.JSは中小企業から中規模のプロジェクトに適していますが、Reactは大規模で複雑なアプリケーションにより適しています。 1。VUE.JSのレスポンシブシステムは、依存関係追跡を介してDOMを自動的に更新し、データの変更を簡単に管理できるようにします。 2.反応は一方向のデータフローを採​​用し、データは親コンポーネントから子コンポーネントに流れ、明確なデータフローと簡単な抽出構造を提供します。

Vue.js vs. React:プロジェクト固有の考慮事項Vue.js vs. React:プロジェクト固有の考慮事項Apr 09, 2025 am 12:01 AM

VUE.JSは、中小規模のプロジェクトや迅速な反復に適していますが、Reactは大規模で複雑なアプリケーションに適しています。 1)Vue.jsは使いやすく、チームが不十分な状況やプロジェクトスケールが小さい状況に適しています。 2)Reactにはより豊富なエコシステムがあり、高性能で複雑な機能的ニーズを持つプロジェクトに適しています。

Vueにタグをジャンプする方法Vueにタグをジャンプする方法Apr 08, 2025 am 09:24 AM

VUEでタグのジャンプを実装する方法には、HTMLテンプレートでAタグを使用してHREF属性を指定する方法が含まれます。 VUEルーティングのルーターリンクコンポーネントを使用します。 JavaScriptでこれを使用します。$ router.push()メソッド。パラメーターはクエリパラメーターに渡すことができ、ルートは動的ジャンプのルーターオプションで構成されています。

VUEのコンポーネントジャンプを実装する方法VUEのコンポーネントジャンプを実装する方法Apr 08, 2025 am 09:21 AM

VUEでコンポーネントジャンプを実装するための次の方法があります。Router-Linkと< router-view>を使用してください。ハイパーリンクジャンプを実行し、ターゲットパスとして属性を指定するコンポーネント。 < router-view>を使用してください現在ルーティングされているレンダリングされているコンポーネントを表示するコンポーネント。プログラマティックナビゲーションには、router.push()およびrouter.replace()メソッドを使用します。前者は歴史を保存し、後者は記録を残さずに現在のルートに取って代わります。

VueのDivにジャンプする方法VueのDivにジャンプする方法Apr 08, 2025 am 09:18 AM

VUEにDIV要素をジャンプするには、VUEルーターを使用してルーターリンクコンポーネントを追加するには、2つの方法があります。 @clickイベントリスナーを追加して、これを呼び出します。$ router.push()メソッドをジャンプします。

ジャンプVUEによって価値を転送する方法ジャンプVUEによって価値を転送する方法Apr 08, 2025 am 09:15 AM

VUEにデータを渡す主な方法は2つあります。PROPS:一方向データバインディング、親コンポーネントから子コンポーネントにデータを渡します。イベント:イベントとカスタムイベントを使用してコンポーネント間でデータを渡します。

VUEの導入方法にジャンプする方法VUEの導入方法にジャンプする方法Apr 08, 2025 am 09:12 AM

Vue.jsは、ジャンプする3つの方法を提供します。ネイティブJavaScript API:Window.Location.hrefを使用してジャンプします。 Vueルーター:< router-link>を使用してくださいタグまたはこれ。$ router.push()ジャンプする方法。 Vuex:トリガールートジャンプを発送するか、突然変異をコミットします。

Vueのジャンプページを設定する方法Vueのジャンプページを設定する方法Apr 08, 2025 am 09:09 AM

ルーターリンクコンポーネントを使用してクリック可能なリンクを作成するなど、VUEでページリダイレクトを設定する方法はいくつかあります。 router.push()メソッドを使用して、履歴スタックに新しいルートを手動で追加します。 router.replace()メソッドを使用して、現在のルートを置き換えます。 location.hrefを使用して直接新しいページにリダイレクトします。

See all articles

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

AI Hentai Generator

AI Hentai Generator

AIヘンタイを無料で生成します。

ホットツール

Safe Exam Browser

Safe Exam Browser

Safe Exam Browser は、オンライン試験を安全に受験するための安全なブラウザ環境です。このソフトウェアは、あらゆるコンピュータを安全なワークステーションに変えます。あらゆるユーティリティへのアクセスを制御し、学生が無許可のリソースを使用するのを防ぎます。

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

AtomエディタMac版ダウンロード

AtomエディタMac版ダウンロード

最も人気のあるオープンソースエディター

SublimeText3 英語版

SublimeText3 英語版

推奨: Win バージョン、コードプロンプトをサポート!

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター