[ES6 ナレッジ] リフレクトとプロキシ

序文

Proxy と Reflect は、オブジェクトを操作するために ES6 で導入された API です。

  • プロキシは、ターゲット オブジェクトの前に「インターセプト」層を設定するものとして理解できます。オブジェクトへの外部アクセスは、まずこのインターセプト層を通過する必要があります。そのため、外部アクセスをフィルタリングして書き換えるメカニズムを提供します。簡単に言うと、このオブジェクトに対するすべての操作はプロキシ オブジェクトによって完了されます。

  • ReflectオブジェクトもProxyオブジェクトと同様、オブジェクトを操作するために ES6 によって提供される新しい API です。Reflectオブジェクトの設計目的は次のとおりです。

    • Object明らかに言語の内部にあるオブジェクトのメソッド (例Object.defineProperty: )をReflectオブジェクトにいくつか配置します。Objectこの段階では、一部のメソッドはとオブジェクトの両方にデプロイされますReflectが、将来の新しいメソッドはReflectオブジェクトにのみデプロイされます。

    • Reflect オブジェクトは、一部のメソッドの戻り結果を変更して、より合理的なものにします。たとえば、Object.defineProperty(obj, name, desc)プロパティを定義できない場合は、エラーがスローされてReflect.defineProperty(obj, name, desc)返されますfalse

Native JavaScriptScipt 事例集
JavaScript + DOM 基本
JavaScript 基礎から上級
Canvas ゲーム開発

1. プロキシ プロキシ オブジェクト

1.1 基本的なアプリケーション

プロキシは、ターゲット オブジェクトの前に「インターセプト」層を設定するものとして理解できます。オブジェクトへの外部アクセスは、まずこのインターセプト層を通過する必要があります。そのため、外部アクセスをフィルタリングして書き換えるメカニズムを提供します。簡単に言うと、このオブジェクトに対するすべての操作はプロキシ オブジェクトによって完了されます。
ES6 は、プロキシ インスタンスを生成するためのプロキシ コンストラクターをネイティブに提供します。

var proxy = new Proxy(target, handler);
  • ターゲットパラメータ。傍受されたすべてのターゲット オブジェクトを示します
  • ハンドラーパラメータ。インターセプト動作をカスタマイズするために使用されるオブジェクト
let target = {
    name: 'Tom',
    age: 24
}
let handler = {
    get: function(target, key) {
        console.log('getting '+key);
        return target[key]; // 不是target.key
    },
    set: function(target, key, value) {
        console.log('setting '+key);
        target[key] = value;
    }
}
let proxy = new Proxy(target, handler)
proxy.name     // 实际执行 handler.get
proxy.age = 25 // 实际执行 handler.set
// getting name
// setting age
// 25
 
// target 可以为空对象
let targetEpt = {}
let proxyEpt = new Proxy(targetEpt, handler)
// 调用 get 方法,此时目标对象为空,没有 name 属性
proxyEpt.name // getting name
// 调用 set 方法,向目标对象中添加了 name 属性
proxyEpt.name = 'Tom'
// setting name
// "Tom"
// 再次调用 get ,此时已经存在 name 属性
proxyEpt.name
// getting name
// "Tom"
 
// 通过构造函数新建实例时其实是对目标对象进行了浅拷贝,因此目标对象与代理对象会互相
// 影响
targetEpt
// {name: "Tom"}
 
// handler 对象也可以为空,相当于不设置拦截操作,直接访问目标对象
let targetEmpty = {}
let proxyEmpty = new Proxy(targetEmpty,{})
proxyEmpty.name = "Tom"
targetEmpty // {name: "Tom"}

1.2 同じインターセプタ関数を設定して、複数の操作をインターセプトできます。

var handler = {
  get: function(target, name) {
    if (name === 'prototype') {
      return Object.prototype;
    }
    return 'Hello, ' + name;
  },

  apply: function(target, thisBinding, args) {
    return args[0];
  },

  construct: function(target, args) {
    return {value: args[1]};
  }
};

var fproxy = new Proxy(function(x, y) {
  return x + y;
}, handler);

fproxy(1, 2) // 1
new fproxy(1, 2) // {value: 2}
fproxy.prototype === Object.prototype // true
fproxy.foo === "Hello, foo" // true

