Глубокое погружение в принципы адаптивности — Vue.js
https://medium.com/vue-mastery/the-best-explanation-of-javascript-reactivity-fea6112dd80d
https://www.vuemastery.com/courses/advanced-components/evan-you-on-proxies/
Многие интерфейсные фреймворки JavaScript, включая, помимо прочего, (Angular, React, Vue), имеют свои собственные адаптивные движки. Понимая принцип отзывчивости и его конкретную реализацию, можно создавать эффективные приложения для существующих адаптивных фреймворков.
Отзывчивая система
Давайте посмотрим на систему реактивности Vue:
<div id="app">
<div>Price: ${{ price }}</div>
<div>Total: ${{ price * quantity }}</div>
<div>Taxes: ${{ totalPriceWithTax }}</div>
<div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
var vm = new Vue({
el: '#app',
data: {
price: 5.00,
quantity: 2
},
computed: {
totalPriceWithTax() {
return this.price * this.quantity * 1.03
}
}
})
</script>
Vue знает, что всякий раз, когдаpriceКогда происходит преобразование, оно делает три вещи:
- обновление на странице
priceценность . - Пересчитать на странице
price * quantityвыражение и обновление. - передача
totalPriceWithTaxметод и обновление.
Но подождите, здесь есть некоторая путаница, откуда Vue знаетpriceПосле обновления как отследить конкретный процесс обновления?
Как реализована отзывчивость в нативном JavaScript?Начнем с объявления переменных~
let price = 5
let quantity = 2
let total = price * quantity // 10 ?
price = 20
console.log(`total is ${total}`) // 10
// 我们想要的 total 值希望是更新后的 40
⚠️ Вопросы
мы должныtotalпроцесс вычисления , так что мы можемpriceилиquantityЗапустите процесс расчета, когда он изменится.
✅ Решения
Во-первых, нам нужно сообщить приложению: «Вот метод расчета, сохраните его, и я запущу его, когда данные будут обновлены».
Мы создаем функцию ведения журнала и запускаем ее:
let price = 5
let quantity = 2
let total = 0
let target = null
target = () => { total = price * quantity }
record() // 记录我们想要运行的实例
target() // 运行 total 计算过程
просто определитьrecord:
let storage = [] // 将 target 函数存在这里
function record () { // target = () => { total = price * quantity }
storage.push(target)
}
мы хранимtargetфункция, нам нужно запустить ее, нам нужно возглавитьreplayfunction для запуска функции, которую мы записали:
function replay () {
storage.forEach( run => run() )
}
В коде мы запускаем:
price = 20
console.log(total) // => 10
replay()
console.log(total) // => 40
Достаточно ли просто, чтобы код был читабельным и его можно было запускать несколько раз. К вашему сведению, здесь используется самый простой способ кодирования, сначала грамотность.
let price = 5
let quantity = 2
let total = 0
let target = null
let storage = []
target = () => { total = price * quantity }
function record () {
storage.push(target)
}
function replay () {
storage.forEach( run => run() )
}
record()
target()
price = 20
console.log(total) // => 10
replay()
console.log(total) // => 40
объективация
⚠️ Вопросы
Мы можем продолжать записывать то, что нам нужноtarget, и выполнитьrecord, но нам нужны более надежные шаблоны для масштабирования нашего приложения. Возможно, объектно-ориентированный способ может поддерживать список целей, и мы используем метод уведомления для обратного вызова.
✅ Класс зависимости схемы
Мы инкапсулируем поведение через собственный класс, стандартный зависимый классDependency Class, который реализует шаблон Observer.
Если мы хотим создать класс, управляющий зависимостями, стандартный шаблон выглядит следующим образом:
class Dep { // Stands for dependency
constructor () {
this.subscribers = [] // 依赖数组,当 notify() 调用时运行
}
depend () {
if (target && !this.subscribers.includes(target)) {
// target 存在并且不存在于依赖数组中,进行依赖注入
this.subscribers.push(target)
}
}
notify () { // 替代之前的 replay 函数
this.subscribers.forEach(sub => sub()) // 运行我们的 targets,或者观察者函数
}
}
Обратите внимание на ранее замененный метод,storageзаменен конструкторомsubscribers.recodфункция заменена наdepend.replayфункция заменена наnotify.
Работает сейчас:
const dep = new Dep()
let price = 5
let quantity = 2
let total = 0
let target = () => { total = price * quantity }
dep.depend() // 依赖注入
target() // 计算 total
console.log(total) // => 10
price = 20
console.log(total) // => 10
dep.notify()
console.log(total) // => 40
Работает нормально, когда кажется странным добраться до этой точки, настроить и запуститьtargetМесто.
наблюдатель
⚠️ Вопросы
Затем мы хотим иметь возможность применять класс Dep к каждой переменной и изящно наблюдать за обновлениями через анонимные функции. может понадобиться наблюдательwatcherфункция, удовлетворяющая такому поведению.
Код, который нам нужно заменить:
target = () => { total = price * quantity }
dep.depend()
target()
Заменить:
watcher( () => {
total = price * quantit
})
✅ Решение Функция наблюдателя
Давайте сначала определим простую функцию наблюдателя:
function watcher (myFunc) {
target = myFunc // 动态配置 target
dep.depend() // 依赖注入
target() // 回调 target 方法
target = null // 重置 target
}
Как вы видете,watcherфункция проходит вmyFuncпараметры, настроить глобальные переменныеtarget,передачаdep.depend()Внедрение зависимостей, обратный вызовtargetметод, наконец, сбросtarget.
Запустить его:
price = 20
console.log(total) // => 10
dep.notify()
console.log(total) // => 40
Вы можете спросить, почему мы хотим использовать глобальную переменнуюtarget
Dep classDepproperties
let data = { price: 5, quantity: 2 }
watcher( () => {
total = data.price * data.quantit
})
data.pricepricequantity
data.priceprice
dep.notify()priceprice
$ total
10
$ price = 20 // 回调 notify() 函数
$ total
40
Object.defineProperty() - JavaScript | MDN
let data = { price: 5, quantity: 2 }
Object.defineProperty(data, 'price', { // 仅定义 price 属性
get () { // 创建一个 get 方法
console.log(`I was accessed`)
},
set (newVal) { // 创建一个 set 方法
console.log(`I was changed`)
}
})
data.price // 回调 get()
// => I was accessed
data.price = 20 // 回调 set()
// => I was changed
getsetvalueget()set()internalValueprice
let data = { price: 5, quantity: 2 }
let internalValue = data.price // 初始值
Object.defineProperty(data, 'price', { // 仅定义 price 属性
get () { // 创建一个 get 方法
console.log(`Getting price: ${internalValue}`)
return internalValue
},
set (newVal) { // 创建一个 set 方法
console.log(`Setting price: ${newVal}`)
internalValue = newVal
}
})
total = data.price * data.quantity // 回调 get()
// => Getting price: 5
data.price = 20 // 回调 set()
// => Setting price: 20
Object.keys(data)
let data = { price: 5, quantity: 2 }
Object.keys(data).forEach(key => {
let internalValue = data[key]
Object.defineProperty(data, key, {
get () {
console.log(`Getting ${key}: ${internalValue}`)
return internalValue
},
set (newVal) {
console.log(`Setting ${key}: ${newVal}`)
internalValue = newVal
}
})
})
total = data.price * data.quantity
// => Getting price: 5
// => Getting quantity: 2
data.price = 20
// => Setting price: 20
total = data.price * data.quantity
pricepricepriceset
-
Get
-
Set
-
Price accessed (get)
dep.depend()target -
Price set
dep.notify()targets
let data = { price: 5, quantity: 2 }
let target = null
class Dep { // Stands for dependency
constructor () {
this.subscribers = [] // 依赖数组,当 notify() 调用时运行
}
depend () {
if (target && !this.subscribers.includes(target)) {
// target 存在并且不存在于依赖数组中,进行依赖注入
this.subscribers.push(target)
}
}
notify () { // 替代之前的 replay 函数
this.subscribers.forEach(sub => sub()) // 运行我们的 targets,或者观察者函数
}
}
// 遍历数据的属性
Object.keys(data).forEach(key => {
let internalValue = data[key]
// 每个属性都有一个依赖类的实例
const dep = new Dep()
Object.defineProperty(data, key, {
get () {
dep.depend()
return internalValue
},
set (newVal) {
internalValue = newVal
dep.notify()
}
})
})
// watcher 不再调用 dep.depend
// 在数据 get 方法中运行
function watcher (myFunc) {
target = myFunce
target()
target = null
}
watcher(()=> {
data.total = data.price * data.quantity
})
data.total
// => 10
data.price = 20
// => 20
data.total
// => 40
data.quantity = 3
// => 3
data.total
// => 60
pricequantity
watchernotifies
- Dep class
- watcher
- Object.defineProperty()