Простое и популярное понимание прокси в Vue3.0

JavaScript

Статья впервые опубликована вличный блог

содержание

  • Proxy
  • Использование Vue 2.0Object.defineProperty()Реализовать ответ данных
  • в Вью 3.0Proxy
  • Другие применения прокси

Proxy

Что такое прокси?Под ним можно понимать установку «перехвата» перед объектом.При обращении к объекту он должен пройти через этот уровень перехвата. Это означает, что вы можете выполнять различные операции в этом слое перехвата. Например, вы можете обработать исходный объект на этом уровне перехвата и вернуть нужную структуру данных.

ES6 изначально предоставляет конструктор Proxy, который объясняется на MDN следующим образом: Объекты Proxy используются для определения настраиваемого поведения для основных операций (таких как поиск свойств, назначение, перечисление, вызовы функций и т. д.).

Давайте сначала посмотрим, как его использовать.

const p = new Proxy(target, handler);
  • target: целевой объект для перехвата (может быть объект любого типа, включая собственные массивы, функции или даже другой прокси).
  • handler: Объект, который определяет поведение, которое будет перехвачено
const p = new Proxy({}, {
    get(target, propKey) {
        return '哈哈,你被我拦截了';
    }
});

console.log(p.name);
// 哈哈,你被我拦截了

Обратите внимание, что прокси используется для управления объектами. Цель прокси — расширить возможности объекта.

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

const p = new Proxy({}, {
    set(target, propKey, value) {
        if (propKey === 'name') {
            throw new TypeError('name属性不允许修改');
        }
        // 不是 name 属性,直接保存
        target[propKey] = value;
    }
});
p.name = 'proxy';
// TypeError: name属性不允许修改
p.a = 111;
console.log(p.a); // 111

Babel используется для преобразования синтаксиса, такого как новые API (например, Array.from, Array.prototype.includes). Нам необходимо установить дополнительные пакеты для поддержки, такие какcore-js/stableиregenerator-runtime/runtime(PS: @babel/polyfill устарел после babel 7.x), а также есть некоторые API (String#normalize, Proxy, fetch и т. д.)core-jsНа данный момент полифилл не предусмотрен, подробности можно найти в официальной документации.core-js#missing-polyfills.

ProxyВсего поддерживается 13 операций перехвата, и вы можете просмотреть их в деталях.MDN.

Как vue2.x реализует ответ данных?

Для рекурсивного обхода данных в данных используйтеObject.defineProperty()Захватите геттер и сеттер, выполните обработку сбора зависимостей данных в геттере, отслеживайте изменения данных в сеттере и уведомляйте место для подписки на текущие данные.Часть исходного кода src/core/observer/index.js#L156-L193, версия 2.6.11 выглядит следующим образом

let childOb = !shallow && observe(val)
 // 对 data中的数据进行深度遍历,给对象的每个属性添加响应式
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
         // 进行依赖收集
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            // 是数组则需要对每一个成员都进行依赖收集,如果数组的成员还是数组,则递归。
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      // 新的值需要重新进行observe,保证数据响应式
      childOb = !shallow && observe(newVal)
      // 将数据变化通知所有的观察者
      dep.notify()
    }
  })

Что плохого в этом?

  • Добавление и удаление свойств объекта не обнаруживается: при добавлении нового свойства к объектуnewProperty, только что добавленное свойство не имеет механизма, позволяющего vue обнаруживать обновления данных (поскольку оно добавляется после инициализации).vue.$setЭто значит, что vue знает, что вы добавили атрибуты, и он выполнит обработку за вас,$setВнутренне также по телефонуObject.defineProperty()иметь дело с
  • Изменение нижнего индекса массива не может быть отслежено, в результате чего значение массива устанавливается непосредственно через нижний индекс массива, и не может реагировать в режиме реального времени.
  • Когда в данных много данных и уровень глубокий, будут проблемы с производительностью, потому что все данные в данных нужно пройти и настроить на отзывчивость.

Возьмите массив в качестве примера (PS: ответ данных в реальном времени относится к отображаемому содержимому страницы, а не к данным самого значения vm.items):

<ul id="example">
    <li v-for="item in items">
        {{ item }}
    </li>
</ul>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
const vm = new Vue({
    el: '#example',
    data: {
        items: ['a', 'b', 'c']
    },
})
// 直接使用下标修改数据不是实时响应
setTimeout(() => {
    vm.items[1] = 'x';
    vm.items[3] = 'd';
    console.log(vm.items);
    // 此时打印结果为 ['a', 'x', 'c', 'd'],但页面内容没有更新
}, 500);
// 使用 $set 修改数据是实时响应
setTimeout(() => {
    vm.$set(vm.items, 1, 'x1')
    vm.$set(vm.items, 3, 'd1')
    console.log(vm.items);
    // 此时打印结果为 ['a', 'x1', 'c', 'd1'],页面内容更新
}, 1000);

Вы можете нажать, чтобы просмотреть код напрямуюcodepen

vue3.0 использует прокси

vue3.0 еще официально не выпущен, ноvue-nextСоответствующий код находится в открытом доступе и в настоящее время находится в альфа-версии.

Зачем использовать прокси для решения вышеуказанной проблемы? В основном потому, что Proxy — это объект перехвата, верно?对象Для осуществления «перехвата» доступ к объекту из внешнего мира должен сначала пройти через этот слой перехвата. К какому бы атрибуту объекта ни обращались, ранее определенному или вновь добавленному, он пойдет на перехват,

Возьми простой 🌰

Используйте следующееObject.defineProperty()иProxyРеализуйте простой ответ данных

использоватьObject.defineProperty()выполнить:

class Observer {
    constructor(data) {
        // 遍历参数data的属性,给添加到this上
        for(let key of Object.keys(data)) {
            if(typeof data[key] === 'object') {
                data[key] = new Observer(data[key]);
            }
            Object.defineProperty(this, key, {
                enumerable: true,
                configurable: true,
                get() {
                    console.log('你访问了' + key);
                    return data[key]; // 中括号法可以用变量作为属性名,而点方法不可以;
                },
                set(newVal) {
                    console.log('你设置了' + key);
                    console.log('新的' + key + '=' + newVal);
                    if(newVal === data[key]) {
                        return;
                    }
                    data[key] = newVal;
                }
            })
        }
    }
}

const obj = {
    name: 'app',
    age: '18',
    a: {
        b: 1,
        c: 2,
    },
}
const app = new Observer(obj);
app.age = 20;
console.log(app.age);
app.newPropKey = '新属性';
console.log(app.newPropKey);

Результатом выполнения приведенного выше кода является

// 修改 obj原有的属性 age的输出
你设置了age
新的age=20
你访问了age
20
// 设置新属性的输出
新属性

Видно, что добавление атрибута в объект не контролируется внутри, и вновь добавленный атрибут нужно использовать снова вручнуюObject.defineProperty()контролировать. Вот почемуvue 2.xСредний Причина добавления и удаления свойств объекта не обнаружена, внутренне установлено$setпозвонивObject.defineProperty()иметь дело с.

Ниже мы используемProxyальтернативаObject.defineProperty()выполнить

const obj = {
    name: 'app',
    age: '18',
    a: {
        b: 1,
        c: 2,
    },
}
const p = new Proxy(obj, {
    get(target, propKey, receiver) {
        console.log('你访问了' + propKey);
        return Reflect.get(target, propKey, receiver);
    },
    set(target, propKey, value, receiver) {
        console.log('你设置了' + propKey);
        console.log('新的' + propKey + '=' + value);
        Reflect.set(target, propKey, value, receiver);
    }
});
p.age = '20';
console.log(p.age);
p.newPropKey = '新属性';
console.log(p.newPropKey);
p.a.d = '这是obj中a的属性';
console.log(p.a.d);

Вы можете увидеть вывод ниже

// 修改原对象的age属性
你设置了age
新的age=20
你访问了age
20

// 设置新的属性
你设置了newPropKey
新的newPropKey=新属性
你访问了newPropKey
新属性

// 给obj的a属性(是个对象)设置属性d
你访问了a
你访问了a
这是obj中a的属性
// 备注:如果对象的属性是对象,需要返回一个新的Proxy
// 稍后会补充一下, 大家也可以先自己考虑一下, 欢迎讨论

PS: Добавьте пример использования Proxy для обработки многоуровневых объектов:How to create a Deep Proxy?

Видно, что вновь добавленные свойства не нуждаются в повторном добавлении адаптивной обработки, т.к.ProxyЭто операция над объектом, пока вы получаете доступ к объекту, вы перейдете кProxyв логике.

Reflect (представленный в ES6) — это встроенный объект, предоставляющий методы для перехвата операций JavaScript. Объект Object, очевидно, является частью внутреннего метода языка (например,Object.defineProperty()) вставитьReflectна объекте. Измените возвращаемые результаты некоторых методов объекта, чтобы сделать их более разумными. Пусть операции с объектами станут функциональным поведением. Просмотр определенного контентаMDN

Другие применения прокси

За исключением предстоящегоvue 3.0Кроме того, какие еще библиотеки используютсяProxyШерстяная ткань?

  • dobjs/dobЭто решение переписать mobx с помощью прокси.
  • immerРеализуйте неизменяемые типы данных. Подход Immer заключается в том, чтобы поддерживать состояние внутри, перехватывать все операции и внутренне оценивать, есть ли изменения, и, наконец, решать, как вернуться. Вы можете увидеть конкретное содержимое.Введение и анализ исходного кода immer.jsЭта статья.

Все используют перехват чтения и записи на объекте и делают некоторые дополнительные суждения и операции при чтении и записи.

Суммировать

  • Proxyиспользуется для манипулирования объектами,Object.defineProperty()Он используется для управления свойствами объектов.
  • vue2.xиспользоватьObject.defineProperty()Реализует отзывчивость данных, но из-заObject.defineProperty()Это операция над свойствами объекта, поэтому необходимо подробно изучить объект, чтобы работать со свойствами.
  • vue3.0использоватьProxyЭто перехват объекта, независимо от того, какая операция выполняется с объектом, он перейдет к логике обработки прокси.
  • vue3.0,dobjs/dob,immerВ настоящее время библиотека используетсяProxy, выполните перехват чтения и записи объекта и выполните некоторую дополнительную обработку.

Ссылаться на

разное

Недавно был запущен 100-дневный расширенный план, в основном для того, чтобы углубиться в принципы, лежащие в основе каждой точки знаний. Добро пожаловать в общедоступный аккаунт WeChat «Звезда Мусимы». Давайте учиться вместе и пробиться в течение 100 дней.