Иностранный учебник You Yuxi: я возьму вас, чтобы написать простую версию Vue!

внешний интерфейс JavaScript Vue.js
Иностранный учебник You Yuxi: я возьму вас, чтобы написать простую версию Vue!

⚠️Эта статья является первой подписанной статьей сообщества Nuggets, перепечатка без разрешения запрещена.

предисловие

Много раз мы правы源码Выказал определенное желание, но на вопрос, зачем он хочет увидеть исходники, ответы были не более чем следующие:

  • для интервью
  • Для того, чтобы написать собственный исходный код в резюме
  • Понять основные принципы и изучить идеи мастеров
  • Изучите некоторые приемы через исходный код (Сан-операция)
  • Любопытно, как фреймворк реализует различные функции
  • Серьезная инволюция, не могу этого сделать, если не видишь, иди против течения
  • Я хочу построить колесо сам, давайте сначала посмотрим, как это сделают другие
  • Всевозможные паблики и продавцы курсов торгуют тревогой и промывкой мозгов

Но на самом деле мало кто действительно поймет исходный код: с одной стороны, слишком много кода, с другой стороны, когда мы читаем чужой код, легко запутаться. Поскольку стиль и привычки кодирования у всех очень разные, может быть очень утомительно смотреть на код человека, чьи привычки кодирования отличаются от ваших собственных.

Более того, не только стиль кодирования у каждого человека очень разный, но и техническое направление и технический уровень, в котором каждый человек хорош.横看成岭侧成峰,远近高低各不同. После устранения вышеуказанных причин более важной причиной является то, что многие люди недостаточно опытны, чтобы использовать фреймворк.APIЕсть только несколько общих, другие не часто используются, но очень высокого уровня.APIЯ им мало пользовался, и мне даже не нужно в нем разбираться.Конечно, такие люди будут сбиты с толку, когда посмотрят исходный код!

Тогда кто-то обязательно скажет: «Ты, Юси, должен использовать 6 для своего кадра?» Я использую его фреймворк для написания кода каждый день, и он не обязательно хорошо со мной разбирается!

В этом действительно есть доля правды, но когда дело доходит до сути, он понимает это лучше, чем кто-либо другой. Одна из важных причин, по которой мы не можем разжевывать исходный код, заключается в том, что в нем слишком много деталей, и каждый может легко упустить из виду ключевые моменты. Эти мелочи, естественно, имеют свои причины для своего существования, но они действительно стали для нас камнем преткновения при изучении исходного кода.

Не по теме

Как узнать исходный код наиболее научным способом? Давайте посмотрим на пример: есть некоторые высокотехнологичные продукты, которые звучат очень высоко, например,电磁轨道炮. Различные военные силы изо всех сил пытаются исследовать эту область, предположим, однажды мы проснемся как国家电磁轨道炮首席研究员, специализируется на исследовании базовой технологии электромагнитных рельсотронов. Тогда когда мы будем разбирать электромагнитный рельсотрон, есть большая вероятность, что вы не сможете понять его внутреннее устройство. Потому что он будет содержать много очень сложных高强度材料,控制磁力的电极,蜿蜒曲折的电线,提高精准度的装置и немного利于使用者操控的封装и т.д…

Тогда вам может быть нелегко понять电磁轨道炮的真正原理, пока я случайно не увидел видео в Интернете, человек в видео использовал несколько магнитов, несколько стальных шариков и несколько материалов, которые мы можем получить в нашей повседневной жизни, чтобы сделать简易版的电磁轨道炮. Так что мы можем понять сразу电磁轨道炮的真正原理, Хотя такая рельсовая пушка не может быть использована в реальном бою, пока мы понимаем самую основную часть, мы можем шаг за шагом расширяться на этой основе и постепенно понимать всю сложную рельсовую пушку, которую можно использовать в реальном бою. .

Исходный код тот же, следуем电磁轨道炮Идея идти шаг за шагом, сначала выяснить самые основные основные части, а затем постепенно продвигаться шаг за шагом. Такой метод обучения лучше, чем мы обязательно разберем полную версию, как только появится.电磁轨道炮гораздо сильнее

Раз у нас есть такая потребность, автор популярного фреймворка должен откликнуться: в ходе обучения,尤雨溪Веди всех, чтобы написать очень миниатюрныйVue3. К сожалению, это однодневный тренинг, который он провел за границей, и нашему отечественному зрителю не посчастливилось им насладиться.被框架作者培训такое учение. но к счастью尤雨溪已经把代码全部上传到了codepen上, вы можете нажатьэта ссылкаПриходите и прочитайте код, написанный Ю Юйси, или вы можете остаться в этой статье и посмотреть, как я объясню вам его на китайском языке.尤雨溪的亲笔代码!

Отзывчивые статьи

尤雨溪В прямом эфире он сказал:Vue3 的源码要比 Vue2 的源码要好学很多.Vue3По сравнению с дизайном архитектуры и взаимосвязью модулейVue2Лучше посмотреть модуль за модулем, так легче понять. Если вы только начинаете, вы можетеReactivityПосмотрите. так какReactivityэто всеVue3Модуль без связи с внешним миром.

Reactivityэто то, что мы всегда говорим响应式, известныйReactВ этом же и смысл, если не верите, внимательно сравните первые пять букв. Так что же такое отзывчивость? подумай об этомReactЧто такое рамки?MVVMПравильно?MVVMГлавный лозунг:

Представления, управляемые данными!

То есть, когда данные изменяются, мы повторно визуализируем компонент, так что после изменения данных место, где данные используются на странице, будет меняться в режиме реального времени. Но вы можете сделать больше, чем просто обновить представление при изменении данных! Юйси создает@vue/reactivityВ этом модуле ссылка@nx-js/observer-utilэта библиотека. Давайте посмотрим на этоGitHubначальствоREADME.mdФрагмент примера кода, показанный в:

import { observable, observe } from '@nx-js/observer-util';

const counter = observable({ num: 0 });
const countLogger = observe(() => console.log(counter.num));

// 这行代码将会调用 countLogger 这个函数并打印出:1
counter.num++;

это похожеVue3изreactiveиwatchEffectА? На самом деле мы определяем функцию заранее, и когда зависимые элементы данных в функции изменяются, эта функция будет автоматически выполняться, что является отзывчивым!

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

import { store, view } from 'react-easy-state';

const counter = store({
  num: 0,
  up() {
    this.num++;
  }
});

// 这是一个响应式的组件, 当 counter.num 发生变化时会自动重新渲染组件
const UserComp = view(() => <div onClick={counter.up}>{counter.num}</div>);

react-easy-stateЭто они(Библиотека, которую Ю Юйси позаимствовала) специально дляReactДля упаковки не трудно увидетьviewЭта функцияobserveвариант функции,observeОн просит вас передать функцию, и вы можете выполнить все, что хотите, в этой функции. иviewЭто вам передать в него компонент, и когда данные изменятся, он выполнит часть логики обновления, которую они написали заранее, так что он будет с вами.observeЭто то же самое, что написать в нем операцию обновления! написано с использованием этой библиотекиReactкак будто пишетVueТакой же.

исходный код

Разобравшись, что такое отзывчивость, нам удобно просмотреть исходный код, давайте посмотрим尤雨溪Как это сделать всего с дюжиной строк кода响应式:

let activeEffect

class Dep {
  subscribers = new Set()
  depend() {
    if (activeEffect) {
      this.subscribers.add(activeEffect)
    }
  }
  notify() {
    this.subscribers.forEach(effect => effect())
  }
}

function watchEffect(effect) {
  activeEffect = effect
  effect()
}

После завершения реализации давайте посмотрим, как его использовать:

const dep = new Dep()

let actualCount = 0
const state = {
  get count() {
    dep.depend()
    return actualCount
  },
  set count(newCount) {
    actualCount = newCount
    dep.notify()
  }
}

watchEffect(() => {
  console.log(state.count)
}) // 0

state.count++ // 1

Если вы чувствуете себя сбитым с толку, наблюдая за этими десятком-двадцатью строками кода, значит, ваш фундамент не очень хорош. Поскольку проницательный человек может увидеть это с первого взгляда, это очень классический шаблон дизайна:发布-订阅模式

модель публикации-подписки

Если вы не знаете发布-订阅模式Если да, то мы можем кратко рассказать об этом. Но если вы уже хорошо знаете эти шаблоны проектирования и можете легко прочитать код прямо сейчас, рекомендуется пока пропустить этот раздел.

существует《JavaScript设计模式与开发实践》книга, автор曾探за发布-订阅模式Вот очень яркий пример:

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

Поэтому Сяо Мин записал номер телефона офиса продаж и будет звонить каждый день, чтобы спросить, не пора ли покупать. Помимо Xiao Ming, Xiaohong, Xiaoqiang и Xiaolong также будут ежедневно консультироваться с офисом продаж по этому вопросу. Через неделю менеджер по продажам решил уволиться, потому что устал отвечать на 1000 звонков с одним и тем же содержанием каждый день.

Конечно, такой дурацкой компании по продажам в реальности не существует.На самом деле история такова: Перед уходом Сяо Мин оставил свой номер телефона в офисе продаж. Менеджер по продажам пообещал ему, что он отправит сообщение, чтобы уведомить Сяо Мина, как только новая недвижимость будет запущена. То же самое верно для Xiaohong, Xiaoqiang и Xiaolong. Все их номера телефонов записаны в реестре офиса продаж. Когда новый объект запускается, менеджер по продажам переворачивает список, проходит по указанным выше номерам телефонов и отправляет текстовое сообщение в свою очередь. Уведомить их.

В только что приведенном примере отправка SMS-уведомлений является типичной моделью публикации-подписки.Покупатели, такие как Сяомин и Сяохун, все являются подписчиками, и они подписываются на новости о продаже дома. Как издатель, отдел продаж будет в нужное время просматривать телефонные номера в списке и по очереди отправлять сообщения покупателям.

если вы когда-либо использовалиxxx.addEventListenerЭта функцияDOMЕсли событие добавлено, то оно фактически используется.发布-订阅模式Ла! Подумайте, похоже ли это на этот пример в офисе продаж:

  • Нам нужно что-то делать при определенных условиях
  • Но чего мы не знаем, так это того, когда это условие будет выполняться
  • Итак, мы покидаем нашу функцию
  • Автоматически выполняться при соблюдении условий

Итак, давайте просто смоделируемaddEventListenerчто случилось, чтобы все могли понять发布-订阅模式:

class DOM {
    #eventObj = {
        click: [],
        mouseover: [],
        mouseout: [],
        mousemove: [],
        keydown: [],
        keyup: []
        // 还有很多事件类型就不一一写啦
    }
    addEventListener (event, fn) {
        this.#eventObj[event].push(fn)
    }
    removeEventListener (event, fn) {
        const arr = this.#eventObj[event]
        const index = arr.indexOf(fn)
        arr.splice(index, 1)
    }
    click () {
        this.#eventObj.click.forEach(fn => fn.apply(this))
    }
    mouseover () {
        this.#eventObj.mouseover.forEach(fn => fn.apply(this))
    }
    // 还有很多事件方法就不一一写啦
}

Давайте попробуем:

const dom = new DOM()

dom.addEventListener('click', () => console.log('点击啦!'))
dom.addEventListener('click', function () { console.log(this) })

dom.addEventListener('mouseover', () => console.log('鼠标进入啦!'))
dom.addEventListener('mouseover', function () { console.log(this) })

// 模拟点击事件
dom.click() // 依次打印出:'点击啦!' 和相应的 this 对象

// 模拟鼠标事件
dom.mouseover() // 依次打印出:'鼠标进入啦!' 和相应的 this 对象

const fn = () => {}
dom.addEventListener('click', fn)
// 还可以移除监听
dom.removeEventListener('click', fn)

Этот простой случай должен быть в состоянии понять发布-订阅模式правильно?

Давайте процитируем《JavaScript设计模式与开发实践》за发布-订阅模式Подведем итоги по трем основным моментам:

  1. Сначала укажите, кто будет выступать в роли издателя (например, офис продаж)在本例中是 dom 这个对象
  2. Затем добавьте к издателю кеш-лист для хранения callback-функции для уведомления подписчика (ростер офиса продаж)在本例中是 dom.#eventObj
  3. Когда сообщение, наконец, опубликовано, издатель будет проходить список кеша и активировать функцию обратного вызова подписчика, хранящуюся в нем, в свою очередь (обходить список и отправлять текстовые сообщения одно за другим).

Запомнив эти три пункта, еще раз взгляните на большой код, чтобы увидеть, соответствует ли он этим трем пунктам:

  • 发布者: отложенный объект
  • 缓存列表: отд.подписчиков
  • 发布消息: отп.уведомить()

Так что это типичный发布-订阅模式

Расширенное издание

尤雨溪Реализация первой версии кода все еще слишком рудиментарна, и ее очень неудобно использовать поначалу, потому что нам нужно писать ее вручную каждый раз, когда мы определяем данныеgetterиsetter, Вручную выполните функцию сбора зависимостей и сработавшую функцию. Эта часть, очевидно, может продолжать инкапсулироваться, так что взгляните еще раз尤雨溪Реализована вторая версия:

let activeEffect

class Dep {
  subscribers = new Set()
  depend() {
    if (activeEffect) {
      this.subscribers.add(activeEffect)
    }
  }
  notify() {
    this.subscribers.forEach(effect => effect())
  }
}

function watchEffect(effect) {
  activeEffect = effect
  effect()
  activeEffect = null
}

function reactive(raw) {
  // 使用 Object.defineProperty
  // 1. 遍历对象上存在的 key
  Object.keys(raw).forEach(key => {
    // 2. 为每个 key 都创建一个依赖对象
    const dep = new Dep()

    // 3. 用 getter 和 setter 重写原对象的属性
    let realValue = raw[key]
    Object.defineProperty(raw, key, {
      get() {
        // 4. 在 getter 和 setter 里调用依赖对象的对应方法
        dep.depend()
        return realValue
      },
      set(newValue) {
        realValue = newValue
        dep.notify()
      }
    })
  })
  return raw
}

Видно, что эта версия намного лучше предыдущей, и по ощущениям尤雨溪Эта версия кода была написана более тщательно, чем предыдущая версия. Поскольку эта версия кода содержит подробные комментарии, это должен быть тщательно объясненный фрагмент кода. Просто исходные заметки все были написаны на английском языке, а я перевел их на китайский.

Но будьте уверены, все, кроме комментариев, которые я перевел на китайский язык, я не тронул ни одной буквы в других местах, даже пробелы сохранены с отступом, чтобы все могли видеть да尤雨溪的一手代码😋

Нетрудно заметить, что эта версия кода использует в реализации два шаблона проектирования, они代理模式и что мы только что сказали发布-订阅模式. Так что учись хорошоШаблоны проектированияКакая важная вещь. если правильноШаблоны проектированияЕсли вам интересно, вы можете пойтиСтанция Бпоискпредварительное обучение, сейчас загружаетсяШаблоны проектированияЯ лично считаю, что он продается лучше, чем MOOC.com288Класс JavaScript Design Patterns гораздо понятнее.

прокси-режим

Режим прокси относительно прост, код не требуется, заимствован《JavaScript设计模式核⼼原理与应⽤实践》автор修言Очень интересный пример даст понять всем:

У меня есть коллега, очень техничный, с красивой прической. На протяжении многих лет из-за моего пристрастия к программированию я откладывал важные события в своей жизни. Желание найти вторую половинку более острое, коллега также является зарегистрированным VIP на нескольких качественных сайтах знакомств высокого класса. Вне работы он часто делится с нами недавнимисвидание в слепуюЭмоциональный жизненный прогресс.

«Посмотрите, разве аватарка этой девушки не супер милая!» В тот день мой коллега обнаружил новое агентство знакомств, поднял свой мобильный телефон и отчаянно помахал им немногим вокруг него.

"Брат, это Юи Арагаки..." Сосед по парте моего коллеги беспомощно покачал головой, не останавливая кодирующую руку.

Коллега восстановил свое спокойствие и вздохнул: «Механизм этой платформы брака и любви настолько строг, что когда вы входите, вы можете видеть только имена, возраст и представление других участников. собственные фотографии или получить контактную информацию другой стороны, вы должны сначала заплатить платформе, чтобы стать VIP. Эй, я должен снова купить VIP ».

Как только я это услышал, вау, эта платформа о браке и любви играет в прокси-режиме довольно 6! Вдумайтесь, объект — коллега А, а цель — неизвестная девушка с аватаром Юи Арагаки. Коллега А не может напрямую общаться с неизвестной девушкой, а может получить некоторую информацию о другой стороне только косвенно через третье лицо (агентство знакомств). не типичный режим прокси?

использование

Эта версия отзывчивости намного удобнее в использовании:

const state = reactive({
  count: 0
})

watchEffect(() => {
  console.log(state.count)
}) // 0

state.count++ // 1

В основном так же, какVue3Использование точно такое же! Можно видеть, что основной принцип отзывчивости на самом деле发布-订阅+代理模式. Это не окончательная версия, потому что он используетES5изObject.definePropertyсделать代理模式, если не учитывать совместимостьIEвсе ещеES6изProxyбольше подходит для代理,так какProxyпереведи это на代理权,代理人значение. такVue3УсыновленныйProxyЧтобы реорганизовать весь реактивный код, давайте посмотрим尤雨溪Окончательный вариант написан(ProxyВерсия)

Прокси-версия

let activeEffect

class Dep {
  subscribers = new Set()

  constructor(value) {
    this._value = value
  }

  get value() {
    this.depend()
    return this._value
  }

  set value(value) {
    this._value = value
    this.notify()
  }

  depend() {
    if (activeEffect) {
      this.subscribers.add(activeEffect)
    }
  }

  notify() {
    this.subscribers.forEach((effect) => {
      effect()
    })
  }
}

function watchEffect(effect) {
  activeEffect = effect
  effect()
  activeEffect = null
}

// proxy version
const reactiveHandlers = {
  get(target, key) {
    const value = getDep(target, key).value
    if (value && typeof value === 'object') {
      return reactive(value)
    } else {
      return value
    }
  },
  set(target, key, value) {
    getDep(target, key).value = value
  }
}

const targetToHashMap = new WeakMap()

function getDep(target, key) {
  let depMap = targetToHashMap.get(target)
  if (!depMap) {
    depMap = new Map()
    targetToHashMap.set(target, depMap)
  }

  let dep = depMap.get(key)
  if (!dep) {
    dep = new Dep(target[key])
    depMap.set(key, dep)
  }

  return dep
}

function reactive(obj) {
  return new Proxy(obj, reactiveHandlers)
}

Вы можете видеть, что эта версия кода немного сложнее, чем предыдущая версия, но использование точно такое же, как и в предыдущей версии:

const state = reactive({
  count: 0
})

watchEffect(() => {
  console.log(state.count)
}) // 0

state.count++ // 1

Давайте сосредоточимся на финальной версии кода, эта версия кода самая优秀из. Хоть воробей и маленький, но у него есть все внутренние органы, не только самые основные发布-订阅模式+代理模式, а также использовали множество мелких хитростей для оптимизации производительности.

Детальное объяснение

Прежде всего, Юда определяетactiveEffectпустая переменная для храненияwatchEffectФункция передана:

// 定义一个暂时存放 watchEffect 传进来的参数的变量
let activeEffect

Далее определитеDepкласс, этоDepдолжно бытьDependenceсокращение от依赖. на самом деле эквивалентно发布-订阅模式Класс издателя в:

// 定义一个 Dep 类,该类将会为每一个响应式对象的每一个键生成一个发布者实例
class Dep {
  // 用 Set 做缓存列表以防止列表中添加多个完全相同的函数
  subscribers = new Set()

  // 构造函数接受一个初始化的值放在私有变量内
  constructor(value) {
    this._value = value
  }

  // 当使用 xxx.value 获取对象上的 value 值时
  get value() {
    // 代理模式 当获取对象上的value属性的值时将会触发 depend 方法
    this.depend()

    // 然后返回私有变量内的值
    return this._value
  }

  // 当使用 xxx.value = xxx 修改对象上的 value 值时
  set value(value) {
    // 代理模式 当修改对象上的value属性的值时将会触发 notify 方法
    this._value = value
    // 先改值再触发 这样保证触发的时候用到的都是已经修改后的新值
    this.notify()
  }

  // 这就是我们常说的依赖收集方法
  depend() {
    // 如果 activeEffect 这个变量为空 就证明不是在 watchEffect 这个函数里面触发的 get 操作
    if (activeEffect) {
      // 但如果 activeEffect 不为空就证明是在 watchEffect 里触发的 get 操作
      // 那就把 activeEffect 这个存着 watchEffect 参数的变量添加进缓存列表中
      this.subscribers.add(activeEffect)
    }
  }

  // 更新操作 通常会在值被修改后调用
  notify() {
    // 遍历缓存列表里存放的函数 并依次触发执行
    this.subscribers.forEach((effect) => {
      effect()
    })
  }
}

В предыдущих двух редакциях переменная определялась снаружи для сохранения响应式对象Значение, соответствующее каждому ключу, и на этот раз значение напрямую помещается вDepВ определении класса он определяется какgetterиsetter, операция сбора зависимостей выполняется при извлечении значения, а операция обновления выполняется при изменении значения.

Далее определитеVue3изwatchEffectФункция с таким же названием:

// 模仿 Vue3 的 watchEffect 函数
function watchEffect(effect) {
  // 先把传进来的函数放入到 activeEffect 这个变量中
  activeEffect = effect

  // 然后执行 watchEffect 里面的函数
  effect()

  // 最后把 activeEffect 置为空值
  activeEffect = null
}

Разве мы не передаем другую функцию в эту функцию, когда используем ее:

watchEffect(() => state.xxx)

Эта функция возложена наactiveEffectПерейдите к этой переменной, а затем немедленно выполните функцию.Вообще говоря, эта функция будет иметь некоторые响应式对象правильно? Если есть, он сработаетgetterПерейдите к операции сбора зависимостей, и коллекция зависимостей должна судитьactiveEffectИмеет ли эта переменная значение, и если да, то добавьте его в список кеша. После выполнения этой функции сразуactiveEffectЭта переменная имеет нулевое значение, чтобы предотвратитьwatchEffectсрабатывает в этой функцииgetterОперация сбора зависимостей также выполняется, когда .

Далее следует определитьProxyОбъект обработки прокси:

const reactiveHandlers = {
  // 当触发 get 操作时
  get(target, key) {
    // 先调用 getDep 函数取到里面存放的 value 值
    const value = getDep(target, key).value

    // 如果 value 是对象的话
    if (value && typeof value === 'object') {
      // 那就把 value 也变成一个响应式对象
      return reactive(value)
    } else {
      // 如果 value 只是基本数据类型的话就直接将值返回
      return value
    }
  },
  // 当触发 set 操作时
  set(target, key, value) {
    // 调用 getDep 函数并将里面存放的 value 值重新赋值成 set 操作的值
    getDep(target, key).value = value
  }
}

если правильноProxyЕсли вы мало что знаете об этом, я предлагаю прочитать книгу Жуань Ифэна.«Вводный курс по ES6», письмо все еще хорошо.

этот объект был простоgetиsetиспользуется в эксплуатацииgetDepЭта функция, которая будет определена позже, будет использовать функцию с именемtargetToHashMapизWeakMapСтруктуры данных для хранения данных:

// 定义一个 WeakMap 数据类型 用于存放 reactive 定义的对象以及他们的发布者对象集
const targetToHashMap = new WeakMap()

Далее идет определениеgetDepфункция:

// 定义 getDep 函数 用于获取 reactive 定义的对象所对应的发布者对象集里的某一个键对应的发布者对象
function getDep(target, key) {
  // 获取 reactive 定义的对象所对应的发布者对象集
  let depMap = targetToHashMap.get(target)

  // 如果没获取到的话
  if (!depMap) {
    // 就新建一个空的发布者对象集
    depMap = new Map()
    // 然后再把这个发布者对象集存进 WeakMap 里
    targetToHashMap.set(target, depMap)
  }

  // 再获取到这个发布者对象集里的某一个键所对应的发布者对象
  let dep = depMap.get(key)

  // 如果没获取到的话
  if (!dep) {
    // 就新建一个发布者对象并初始化赋值
    dep = new Dep(target[key])
    // 然后将这个发布者对象放入到发布者对象集里
    depMap.set(key, dep)
  }

  // 最后返回这个发布者对象
  return dep
}

Это место немного в обход, давайте сделаем снимок выше:

