vue源码分析(web平台相关)-01

src/platforms/web 这个是 vue 针对web 平台

拉取Vue源码

git clone https://github.com/vuejs/vue.git

版本为 Vue 2.6.10

在根目录下执行:

// 安装依赖
npm i

安装rollup:

因为在 package.json 文件里面发现有这个 rollup

 npm i -g rollup

修改dev脚本,添加sourcemap,在 package.jsonscripts 节点下
"dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev",

sourcemap : source-map 的基本原理是,在编译处理的过程中,在生成产物代码的同时生成产物代码中被转换的部分与源代码中相应部分的映射关系表。
也就是说,在本地调试的时候,保证运行的代码和源码里面,位置是对应的。
我们就可以通过 Chrome 控制台中的"Enable Javascript source map"来实现调试时的显示与定位源代码功能

引用网络上的一张图描述

目录结构:
dist 发布目录
examples 范例
packages Vue核心之外的一些独立库(比如:ssr-serve 服务端渲染,template 模板, weex等)
scripts 构建脚本
src 源码目录
types 类型申明

源码目录结构: (src下的目录)
compiler 编译器相关
platforms 平台相关(web 平台【这次主要看这个】,weex 平台,ssr 平台)
core 核心代码库
-- components 通用组件(但是只有 keep-alive 组件)
-- global-api 全局Api
-- instance 构造函数
-- observer 响应式相关
-- vdom 虚拟dom

入口文件 entry-runtime-with-compiler.js

package.json

通过运行脚本 scripts\config.js 找到这个文件

// 运行脚本

  // Runtime+compiler development build (Browser)
  'web-full-dev': {
    // 入口
    entry: resolve('web/entry-runtime-with-compiler.js'),
    dest: resolve('dist/vue.js'),
    format: 'umd',
    env: 'development',
    alias: { he: './entity-decoder' },
    banner
  },

通过上面脚本 resolve 这个地方找到

const aliases = require('./alias')
const resolve = p => {
  const base = p.split('/')[0]
  if (aliases[base]) {
    return path.resolve(aliases[base], p.slice(base.length + 1))
  } else {
    return path.resolve(__dirname, '../', p)
  }
}

通过上面脚本 aliases 这个地方找到所有的前缀映射

const path = require('path')

const resolve = p => path.resolve(__dirname, '../', p)

module.exports = {
  vue: resolve('src/platforms/web/entry-runtime-with-compiler'),
  compiler: resolve('src/compiler'),
  core: resolve('src/core'),
  shared: resolve('src/shared'),
  web: resolve('src/platforms/web'),
  weex: resolve('src/platforms/weex'),
  server: resolve('src/server'),
  sfc: resolve('src/sfc')
}

那么完整入口文件 entry-runtime-with-compiler.js位置就在 src/platforms/web/entry-runtime-with-compiler.js

测试文件 01.html

<!DOCTYPE html>
<html lang="en">
    <script src="../../dist/vue.min.js"></script>
    <body>
        <div id='app'>{{ num }}</div>
    </body>
    <script>
        // 1.render 和 template 和 el 优先级?
        // 2.el 和 $mount 
        new Vue({
            el: '#app',
            // template: '<div>6666</div>',
            // render: h => h('div', '777'),
            data() {
                return {
                    num: 5
                }
            }
        })// .$mount('#app')
    </script>
</html>

通过 src\platforms\web\entry-runtime-with-compiler.js 入口文件

粘贴的源码里面,我添加了注释,并删除了无用代码

/* @flow */
// 入口文件。覆盖 $mount ,执行模板解析和编译工作
import config from 'core/config'
import { warn, cached } from 'core/util/index'
import { mark, measure } from 'core/util/perf'

import Vue from './runtime/index'
import { query } from './util/index'
import { compileToFunctions } from './compiler/index'
import { shouldDecodeNewlines, shouldDecodeNewlinesForHref } from './util/compat'

const idToTemplate = cached(id => {
  const el = query(id)
  return el && el.innerHTML
})

// 保存一份原来的 $mount 函数
const mount = Vue.prototype.$mount
// 覆盖(重写) Vue 上的 $mount 函数
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)

  // 解析option
  const options = this.$options
  // resolve template/el and convert to render function
  if (!options.render) {
    let template = options.template
    // 模板解析
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          template = idToTemplate(template)
        }
      } else if (template.nodeType) {
        template = template.innerHTML
      } else {
        if (process.env.NODE_ENV !== 'production') {
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if (el) {
      template = getOuterHTML(el)
    }
    // r如果模板存在,执行编译
    if (template) {

      // 编译得到渲染函数
      const { render, staticRenderFns } = compileToFunctions(template, {
        outputSourceRange: process.env.NODE_ENV !== 'production',
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns

    }
  }

  // 执行挂载
  // 父级的 mount 函数
  return mount.call(this, el, hydrating)
}

/**
 * Get outerHTML of elements, taking care
 * of SVG elements in IE as well.
 */
function getOuterHTML (el: Element): string {
  if (el.outerHTML) {
    return el.outerHTML
  } else {
    const container = document.createElement('div')
    container.appendChild(el.cloneNode(true))
    return container.innerHTML
  }
}

Vue.compile = compileToFunctions

export default Vue

发现在最开始的位置const mount = Vue.prototype.$mount,做了一次保存一份Vue原有 $mount 函数,然后紧接着又重写了 $mount 上的函数。

为什么重写了 $mount 函数?

发现重写的$mount 函数里面,做了一个事情,就是用 render 还是用 template 或者 el 进行渲染。

但是最后还是使用原本Vue的 $mount 函数进行挂载(因为之前保存了一份 Vue 原有 $mount 函数)

// 执行挂载
  // 父级的 mount 函数
  return mount.call(this, el, hydrating)

发现这段源码里面

// r如果模板存在,执行编译
    if (template) {

      // 编译得到渲染函数
      const { render, staticRenderFns } = compileToFunctions(template, {
        outputSourceRange: process.env.NODE_ENV !== 'production',
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns

    }

可以看出Vue最终渲染的属性始终是 render (位置:options.render = render)。虽然平常使用 template 方式,但是最终都会赋值给 render 进行渲染。

结论:

  • render 和 template 和 el 优先级?
    通过源码的顺序来看,如果 render 存在那么就直接执行 render,如果没有就执行 template,如果没有就执行 el (是通过 getOuterHTML函数它的获取HTML)

所有的,最终都是渲染 HTML

render ==> template ==> el

  • el 和 $mount 作用
    都是指定挂载带一个节点上

在看看 src\platforms\web\runtime\index.js

这个文件是 web 平台的起始 js 文件

src\platforms\web\runtime\index.js
/* @flow */

import Vue from 'core/index'
import config from 'core/config'
import { extend, noop } from 'shared/util'
import { mountComponent } from 'core/instance/lifecycle'
import { devtools, inBrowser } from 'core/util/index'

import {
  query,
  mustUseProp,
  isReservedTag,
  isReservedAttr,
  getTagNamespace,
  isUnknownElement
} from 'web/util/index'

import { patch } from './patch'
import platformDirectives from './directives/index'
import platformComponents from './components/index'

// install platform specific utils
Vue.config.mustUseProp = mustUseProp
Vue.config.isReservedTag = isReservedTag
Vue.config.isReservedAttr = isReservedAttr
Vue.config.getTagNamespace = getTagNamespace
Vue.config.isUnknownElement = isUnknownElement

// install platform runtime directives & components
extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)

// 指定补丁方法:传入虚拟donm转换为真是dom
// install platform patch function
Vue.prototype.__patch__ = inBrowser ? patch : noop

// 实现$mount
// public mount method
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  // 初始化,将首次渲染结果替换el
  return mountComponent(this, el, hydrating)
}

export default Vue

虚拟dom后面在分析

看到这个代码的最下面,发现是做初始化,将首次渲染结果替换为 el 节点
也重新定义了 $mount 函数

上面的分析是 Vue 关于 web 平台相关的 src\platforms\web\runtime\index.js

现在是看 Vue 核心 code 相关!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269