ウィークマップとは何ですか? Vue3 レスポンシブ ソース コードのキャッシュ領域として使用されるのはなぜですか?

青灯夜游
リリース: 2021-12-22 10:20:30
転載
2568 人が閲覧しました

この記事では、Vue3 の WeakMap を理解し、Vue 3 のレスポンシブ ソース コードの「キャッシュ領域」として WeakMap が使用される理由を紹介します。

ウィークマップとは何ですか? Vue3 レスポンシブ ソース コードのキャッシュ領域として使用されるのはなぜですか?

[関連する推奨事項: 「vue.js チュートリアル 」]

Vue 3 の応答原理のコードを読みながら見てください。応答処理を実行している場合、WeakMap を使用してオブジェクトごとに「キャッシュ領域」が作成されます。コードは次のとおりです:

// 注意下面这句代码!
const reactiveMap = new WeakMap();

// 核心进行劫持的方法  处理 get 和 set 的逻辑
const mutableHandlers = {
    get,
    set
}

function reactive(target: object) {
    return createReactiveObject(target, mutableHandlers, reactiveMap);
}

/**
 * @description 创建响应式对象 
 * @param {Object} target 需要被代理的目标对象
 * @param {Function} baseHandlers 针对每种方式对应的不同处理函数
 * @param {Object} proxyMap WeakMap 对象
 */
function createReactiveObject(target, baseHandlers, proxyMap) {
    // 检测 target 是不是对象,不是对象直接返回,不进行代理
    if (!isObject(target)) {
        return target
    }
    const existsProxy = proxyMap.get(target);
    // 如果该对象已经被代理过了,则直接返回,不进行重复代理
    if (existsProxy) {
        return existsProxy
    }
    // 未被代理过,则创建代理对象
    const proxy = new Proxy(target,baseHandlers);
    // 缓存,避免重复代理,即避免 reactive(reactive(Object)) 的情况出现
    proxyMap.set(target,proxy); 
    return proxy
}
ログイン後にコピー

上記のコードからわかるように、 WeakMap キャッシュ領域の目的は、オブジェクトが繰り返しプロキシされるのを防ぐことです。

Vue 3 がプロキシ オブジェクトをキャッシュするために WeakMap を使用するのはなぜですか? Map などの他のキャッシュ方法を使用してみてはいかがでしょうか?

WeakMapとは

WeakMap オブジェクトはキーと値のペアのコレクションであり、キーは 弱い参照です。キーは objects である必要があり、値は任意です。

Syntax

new WeakMap([iterable])
ログイン後にコピー

Iterable は、要素がキーと値のペアである配列 (2 要素配列) またはその他の反復可能なオブジェクトです。各キーと値のペアは新しい WeakMap に追加されます。

メソッド

WeakMap 4つのメソッドがあります: getsethasdelete では、その一般的な使用法を見てみましょう:

const wm1 = new WeakMap(),
      wm2 = new WeakMap(),
      wm3 = new WeakMap();

const o1 = {},
      o2 = function() {},
      o3 = window;

wm1.set(o1, 37);
wm1.set(o2, "azerty");
wm2.set(o1, o2); // value 可以是任意值,包括一个对象或一个函数
wm2.set(o3, undefined);
wm2.set(wm1, wm2); // 键和值可以是任意对象,甚至另外一个 WeakMap 对象

wm1.get(o2); // "azerty"
wm2.get(o2); // undefined,wm2 中没有 o2 这个键
wm2.get(o3); // undefined,值就是 undefined

wm1.has(o2); // true
wm2.has(o2); // false
wm2.has(o3); // true (即使值是 undefined)

wm3.set(o1, 37);
wm3.get(o1); // 37

wm1.has(o1);   // true
wm1.delete(o1);
wm1.has(o1);   // false
ログイン後にコピー

Map の代わりに

WeakMap

を使用する理由 JavaScript では、 map API は、2 つの配列 (1 つはキー用、もう 1 つは値用) を共有する 4 つの API メソッドを通じて実装できます。このように、この map に値を設定すると、キーと値が両方の配列の末尾に同時に追加されます。これにより、キーと値のインデックスが 2 つの配列内で対応します。この map から値を取得する場合は、すべてのキーを反復処理し、インデックスを使用して、値が格納されている配列から対応する値を取得する必要があります。

しかし、そのような実装には 2 つの大きな欠点があります。まず、代入および検索操作の時間計算量は O(n) (n はキー番号です) です。両方の操作で一致するには配列全体を走査する必要があるためです。

もう 1 つの欠点は、配列が常に各キーと値を参照するため、メモリ リークが発生する可能性があることです。このような参照は、たとえ他に参照が存在しない場合でも、ガベージ コレクション アルゴリズムによるリサイクルを妨げます。

let jser = { name: "dachui" };

let array = [ jser ];

jser = null; // 覆盖引用
ログイン後にコピー

上記のコードでは、オブジェクトを配列に配置します。その後、配列が存在する限り、オブジェクトへの参照が他にない場合でも、オブジェクトも存在します## #。

let jser = { name: "dachui" };

let map = new Map();
map.set(jser, "");

jser = null; // 覆盖引用
ログイン後にコピー
同様に、オブジェクトを通常の Map

のキーとして使用する場合、そのオブジェクトは

Map が存在するときに存在します。これはメモリを占有し、ガベージ コレクション メカニズムによって再利用されません。 対照的に、ネイティブの WeakMap

は各キー オブジェクトの

弱い参照を保持します。これは、ガベージ コレクションが正しく実行できることを意味します。 まさにこのような弱参照のため、 WeakMap

key は列挙可能ではありません (すべての key を指定できるメソッドはありません)。 key が列挙可能な場合、そのリストはガベージ コレクション メカニズムの影響を受けるため、結果は未定義になります。したがって、このタイプのオブジェクトの key 値のリストが必要な場合は、Map を使用する必要があります。 要約すると、次の結論を導き出すことができます: WeakMap のキーが指すオブジェクトは、ガベージ コレクション メカニズムではカウントされません

したがって、オブジェクトにデータを追加し、ガベージ コレクション メカニズムに干渉したくない場合は、WeakMap

を使用できます。

これを見れば誰もがわかるはずですが、Vue 3 が WeakMap

をバッファとして使用する理由は、使用されなくなったデータを正しくガベージ コレクションするためです

弱参照とは 「弱参照」については、Wikipedia に次のような答えがあります:

コンピューター プログラミングでは、

弱い参照

強力な参照
相対。参照するオブジェクトがガベージ コレクターによってリサイクルされないことを保証できない参照を指します。オブジェクトが弱い参照によってのみ参照されている場合、そのオブジェクトはアクセスできない (または弱いアクセス可能である) とみなされ、したがって はいつでもリサイクルされる可能性があります なぜ弱い参照が現れるのですか?それでは、なぜ弱い参照が現れるのでしょうか?上記の問題の解決に加えて、弱い参照によって他にどのような問題が解決できるでしょうか?これらの質問に答えるには、まず
V8

エンジンがガベージ コレクションを実行する方法を理解する必要があります。

JSer の場合、メモリ管理は自動的に行われ、目に見えません。これはすべて、

V8

エンジンがバックグラウンドで使用する必要のないファイルを自動的に見つけてくれるおかげです。 . メモリを削除してクリーンアップします。 それでは、何かが必要なくなったとき、V8 エンジンはどのようにしてそれを見つけてクリーンアップするのでしょうか?

现在各大浏览器通常用采用的垃圾回收有两种方法,一种是「引用计数」,另外一种就是「标记清除」。下面我们来看一下:

标记清除

标记清除被称为 mark-and-sweep,它是基于 可达性 来判断对象是否存活的,它会定期执行以下「垃圾回收」步骤:

  • 垃圾收集器找到所有的根,并标记(记住)它们。

  • 然后它遍历并标记来自它们的所有引用。所有被遍历到的对象都会被记住,以免将来再次遍历到同一个对象。

  • ……如此操作,直到所有可达的(从根部)引用都被访问到。

  • 没有被标记的对象都会被删除。

我们还可以将这个过程想象成从根溢出一个巨大的油漆桶,它流经所有引用并标记所有可到达的对象,然后移除未标记的。

引用计数

引用计数方式最基本的形态就是让每个被管理的对象与一个引用计数器关联在一起,该计数器记录着该对象当前被引用的次数,每当创建一个新的引用指向该对象时其计数器就加 1,每当指向该对象的引用失效时计数器就减 1。当该计数器的值降到 0 就认为对象死亡。

区别

引用计数与基于「可达性」的标记清除的内存管理方式最大的区别就是,前者只需要 局部的信息,而后者需要 全局的信息

在引用计数中每个计数器只记录了其对应对象的局部信息 —— 被引用的次数,而没有(也不需要)一份全局的对象图的生死信息。

由于只维护局部信息,所以不需要扫描全局对象图就可以识别并释放死对象。但也因为缺乏全局对象图信息,所以 无法处理循环引用 的状况。

所以,更高级的引用计数实现会引入 弱引用 的概念来打破某些已知的循环引用。

WeakMap 应用

存储 DOM 节点

WeakMap 应用的典型场合就是以 DOM 节点作为键名。下面是一个例子。

const myWeakmap = newWeakMap();
myWeakmap.set(
  document.getElementById('logo'),
  { timesClicked: 0 },
);
document.getElementById('logo').addEventListener('click', () => {
  const logoData = myWeakmap.get(document.getElementById('logo'));
  logoData.timesClicked++;
}, false);
ログイン後にコピー

上面代码中,document.getElementById('logo') 是一个 DOM 节点,每当发生 click 事件,就更新一下状态。我们将这个状态作为值放在 WeakMap 里,对应的键就是这个节点对象。一旦这个 DOM 节点删除,该状态就会自动消失,不存在内存泄漏风险。

数据缓存

谜底就在谜面上,文章一开头我们提出的问题就是这里的答案。Vue 3 在实现响应式原理的时候就是使用了 WeakMap 来作为响应式对象的「缓存区」。

关于这一点用法也很简单,当我们需要关联对象和数据,比如在不修改原有对象的情况下储存某些属性或者根据对象储存一些计算的值等,而又不想手动去管理这些内存问题的时候就可以使用 WeakMap

部署类中的私有属性

WeakMap 的另一个用处是部署类中的私有属性。

值得一提的是,TypeScript 中已经实现的 private 私有属性原理就是利用 WeakMap

私有属性应该是不能被外界访问到,不能被多个实例共享,JavaScript 中约定俗成地使用下划线来标记私有属性和方法,一定程度来说是不靠谱的。

下面我们用三种方法来实现:

  • 版本一:闭包
const testFn = (function () {
  let data;

  class Test {
    constructor(val) {
      data = val
    }
    getData() {
      return data;
    }
  }
  return Test;
})();

let test1 = new testFn(3);
let test2 = new testFn(4);
console.log(test1.getData()); // 4
console.log(test2.getData()); // 4
ログイン後にコピー

可以看到最后都输出 4,多实例共享私有属性了,所以版本一不符合。

  • 版本二:Symbol
const testFn = (function () {
  let data = Symbol('data')

  class Test {
    constructor(val) {
      this[data] = val
    }
    getData() {
      return this[data]
    }
  }
  return Test;
})();

let test1 = new testFn(3);
let test2 = new testFn(4);
console.log(test1.getData()); // 3
console.log(test2.getData()); // 4

console.log(test1[Object.getOwnPropertySymbols(test1)[0]]); // 3
console.log(test2[Object.getOwnPropertySymbols(test2)[0]]); // 4
ログイン後にコピー

使用 Symbol 虽然实现了而且正确输出了 34,但是我们发现可以在外界不通过 getData 方法直接拿到私有属性,所以这种方法也不满足我们的要求。

  • 版本三:WeakMap
const testFn = (function () {
  let data = new WeakMap()

  class Test {
    constructor(val) {
      data.set(this, val)
    }
    getData() {
      return data.get(this)
    }
  }
  return Test;
})();

let test1 = new testFn(3);
let test2 = new testFn(4);
console.log(test1.getData()); // 3
console.log(test2.getData()); // 4
ログイン後にコピー

如上,完美解决~~

更多编程相关知识,请访问:编程入门!!

以上がウィークマップとは何ですか? Vue3 レスポンシブ ソース コードのキャッシュ領域として使用されるのはなぜですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:segmentfault.com
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
関連するチュートリアル
人気のおすすめ
最新のコース
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート