vue源码-vue项目配置和入口文件,数据响应化处理

Posted suwu150

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue源码-vue项目配置和入口文件,数据响应化处理相关的知识,希望对你有一定的参考价值。

vue源码(四)-vue项目配置和入口文件,数据响应化处理

一、获取vue项目代码

项目地址:https://github.com/vuejs/vue
迁出项目: git clone https://github.com/vuejs/vue.git 当前版本号:2.6.10

二、文件结构

如上所示文件结构:

  • dist: 所有产出的文件地址
  • examples:官方案例,可参考
  • flow:强类型编译语言,类型声明
  • packages:独立于Vue核心包的库
  • scripts:打包的一些脚本和配置。
  • src:核心代码存放位置,包含编译器、核心代码、平台特有代码、服务端相关,解释器,共享代码和常量
  • test:测试文件目录
  • types:ts类型文件

三、调试环境搭建

1.安装打包工具rollup

npm i -g rollup

修改package.json中dev脚本,添加sourcemap,对打包出的产出文件添加映射信息,方便代码调试。

"dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web- full-dev",

运行开发命令: npm run dev ,重新生成开发版vue.js代码

引入前面创建的vue.js到index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no">
    <title>Vue.js elastic header example</title>
    <script src="../../dist/vue.js"></script>
  </head>
  <body>

    <div id="app">
      message
    </div>

    <script>
      new Vue(
        el: '#app',
        data: 
          message: 'hello world!'
        
      )
    </script>
  </body>
</html>

接下来打开html就可以在浏览器调试代码了!!

四、项目入口

其中package.json中dev脚本中-c scripts/config.js指明配置文件所在位置scripts/config.js,内容截图如下。

参数 TARGET:web-full-dev指明输出文件配置项,然后在配置项中指定入口文件entry-runtime-with-compiler.js,目标文件位置dest: resolve('dist/vue.js'),,运行npm run dev时所对应的配置如下,能够看到配置了entry,format、env、 alias、banner等

在运行dev脚本时,会通过判断获取 TARGET 具体的配置,由于我们传递的是TARGET:web- full-dev,所以会匹配web- full-dev配置项。

if (process.env.TARGET) 
  module.exports = genConfig(process.env.TARGET)
 else 
  exports.getBuild = genConfig
  exports.getAllBuilds = () => Object.keys(builds).map(genConfig)

五、项目初始化流程

我们通过package.json脚本中dev的运行能够知道入口文件是./src/platforms/web/entry-runtime-with-compiler.js,打开该文件查看

...
// 扩展默认$mount方法:处理template或el选项
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component 
  el = el && query(el)
...
  // 处理template,其中优先级render>template>el
  const options = this.$options;
  // resolve template/el and convert to render function
  // 优先检查render方法,render优先级最高
  if (!options.render) 
    let template = options.template
    // 判断template属性是否存在,如果存在,处理template
    if (template) 
    // 由于template既可以是字符串模版,也可以是‘#app’这种形式,所以在这里进行判断
      if (typeof template === 'string') 
        if (template.charAt(0) === '#') 
          template = idToTemplate(template)
        
       else if (template.nodeType) 
      // 如果传进来的是dom元素,进行处理
        template = template.innerHTML
       else 
        if (process.env.NODE_ENV !== 'production') 
          warn('invalid template option:' + template, this)
        
        return this
      
     else if (el) 
    // 只设置el的时候
      template = getOuterHTML(el)
    
    // 从这里开始,执行编译:用户编写的模版转换为渲染函数
    if (template) 
    // 获取render函数,当然这个render的获取是因为用户没有传递自己的render的情况下才使用
      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
    
  
  // 原先挂载逻辑:虚拟dom->真是dom
  return mount.call(this, el, hydrating)

我们能够看到Vue.prototype.$mount 对$mount进行了扩展,进行处理template和el。从if (!options.render) 上面代码可以看到,执行优先级render>template>el,在导入页面中使用的vue.js的html页面中,打开该文件,我们进行断点调试

能够看到el参数值为#app,然后通过query进行处理,获取到html标签,然后将el提供给方法getOuterHTML进行使用,但是根据我们使用的常规用法,在Vue页面中,根据生命周期的运行过程,我们是需要通过new Vue的形式创建实例,如下面代码创建过程:

    <script>
      new Vue(
        el: '#app',
        data: 
          message: 'hello world!初始化流程和入口文件!'
        
      )
    </script>

那么,Vue又是从哪里导入的,我们能够在文件上方看到导入路径import Vue from './runtime/index',能够看到来自runtime包。

