(Рекомендуемая коллекция) Вид от первого лица перенесет вас в мир исходного кода Vue.

внешний интерфейс JavaScript Vue.js
(Рекомендуемая коллекция) Вид от первого лица перенесет вас в мир исходного кода Vue.

предисловие

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

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

Начните с нового экземпляра Vue

Готов к работе:хром открытыйVue адрес гитхаба

> cd 「你的路径」
> git clone https://github.com/vuejs/vue.git
> code vue (ps: 此命令为用VS code 打开 vue 项目)

Взгляните на структуру каталогов проекта vue:

image.png

Вау, структура очень четкая! Кажется, необъяснимый лес такой сложный (психологическая конструкция для анализа исходного кода)

Установить подачу глобально:

> npm i -g serve
Or
> yarn global add serve
> serve .

So you shoud open in: localhost:5000

image.png

Эй~ Структура каталогов проекта визуализируется в браузере.

нажмитеexamples, затем найдитеmarkdown, на данный момент ваш URLhttp://localhost:5000/examples/markdown/

Вау, он готов к игре. Например:

image.png

Эй~ Напишите синтаксис уценки слева и покажите его в реальном времени справа? Нет, это не важно. Главное код!

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Vue.js markdown editor example</title>
    <link rel="stylesheet" href="style.css">
    <script src="https://unpkg.com/marked@0.3.6"></script>
    <script src="https://unpkg.com/lodash@4.16.0"></script>
    <!-- Delete ".min" for console warnings in development -->
    <script src="../../dist/vue.min.js"></script>
  </head>
  <body>

    <div id="editor">
      <textarea :value="input" @input="update"></textarea>
      <div v-html="compiledMarkdown"></div>
    </div>

    <script>
      new Vue({
        el: '#editor',
        data: {
          input: '# hello'
        },
        computed: {
          compiledMarkdown: function () {
            return marked(this.input, { sanitize: true })
          }
        },
        methods: {
          update: _.debounce(function (e) {
            this.input = e.target.value
          }, 300)
        }
      })
    </script>

  </body>
</html>

Внимательно посмотрите на наши теги script,new Vue({...})Что-то начинает происходить?

Снова

 <!DOCTYPE html>
<html lang="en">
  <head>
    ...
  </head>
  <body>
    <!-- template for the modal component -->
    <script type="text/x-template" id="modal-template">
    ...
    </script>

    <!-- app -->
    <div id="app">
      <button id="show-modal" @click="showModal = true">Show Modal</button>
      <!-- use the modal component, pass in the prop -->
      <modal v-if="showModal" @close="showModal = false">
        <!--
          you can use custom content here to overwrite
          default content
        -->
        <h3 slot="header">custom header</h3>
      </modal>
    </div>

    <script>
      // register modal component
      Vue.component('modal', {
        template: '#modal-template'
      })

      // start app
      new Vue({
        el: '#app',
        data: {
          showModal: false
        }
      })
    </script>
  </body>
</html>

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

image.png

Теперь вы удаляете тег в скрипте шаблона для модала и нажимаете кнопку на странице.

image.pngНа странице не появляется всплывающее окно, и консоль не выводит никаких сообщений об ошибках. Точно так же, если вы убьете регистрацию глобального компонента Vue, результатом будет то, что содержимое страницы изменилось, но всплывающее окно по-прежнему отсутствует.

image.png

Итак, исходя из вышеизложенных попыток, можно резюмировать следующие моменты:

  • Страница — это экземпляр Vue. потому что есть стандартnewграмматика
  • Если страница хочет использовать другие компоненты (компоненты вне кода приложения), ее необходимо сначала зарегистрировать.
  • Тег script на самом деле записывает тег, аналогичный html (например,div), он должен быть скомпилирован в исполняемый код

Без лишних слов добавьте несколько строк кода, чтобы увидеть.

 const app =  new Vue({
        el: '#app',
        data: {
          showModal: false
        }
      })

console.log('Vue.component:',Vue.component)
console.log('Vue:',Vue, 'new Vue:', app)

Посмотрите на картинку:

image.png

Вау! Результаты печати восхитительны! Что ты видишь? ? Отбросим прежние знания,Как вы думаете, какие слова вы знаете?

Первый взглядVue.componentраспечатать результат.

  • Очевидно, этоfunction, и напрямую возвращать результат, аналогичный результату, полученному после выполнения оператора суждения? (обозначено красной рамкой на рисунке)
  • Кажется, что при входе в женьшень смотреть не на что, так что оставайтесь пока.

посмотри сноваnew Vue({...})распечатать результат.

  • Сам Vue также является функцией
  • Экземпляр Vue имеет много свойств, некоторые из которых знакомы
    • vnode: Угадай виртуальный DOM?
    • $createEmelemt: Разве это не означает создание узла?
    • _watcher: Что означает слово смотреть?

Следующий шаг: нажмите точку останова! Как показано

image.png

image.png

Затем страница обновляется, и код останавливается. Вы обнаружите, что код будет проходить через emptyObject, Vnode, Observer, Watcher (поскольку я установил точки останова, вы можете найти и установить точки останова самостоятельно в соответствии с рисунком выше).

В конце концов, вы доберетесь сюда.

image.png

Как вы думаете, этот код очень похож на то, что вы видели выше? правильно! Это то, что печатает консольVue.component .

Дело раскрыто! Наконец нашел вход, чтобы увидеть исходный код! !

Успокойтесь, подумайте, как мы можем успешно войти в мир исходного кода

Вы пишете Vue, что вы видите? Начнем с самого простого (что видишь, то и получаешь).

Компиляция шаблона

будетtemplateпреобразовать вфункция рендеринга, что мы часто говоримrender. (React также имеетrenderПонятие функции, но это не одно и то же, есть отличия)

// 此为 我们在  template 中 使用(写)的 HTML标签语法内容,
<button id="show-modal" @click="showModal = true">Show Modal</button>

那,其对应的render函数是什么?

render(h){
    return h(
    'button',
    {
        on: {
            click: ()=> this.showModal = true
        }
    },
    Show Modal
    )
}

а? Вы спрашиваете меня, что h, что это? Упс, я вроде не вдруг остановился, машина уехала далеко. Нет проблем, я объясню позже (Virtual DOM). Что касается того, как доказать, что компиляция шаблона в конечном счете является рендерингом, на самом деле вы можете найти это с первого взгляда в файлах, упакованных webpack. 我们的.vue 文件就会被转化成render函数(通过 vue-loader)。

Сначала небольшое доказательство: что такое функция рендеринга. Добавьте код строки:

      // register modal component
      Vue.component('modal', {
        template: '#modal-template'
      })

      // start app
     const vm =  new Vue({
        el: '#app',
        data: {
          showModal: false
        }
      })
     console.log('render:',vm.$options.render)

Что выводит эта консоль? Как показано на рисунке:

Итак, где разбирается этот вопрос? Расположение источника:/vue-dev/src/platforms/web/entry-runtime-with-compiler.js

ps: Перехвачена только часть исходного кода, а ключевые позиции аннотированы на китайском языке.

Игнорировать код: код ...

// 只截取部分源码 
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el) // 获取传入的el对象

  /* istanbul ignore if */ 
  /* el不可以是 body 和 html对象 */
  if (el === document.body || el === document.documentElement) {
    process.env.NODE_ENV !== 'production' && warn(
      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
    )
    return this
  }

  const options = this.$options
  // resolve template/el and convert to render function
  // 将template/el 转成 render 函数。render挂在this.options 身上
  if (!options.render) { // 如果没有render属性,则 想办法 搞一个 render 函数
    let template = options.template
    if (template) { 
      if (typeof template === 'string') {
        // code ...
        template = idToTemplate(template) //该方法返回 el 的innerHTML
        // code ...
      } else if (template.nodeType) { // nodeType 是代表 template 和 el 是一样的。
        template = template.innerHTML // 如果模板是element ,拿template的 innerHTML
      } else {
        if (process.env.NODE_ENV !== 'production') { // 啥也不是,非法
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if (el) {
      template = getOuterHTML(el); // 没有template,就用el的 outerHTML 作为模板
    }
    if (template) { // 好的,经过以上处理,这时候肯定有模板了!
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile')
      }

      // 敲黑板,划重点!! 此处 compileToFunctions 把 template 转换成 render 函数
      const { render, staticRenderFns } = compileToFunctions(template, {
        // code ...
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns

      /* istanbul ignore if */
      //code...
    }
  }
  // 最终,要渲染DOM了。 
  return mount.call(this, el, hydrating)
}

Подытожу немного(new Vue({...})что сделал):

  1. 最终目标是为了渲染真实DOM
  2. $mountположитьtemplate/elКонвертировано вrender
    • судить первымthis.$optionsу тебя естьrenderметод, если нет, его нужно преобразовать
    • templateПреобразовать, если он не существуетel
  3. Вызовите mount рекурсивно и исправьте эту точку (должна всегда указывать на Vue)

Ха~ это слишком многословно. Подводя итог в одном предложении: убедиться, что метод Vue.$options.render существует. Потому что для расчета ВДОМ.

Вернемся к началу, что именно печатает функция рендеринга? Как показано на рисунке:

image.png

Я форматирую код:

  (function anonymous(
  ) {
    with (this) {
      return _c(
        'div',
        { attrs: { "id": "app" } },
        [_c(
          'button',
          {
            attrs: { "id": "show-modal" },
            on: { "click": function ($event) { showModal = true } }
          },
          [_v("Show Modal")]
        ),
        _v(" "),
        (showModal)
          ?
          _c('modal',
            {
              on: { "close": function ($event) { showModal = false } }
            },
            [
              _c('h3',
                { attrs: { "slot": "header" }, slot: "header" },
                [_v("custom header")]
              )]
          )
          :
          _e()],
        1)
    }
  })

Видно, что преобразованная функция рендеринга на самом деле является анонимной функцией (не замыканием). в_cМетод на самом деле вышеупомянутыйh(Хлоп! Это недоразумение, легко быть непонятым. _c есть h?).

Вот еще немного, зачем тамshowModal ? _c : _e? Что это означает? Ха-ха, я выпишу код шаблона и посмотрю.

    <div id="app">
      <button id="show-modal" @click="showModal = true">Show Modal</button>
      <!-- use the modal component, pass in the prop -->
      <modal v-if="showModal" @close="showModal = false">
        <!--
          you can use custom content here to overwrite
          default content
        -->
        <h3 slot="header">custom header</h3>
      </modal>
    </div>

Да, вы правильно угадали.v-if="showModal"этоshowModal ? _c : _eИ дом с и без разницы.

пс: кто-то хочет спроситьwith (this)Что это означает. Это означает, что в рамках with это объект самого высокого уровня в области видимости. То есть в «окне» в рамках with при доступе к _c, _e и т. д. по умолчанию будет найден with.

Однако, согласно здравому смыслу, в целом_xЭтот однобуквенный метод обычно находится в основном каталоге, и Vue не является исключением.

  • _c: /src/core/instance/render.js
  • _v/_s и т.д.:/src/core/render-helpers/index.js

значит, надо найти определение_cместонахождение метода. В исходном коде:

export function initRender (vm: Component) {
  vm._vnode = null // the root of the child tree
  vm._staticTrees = null // v-once cached trees
  const options = vm.$options
  const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
  const renderContext = parentVnode && parentVnode.context
  vm.$slots = resolveSlots(options._renderChildren, renderContext)
  vm.$scopedSlots = emptyObject
  // bind the createElement fn to this instance
  // so that we get proper render context inside it.
  // args order: tag, data, children, normalizationType, alwaysNormalize
  // internal version is used by render functions compiled from templates
  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false) // 1
  // normalization is always applied for the public version, used in
  // user-written render functions.
  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true) // 2

  // $attrs & $listeners are exposed for easier HOC creation.
  // they need to be reactive so that HOCs using them are always updated
  const parentData = parentVnode && parentVnode.data

  /* istanbul ignore else */
  if (process.env.NODE_ENV !== 'production') {
    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => {
      !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
    }, true)
    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => {
      !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm) // 3
    }, true)
  } else {
    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
  }
}

Пожалуйста, внимательно посмотрите на три точки, цифры отмечены в коде.

  1. определениеvm._c, позвонивcreateElementGet, последний переданный параметр является ложным.
  2. определениеvm.$createElement, позвонивcreateElementПолучить, последний переданный параметр является истинным.
  3. isUpdatingChildComponent

Обобщить:

  • vm._cиvm.$createElementНа самом деле это одно, а отношение к детям другое. (не вдаваясь пока в подробности)
  • Формальная средаproductionВ режиме он не будет судить, обновляется ли текущий подкомпонент, потому что для сравнения vm. Но среда разработки не имеет значения, и даже нужно сравнивать. Потому что в режиме dev объем vm намного больше, чем объем производства.

посмотри снова/src/core/render-helpers/index.js. Предполагается, что после просмотра это станет намного яснее.

/* @flow */
//code ...
import ...
//code ...

export function installRenderHelpers (target: any) {
  target._o = markOnce
  target._n = toNumber
  target._s = toString
  target._l = renderList
  target._t = renderSlot
  target._q = looseEqual
  target._i = looseIndexOf
  target._m = renderStatic
  target._f = resolveFilter
  target._k = checkKeyCodes
  target._b = bindObjectProps
  target._v = createTextVNode
  target._e = createEmptyVNode
  target._u = resolveScopedSlots
  target._g = bindObjectListeners
  target._d = bindDynamicKeys // 1
  target._p = prependModifier
}

Видишь, стало намного яснее?так много_Здесь назначаются функции в начале.посмотри на этоbindDynamicKeys, да, вы правы, ключ, который вы связываете, когда используете v-for, - это то, что он делает! (Если вы попросите исходный код, чтобы дать вам пощечину здесь, я не буду его переворачивать...)

эммм, похоже, что метод _v появился в предыдущем каштане, мне еще предстоит его объяснитьcreateTextVNodeметод.

path: src/core/vdom/vnode.js

// code...
export const createEmptyVNode = (text: string = '') => {
  const node = new VNode()
  node.text = text
  node.isComment = true
  return node
}

export function createTextVNode (val: string | number) {
  return new VNode(undefined, undefined, undefined, String(val))
}

// code...

Увы, это не так много, чтобы сказать. Существует класс VNode, который может создавать пустые vnode (виртуальные узлы) и vnode текстового типа. Что касается того, к каким свойствам и методам имеют доступ vnode, вы можете продолжать возвращаться к корням...

Вот, чтобы у всех было более глубокое понимание процесса html -> render, вот забавный адрес:template-explorer

В: Мы знаем, что шаблон в итоге будет сконвертирован в js, иначе как его распознает браузер? Как шаблон конвертируется в окончательный js? Ответ: шаблон -> AST -> оптимизированный AST -> рендеринг

Что ж, продолжайте исходный код. дорожка:/src/compiler/index.js

/* @flow */

import { parse } from './parser/index'
import { optimize } from './optimizer'
import { generate } from './codegen/index'
import { createCompilerCreator } from './create-compiler'

// `createCompilerCreator` allows creating compilers that use alternative
// parser/optimizer/codegen, e.g the SSR optimizing compiler.
// Here we just export a default compiler using the default parts.
export const createCompiler = createCompilerCreator(function baseCompile (
  template: string,
  options: CompilerOptions
): CompiledResult {
  const ast = parse(template.trim(), options) // template -> AST
  if (options.optimize !== false) {
    optimize(ast, options) // AST -> 优化后的AST
  }
  const code = generate(ast, options) // 优化后的AST -> code.render
  return {
    ast,
    render: code.render, // 这个render 是 string, 使用时需要转化成function
    staticRenderFns: code.staticRenderFns // 静态渲染函数,能得到一颗静态的VNode树
  }
})

Этого исходного кода не так много, и в ключевых позициях нет ни одного комментария, который можно понять с первого взгляда.

Что такое компонентизация Vue?

Официальное заявление: компонент Vue — это экземпляр Vue с предопределенными свойствами.Речь это:new Vue({...})

