Maison >interface Web >js tutoriel >Introduction détaillée à DefineProperty et proxy dans ES6 (exemple de code)

Introduction détaillée à DefineProperty et proxy dans ES6 (exemple de code)

不言
不言avant
2018-11-12 16:45:192307parcourir

Cet article vous apporte une introduction détaillée (exemple de code) sur DefineProperty et proxy dans ES6. Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer.

Nous avons plus ou moins entendu le mot « liaison de données ». La clé de la « liaison de données » est de surveiller les modifications des données, mais pour un tel objet : var obj = {value: 1}, comment faire on sait que obj a changé ?

definePropety

ES5 fournit la méthode Object.defineProperty, qui peut définir une nouvelle propriété sur un objet, ou modifier une propriété existante d'un objet, et renvoyer cet objet .

Syntaxe

Object.defineProperty(obj, prop, descriptor)

Paramètres

obj: 要在其上定义属性的对象。

prop:  要定义或修改的属性的名称。

descriptor: 将被定义或修改的属性的描述符。

Par exemple :

var obj = {};
Object.defineProperty(obj, "num", {
    value : 1,
    writable : true,
    enumerable : true,
    configurable : true
});
//  对象 obj 拥有属性 num,值为 1

Bien que nous puissions ajouter des propriétés et des valeurs directement, nous pouvons faire plus de configuration en utilisant cette méthode. Le descripteur d'attribut représenté par le troisième descripteur de paramètre de la fonction

a deux formes : descripteur de données et descripteur d'accès .

Les deux ont les deux valeurs clés suivantes  :

configurable

Si et seulement si l'attribut configurable est Quand vrai, le descripteur de propriété peut être modifié ou supprimé. La valeur par défaut est fausse.

énumérable

Si et seulement si l'énumérable de la propriété est vrai, la propriété peut apparaître dans la propriété d'énumération de l'objet. La valeur par défaut est fausse.

Le descripteur de données possède également les valeurs clés facultatives suivantes  :

valeur

La valeur correspondant à cet attribut. Il peut s'agir de n'importe quelle valeur JavaScript valide (nombre, objet, fonction, etc.). La valeur par défaut n'est pas définie.

writable

Cette propriété peut être modifiée par l'opérateur d'affectation si et seulement si l'écriture de la propriété est vraie. La valeur par défaut est fausse.

Le descripteur d'accès a également les valeurs de clés facultatives suivantes  :

get

celle fournie pour la méthode Getter d'attribut, s'il n'y a pas de getter, il n'est pas défini. La valeur de retour de cette méthode est utilisée comme valeur d'attribut. La valeur par défaut n'est pas définie.

set

Une méthode qui fournit un setter pour une propriété S'il n'y a pas de setter, elle n'est pas définie. Cette méthode acceptera un paramètre unique et attribuera la nouvelle valeur du paramètre à la propriété. La valeur par défaut n'est pas définie.

Il convient de noter :

Le descripteur d'attribut doit être l'une des deux formes : descripteur de données ou descripteur d'accès, ne peut pas être les deux en même temps. Cela signifie que vous pouvez :

Object.defineProperty({}, "num", {
    value: 1,
    writable: true,
    enumerable: true,
    configurable: true
});

Également :

var value = 1;
Object.defineProperty({}, "num", {
    get : function(){
      return value;
    },
    set : function(newValue){
      value = newValue;
    },
    enumerable : true,
    configurable : true
});

Mais pas :

// 报错
Object.defineProperty({}, "num", {
    value: 1,
    get: function() {
        return 1;
    }
});

De plus, toutes les descriptions d'attributs Les caractères sont facultatifs , mais le champ descripteur est obligatoire Si vous ne configurez rien, vous pouvez faire ceci :

var obj = Object.defineProperty({}, "num", {});
console.log(obj.num); // undefined

Setters et Getters

La raison pour laquelle En parlant de. définirProperty, c'est parce que nous devons utiliser get et set dans le descripteur d'accès. Ces deux méthodes sont également appelées getter et setter. Les propriétés définies par les getters et les setters sont appelées « propriétés d'accesseur ».

Lorsqu'un programme interroge la valeur d'une propriété d'accesseur, JavaScript appelle la méthode getter. La valeur de retour de cette méthode est la valeur de l'expression d'accès à l'attribut. Lorsqu'un programme définit la valeur d'une propriété d'accesseur, JavaScript appelle la méthode setter, en transmettant la valeur située à droite de l'expression d'affectation en tant que paramètre au setter. Dans un sens, cette méthode est chargée de « définir » la valeur de la propriété. La valeur de retour de la méthode setter peut être ignorée.

Par exemple :

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

N'est-ce pas la méthode avec laquelle nous souhaitons surveiller les modifications des données ? Encapsulons-le à nouveau :

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

Étant donné que les modifications des données peuvent être surveillées, j'imagine que lorsque les données changent, le rendu fonctionnera automatiquement. Par exemple :

Il y a une balise span et une balise bouton en HTML

<span id="container">1</span>
<button id="button">点击加 1</button>

Lorsque l'on clique sur le bouton, la valeur de la balise span est augmentée de 1.

L'approche traditionnelle est :

document.getElementById('button').addEventListener("click", function(){
    var container = document.getElementById("container");
    container.innerHTML = Number(container.innerHTML) + 1;
});

Si DefineProperty est utilisé :

var obj = {
    value: 1
}

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

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

document.getElementById('button').addEventListener("click", function() {
    obj.value += 1;
});

Le code semble augmenter, mais lorsque nous devons changer la valeur dans le span tagQuand, modifiez simplement la valeur de obj.value directement.

Cependant, avec la façon actuelle d'écrire, nous devons toujours déclarer une variable distincte pour stocker la valeur de obj.value, car si vous obj.value = newValue directement dans set, vous tomberez dans une boucle infinie. De plus, nous devrons peut-être surveiller les changements dans de nombreuses valeurs d'attributs. Il serait fastidieux de les écrire une par une, nous écrivons donc simplement une fonction de surveillance. L'effet d'utilisation est le suivant :

var obj = {
    value: 1
}

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

document.getElementById('button').addEventListener("click", function(){
    obj.value += 1
});

Écrivons cette fonction de surveillance :

(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;
})()

Nous pouvons maintenant surveiller les changements dans les valeurs d'attribut de l'objet, et en fonction des changements dans le valeurs d'attribut, ajoutez une fonction de rappel, génial ~

proxy

L'utilisation de definitionProperty ne peut redéfinir que les comportements de lecture (get) et de définition (set) des propriétés dans ES6, il est fourni Sans proxy, davantage de comportements peuvent être redéfinis, tels que l'entrée, la suppression, les appels de fonction, etc.

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('设置 get 操作')
        return obj[prop];
    },
    set: function(obj, prop, value) {
        console.log('设置 set 操作')
        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] === '_') {
      return false;
    }
    return key in target;
  }
};
var target = { _prop: 'foo', prop: 'foo' };
var proxy = new Proxy(target, handler);
console.log('_prop' in proxy); // false

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

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

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

var p = new Proxy(target, handler);

p();
// "I am the proxy"

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

  • Object.getOwnPropertyNames()

  • Object.getOwnPropertySymbols()

  • Object.keys()

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

let target = {
  _bar: 'foo',
  _prop: 'bar',
  prop: 'baz'
};

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

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 == 'value') document.getElementById('container').innerHTML = newvalue;
})

document.getElementById('button').addEventListener("click", function() {
    newObj.value += 1
});

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

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer