美文网首页
Vue早期源码阅读(二)

Vue早期源码阅读(二)

作者: lizhihua | 来源:发表于2016-06-09 19:07 被阅读157次

Vue.js 早期源码阅读(版本号154861f71d4886251e0057c74f07c786f5262081),关于ViewModel的一点分析,这一次代码来的比上一次复杂的多,完全不谦虚的说,以我的能力可谓是勉强往下看。总体感觉代码写的非常好(和自己平时写的一坨代码相比,完全不是一个档次),悟性太低看了只有一点点小收获而已。代码太长粘在最下面。

一、依赖关系
main.js
require('./directive')
require('./directives')

directive
require('./directives')
require('./filters')

二、导出函数和作用域内函数

main.js
1.module.exports.create
  1)Seed.constructor
  2)Seed.prototype.compileNode
  3)Seed.prototype.bind
  4)Seed.prototype.createBinding
  5)Seed.prototype.dump
  6)Seed.prototype.destroy
  7)scope.cloneAttributes
2.module.exports.directive
3.module.exports.filter

directive.js
1.module.exports.parse
  1)Directive.constructor
  2)Directive.prototype.update
  3)Directive.prototype.applyFilters

directives.js
1.module.exports.text
2.module.exports.show
3.module.exports.class
4.module.exports.on
  1)module.exports.on.update
  2)module.exports.on.unbind
5.module.exports.each
6.scope.augmentArray

filters.js
1.module.exports.capitalize
2.module.exports.uppercase
3.module.exports.delegate

三、执行分析
1.Seed.create(此Seed是变量名)传入参数创建Seed实例(此Seed是main.js内部的函数名)
2.Seed做了3件事
以下root指#test,select指[sd-text],[sd-show],[sd-class],[sd-on],[sd-each]
1)遍历具有指令的元素(root.querySelectorAll(selector)),调用compileNode解析节点,此时的生命周期处于beforeCompile(按照官网文档参考,命名不一定准确)
2)对根节点root,调用compileNode解析节点
3)上面已经为劫持了data(绑定了getter和setter),并将具有指令的元素和setter建立了映射关系

四、函数分析

dev.html

<style type="text/css">
  .red {
    color: red;
  }
</style>
<body>
    <div id="test" sd-on-click="changeMessage | delegate .button">
          <p sd-text="msg.wow | capitalize" sd-on-click="remove"></p>
          <p sd-text="msg.wow | uppercase" class="button"></p>
          <p sd-class-red="error" sd-text="hello"></p>
          <div sd-each="todos">
            <span sd-text="text"></span>
          </div>
      </div>
  <script>
    var Seed = require('seed')
    var app = Seed.create({
        id: 'test',
        // template
        scope: {
            'msg.wow': 'wow',
            hello: 'hello',
            changeMessage: function () {
                app.scope['msg.wow'] = 'hola'
            },
            remove: function () {
                app.destroy()
            },
            todos: [
              {
                title: 'make this shit work',
                done: false
              },
              {
                title: 'make this shit kinda work',
                done: true
              }
            ]
        }
    })
  </script>
</body>

main.js

var prefix      = 'sd',
    Directive   = require('./directive'),
    Directives  = require('./directives'),
    selector    = Object.keys(Directives).map(function (d) {
        return '[' + prefix + '-' + d + ']'
    }).join()

function Seed (opts) {

    var self = this,
        root = this.el = document.getElementById(opts.id),
        els  = root.querySelectorAll(selector)

    self.bindings = {}
    self.scope = {}

    // process nodes for directives
    ;[].forEach.call(els, this.compileNode.bind(this))
    this.compileNode(root)

    // initialize all variables by invoking setters
    for (var key in self.bindings) {
        self.scope[key] = opts.scope[key]
    }

}

Seed.prototype.compileNode = function (node) {
    var self = this
    cloneAttributes(node.attributes).forEach(function (attr) {
        var directive = Directive.parse(attr, prefix)
        if (directive) {
            self.bind(node, directive)
        }
    })
}

Seed.prototype.bind = function (node, directive) {

    directive.el = node
    node.removeAttribute(directive.attr.name)

    var key      = directive.key,
        binding  = this.bindings[key] || this.createBinding(key)

    // add directive to this binding
    binding.directives.push(directive)

    // invoke bind hook if exists
    if (directive.bind) {
        directive.bind(node, binding.value)
    }

}

Seed.prototype.createBinding = function (key) {

    var binding = {
        value: undefined,
        directives: []
    }

    this.bindings[key] = binding

    // bind accessor triggers to scope
    Object.defineProperty(this.scope, key, {
        get: function () {
            return binding.value
        },
        set: function (value) {
            binding.value = value
            binding.directives.forEach(function (directive) {
                directive.update(value)
            })
        }
    })

    return binding
}

Seed.prototype.dump = function () {
    var data = {}
    for (var key in this._bindings) {
        data[key] = this._bindings[key].value
    }
    return data
}

Seed.prototype.destroy = function () {
    for (var key in this._bindings) {
        this._bindings[key].directives.forEach(unbind)
    }
    this.el.parentNode.remove(this.el)
    function unbind (directive) {
        if (directive.unbind) {
            directive.unbind()
        }
    }
}

// clone attributes so they don't change
function cloneAttributes (attributes) {
    return [].map.call(attributes, function (attr) {
        return {
            name: attr.name,
            value: attr.value
        }
    })
}

