defineProperty и прокси в серии ES6

внешний интерфейс GitHub JavaScript Promise
defineProperty и прокси в серии ES6

предисловие

Мы более или менее слышали слово "привязка данных". Ключом к "привязке данных" является отслеживание изменений в данных, но для такого объекта:var obj = {value: 1}, как мы узнаем, что 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

Хотя мы можем добавлять атрибуты и значения напрямую, мы можем использовать этот способ, мы можем сделать больше конфигураций.

Дескриптор атрибута, представленный дескриптором третьего параметра функции, имеет две формы:Дескрипторы данных и дескрипторы доступа.

Оба имеют следующие два параметра "ключ-значение":

configurable

当且仅当该属性的 configurable 为 true 时,该属性描述符才能够被改变,也能够被删除。默认为 false。

enumerable

当且仅当该属性的 enumerable 为 true 时,该属性才能够出现在对象的枚举属性中。默认为 false。

Дескриптор данных также имеет следующие необязательные ключи:

value

该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined。

writable

当且仅当该属性的 writable 为 true 时,该属性才能被赋值运算符改变。默认为 false。

Дескриптор доступа также имеет следующие необязательные ключи:

get

一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。该方法返回值被用作属性值。默认为 undefined。

set

一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。该方法将接受唯一参数,并将该参数的新值分配给该属性。默认为 undefined。

Стоит отметить, что:

**Дескриптор атрибута должен быть либо дескриптором данных, либо дескриптором доступа, но не тем и другим одновременно. ** Это означает, что вы можете:

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

Сеттеры и геттеры

Мы говорим о defineProperty, потому что хотим использовать get и set в дескрипторе доступа, которые также называются геттерами и сеттерами. Свойства, определенные геттерами и сеттерами, называются «свойствами доступа».

Когда программа запрашивает значение свойства средства доступа, JavaScript вызывает метод получения. Возвращаемое значение этого метода является значением выражения доступа к свойству. Когда программа устанавливает значение свойства средства доступа, JavaScript вызывает метод установки, передавая значение в правой части выражения присваивания в качестве аргумента для установки. В некотором смысле этот метод отвечает за «установку» значения свойства. Возвращаемое значение метода установки можно игнорировать.

Например:

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

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

console.log(obj.num); // 执行了 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 и тег кнопки

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

При нажатии кнопки значение в теге span увеличивается на 1.

Традиционный подход заключается в следующем:

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

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

Код, кажется, увеличивается, но когда нам нужно изменить значение в теге span, мы можем напрямую изменить значение obj.value.

Однако при текущем способе написания нам также необходимо объявить переменную для хранения значения obj.value отдельно, потому что если вы напрямуюobj.value = newValueпопадет в бесконечный цикл. Кроме того, нам может понадобиться следить за изменением множества значений атрибутов, и писать их по одному будет очень утомительно, поэтому мы просто пишем функцию наблюдения. Эффект следующий:

var obj = {
    value: 1
}

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

document.getElementById('button').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 может переопределить только чтение (получение) и установку (установку) поведения свойств.В ES6 предоставляется прокси-сервер, который может переопределять больше действий, таких как вход, удаление, вызовы функций и многое другое.

Первоначальное значение слова «прокси» — «прокси», которое используется здесь для обозначения того, что оно используется для «проксирования» некоторых операций.ES6 изначально предоставляет конструктор прокси для создания экземпляра прокси. Давайте посмотрим на его синтаксис:

var proxy = new Proxy(target, 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

Помимо получения и установки, прокси может перехватывать до 13 операций, таких как has(target, propKey), которые могут перехватывать операцию propKey в прокси и возвращать логическое значение.

// 使用 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 для перехвата вызовов функций, вызовов и операций применения.

Метод применения может принимать три параметра, а именно целевой объект, объект контекста (этот) целевого объекта и массив параметров целевого объекта, но здесь мы кратко продемонстрируем:

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

Стоит отметить, что самая большая проблема прокси заключается в том, что поддержки браузера недостаточно, а многие эффекты невозможно компенсировать с помощью poilyfill.

смотреть оптимизацию API

Давайте снова воспользуемся прокси, чтобы написать функцию наблюдения. Эффект следующий:

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

        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 и прокси заключается в том, что при использовании defineProperty мы можем инициировать перехват, изменяя исходный объект obj, а при использовании прокси мы должны модифицировать прокси-объект, то есть экземпляр Proxy может инициировать перехват.

серия ES6

Адрес каталога серии ES6:GitHub.com/ в настоящее время имеет бриз…

Ожидается, что в серии ES6 будет написано около 20 статей, направленных на углубление понимания некоторых точек знаний ES6, с упором на область действия на уровне блоков, шаблоны меток, функции стрелок, реализацию моделирования символов, наборов, карт и обещаний, схему загрузки модулей, асинхронность. обработка и т.п. содержание.

Если есть какие-либо ошибки или неточности, пожалуйста, поправьте меня, большое спасибо. Если вам нравится или у вас есть вдохновение, добро пожаловать в звезду, что также является поощрением для автора.