javascript代码实例教程-MVVM大比拼之vue.js源码精析

发布时间:2019-02-04 发布网站:脚本宝典
脚本宝典收集整理的这篇文章主要介绍了javascript代码实例教程-MVVM大比拼之vue.js源码精析脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。
小宝典致力于为广大程序猿(媛)提供高品质的代码服务,请大家多多光顾小站,小宝典在此谢过。 VUE 源码分析

简介

Vue 是 MVVM 框架中的新贵,如果我没记错的话作者应该毕业不久,现在在GOOGLE。vue 如作者自己所说,在api设计上受到了很多来自knockout、Angularjs等大牌框架影响,但作者相信 vue 在性能、易用性方面是有优势。同时也自己做了和其它框架的性能对比,在这里。

今天以版本 0.10.4 为准

 

入口

Vue 的入口也很直白:

 

1

VAR demo = new Vue({ el: '#demo', data: { message: 'Hello Vue.js!' } })

  

 

和 ko 、avalon 不同的是,vue 在一开始就必须指定 el 。个人认为这里设计得不是很合理,因为如果一份数据要绑定到两个不同dom节点上,那就不得不指定一个同时包含了这两个dom节点的祖先dom节点。

接下来去找 Vue 的定义。翻开码,vue 用 grunt。build命令中用了作者自己写的gulp-component来组合代码片段。具体请读者自己看看,这里不仔细说了。

 

从 /src/main.js 里看到,Vue 的定义就是 ViewModal 的定义。打开 ViewModel,发现它的定义中只是实例化了一个 Compiler,把自己作为参数传给构造函数。同时看到 ViewModel 原型上定义了一些方法,基本上是跟内部事件、dom 操作有关。那接下来我们就主要看看这个 compiler了。不要忘了我们第一个目的是找到它双工绑定的主要原理。

 

双工绑定

翻到 compiler 的定义,代码太长。犹豫了一下决定还是删掉一些注释贴出来,因为基本上大部分值得看的都在这里,愿深入的读者最好看源文件。

 

 

function Compiler (vm, options) {

    var compiler = this,

        key, i

 

    compiler.inIT       = true

    compiler.destroyed  = false

    options = compiler.options = options || {}

    utils.PRocessOptions(options)

    extend(compiler, options.COMpilerOptions)

    compiler.rePEat   = compiler.repeat || false

    compiler.expCache = compiler.expCache || {}

    var el = compiler.el = compiler.SETUPElement(options)

    utils.LOG('/nnew VM instance: ' + el.tagName + '/n')

     

    compiler.vm       = el.vue_vm = vm

    compiler.bindings = utils.hash()

    compiler.dirs     = []

    compiler.deferred = []

    compiler.computed = []

    compiler.children = []

    compiler.emitter  = new Emitter(vm)

 

    if (options.methods) {

        for (key in options.methods) {

            compiler.createBinding(key)

        }

    }

 

    if (options.computed) {

        for (key in options.computed) {

            compiler.createBinding(key)

        }

    }

 

    // VM ---------------------------------------------------------------------

 

    vm.$         = {}

    vm.$el       = el

    vm.$options  = options

    vm.$compiler = compiler

    vm.$event    = null

 

    var parentVM = options.parent

    if (parentVM) {

        compiler.parent = parentVM.$compiler

        parentVM.$compiler.children.push(compiler)

        vm.$parent = parentVM

    }

    vm.$root = getRoot(compiler).vm

 

    // DATA -------------------------------------------------------------------

    compiler.setupObserver()

     

    var data = compiler.data = options.data || {},

        defaultData = options.defaultData

    if (defaultData) {

        for (key in defaultData) {

            if (!hasOwn.call(data, key)) {

                data[key] = defaultData[key]

            }

        }

    }

 

    var params = options.paramAttributes

    if (params) {

        i = params.length

        while (i--) {

            data[params[i]] = utils.checkNumber(

                compiler.eval(

                    el.getAttribute(params[i])

                )

            )

        }

    }

 

    extend(vm, data)

    vm.$data = data

    compiler.execHook('created')

    data = compiler.data = vm.$data

 

    var vmProp

    for (key in vm) {

        vmProp = vm[key]

        if (

            key.charAt(0) !== '$' &&

            data[key] !== vmProp &&

            typeof vmProp !== 'function'

        ) {

            data[key] = vmProp

        }

    }

 

    compiler.observeData(data)

 

    // COMPILE ----------------------------------------------------------------

    if (options.template) {

        this.resolveContent()

    }

    while (i--) {

        compiler.bindDirective(compiler.deferred[i])

    }

    compiler.deferred = null

 

    if (this.computed.length) {

        DepsParser.parse(this.computed)

    }

 

    compiler.init = false

    compiler.execHook('ready')

}

  

 

注释就已经写明了 compiler 实例化分为四个阶段,第一阶段是一些基础的设置。两个值得注意的点:一是在 compiler 里面定义一个 vm 属性来保存对传入的 ViewModel 的引用;二是对 method 和 computed 的每一个成员都调用了 createBinding 。跳到 createBinding:

 

 