каждый входящийreactiveОбъекты, которые входят, будут существоватьWeakMapключ в. Значение, соответствующее каждому ключу, представляет собойMap:

                                    //     targetToHashMap: {
const obj1 = reactive({ num: 1 })   //        { num: 1 }: new Map(),
const obj2 = reactive({ num: 2 })   //        { num: 2 }: new Map(),
const obj3 = reactive({ num: 3 })   //        { num: 3 }: new Map()
                                    //     }

это значение (Map) что в нем хранится? Что сохраняется:

WX20210729-192101.png

Предположим, мыreactiveобъект{ a: 0, b: 1, c: 2 },ТакMapВнутри находятся:

{
  'a': new Dep(0),
  'b': new Dep(1),
  'c': new Dep(2)
}

заключается в том, чтобы поместить ключ объекта вMapключ, затем используйтеnew DepСоздайте объект издателя и передайте значение вDep. Один из очень важных моментов оптимизации, который делает Vue3 намного сильнее, чем Vue2, заключается в следующем.Proxy. не тоProxyпроизводительность, чемObject.definePropertyНасколько выше, но сказать вProxyСпособ обработки вVue2Времена намного лучше:Vue2Отзывчивость - это еда, как только она появляется遍历+递归Сделайте все данные, которые вы определяете, отзывчивыми, что приведет к тому, что если на странице много сложных структур данных, используйтеVue2Написанная страница будет пустой в течение короткого времени. после всего遍历+递归Все еще относительно медленная операция!

иReactтакой проблемы конечно нетVue3Там не будет этого неправильно. Как видно из кода, когда мы получаем значение ключа, соответствующего объекту, сначала он определяет значение в конце, нет соответствующих объектов издателя, если не повторно создать объекты издателя. И когда полученное значение, то это значение становится ссылочным типом响应式对象, а затем создайте новый объект издателя при использовании значения в реактивном объекте.

Если резюмировать одним предложением:Vue3Какая часть данных используется, а затем данные становятся чувствительными. иVue2Это означает, что независимо от того, будет ли это 3721, все это станет для вас ответными данными в начале игры.

Последний шаг – определитьreactiveфункция:

// 模仿 Vue3 的 reactive 函数
function reactive(obj) {
  // 返回一个传进来的参数对象的代理对象 以便使用代理模式拦截对象上的操作并应用发布-订阅模式
  return new Proxy(obj, reactiveHandlers)
}

блок-схема

Для того, чтобы всем было легче понять, мы снова используем егоreactiveиwatchEffectфункция, и, кстати, посмотрите, что происходит:

WX20210730-132843.png

Сначала мы используемreactiveфункция определяет объект{ num: 0 }, этот объект будет переданProxyПервый параметр , в это время ничего не произошло, то мы находимся вwatchEffectраспечатать этот объектnumАтрибуты:

WX20210730-133331.png

в это время кwatchEffectЭта функция будет возложена наactibveEffectЭта переменная идет вверх, а затем функция выполняется немедленно:

WX20210730-133925.png

В ходе исполнения выяснилось, чтоgetоперация, поэтомуProxyперехватил, пошелgetЭтот шаг:

WX20210730-140428.png

из-заgetтребуется в эксплуатацииgetDepфункция, поэтому{ num: 0 }перешел кgetDep, ключ числовой, поэтому он эквивалентенgetDep({ num: 0 }, 'num'). ВходитьgetDepВ теле функции вам нужно использоватьtargetToHashMapполучить{ num: 0 }Значение, соответствующее этому ключу, но в настоящее времяtargetToHashMapпуст, поэтому вообще ничего не извлекается. Итак, введите приговор, создайте новыйMapназначить вtargetToHashMap, что эквивалентно:targetToHashMap.set({ num: 0 }, new Map()), а затем получить этоMapизkeyСоответствующее значение:

WX20210730-141344.png

так какMapОн также пуст, поэтому я все еще не могу получить значение, поэтому ввожу суждение и создаю новое.DepОбъект:

WX20210730-141809.png

Поскольку он используетсяgetDep(...xxx).valueполучить объектvalueсвойство, поэтому оно вызоветgetter:

WX20210730-142346.png

