ES6 でのdefineProperty と proxy の詳細な紹介 (コード例)

不言
リリース: 2018-11-15 16:59:31
転載
2375 人が閲覧しました

この記事では、ES6 の defineProperty と proxy について詳しく説明します (コード例)。必要な方は参考にしていただければ幸いです。

「データ バインディング」という言葉は多かれ少なかれ聞いたことがあるでしょう。「データ バインディング」の鍵はデータの変更を監視することですが、このようなオブジェクトの場合は次のようになります。 }、obj が変更されたことはどうやってわかるのでしょうか?

definePropety

ES5 は、オブジェクトの新しいプロパティを定義したり、オブジェクトの既存のプロパティを変更したり、オブジェクトを返すことができる Object.defineProperty メソッドを提供します。

構文

Object.defineProperty(obj, prop, descriptor)
ログイン後にコピー

パラメータ

obj: 要在其上定义属性的对象。
prop:  要定义或修改的属性的名称。
descriptor: 将被定义或修改的属性的描述符。
ログイン後にコピー

例:

var obj = {};
Object.defineProperty(obj, "num", {
    value : 1,
    writable : true,
    enumerable : true,
    configurable : true
});
//  对象 obj 拥有属性 num,值为 1
ログイン後にコピー

ただし、直接追加することもできます。プロパティと値ですが、この方法でさらに多くの構成を行うことができます。

関数の 3 番目のパラメーター記述子によって表される属性記述子には、

データ記述子とアクセス記述子の 2 つの形式があります。

どちらにも次の 2 つのキー値があります :

configurable

属性の構成可能値が true の場合に限り、プロパティ記述子は変更または削除できます。デフォルトは false です。


enumerable

プロパティの列挙可能性が true の場合に限り、プロパティはオブジェクトの列挙プロパティに表示されます。デフォルトは false です。


データ記述子には、次のオプションのキー値もあります:

value

これに対応する値属性 。任意の有効な JavaScript 値 (数値、オブジェクト、関数など) を指定できます。デフォルトは未定義です。


writable

プロパティの writable が true である場合に限り、プロパティは代入演算子によって変更できます。デフォルトは false です。


アクセス記述子には、次のオプションのキー値もあります:

get

属性に指定される 1 つgetter メソッドは、getter が存在しない場合は未定義です。このメソッドの戻り値が属性値として使用されます。デフォルトは未定義です。


set

プロパティにセッターを提供するメソッド。セッターがない場合は、次のようになります。未定義。このメソッドは一意のパラメータを受け入れ、パラメータの新しい値をプロパティに割り当てます。デフォルトは未定義です。


注目に値する:


属性記述子は、データ記述子またはアクセス記述子の 2 つの形式のいずれかである必要があり、同時に両方を指定することはできません。 これは、次のことができることを意味します:

Object.defineProperty({}, "num", {
    value: 1,
    writable: true,
    enumerable: true,
    configurable: true
});
ログイン後にコピー

次のこともできます:

var value = 1;
Object.defineProperty({}, "num", {
    get : function(){
      return value;
    },
    set : function(newValue){
      value = newValue;
    },
    enumerable : true,
    configurable : true
});
ログイン後にコピー

ただし、次のことはできません:

// 报错
Object.defineProperty({}, "num", {
    value: 1,
    get: function() {
        return 1;
    }
});
ログイン後にコピー

さらに、すべての属性記述子はオプションですが、記述子はフィールドは必須です。設定を何も実行しない場合は、次のようにすることができます:

var obj = Object.defineProperty({}, "num", {});
console.log(obj.num); // undefined
ログイン後にコピー

Setters と Getters

defineProperty について説明する理由は、アクセス記述子を使用したいためです。 get と set、これら 2 つのメソッドはゲッターおよびセッターとも呼ばれます。ゲッターとセッターによって定義されたプロパティは「アクセサー プロパティ」と呼ばれます。

