Vue3.0では、なぜObject.definePropertyを放棄し、プロキシを使用してデータハイジャックを実装するのですか?

Vue3.0では、なぜObject.definePropertyを放棄し、プロキシを使用してデータハイジャックを実装するのですか?

序文

最近、Vueをレビューして、学習記録を比較しています〜

文章

問題を説明する前に、ProxyとObject.definePropertyの知識を確認しましょう。

1.1プロキシ

プロキシとは何ですか?

Proxyオブジェクトは、オブジェクトのプロキシを作成するために使用され、基本的な操作(プロパティの検索、割り当て、列挙、関数の呼び出しなど)の傍受とカスタマイズを可能にします。ターゲットオブジェクトの前に「傍受」の層が設定されており、外界からのオブジェクトへのアクセスは、最初にこの傍受の層を通過する必要があることが理解できます。したがって、アクセスをフィルタリングして書き換えるメカニズムが提供されます。外の世界の。

文法:

const p = new Proxy(target, handler)
  • target:プロキシでラップするターゲットオブジェクト(ネイティブ配列、関数、または別のプロキシを含む、任意のタイプのオブジェクトにすることができます)。
  • handler:通常は属性としての機能を持つオブジェクトであり、各属性の関数は、さまざまな操作を実行するときのプロキシpの動作を定義します。

注:Proxy.revocable(target, handler);プロキシオブジェクト自体とその失効メソッドの取り消しを含む取り消し可能なプロキシオブジェクトを返す取り消し可能なプロキシオブジェクトを作成するために使用されます。

プロキシオブジェクトが取り消されると、ほぼ完全に呼び出せなくなり、プロキシ操作を実行するとTypeError例外がスローされます(14種類のプロキシ操作があり、これらの14の操作以外の操作は実行されないことに注意してください)。例外)。取り消されると、プロキシオブジェクトを直接元の状態に復元することはできず、それに関連付けられているターゲットオブジェクトとプロセッサオブジェクトがガベージコレクションされる可能性があります

var revocable = Proxy.revocable({
    
    }, {
    
    
  get(target, name) {
    
    
    return "[[" + name + "]]";
  }
});
var proxy = revocable.proxy;
proxy.foo;              // "[[foo]]"

revocable.revoke();

console.log(proxy.foo); // 抛出 TypeError
proxy.foo = 1           // 还是 TypeError
delete proxy.foo;       // 又是 TypeError
typeof proxy            // "object",因为 typeof 不属于可代理操作

ハンドラ

get()
は、オブジェクトのプロパティの読み取り操作をインターセプトするために使用されます。

get: function(target, property, receiver) {
    
    
  }

このメソッドは、ターゲットオブジェクトに対する次の操作をインターセプトします。

  • アクセス属性:proxy[foo]およびproxy.bar
  • プロトタイプチェーンのプロパティにアクセスします。Object.create(proxy)[foo]
  • Reflect.get()
var p = new Proxy({
    
    }, {
    
    
  get: function(target, prop, receiver) {
    
    
    console.log("called: " + prop);
    return 10;
  }
});

console.log(p.a); // "called: a"
                  // 10

プロパティ値操作を設定するためのset()
キャッチャー

set: function(target, property, value, receiver) {
    
    
  }

このメソッドは、ターゲットオブジェクトに対する次の操作をインターセプトします。

  • プロパティ値を指定します:proxy [foo]=barおよびproxy.foo=bar
  • 継承者のプロパティ値を指定します:Object.create(proxy)[foo] = bar
  • Reflect.set()
var p = new Proxy({
    
    }, {
    
    
  set: function(target, prop, value, receiver) {
    
    
    target[prop] = value;
    console.log('property set: ' + prop + ' = ' + value);
    return true;
  }
})

console.log('a' in p);  // false

p.a = 10;               // "property set: a = 10"
console.log('a' in p);  // true
console.log(p.a);       // 10

空のオブジェクトにインターセプトのレイヤーを設定することは、実際にはドット演算子をオーバーロードするようなものです。つまり、言語の元の定義を独自の定義で上書きします。

var obj = new Proxy({
    
    },{
    
    
    get(target,prop){
    
    
        console.log(`读取了${
      
      prop}`);
        return Reflect.get(target, prop);
    },
    set(target,prop,value){
    
    
        console.log(`更新了${
      
      prop}`);
        return Reflect.set(target, prop, value);
    }
})
obj.a=1; //更新了a
console.log(obj.a); //读取了a
                    // 1

注意プロキシを機能させるには、ターゲットオブジェクト(上記の例では空のオブジェクト)ではなく、プロキシインスタンス(上記の例ではプロキシオブジェクト)を操作する必要があります。


ハンドラオブジェクトの他のメソッド:

  • applyメソッドは、関数呼び出しをインターセプトし、操作を呼び出して適用します。
  • has()このメソッドは、HasProperty操作をインターセプトするために使用されます。つまり、オブジェクトに特定のプロパティがあるかどうかが判断されると、このメソッドが有効になります。典型的な操作はin演算子です。in演算子はfor...inループでも使用されますが、has()インターセプトはfor...inループでは機能しません。
  • deleteProperty(target, propKey):delete proxy [propKey]の操作をインターセプトし、ブール値を返します。
  • ownKeys(target):Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)をインターセプトし、ループ内で配列を返します。このメソッドは、ターゲットオブジェクトのすべての独自のプロパティのプロパティ名を返し、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)をインターセプトし、ブール値を返します。ターゲットオブジェクトが関数の場合、インターセプトできる2つの追加操作があります。
  • construct(target, args):プロキシインスタンスの操作を、new proxy(... args)などのコンストラクター呼び出しとしてインターセプトします。
  • defineProperty():オブジェクトに対するObject.defineProperty()操作をインターセプトするために使用されます。

1.2 Object.defineProperty()

定義と構文

Object.defineProperty()メソッドは何をしますか?

Object.defineProperty()このメソッドは、オブジェクトに直接新しいプロパティを定義するか、オブジェクトの既存のプロパティを変更して、オブジェクトを返します。

文法:

Object.defineProperty(obj, prop, descriptor)
  • obj:プロパティを定義するオブジェクト。
  • prop:定義または変更するプロパティの名前またはシンボル。
  • descriptor:定義または変更するプロパティ記述子。属性記述子には、データ記述子とアクセス記述子の2つの主要な形式があります。データ記述子は、書き込み可能である場合とできない場合がある値を持つ属性です。アクセス記述子は、getterおよびsetter関数によって記述されるプロパティです。

ディスクリプタ

  • valueこのプロパティに対応する値を示します。デフォルトは未定義です
  • writableプロパティがfalseに設定されている場合、プロパティは「書き込み不可」であると言われます。再割り当てすることはできません。書き込み不可能なプロパティに書き込もうとしても、プロパティは変更されず、エラーも発生しませんデフォルト値はfalse
  • enumerableオブジェクトのプロパティをfor...inループおよびObject.keys()に列挙できるかどうかを定義します。デフォルト値はfalse
  • configurable属性は、オブジェクトのプロパティを削除できるかどうか、および値と書き込み可能な属性以外の他の属性を変更できるかどうかを示します。デフォルト値はfalse

存取描述符次のオプションのキー値もあります。

  • get:プロパティのgetter関数、またはgetterがない場合は未定義。この関数は、プロパティにアクセスしたときに呼び出されます実行中にパラメーターは渡されませんが、thisオブジェクトは渡されます(継承関係のため、これは必ずしもプロパティを定義するオブジェクトではありません)。この関数の戻り値は、プロパティの値として使用されます。デフォルトは未定義です。
  • set:プロパティのセッター関数、またはセッターがない場合は未定義。この関数は、プロパティ値が変更されたときに呼び出されます。このメソッドは、1つのパラメーター(つまり、割り当てられる新しい値)を受け入れ、割り当て時にこのオブジェクトを渡します。デフォルトは未定義です。

数据描述符存取描述符違いは次のとおりです。
ここに画像の説明を挿入

使用する

指定されたプロパティがオブジェクトに存在しない場合、Object.defineProperty()がプロパティを作成します。記述子から特定のフィールドが省略されている場合、それらのフィールドはデフォルト値を使用します。プロパティがすでに存在する場合、Object.defineProperty()は、記述子の値とオブジェクトの現在の構成に基づいてプロパティを変更しようとします。

var o = {
    
    }; // 创建一个新对象

// 在对象中添加一个属性与数据描述符的示例
Object.defineProperty(o, "a", {
    
    
  value : 37,
  writable : true,
  enumerable : true,
  configurable : true
});

// 对象 o 拥有了属性 a,值为 37

// 在对象中添加一个设置了存取描述符属性的示例
var bValue = 38;
Object.defineProperty(o, "b", {
    
    
  // 使用了方法名称缩写(ES2015 特性)
  // 下面两个缩写等价于:
  // get : function() { return bValue; },
  // set : function(newValue) { bValue = newValue; },
  get() {
    
     return bValue; },
  set(newValue) {
    
     bValue = newValue; },
  enumerable : true,
  configurable : true
});
console.log(o.a); // 37
console.log(o.b); // 38

o.a=100
bValue=9;
console.log(o.a); // 100
console.log(o.b); // 9

注意get関数でobを直接返す場合、ここのobはget関数も1回呼び出すため、無限ループに陥ります。同じことがset関数にも当てはまるため、サードパーティの変数bValueを使用して無限ループを防ぎます。
ここに画像の説明を挿入