1.3 Proxyがサポートする傍受操作一覧、合計13種類:

  • get(target, propKey,receiver)proxy.foo :や などのオブジェクト プロパティの読み取りをインターセプトします。proxy['foo']

    ターゲットパラメータ。インターセプトするターゲット オブジェクト

    propKey パラメータ。ターゲットオブジェクトのターゲット属性名

    受信側パラメータ。元の操作動作が配置されているオブジェクト (通常は Proxy インスタンス自体) を表します。

    var person = {
      name: "张三"
    };
    
    var proxy = new Proxy(person, {
      get: function(target, propKey) {
        if (propKey in target) {
          return target[propKey];
        } else {
          throw new ReferenceError("Prop name \"" + propKey + "\" does not exist.");
        }
      }
    });
    
    proxy.name // "张三"
    proxy.age // 抛出一个错误
    

    上記のコードは、ターゲット オブジェクトに存在しないプロパティにアクセスするとエラーがスローされることを示しています。このインターセプト関数がないと、存在しないプロパティにアクセスすると が返されるだけになりますundefined

  • set(target, propKey, value, レシーバー)proxy.foo = v : またはなどのオブジェクト プロパティの設定をインターセプトし、proxy['foo'] = vブール値を返します。

    ターゲット、propKey、レシーバーのパラメーター。ゲットと同じ。

    値パラメータ。対応する属性 propKey 設定する属性値

    let validator = {
        set: function(obj, prop, value) {
            if (prop === 'age') {
                if (!Number.isInteger(value)) {
                    throw new TypeError('The age is not an integer');
                }
                if (value > 200) {
                    throw new RangeError('The age seems invalid');
                }
            }
            // 对于满足条件的 age 属性以及其他属性,直接保存
            obj[prop] = value;
        }
    };
    let proxy= new Proxy({}, validator)
    proxy.age = 100;
    proxy.age           // 100
    proxy.age = 'oppps' // 报错
    proxy.age = 300     // 报错
    
  • has(target, propKey) : HasProperty オペレーションをインターセプトするために使用されます。つまり、ターゲット オブジェクトに propKey 属性があるかどうかを判断するときに、このメソッドによってインターセプトされます。

    let  handler = {
        has: function(target, propKey){
            console.log("handle has");
            return propKey in target;
        }
    }
    let exam = {name: "Tom"}
    let proxy = new Proxy(exam, handler)
    'name' in proxy
    // handle has
    // true
    
  • deleteProperty(target, propKey) : 削除操作をインターセプトするために使用されます。このメソッドがエラーをスローするか false を返す場合、propKey プロパティは delete コマンドで削除できません。

  • 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) : Interception Object.preventExtensions(proxy)、ブール値を返します。

  • getPrototypeOf(target) :Object.getPrototypeOf(proxy)オブジェクトをインターセプトして返します。

  • isExtensible(target) : Interception Object.isExtensible(proxy)、ブール値を返します。

  • setPrototypeOf(target, proto) : インターセプトしObject.setPrototypeOf(proxy, proto)てブール値を返します。ターゲット オブジェクトが関数の場合、インターセプトできる操作がさらに 2 つあります。

  • apply(target, ctx args) : 関数呼び出し、呼び出しおよび応答操作のインターセプトに使用されます。target はターゲット オブジェクトを表し、ctx はターゲット オブジェクトのコンテキストを表し、args はターゲット オブジェクトのパラメーター配列を表します。

    function sub(a, b){
        return a - b;
    }
    let handler = {
        apply: function(target, ctx, args){
            console.log('handle apply');
            return Reflect.apply(...arguments);
        }
    }
    let proxy = new Proxy(sub, handler)
    proxy(2, 1) 
    // handle apply
    // 1
    
  • construct(target, args) : コンストラクター呼び出しの操作として Proxy インスタンスをインターセプトします (新しいコマンドをインターセプトします。戻り値はオブジェクトである必要があります)。たとえば、new proxy(...args)

    let handler = {
        construct: function (target, args, newTarget) {
            console.log('handle construct')
            return Reflect.construct(target, args, newTarget)  
        }
    }
    class Exam { 
        constructor (name) {  
            this.name = name 
        }
    }
    let ExamProxy = new Proxy(Exam, handler)
    let proxyObj = new ExamProxy('Tom')
    console.log(proxyObj)
    // handle construct
    // exam {name: "Tom"}
    

2. オブジェクトを反射する

2.1 基本的な使い方

ReflectオブジェクトもProxyオブジェクトと同様、オブジェクトを操作するために ES6 によって提供される新しい API です。Reflectオブジェクトの設計目的は次のとおりです。

  • Object明らかに言語の内部にあるオブジェクトのメソッド (例Object.defineProperty: )をReflectオブジェクトにいくつか配置します。Objectこの段階では、一部のメソッドはとオブジェクトの両方にデプロイされますReflectが、将来の新しいメソッドはReflectオブジェクトにのみデプロイされます。

  • Reflect オブジェクトは、一部のメソッドの戻り結果を変更して、より合理的なものにします。たとえば、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
    }
    
  • Reflect オブジェクトは、関数を使用してオブジェクトの命令型操作を実装します。name in objdelete obj[name]や などReflect.has(obj, name)Reflect.deleteProperty(obj, name)関数的な動作にします。

    // 老写法
    'assign' in Object // true
    
    // 新写法
    Reflect.has(Object, 'assign') // true
    
  • 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メソッドはtargetオブジェクトの属性割り当て動作をインターセプトします。メソッドを使用してReflect.setオブジェクトのプロパティに値を割り当て、元の動作が完了していることを確認してから、追加の機能をデプロイします。

    var loggedObj = new Proxy(obj, {
      get(target, name) {
        console.log('get', target, name);
        return Reflect.get(target, name);
      },
      deleteProperty(target, name) {
        console.log('delete' + name);
        return Reflect.deleteProperty(target, name);
      },
      has(target, name) {
        console.log('has' + name);
        return Reflect.has(target, name);
      }
    })
    

    上記のコードでは、各Proxyオブジェクトのインターセプト操作 ( getdeletehas) が対応するReflectメソッドを内部的に呼び出して、ネイティブ動作が正常に実行できることを確認します。追加された作業は、操作ごとに 1 行のログを出力することです。

2.2Reflectオブジェクトには合計 13 個の静的メソッドがあります

  • Reflect.apply(target, thisArg, args)
  • Reflect.construct(ターゲット, 引数)
  • Reflect.get(ターゲット、名前、受信者)
  • Reflect.set(ターゲット、名前、値、受信者)
  • Reflect.defineProperty(ターゲット, 名前, 説明)
  • Reflect.deleteProperty(ターゲット, 名前)
  • Reflect.has(ターゲット, 名前)
  • Reflect.ownKeys(ターゲット)
  • Reflect.isExtensible(ターゲット)
  • Reflect.preventExtensions(ターゲット)
  • Reflect.getOwnPropertyDescriptor(ターゲット, 名前)
  • Reflect.getPrototypeOf(ターゲット)
  • Reflect.setPrototypeOf(ターゲット, プロトタイプ)

上記のメソッドのほとんどは、Objectオブジェクトに対する同じ名前のメソッドと同じ効果があります。

Reflect オブジェクトのメソッドと Proxy オブジェクトのメソッドの間には 1 対 1 の対応関係があります。したがって、Proxy オブジェクトのメソッドは、Reflect オブジェクトのメソッドを呼び出すことでデフォルトの動作を取得し、追加の操作を実行できます。

let exam = {
    name: "Tom",
    age: 24
}
let handler = {
    get: function(target, key){
        console.log("getting "+key);
        return Reflect.get(target,key);
    },
    set: function(target, key, value){
        console.log("setting "+key+" to "+value)
        Reflect.set(target, key, value);
    }
}
let proxy = new Proxy(exam, handler)
proxy.name = "Jerry"
proxy.name
// setting name to Jerry
// getting name
// "Jerry"

3. プロキシを使用してオブザーバー モードを実装する

オブザーバーモードとは、データオブジェクトを自動的に監視する機能のことで、オブジェクトが変化すると自動的に実行されます。

// 定义 Set 集合
const queuedObservers = new Set();
// 把观察者函数都放入 Set 集合中
const observe = fn => queuedObservers.add(fn);
// observable 返回原始对象的代理,拦截赋值操作
const observable = obj => new Proxy(obj, {set});
// 拦截函数set
function set(target, key, value, receiver) {
  // 获取对象的赋值操作
  const result = Reflect.set(target, key, value, receiver);
  // 执行所有观察者
  queuedObservers.forEach(observer => observer());
  // 执行赋值操作
  return result;
}

// 使用
const person = observable({
  name: '张三',
  age: 20
});

function print() {
  console.log(`${person.name}, ${person.age}`)
}

observe(print);
person.name = '李四';
// 输出
// 李四, 20

おすすめ

転載: blog.csdn.net/qq_39335404/article/details/133012194
おすすめ