美文网首页
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早期源码阅读(二)

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