Идеальное объяснение принципов реактивного программирования Javascript

JavaScript Vue.js

Глубокое погружение в принципы адаптивности — 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 setdep.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()