ドット演算子とObject.defineProperty()を使用してオブジェクトにプロパティを割り当てる場合、データ記述子のプロパティのデフォルト値は異なります

var o = {
    
    };

o.a = 1;
// 等同于:
Object.defineProperty(o, "a", {
    
    
  value: 1,
  writable: true,
  configurable: true,
  enumerable: true
});


// 另一方面,
Object.defineProperty(o, "a", {
    
     value : 1 });
// 等同于:
Object.defineProperty(o, "a", {
    
    
  value: 1,
  writable: false,
  configurable: false,
  enumerable: false
});

訪問者のプロパティが継承されている場合、子オブジェクトのプロパティがアクセスまたは変更されたときに、そのgetメソッドとsetメソッドが呼び出されます。これらのメソッドが変数を使用して値を格納する場合、その値はすべてのオブジェクトによって共有されます。

function myclass() {
    
    
}

var value;
Object.defineProperty(myclass.prototype, "x", {
    
    
  get() {
    
    
    return value;
  },
  set(x) {
    
    
    value = x;
  }
});

var a = new myclass();
var b = new myclass();
a.x = 1;
console.log(b.x); // 1

これは、値を別のプロパティに格納することで解決できます。getおよびsetメソッドでは、これは、プロパティがアクセスおよび変更されているオブジェクトを指します。

function myclass() {
    
    
}

Object.defineProperty(myclass.prototype, "x", {
    
    
  get() {
    
    
    return this.stored_x;
  },
  set(x) {
    
    
    this.stored_x = x;
  }
});

var a = new myclass();
var b = new myclass();
a.x = 1;
console.log(b.x); // undefined

1.3分析

Vue2_

//源数据
let person = {
    
    
	name:'张三',
	age:18
}

let p = {
    
    }
Object.defineProperty(p,'name',{
    
    
	configurable:true,
	get(){
    
     //有人读取name时调用
		return person.name
	},
	set(value){
    
     //有人修改name时调用
		console.log('有人修改了name属性,我发现了,我要去更新界面!')
		person.name = value
	}
})
  • オブジェクトプロパティの場合、Object.definePropertyを使用して、プロパティの読み取り、書き込み、および変更をインターセプトします(データハイジャック)
  • 配列タイプの場合、インターセプトは、配列を更新する一連のメソッドをオーバーライドすることによって実装されます

これを行うことの欠点は次のとおりです。

  • Object.definePropertyはオブジェクトのプロパティをハイジャックできますが、オブジェクトの各プロパティをトラバースしてハイジャックする必要があります。

  • 次のコマンドを使用して追加しない限り、オブジェクトプロパティへの直接の追加または削除は検出できません。

// 监测不到
this.person.sex=delete this.person.name

// 监测得到
this.$set(this.person,'sex',)
// Vue.set(this.person,'sex',女)
this.$delete(this.person.'name')
//Vue.delete(this.person.'name')
  • 配列要素の変更を検出できません。配列メソッドを書き直す必要があります
// 监测不到
this.person.hobby[0]='学习'

// 监测得到
this.$set(this.person.hobby,0,'逛街')
this.person.hobby.splice(0,1,splice)

Vue3_

//源数据
let person = {
    
    
	name:'张三',
	age:18
}

const p = new Proxy(person,{
    
    
	//有人读取p的某个属性时调用
	get(target,propName){
    
    
		console.log(`有人读取了p身上的${
      
      propName}属性`)
		return Reflect.get(target,propName)
	},
	//有人修改p的某个属性、或给p追加某个属性时调用
	set(target,propName,value){
    
    
		console.log(`有人修改了p身上的${
      
      propName}属性,我要去更新界面了!`)
		Reflect.set(target,propName,value)
	},
	//有人删除p的某个属性时调用
	deleteProperty(target,propName){
    
    
		console.log(`有人删除了p身上的${
      
      propName}属性,我要去更新界面了!`)
		return Reflect.deleteProperty(target,propName)
	}
})

プロキシを使用してObject.definePropertyを比較し、データを乗っ取る利点は次のとおりです。

  • Object.definePropertyがプロパティをハイジャックするのと比較して、Proxyはより徹底的であり、プロパティを制限するのではなく、オブジェクト全体を直接プロキシします。
  • プロキシは、オブジェクト属性の追加と削除を監視できます。
// 检查得到
person.sex=delete person.name
  • 配列の添え字や配列の長さの変更であろうと、関数呼び出しによるものであろうと、Proxyは変更を非常によく監視できます。また、一般的に使用されるgetとsetに加えて、Proxyは13のインターセプト操作をサポートします。
// 可检测
person.hobby[0]='学习'

この記事はどこでも終わります、あなたがそれがあなたに役立つと思うならば、ブックマークすることを忘れないでください〜

おすすめ

転載: blog.csdn.net/qq_45890970/article/details/123992485