Vue源码起步

小恐龙 等级 386 0 0

搞事!搞事!

截止2017.5.16,终于把vue的源码全部抄完,总共有9624行,花时大概一个月时间,中间迭代了一个版本(2.2-2.3),部分代码可能不一致,不过没关系!

上一个链接https://github.com/pflhm2005/vue

进入阶段2:尝试一下,从小案例看一看代码在vue源码中的走向,Go!(语文不好,将就看看)

从最简单的案例开始,摘抄官网的起步:

 <body>
        <div id='app'> {{message}} </div>
    </body>
    <script src='./vue.js'></script>
    <script>
        var app = new Vue({
            el: '#app',
            data: {
                message: 'Hello Vue!' 
         }
        }); 
    </script>

打断点,开始执行!

初始化函数

html代码中,包含2大部分,挂载DOM节点,以及初始化vue的js代码。

有几个小地方,虽然按照官网的案例不会出现问题,但是还是说明一下:

(1)、el不能挂载到html或者body标签上

 // Line-9547
    if (el === document.body || el === document.documentElement) { "development" !== 'production' && warn( "Do not mount Vue to <html> or <body> - mount to normal elements instead." ); return this }

(2)、关于代码各处可见的"development" !== 'production'

这个是dev模式才有,vue.js文件中对所有警告的代码判断条件进行了替换,报错方便调试,在发布模式中会自动清除,方便开发。

与jQuery不一样,这里需要手动new才会创建一个vue实例。

直接上源码。

jQuery:

 // Line-94 jQuery 3.2.1
    // 顺便吐槽一下 这个版本终于把初始化提前了 代码结构应该棒棒的
    var jQuery = function(selector, context) { // The jQuery object is actually just the init constructor 'enhanced'
        // Need init if jQuery is called (just allow error to be thrown if not included)
        return new jQuery.fn.init(selector, context);
    }

Vue:

 // Line-9622
    return Vue$3;

但是我们看到源码最后其实返回的是Vue$3,至于为什么new的是Vue也行呢?看一下源码开头的整个IIFE表达式也就明白了。

    (function(global, factory) { // 兼容各种宿主环境
        typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : // 浏览器环境
            (global.Vue = factory());
    }(this, /\*vue\*/ ));

基本上框架都是这个套路,引入一个宿主环境的对象以及框架本身。

上述代码形参中,global在浏览器环境中相当于window,由于有时会在node、webpack等环境中运行,所以需要进行兼容处理,于是有很长的typeof。

对于浏览器来讲,上述代码其实就是window.Vue = Vue$3({options}),所以这就很明了了。

起步流程两个框架都是一样的,首先通过一个init函数进行全局初始化。

 // Line-4055
    function Vue$3(options) { if ("development" !== 'production' &&
            !(this instanceof Vue$3)) {
            warn('Vue is a constructor and should be called with the \`new\` keyword');
        } this.\_init(options);
    }

这里的options参数,很明显就是我们在new对象的时候传进去的对象,目前只有el和data两个。

入口函数只是简单的判断了一下有没有new,然后自动调用了原型函数_init。

_init函数的定义地点有点意思,是在一个函数内部定义,然后在后面调用了这个函数。

    // Line-3924
    function initMixin(Vue) {
        Vue.prototype.\_init \= function(options) { //....
 };
    } // Line-4063
    initMixin(Vue$3);

  整个函数只定义了_init这个初始化原型函数,原因在某个注释中写,直接定义原型会出现问题,所以采用这种方法进行规避。

  至于具体什么问题,我找不到那行注释了。。。

接下来看看初始化函数里面都干了啥事。

    // Line-3926
    // 生成的实例保存为别名vm
    var vm = this; // 全局记数 表示有几个vue实例
    vm.\_uid = uid$1++; var startTag, endTag; // 这里的config.performance开发版默认是false
    if ("development" !== 'production' && config.performance && mark) {
        startTag \= "vue-perf-init:" + (vm.\_uid);
        endTag \= "vue-perf-end:" + (vm.\_uid);
        mark(startTag);
    } // 代表这是一个vue实例
    vm.\_isVue = true; // 非组件 跳过
    if (options && options.\_isComponent) {
        initInternalComponent(vm, options);
    } // 正常实例初始化
    // 在这里对参数进行二次加工
    else {
        vm.$options \= mergeOptions(
            resolveConstructorOptions(vm.constructor),
            options || {},
            vm
        );
    } // ...more

  前面基本上没做什么事,对于config对象,在开发版中的默认参数如下:

    // 开发版的默认配置
    var config = ({
        optionMergeStrategies: Object.create(null),
        silent: false,
        productionTip: "development" !== 'production',
        devtools: "development" !== 'production',
        performance: false,
        errorHandler: null,
        ignoredElements: \[\],
        keyCodes: Object.create(null),
        isReservedTag: no,
        isReservedAttr: no,
        isUnknownElement: no,
        getTagNamespace: noop,
        parsePlatformTagName: identity,
        mustUseProp: no, // 历史遗留
 \_lifecycleHooks: LIFECYCLE\_HOOKS
    });

由于提示信息不是重点,所以第一步直接可以走到mergeOptions这里,从名字就可以看出这是一个参数合并的函数,接受3个参数:

1、resolveConstructorOptions(vm.constructor)

这个函数属于内部初始化,接受的参数就是Vue函数自身,如下:

    // Line-4136
    Sub.prototype.constructor = Sub;

  跳进去看一眼这个函数做了什么:

    // Line-3998
    function resolveConstructorOptions(Ctor) { // Ctor=Constructor
        // options为所有vue实例基础参数
        // 包含components,directives,filters,\_base
        var options = Ctor.options; // 这个属性比较麻烦 暂时没有 跳过
        if (Ctor.super) { //...
 } // 返回修正后的options
        return options
    }

如果忽略那个super属性的话,返回的其实就是Vue$3.constructor.options,该对象包含4个属性,如图所示。

  Vue源码起步

    // Line-4368
    // Vue函数自身的引用
    Vue.options.\_base = Vue; // Line-7523
    extend(Vue$3.options.directives, platformDirectives);
    extend(Vue$3.options.components, platformComponents); // Line-7161
    // 指令相关方法
    var platformDirectives = {
        model: model$1,
        show: show
    }; // Line-7509
    // 组件相关
    var platformComponents = {
        Transition: Transition,
        TransitionGroup: TransitionGroup
    };

其中filters属性暂时是空的,其余3个属性在2个地方有定义,一个是组件、指令方法集,一个是vue函数自身引用。

2、options || {} => 传进来的参数

3、vm => 当前vue实例

最后,总览3个参数如下:Vue源码起步

   带着3个小东西,跳进了mergeOptions函数进行参数合并。

    // Line-1298
    // 父子组件合并参数 本案例父组件为默认对象
    function mergeOptions(parent, child, vm) { // 检测components参数中键是否合法
 checkComponents(child); if (typeof child === 'function') {
            child \= child.options;
        } // 格式化props,directives参数
 normalizeProps(child);
        normalizeDirectives(child); // 格式化extends参数
        var extendsFrom = child.extends; if (extendsFrom) {
            parent \= mergeOptions(parent, extendsFrom, vm);
        } // mixins参数
        if (child.mixins) { for (var i = 0, l = child.mixins.length; i < l; i++) {
                parent \= mergeOptions(parent, child.mixins\[i\], vm);
            }
        } // 本案例中上面的都会跳过
        var options = {}; var key; // 遍历父组件对象 合并键
        for (key in parent) {
            mergeField(key);
        } // 遍历子组件对象 若有父组件没有的 合并键
        for (key in child) { if (!hasOwn(parent, key)) {
                mergeField(key);
            }
        } // 合并函数
        function mergeField(key) { var strat = strats\[key\] || defaultStrat;
            options\[key\] \= strat(parent\[key\], child\[key\], vm, key);
        } return options
    }

这个函数中前半部分可以跳过,因为只有简单的el、data参数,所以直接从mergeField开始执行。

上面已经列举出父组件的键,有components、directives、_filters、_base四个。

这里又多出一个新的东西,叫strats,英文翻译成战略,所以应该怎么叫我也是很懵逼的。这个对象内容十分丰富,从生命周期到data、computed、methods都有,如下所示:

Vue源码起步

  方法太多,就不一个一个讲了,说说本案例相关的几个方法。

  看起来非常吓人,其实定义简单粗暴,上代码看看就明白了。

    // Line-281
    var ASSET\_TYPES = \[ 'component', 'directive', 'filter' \]; // Line-1182
    ASSET\_TYPES.forEach(function(type) {
        strats\[type \+ 's'\] = mergeAssets;
    }); // Line-1175
    function mergeAssets(parentVal, childVal) { var res = Object.create(parentVal || null); return childVal ? extend(res, childVal) :
            res
    }

  简单讲就是,3个键对应的是同一个方法,接受2个参数,方法还贼简单。

  所以,对上面的mergeOptions函数进行简化,可以转换成如下代码:

    // parent键:components、directives、\_filters、\_base
    // child键:data、el
    function mergeOptions(parent, child, vm) { var options = {}; var key; // 父子对象键没有重复 参数直接可以写undefined 一步一步简化
        for (key in parent) { //options\[key\] = mergeAssets(parent\[key\], child\[key\], vm, key);
            //options\[key\] = mergeAssets(parent\[key\], undefined);
            options\[key\] = Object.create(parent\[key\]);
        } // 子键data和el需要额外分析 第一个参数同样可以写成undefined
        for (key in child) { if (!hasOwn(parent, key)) { //options\[key\] = strats\[key\](parent\[key\], child\[key\], vm, key);
                options\[key\] = strats\[key\](undefined, child\[key\], vm, key);
            }
        } return options
    } function mergeAssets(parentVal, childVal) { var res = Object.create(parentVal || null); return childVal ? extend(res, childVal) :
            res
    }

  遍历父对象其实啥也没做,直接把几个方法加到了options上面,然后开始遍历子对象,子对象包含我们传进去的el、data。

  el比较简单,只是做个判断然后丢回来。

    // Line-1064
    // 简单判断是否是vue实例挂载的el
    strats.el = strats.propsData = function(parent, child, vm, key) { if (!vm) {
            warn( "option "" + key + "" can only be used during instance " +
                'creation with the \`new\` keyword.' );
        } return defaultStrat(parent, child)
    };

  data则分两种情况,一种是未挂载的组件,一种是实例化的vue。

  不管未挂载,直接看实例化vue是如何处理data参数。

    // Line-1098
    strats.data = function(parentVal, childVal, vm) { // 未挂载
        if (!vm) { //...
 } // new出来的
        // 传进来的parentVal、childVal分别为undefined、{message:'Hello Vue!} 
        else if (parentVal || childVal) { return function mergedInstanceDataFn() { var instanceData = typeof childVal === 'function' ? childVal.call(vm) :
                    childVal; var defaultData = typeof parentVal === 'function' ? parentVal.call(vm) :
                    undefined; if (instanceData) { return mergeData(instanceData, defaultData)
                } else { return defaultData
                }
            }
        }
    };

这里直接返回了一个函数,暂时不做分析,后面执行时候再来看。

到此,整个mergeOptions函数执行完毕,返回一个处理过的options,将这个结果给了实例的$options属性:Vue源码起步

最后,用一张图结束这个乱糟糟的源码小跑第一节吧。

Vue源码起步

撒花!撒花!

收藏
评论区

相关推荐