プログラムがアクセサー プロパティの値をクエリするとき、JavaScript はゲッター メソッドを呼び出します。このメソッドの戻り値は、属性アクセス式の値です。プログラムがアクセサー プロパティの値を設定すると、JavaScript は setter メソッドを呼び出し、代入式の右側の値をパラメーターとして setter に渡します。ある意味、このメソッドはプロパティ値を「設定」する役割を果たします。 setterメソッドの戻り値は無視して構いません。

例:

var obj = {}, value = null;
Object.defineProperty(obj, "num", {
    get: function(){
        console.log('执行了 get 操作')
        return value;
    },
    set: function(newValue) {
        console.log('执行了 set 操作')
        value = newValue;
    }
})

obj.value = 1 // 执行了 set 操作

console.log(obj.value); // 执行了 get 操作 // 1
ログイン後にコピー

これはデータの変更を監視したい方法ではないでしょうか?もう一度カプセル化しましょう:

function Archiver() {
    var value = null;
    // archive n. 档案
    var archive = [];

    Object.defineProperty(this, 'num', {
        get: function() {
            console.log('执行了 get 操作')
            return value;
        },
        set: function(value) {
            console.log('执行了 set 操作')
            value = value;
            archive.push({ val: value });
        }
    });

    this.getArchive = function() { return archive; };
}

var arc = new Archiver();
arc.num; // 执行了 get 操作
arc.num = 11; // 执行了 set 操作
arc.num = 13; // 执行了 set 操作
console.log(arc.getArchive()); // [{ val: 11 }, { val: 13 }]
ログイン後にコピー

watch API

データの変更を監視できるので、データが変更されるとレンダリング作業が自動的に実行されることが想像できます。例:

HTML には、span タグと button タグがあります。

<span id="container">1</span>
<button id="button">点击加 1</button>
ログイン後にコピー

ボタンがクリックされると、span タグの値が 1 増加します。

従来のアプローチは次のとおりです:

document.getElementById(&#39;button&#39;).addEventListener("click", function(){
    var container = document.getElementById("container");
    container.innerHTML = Number(container.innerHTML) + 1;
});
ログイン後にコピー

defineProperty を使用する場合:

var obj = {
    value: 1
}

// 储存 obj.value 的值
var value = 1;

Object.defineProperty(obj, "value", {
    get: function() {
        return value;
    },
    set: function(newValue) {
        value = newValue;
        document.getElementById(&#39;container&#39;).innerHTML = newValue;
    }
});

document.getElementById(&#39;button&#39;).addEventListener("click", function() {
    obj.value += 1;
});
ログイン後にコピー

コードは増加するように見えますが、span タグの値を直接変更する必要がある場合は、 obj.value の値を変更するだけです。

ただし、現在の書き方では、obj.value の値を格納する別の変数を宣言する必要があります。これは、セット内で

obj.value = newValue を直接実行する場合があるためです。 、無限ループに陥ってしまいます。さらに、多くの属性値の変更を監視する必要がある場合があります。それらを 1 つずつ記述するのは面倒なので、単純に監視関数を記述します。使用の効果は次のとおりです:

var obj = {
    value: 1
}

watch(obj, "num", function(newvalue){
    document.getElementById(&#39;container&#39;).innerHTML = newvalue;
})

document.getElementById(&#39;button&#39;).addEventListener("click", function(){
    obj.value += 1
});
ログイン後にコピー

この監視関数を作成しましょう:

(function(){
    var root = this;
    function watch(obj, name, func){
        var value = obj[name];

        Object.defineProperty(obj, name, {
            get: function() {
                return value;
            },
            set: function(newValue) {
                value = newValue;
                func(value)
            }
        });

        if (value) obj[name] = value
    }

    this.watch = watch;
})()
ログイン後にコピー

これで、オブジェクトの属性値の変化を監視し、属性値の変化に基づいてコールバック関数を追加できるようになりました。素晴らしい ~

proxy

defineProperty を使用すると、プロパティの読み取り (get) と設定 (set) の動作のみを再定義できます。ES6 では、Proxy が提供され、さらに多くの動作を再定義できます。たとえば、削除、関数呼び出しなどの動作です。

Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。我们来看看它的语法:

var proxy = new Proxy(target, handler);
ログイン後にコピー

proxy 对象的所有用法,都是上面这种形式,不同的只是handler参数的写法。其中,new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。

var proxy = new Proxy({}, {
    get: function(obj, prop) {
        console.log(&#39;设置 get 操作&#39;)
        return obj[prop];
    },
    set: function(obj, prop, value) {
        console.log(&#39;设置 set 操作&#39;)
        obj[prop] = value;
    }
});

proxy.time = 35; // 设置 set 操作

console.log(proxy.time); // 设置 get 操作 // 35
ログイン後にコピー

除了 get 和 set 之外,proxy 可以拦截多达 13 种操作,比如 has(target, propKey),可以拦截 propKey in proxy 的操作,返回一个布尔值。

// 使用 has 方法隐藏某些属性,不被 in 运算符发现
var handler = {
  has (target, key) {
    if (key[0] === &#39;_&#39;) {
      return false;
    }
    return key in target;
  }
};
var target = { _prop: &#39;foo&#39;, prop: &#39;foo&#39; };
var proxy = new Proxy(target, handler);
console.log(&#39;_prop&#39; in proxy); // false
ログイン後にコピー

又比如说 apply 方法拦截函数的调用、call 和 apply 操作。

apply 方法可以接受三个参数,分别是目标对象、目标对象的上下文对象(this)和目标对象的参数数组,不过这里我们简单演示一下:

var target = function () { return &#39;I am the target&#39;; };
var handler = {
  apply: function () {
    return &#39;I am the proxy&#39;;
  }
};

var p = new Proxy(target, handler);

p();
// "I am the proxy"
ログイン後にコピー

又比如说 ownKeys 方法可以拦截对象自身属性的读取操作。具体来说,拦截以下操作:

  • Object.getOwnPropertyNames()

  • Object.getOwnPropertySymbols()

  • Object.keys()

下面的例子是拦截第一个字符为下划线的属性名,不让它被 for of 遍历到。

let target = {
  _bar: &#39;foo&#39;,
  _prop: &#39;bar&#39;,
  prop: &#39;baz&#39;
};

let handler = {
  ownKeys (target) {
    return Reflect.ownKeys(target).filter(key => key[0] !== &#39;_&#39;);
  }
};

let proxy = new Proxy(target, handler);
for (let key of Object.keys(proxy)) {
  console.log(target[key]);
}
// "baz"
ログイン後にコピー

更多的拦截行为可以查看阮一峰老师的 《ECMAScript 6 入门》

值得注意的是,proxy 的最大问题在于浏览器支持度不够,而且很多效果无法使用 poilyfill 来弥补。

watch API 优化

我们使用 proxy 再来写一下 watch 函数。使用效果如下:

(function() {
    var root = this;

    function watch(target, func) {

        var proxy = new Proxy(target, {
            get: function(target, prop) {
                return target[prop];
            },
            set: function(target, prop, value) {
                target[prop] = value;
                func(prop, value);
            }
        });

        if(target[name]) proxy[name] = value;
        return proxy;
    }

    this.watch = watch;
})()

var obj = {
    value: 1
}

var newObj = watch(obj, function(key, newvalue) {
    if (key == &#39;value&#39;) document.getElementById(&#39;container&#39;).innerHTML = newvalue;
})

document.getElementById(&#39;button&#39;).addEventListener("click", function() {
    newObj.value += 1
});
ログイン後にコピー

我们也可以发现,使用 defineProperty 和 proxy 的区别,当使用 defineProperty,我们修改原来的 obj 对象就可以触发拦截,而使用 proxy,就必须修改代理对象,即 Proxy 的实例才可以触发拦截。

以上がES6 でのdefineProperty と proxy の詳細な紹介 (コード例)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

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