module.exports = {
    create: function (opts) {
        return new Seed(opts)
    },
    directive: function () {
        // create dir
    },
    filter: function () {
        // create filter
    }
}

directive.js

var Directives = require('./directives'),
    Filters    = require('./filters')

var KEY_RE = /^[^\|]+/,
    FILTERS_RE = /\|[^\|]+/g

function Directive (def, attr, arg, key) {

    if (typeof def === 'function') {
        this._update = def
    } else {
        for (var prop in def) {
            if (prop === 'update') {
                this['_update'] = def.update
                continue
            }
            this[prop] = def[prop]
        }
    }

    this.attr = attr
    this.arg  = arg
    this.key  = key
    
    var filters = attr.value.match(FILTERS_RE)
    if (filters) {
        this.filters = filters.map(function (filter) {
            // TODO test performance against regex
            var tokens = filter.replace('|', '').trim().split(/\s+/)
            return {
                apply: Filters[tokens[0]],
                args: tokens.length > 1 ? tokens.slice(1) : null
            }
        })
    }
}

Directive.prototype.update = function (value) {
    // apply filters
    if (this.filters) {
        value = this.applyFilters(value)
    }
    this._update(value)
}

Directive.prototype.applyFilters = function (value) {
    var filtered = value
    this.filters.forEach(function (filter) {
        filtered = filter.apply(filtered, filter.args)
    })
    return filtered
}

module.exports = {

    // make sure the directive and value is valid
    parse: function (attr, prefix) {
        
        if (attr.name.indexOf(prefix) === -1) return null

        var noprefix = attr.name.slice(prefix.length + 1),
            argIndex = noprefix.indexOf('-'),
            arg = argIndex === -1
                ? null
                : noprefix.slice(argIndex + 1),
            name = arg
                ? noprefix.slice(0, argIndex)
                : noprefix,
            def = Directives[name]

        var key = attr.value.match(KEY_RE)

        return def && key
            ? new Directive(def, attr, arg, key[0].trim())
            : null
    }
}

directives.js

module.exports = {

    text: function (value) {
        this.el.textContent = value || ''
    },

    show: function (value) {
        this.el.style.display = value ? '' : 'none'
    },

    class: function (value) {
        this.el.classList[value ? 'add' : 'remove'](this.arg)
    },

    on: {
        update: function (handler) {
            var event = this.arg
            if (!this.handlers) {
                this.handlers = {}
            }
            var handlers = this.handlers
            if (handlers[event]) {
                this.el.removeEventListener(event, handlers[event])
            }
            if (handler) {
                handler = handler.bind(this.el)
                this.el.addEventListener(event, handler)
                handlers[event] = handler
            }
        },
        unbind: function () {
            var event = this.arg
            if (this.handlers) {
                this.el.removeEventListener(event, this.handlers[event])
            }
        }
    },

    each: {
        update: function (collection) {
            augmentArray(collection, this)
        },
        mutate: function (mutation) {
            console.log(mutation)
        }
    }

}

var push = [].push,
    slice = [].slice

function augmentArray (collection, directive) {
    collection.push = function (element) {
        push.call(this, arguments)
        directive.mutate({
            event: 'push',
            elements: slice.call(arguments),
            collection: collection
        })
    }
}

filters.js

module.exports = {

    capitalize: function (value) {
        value = value.toString()
        return value.charAt(0).toUpperCase() + value.slice(1)
    },

    uppercase: function (value) {
        return value.toUpperCase()
    },

    delegate: function (handler, selectors) {
        return function (e) {
            var match = selectors.every(function (selector) {
                return e.target.webkitMatchesSelector(selector)
            })
            if (match) handler.apply(this, arguments)
        }
    }

}

相关文章

  • Vue早期源码阅读(二)

    Vue.js 早期源码阅读(版本号154861f71d4886251e0057c74f07c786f5262081...

  • Vue.js早期源码阅读(一)

    Vue.js 早期源码阅读(版本号a5e27b1174e9196dcc9dbb0becc487275ea2e84c...

  • Vue源码阅读(二)

    组件化 vue可以使用组件化来开发,在前边介绍_createElement方法时,在对原生的tag时直接创建vno...

  • Vue源码阅读(二)

    Vue实例   如果简单了解过些Vue的API的话,肯定会对一下这个特别熟悉,在上一篇里,分析了Vue的核心文件c...

  • Vue源码学习

    源码地址[https://github.com/vuejs/vue]源码阅读路径 scripts/build.js...

  • 深入Vue - 源码目录及构建过程分析

    摘要: Vue源码阅读第一步。 原文:深入vue - 源码目录及构建过程分析 公众号:前端小苑 Fundebug经...

  • 动态数据绑定(一)

    动态数据绑定(一) vue早期源码学习系列之一:如何监听一个对象的变化 方法一

  • 动态数据绑定(二)

    动态数据绑定(一) vue早期源码学习系列之一:如何监听一个对象的变化 方法一

  • 2020 一起读 vue 源码

    最近在收集一些资料来帮助自己理解和阅读 vue 的源码。在学习和阅读过程中,发现我们读解 vue 源码难度是在于很...

  • Vue 学习笔记

    [TOC] Vue 学习笔记 Vue 源码解析 - 主线流程 Vue 源码解析 - 模板编译 Vue 源码解析 -...

网友评论

      本文标题:Vue早期源码阅读(二)

      本文链接:https://www.haomeiwen.com/subject/hqfsdttx.html