следитьgetterздесь мы сноваdependметод, так какactiveEffectЕсть ценность, так что вступай в суждение, ставьactiveEffectпринять участие вsubscribesэтоSetв структуре. На этом часть сбора зависимостей подошла к концу, и тогда мы изменимobj.numзначение, чтобы увидеть, что происходит:

WX20210730-144029.png

будет первымProxyперехватыватьsetоперации, затем позвонитеgetDepфункция:

WX20210730-143810.png

получитьdepобъект, он изменяет свойvalueсвойство, вызывающееsetterработать:

WX20210730-144603.png

Наконец доходим до уведомления(notify) мы найдем наш список кэшей (subscribers), а затем по очереди запускать функции внутри:

WX20210730-144912.png

тогда он будет работать() => console.log(obj.num)Как вы думаете, эта функция закончилась? конечно, нет! из-за бегаobj.numЭта операция, поэтому она снова сработаетgetоперацияProxyПерехват:

WX20210730-145721.png

После получения объекта издателя, который мы создали ранее, он активирует объект издателя.getterработать:

WX20210730-150316.png

обойти, обойтиdependметод, нам нужно проверитьactiveEffectэта переменная:

WX20210730-150635.png

Так как в суд не войду, казню одиночество(Ничего не сделал), то следующий код:

WX20210730-151831.png

наконец-то распечатано10.

Эпилог

Я не ожидал, что семьдесят или около того строк кода будут такими извилистыми, верно? Так насколько же важен метод обучения забою коконов. Если посмотреть исходный код напрямую, в нем обязательно будут различные суждения. НапримерwatchEffectНе вынося никаких суждений сейчас, верно? тогда, когда мы даемwatchEffectЧто происходит, когда передается параметр, не являющийся функцией? когда мы даемreactiveЧто происходит, когда объекту передается массив? когда биографияMap,Setкогда? Как насчет передачи базовых типов данных? И даже если мы сейчас не рассматриваем эти ситуации, просто передаем объект без массивов и прочего в нем,watchEffectТакже только функции передачи. Так что на самом деле есть еще немного опыта использованияVue3изwatchEffectдругое место, то есть не вwatchEffectВнутри измените значение реактивного объекта:

WX20210730-152653.png

И нет никакой проблемы написать это так:

WX20210730-152902.png

но вVue3изwatchEffectНе будет такой ситуации. Это потому, что еслиwatchEffectЕсли операция присваивания выполняется над реагирующим объектом, она снова запустится.setоперации, тем самымProxyперехватить, а затем объехатьnotifyМетод выше пошел,notifyбудет сноваwatchEffectФункция в нем запускается снова, и оказывается, что естьsetработать(Потому что это тот же код), а потом бежатьnotifyметод, продолжайте запускатьsetОперация вызывает бесконечный цикл.

Так что нам тоже нужно рассмотреть ситуацию с этим бесконечным циклом, но если уж так всесторонне рассматривать, то я полагаю, что объем кода довольно большой, и мы еще больше запутаемся. Итак, сначала разберитесь с этим кодом, а затем постепенно давайте посмотрим, как реальный исходный код обрабатывает эти ситуации. Или вы также можете подумать, как справиться с этими проблемами, не заглядывая сначала в исходный код, затем написать свою логику, а затем выполнить тест, если проблемы нет.Vue3Сравните его с исходным кодом .

На этом статья подходит к концу, но это не конец, просто响应式部分. после этого虚拟DOM,diff算法,组件化,根组件挂载и так далее.

Если вам не терпится прочитать следующую аналитическую статью, вы также можете напрямуюнажмите на эту ссылкуВходитьcodepenсамообучение尤雨溪написанный код. Объем кода очень маленький, учимсяVue3принцип绝佳资料! Изучив принцип, даже если вы не смотрите настоящий исходный код, вы можете поговорить с интервьюером во время интервью. Ведь ни один интервьюер при изучении исходного кода не спросит: Скажи мнеVue3такого-то документа996Какая строчка кода написана? Исследование также должно быть сосредоточено на принципе и редко изучать граничные условия различных параметров суждения. так点赞+关注, с последующим尤雨溪Изучите исходный код, не теряясь!

Замечательные статьи в прошлом