Что содержит этот компонент Vue? Ясно...

  1. стиль
  2. js-скрипт
  3. template

Затем вы можете зарегистрироваться в Vueглобальный компонентиместные компоненты. Как это сделать? Давайте рассмотрим каштан выше:

  // register modal component 。modal 就是全局组件
      Vue.component('modal', {
        template: '#modal-template'
      })

      // start app
     const vm =  new Vue({
        el: '#app',
        data: {
          showModal: false
        }
      })

Очевидно, что глобальные компоненты регистрируются через Vue.component. В исходном коде путь:/src/core/global-api/index.js

// code ...
export function initGlobalAPI (Vue: GlobalAPI) {
 // code ...
  Object.defineProperty(Vue, 'config', configDef) // 响应式……的开始?
// code ...

  // this is used to identify the "base" constructor to extend all plain-object
  // components with in Weex's multi-instance scenarios.
  Vue.options._base = Vue // 把Vue 的构造函数赋给 Vue.options._base

  extend(Vue.options.components, builtInComponents) // keep-alive setting

  initUse(Vue)
  initMixin(Vue)
  initExtend(Vue)
  initAssetRegisters(Vue)
}

initAssetRegisters, Что? Инициализировать что? в исходном коде

// path: /src/shared/constants.js
export const ASSET_TYPES = [
  'component',
  'directive',
  'filter'
]

// path:/src/core/global-api/assets.js
export function initAssetRegisters (Vue: GlobalAPI) {
  /**
   * Create asset registration methods.
   */
  ASSET_TYPES.forEach(type => {
    Vue[type] = function (
      id: string,
      definition: Function | Object // 这里能看出啥??
    ): Function | Object | void {
      if (!definition) { // 2. 如果没传 definition ,说明可以直接获取先前已经定义好的全局组件
        return this.options[type + 's'][id]
      } else { 
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && type === 'component') {
          validateComponentName(id)
        }
        if (type === 'component' && isPlainObject(definition)) {
          definition.name = definition.name || id
          definition = this.options._base.extend(definition) // 3. 把组件的配置选项转化成组件的构造函数
        }
        if (type === 'directive' && typeof definition === 'function') {
          definition = { bind: definition, update: definition }
        }
        this.options[type + 's'][id] = definition //最终进行全局注册
        return definition
      }
    }
  })
}

Просто подытожу:

  1. Vue[type] напрямую определяет три метода свойств Vue, которыеVue.component,Vue.directive,Vue.filter
  2. Если информация о конфигурации не предоставлена, считается, что она напрямую извлекает определенные глобальные компоненты.
  3. Если вы вызываете Vue.component , это преобразует вашу информацию о конфигурации в конструктор компонента (чтобы экземпляр Vue мог получить доступ к другим свойствам метода)
  4. Глобальная регистрация. В следующий раз, когда вы получите доступ к этому идентификатору компонента, компонент будет возвращен напрямую.

Вопрос:definition: Function | Object // 这里能看出啥??Что делает эта строка кода?

отвечать:Говорят, что Vue также может писать jsx. Причина здесь, потому что jsx — это функция

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

Ответ: Поскольку компоненты Vue наследуют Vue. дорожка:/src/core/global-api/extend.js

/* @flow */

import ...

export function initExtend (Vue: GlobalAPI) {
  //code ...

  /**
   * Class inheritance
   */
  Vue.extend = function (extendOptions: Object): Function {
    extendOptions = extendOptions || {}
    const Super = this
    // code...

    const Sub = function VueComponent (options) {
      this._init(options)
    }
    // 此处Sub类继承了Super,Super 是this ,this指向Vue。 所以Sub的实例能访问Vue的属性
    Sub.prototype = Object.create(Super.prototype)
    Sub.prototype.constructor = Sub
   
   // code...
  
    return Sub
  }
}

Розыгрыши

Как принять участие: Добро пожаловать, чтобы оставить сообщение под полем для комментариев.

Метод розыгрыша: 2 детские туфли выбираются случайным образом, чтобы отправить 1 значок Nuggets.

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

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

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