打开./src/platforms/web/runtime/index.js文件,在这个文件中,可以看到挂载函数$mount和补丁函数 __patch__ 的定义,同时我们发现$mount的真正挂载其实使用的是mountComponent方法进行了处理,但是原始Vue构造函数仍然是根据import Vue from 'core/index'导入进来的。

打开src/core/index.js文件如下,发现Vue仍然是通过import Vue from './instance/index',但是在该文件中,看到第六行运行了initGlobalAPI(Vue),这句代码就是初始化全局的api,同时对Vue.prototype进行了$isServer$ssrContext的拦截监听,所以这个文件总的来说就是实现了全局api的初始化:

打开initGlobalAPI(Vue)这个文件,37行初始化了Vue.util工具类,43到60进行Vue方法的初始化,61到64分别进行初始化插件Vue.use函数、Vue.mixin混入、Vue.extend方法、注册实现Vue.component,Vue.directive,Vue.filter方法

看完文件initGlobalAPI(Vue),继续寻找Vue,打开文件import Vue from './instance/index',

在这个文件中,我们终于找到了Vue的定义,第8行实现了Vue构造函数,观察构造函数,我们发现只执行调用了this._init(options)这个方法,其中 options就是new Vue(options)实例时传递的参数。同时从17行到21行对Vue进行了混入调用。

唯一执行的this._init(options)这个方法,没有在本页面定义,我们猜测可能是在17行到21行进行了混入,打开initMixin(Vue)文件,在16行我们发现对_init方法进行了实现。

代码中,我们能够发现50行到68行进行了大量初始化操作,其中进行了初始化生命周期初始化事件监听初始化渲染执行生命周期钩子beforeCreate解决注入数据解决依赖数据初始化组件状态执行生命周期钩子created
至此,我们找到了最原始的Vue在src/core/instance/index.js,同时查看了在index.js中初始化的方法src/core/instance/init.js,我们在该处打断点,刷新页面会发现断点移动顺序是先初始化,调用initMixin方法中的_init方法进行初始化准备工作,然后才会去调用entry-runtime-with-compiler.js中的挂载方法进行挂载。我们继续调试,点击F11按钮进入代码,会发现调用lifecycle.jsmountComponent方法

在该方法mountComponent中,可以发现执行生命周期钩子beforeMount,然后再197行创建监听,执行生命周期钩子beforeUpdate,210行执行生命周期钩子mounted
以上就是整个Vue的初始化入口过程。

六、数据响应式

在数据响应式中需要知道的是Object.defineProperty()方法对数据的拦截处理.

1.普通数据响应

从初始化过程中我们能够知道,数据处理应该在文件src/core/instance/init.js中的initState方法页面中处理。打开src/core/instance/state.js文件

在51行开始,进行属性处理,53行判断属否有data传递,如果有传递,执行 initData 方法处理数据,否则运行observe方法,初始化默认数据。
打开src/core/observer/index.js文件,查看observe方法,能够发现返回Observer类。

查看本页面的Observer

Observer类构造方法中,对传入的值定义__ob__属性,47行进行判断传入的数据是否为数组,55行执行非数组数据。在64行循环调用defineReactive方法添加Object.defineProperty中get/set方法的重写处理所有的健。

2.数组的响应

同样的,在文件src/core/observer/index.js中,47行查看对数组的处理,对于数组的处理,首先进行了判断是否有原型操作,如果有则执行protoAugment,反之执行copyAugment方法,查看方法 protoAugment(value, arrayMethods)copyAugment(value, arrayMethods, arrayKeys)能够看到传递了从src/core/observer/array.js文件导入的arrayMethods参数,打开文件src/core/observer/array.js,查看到通过数组原型创建了arrayMethods,并且通过循环的方式,进行重新定义(defineProperty)数组原型操作方法,为数组原型中的7个可以改变内容的方法定义拦截器,实现数组的响应化。

处理完protoAugmentcopyAugment之后调用了类中observeArray方法,该方法位于74行,同样的看到了对items的for循环进行observe处理。

observe的处理过程中,124行实际是对value的处理,我们发现是将value传递给类Observer生成新的Observer,实际来看是一种递归的对对象类型的处理。

以上就是Vue项目配置和入口文件,数据响应化处理的记录。


以上是关于vue源码-vue项目配置和入口文件,数据响应化处理的主要内容,如果未能解决你的问题,请参考以下文章

vue-cli 目录理解二

重建基于vue的多入口项目——项目的重建和配置文件的修改部分

vue2源码学习-响应式原理

vue2源码学习-响应式原理

Vue3.0源码系列响应式原理 - Reactivity

Vue3.0源码系列响应式原理 - Reactivity