CompilerProto.createBinding = function (key, directive) {

    /*省略*/

    var compiler = this,

        methods  = compiler.options.methods,

        isExp    = directive && directive.isExp,

        isFn     = (directive && directive.isFn) || (methods && methods[key]),

        bindings = compiler.bindings,

        computed = compiler.options.computed,

        binding  = new Binding(compiler, key, isExp, isFn)

 

    if (isExp) {

        /*省略*/

    } else if (isFn) {

        bindings[key] = binding

        binding.value = compiler.vm[key] = methods[key]

    } else {

        bindings[key] = binding

        if (binding.root) {

            /*省略*/

            if (computed && computed[key]) {

                // computed property

                compiler.defineComputed(key, binding, computed[key])

            } else if (key.charAt(0) !== '$') {

                /*省略*/

            } else {

                /*省略*/

            }

        } else if (computed && computed[utils.baseKey(key)]) {

            /*省略*/

        } else {

            /*省略*/

        }

    }

    return binding

}

  

 

它做了两件事情:一是实例化了一个叫做 Bingding 的东西,二是将 method 和 computed 成员的 bingding 进行了一些再处理。凭直觉和之前看过的代码,我们可以大胆猜测这个实例化的 bingding 很可能就是用来保存数据和相应地"更新回调函数"的集合。点进 /src/binding 里。果然,看到其中的 update 、pub 等函数和 sub 、dir 等对象成员,基本证明猜对了。

 

到这里,实例化的对象已经有点多了。后面还会更多,为了让各位不迷失,请提前看看这张关键对象图:

 

 

 

看完 bingding,我们继续回到 createBinding 中,刚才还说到对 method 和 computed 成员的 bingding 做了一些再处理。对 method,就直接在 vm 上增加了一个同名的引用,我们可以把 vm 看做一个公开的载体,在上面做引用就相当于把自己公开了。对 computed 的成员,使用defineComputed 做的处理是:在vm上定义同名属性,并将 getter/setter 对应到相应computed成员的$get和$set。

 

至此,compiler 的第一部分做完,基本上把数据的架子都搭好了。我们看到 bingding 的 pub 和 sub, 知道了 vue 也是就与 observe 模式,那接下来就看看它是如何把把视图编译成数据更新函数,并注册到bingding里。

 

回到compiler里,第二部分处理了一下vm,增加了一些引用。 第三部分关键的来了,一看就知道最重要的就是第一句 compiler.setupObserver() 和最后一句compiler.observeData(data) 。直接看源码的读者,注释里已经很清楚了。第一句是用来注册一些内部事件的。最后一句是用来将数据的成员转化成 getter/setter。并和刚刚提到的bingding 相互绑定。值得注意的是,如果遇到数据成员是对象或者数组,vue 是递归式将它们转化成 getter/setter 的,所以你嵌套多深都没关系,直接替换掉这些成员也没关系,它对新替换的对象重新递归式转化。

 

这里的代码都很易懂,读者可以自己点进去看。我只想说一点,就是 vue 在内部实现中使用了很多事件派发器,也就是 /src/emitter。比如对数据的 set 操作。在 set 函数只是触发一个 set 事件,后面的视图更新函数什么都是注册这个事件下的。这个小小的设计让关键的几个模块解耦得非常好,能够比较独立地进行测试。同时也为框架本身的扩展提供了很多很多的空间。下面这张图展示了对data的成员进行修改时内部的事件派发:

 

 

 

视图渲染和扩展

看到最后一部分视图渲染,这里值得注意的是,vue 支持的是 angular 风格的可复用的directive。directive 的具体实现和之前的 ko 什么的没太大区别,都是声明 bind、update等函数。

 

至于扩展方面,vue已有明确的 component 和 plugin 的概念,很好理解,读者看看文档即可。 另外注意下,vue 是到最后才处理 computed 和普通数据的依赖关系的。

 

总结

总体来说,vue 在内核架构上很精巧。精指的是没有像ko一样先实现一些强大但复杂的数据结构,而是需要什么就实现什么。巧指的是在代码架构上既完整实现了功能,又尽量地解耦,为扩展提供了很大的空间。比如它使用了 binding 这样一个中间体,而不是将试图更新函数直接注册到数据的set函数中等等,这些设计都是值得学习了。 当然我们也看到了一些有异议的地方: 比如是否考虑将数据的转化和视图编译明确分成两个过程?这样容易实现数据的复用,也就是最开始讲的问题。这样改的话,compiler 的实例化的代码也可以稍微更优雅一些:先处理数据和依赖关系,再建立bingding并绑定各种事件,最后处理视图。

 

 

觉得可用,就经常来吧! 脚本宝典 欢迎评论哦! js脚本,巧夺天工,精雕玉琢。小宝典献丑了!

脚本宝典总结

以上是脚本宝典为你收集整理的javascript代码实例教程-MVVM大比拼之vue.js源码精析全部内容,希望文章能够帮你解决javascript代码实例教程-MVVM大比拼之vue.js源码精析所遇到的问题。

如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典推荐好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。