【官宣】Vue 3.0 发布!
Vue 团队于 2020 年 9 月 18 日晚 11 点半发布了 Vue 3.0 版本,我们连夜对 Release 进行了翻译。由于时间仓促,文中如有翻译不当的地方还望提出。如有侵权,请联系删帖。以下为译文正文。 原文:https://github.com/vuejs/vuenext/releases 作者:Vue 团队 译文:https://zh
用 webpack 4.0 撸单页/多页脚手架 (jquery, react, vue, typescript)
1.导语 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;首先来简单介绍一下webpack:现代 JavaScr
Android webview 与 js(Vue) 交互
js 与原生交互分为两种情况:js 调用原生方法,原生调用 js 方法。 本文将对这两种情况分别讲解,H5 端用 vue 实现。 一、前期准备(Vue项目准备) 本文的 H5 端用Vue 实现,所以在正式开始前先把 Vue 项目环境准备好。 项目写好后,执行 npm run serve 命令启动项目,启动成功后会在命令
Vue 组件通信方式及其应用场景总结
前言 相信实际项目中用过vue的同学,一定对vue中父子组件之间的通信并不陌生,vue中采用良好的数据通讯方式,避免组件通信带来的困扰。今天笔者和大家一起分享vue父子组件之间的通信方式,优缺点,及其实际工作中的应用场景 首先我们带着这些问题去思考 1 vue中到底有多少种父子组件通信方式? 2 vue中那种父子组件最佳通信方式是什么? 3
基于Vue实现一个有点意思的拼拼乐小游戏
笔者去年曾写过一个类似的拼拼乐小游戏,技术栈采用自己的Xuery框架和原生javascript实现的,脚手架采用gulp来实现,为了满足对vue的需求,笔者再次使用vue生态将其重构,脚手架采用比较火的vuecli。 前言 为了加深大家对vue的了解和vue项目实战,笔者采用vue生态来重构此项目,方便大家学习和探索。技术栈如下: vuecli4
了解Vuex状态管理模式
1 Vuex是什么呢?它是Vue的状态管理模式,在使用vue的时候,需要在vue中各个组件之间传递值是很痛苦的,在vue中我们可以使用vuex来保存我们需要管理的状态值,值一旦被改变,所有引用该值的地方就会自动更新。是不是很方便,很好用呢? vuex是专门为vue.js设计的状态管理模式,集中式存储和管理应用程序中所有组件的状态,vuex也集成了vue的
史上最全前端面试题(但是没有答案 自己百度 手动狗头!)
Vue面试题 生命周期函数面试题 1.什么是 vue 生命周期 2.vue生命周期的作用是什么 3.第一次页面加载会触发哪几个钩子 4.简述每个周期具体适合哪些场景 5.created和mounted的区别 6.vue获取数据在哪个周期函数 7.请详细说下你对vue生命周期的理解? vue路由面试题 1.mvvm 框架是什么? 2.vuerout
Vue入门系列之Vue实例详解与生命周期
Vue的实例是Vue框架的入口,其实也就是前端的ViewModel,它包含了页面中的业务逻辑处理、数据模型等,当然它也有自己的一系列的生命周期的事件钩子,辅助我们进行对整个Vue实例生成、编译、挂着、销毁等过程进行js控制。 5.1. Vue实例初始
前端技术栈:5分钟入门VUE+Element UI
目录前端技术栈:5分钟入门VUEElement UI前言2021了,VUE都出3.0了,还不开始学习VUE?那不是一个全栈攻城狮的自我修养,虽然VUE出3.0了,但是入门还是建议VUE2.0 Element UI,毕竟3.0还要等养肥了在学,现在教程太少,学习2.0之后在学3.0也能更加理解为什么要这么去改进VUE是啥?简单来说就是
介绍 | Vue3中文文档
已经了解 Vue 2,只想了解 Vue 3 的新功能可以参阅 Vue.js 是什么Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与以及各种 结合使用时
30 道 Vue 面试题,内含详细讲解(涵盖入门到精通,自测 Vue 掌握程度)
30 道 Vue 面试题,内含详细讲解(涵盖入门到精通,自测 Vue 掌握程度)(https://www.zhihu.com/people/huobodexiaobaiyang).css1cd9gw4{marginleft:.3em;}545 人赞同了该文章前言
Vue 3 计划放弃支持 IE11
Vue.js 作者尤雨溪就 Vue 3 支持 IE11 的计划提交了新提案。提案摘要:1. Vue 3 将不会支持 IE11 2. 原定投入 Vue 3 IE11 支持的精力将投入给 2.7,移植 3.x 兼容的新功能,包括: Composition API \<script setup\ 以及其它新的单文件组件特性
npm发布包以及更新包还有需要注意的几点问题(这里以发布vue插件为例)
前言在此之前,你需要去npm官网注册一个属于自己的账号,记住自己的账户名以及密码、邮箱,后面会用的到。第一步,安装webpack简易框架vue init webpacksimple marquee 这里会用到vue init 命令,如果你的cli版本是3或者以上,那么在此之前你需要安装vue/cliinit npm install g @vue
前端培训-Vue专题之Vue基础
简介特点:MVVM框架,双向绑定,数据驱动,单页面,组件化。 区别Vue 和 jQuery 的区别:不直接操作DOM,而是操作数据。案例:Hello World 你好,世界HTML代码:xml<h1msg</h1jQuery实现javascript$("h1").text("你好,世界");Vue 实现javascriptthis.msg '你好,世界'
Vue 从安装到创建项目
1.安装Node可以直接在官网中下载安装默认自动安装Node和NPM(Node Package Manager) 完成后检查安装版本:node v npm v2.安装webpack webpack全局安装npm install webpack g3.安装vue脚手架 全局安装脚手架3npm install @vue/cli g 备注