test

作者: Obeing | 来源:发表于2017-06-07 08:54 被阅读0次
  • 上一节说到,require(‘vue’) 最终返回的是require('vue/src/main.js')里面的module.exports,而在require('vue/src/main.js')内部有这样一句,可以得知,最后返回的是viewModel
Paste_Image.png
  • ViewModel = require('./viewmodel') 如何转为ViewModel =require(‘vue/src/viewmodel.js’)

  • 源码没有‘./viewmodel’路径的注册模块,而是通过require.relative来处理

  • 由于是在‘vue/src/viewmodel.js’模块中,所以parent路径为‘vue/src/viewmodel.js’,得到的上级路径就是‘vue/src’从而寻找到了‘vue/src/viewmodel.js’ 模块

  • 至此 new Vue相当于 new ViewModel

    Paste_Image.png
  • ViewModel构造函数,实际上又调用了另一个构造函数 Compiler

Paste_Image.png
  • Compiler位于注册模块‘vue/src/compiler.js‘, 这部分代码比较好理解,传ViewModel的实例(this)设置一些状态属性,把options传到Compiler等,但是要注意 utils.processOptions(options)这一句
Paste_Image.png
  • 理解utils.processOptions(options)的处理

    • 可以看到processOptions对每一个组件、partials(vue2.0已移除)、过滤器、模板进行相应处理


      processOptions
  • todo 这里可以先记录utils.toContructor()、parseTemplateOption()、checkFilter()方法,以后有时间再深入研究做了什么处理

  • 接下来的三句代码中由于现在传给vue的options只有el和data所以并没有什么意义

Paste_Image.png
  • 接下来就是初始化元素
// initialize element
    var el = compiler.el = compiler.setupElement(options)
// setupElement方法 
CompilerProto.setupElement = function (options) {
    // create the node first 判断el是不是字符串,如果是则用querySelector去获取,如果不是,则判断是否存在,如果否根据tagName创建一个标签
    var el = typeof options.el === 'string'
        ? document.querySelector(options.el)
        : options.el || document.createElement(options.tagName || 'div')

    var template = options.template,
        child, replacer, i, attr, attrs
// 如果template选项存在则,遍历循环el元素的子元素,并添加到rawContent中,其中由于使用了appendChild方法,每次添加后el.firstchild都会变成下一个子元素
    if (template) {
        // collect anything already in there
        if (el.hasChildNodes()) { // 这里处理时已经将el的子元素append到了this.rawContent上
            this.rawContent = document.createElement('div')
            /* jshint boss: true */
            while (child = el.firstChild) {
                this.rawContent.appendChild(child)
            }
        }
        // replace option: use the first node in
        // the template directly 这里是判断是否传入了replace属性, 如果为false将模板插入(注意原来el里面的内容不保留),如果为true,默认为true,template内容直接替换
        if (options.replace && template.firstChild === template.lastChild) { 处理template只有一个元素并且repalce为true时的情况
            replacer = template.firstChild.cloneNode(true)
            if (el.parentNode) {
                el.parentNode.insertBefore(replacer, el)
                el.parentNode.removeChild(el)
            }
            // copy over attributes
            if (el.hasAttributes()) {
                i = el.attributes.length
                while (i--) {
                    attr = el.attributes[i]
                    replacer.setAttribute(attr.name, attr.value)
                }
            }
            // replace
            el = replacer
        } else {
            el.appendChild(template.cloneNode(true))
        }

    }

    // apply element options
    if (options.id) el.id = options.id
    if (options.className) el.className = options.className
    attrs = options.attributes
    if (attrs) {
        for (attr in attrs) {
            el.setAttribute(attr, attrs[attr])
        }
    }

    return el
}
repalce用法 repalce用法2
  • 经过下列代码的设置,得到了compiler 和 vm上面的属性,其中Compiler实例.vm指向vm, ViewModel的实例.$compiler指向Compiler


    Paste_Image.png
Paste_Image.png Paste_Image.png
  • 这里主要获取并设置vm的$root, => todo options.parent在哪里出现
Paste_Image.png Paste_Image.png
  • compiler.setupObserver()
钩子函数的定义.png
CompilerProto.setupObserver = function () {
    var compiler = this,
        bindings = compiler.bindings,
        options  = compiler.options,
        observer = compiler.observer = new Emitter(compiler.vm)

    // a hash to hold event proxies for each root level key
    // so they can be referenced and removed later
    //用于保存每个根级别密钥的事件代理的哈希以便以后可以引用和删除它们
    observer.proxies = {}

    // add own listeners which trigger binding updates
    // 为compiler的binding绑定事件
    // compiler.bindings = utils.hash() 追踪utils..hash() 可以找到 
  /**
     *  Create a prototype-less object
     *  which is a better hash/map 
    * 通过Object.create(null)创建出来的对象是一个无属性对象,可以更好的做一个hash映射
       * hash: function () {
       *  return Object.create(null)
    },
    */
    
    observer
        .on('get', onGet)
        .on('set', onSet)
        .on('mutate', onSet)
    
    //注册钩子函数,钩子数组见上图
    // register hooks
    var i = hooks.length, j, hook, fns
    while (i--) {
        hook = hooks[i]
        fns = options[hook]
        if (Array.isArray(fns)) { // 从源码看每个钩子可以挂一个函数也可以挂一个数组
            j = fns.length
            // since hooks were merged with child at head,
            // we loop reversely.
            while (j--) {
                registerHook(hook, fns[j])
            }
        } else if (fns) {
            registerHook(hook, fns)
        }
    }

    // broadcast attached/detached hooks
    observer
        .on('hook:attached', function () {
            broadcast(1)
        })
        .on('hook:detached', function () {
            broadcast(0)
        })
// DepsParser 是通过一个记录触发getterd时自动提取一个计算属性依赖关系的模块
    function onGet (key) {
        check(key)
        DepsParser.catcher.emit('get', bindings[key])
    }
    function onSet (key, val, mutation) {
        observer.emit('change:' + key, val, mutation)
        check(key)
        bindings[key].update(val)
    }
    function registerHook (hook, fn) {
        observer.on('hook:' + hook, function () {
            fn.call(compiler.vm)
        })
    }
    function broadcast (event) {
        var children = compiler.children
        if (children) {
            var child, i = children.length
            while (i--) {
                child = children[i]
                if (child.el.parentNode) {
                    event = 'hook:' + (event ? 'attached' : 'detached')
                    child.observer.emit(event)
                    child.emitter.emit(event)
                }
            }
        }
    }
    function check (key) {
        if (!bindings[key]) {
            compiler.createBinding(key)   // 下面分析createBinding ,这里先理解为创建一个对象
        }
    }
}
Paste_Image.png
  • 以method:{testFn: function(){}}为例分析compiler.createBinding()
Paste_Image.png
  • new Binding(compiler, key, isExp, isFn)
Paste_Image.png Paste_Image.png
  • compiler.defineVmProp(key, binding, methods[key])

    • 里面的def 是Object.defineProperty缩写 Object.defineProperty(obj, prop, descriptor)


      Paste_Image.png
  • 将testFn挂在到vm实例上,此时的实例对象如图,这也就不难解释我们为什么能在实例中直接用this.testFn()调用

Paste_Image.png
  • 挂在数据到vm上和$data中


    Paste_Image.png
  • 开始执行created钩子compiler.execHook('created')

CompilerProto.execHook = function (event) {
    event = 'hook:' + event
    this.observer.emit(event)
    this.emitter.emit(event)
}
  • 经过了created的钩子函数用户可能已经更新了数据,所以有下面一句
data = compiler.data = vm.$data
  • 用户还可以在vm中设置一些属性,在这种情况下,我们应该将其复制回$data,这里的data与上面的compiler.data = vm.$data均为引用同一个对象的关系,所以改变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) 由于涉及到事件问题,这里暂不深入理解

/**
现在我们可以观察数据。
这将将数据属性转换为getter / setter
并发出第一批设置事件
反过来又创建相应的绑定。
**/


- 解析template模板

```js
CompilerProto.resolveContent = function () {
    var outlets = slice.call(this.el.getElementsByTagName('content')), // 这里抽离出template中的content,可以将原来el中的内容提出,
                                                                       // 例如: template: "<div><content></content></div>"
        raw = this.rawContent, // 在初始化元素中设置的rawContent,里面包含el原有的元素
        outlet, select, i, j, main

  i = outlets.length
    if (i) {
        // first pass, collect corresponding content
        // for each outlet.
        while (i--) {
            outlet = outlets[i]
            if (raw) { // 如果el原来存在有子元素
                select = outlet.getAttribute('select')
                if (select) { // select content
                    outlet.content =
                        slice.call(raw.querySelectorAll(select))
                } else { // default content
                    main = outlet
                }
            } else { // fallback content
                outlet.content =
                    slice.call(outlet.childNodes)
            }
        }
        // second pass, actually insert the contents
        for (i = 0, j = outlets.length; i < j; i++) {
            outlet = outlets[i]
            if (outlet === main) continue
            insert(outlet, outlet.content)
        }
        // finally insert the main content
        if (raw && main) {
            insert(main, slice.call(raw.childNodes)) //将原来el的子元素插入content的父元素中
        }
    }

    function insert (outlet, contents) {
        var parent = outlet.parentNode,
            i = 0, j = contents.length
        for (; i < j; i++) {
            parent.insertBefore(contents[i], outlet)
        }
        parent.removeChild(outlet)
    }

    this.rawContent = null
}

  • compiler.compile(el, true) 1096行
    1. 解析el为元素且不为script的情况

if (nodeType === 1 && node.tagName !== 'SCRIPT') { // a normal node
this.compileElement(node, root)
}

- **this.compileElement(node, root)** // node为el,root = true

```js
// 以<div id="demo">
//  <b>{{test}}</b>
// </div> 分析vue是如何编译 
CompilerProto.compileElement = function (node, root) {

     //textarea很烦人
     //因为它的值创建childNodes
     //我们不想编译。
    if (node.tagName === 'TEXTAREA' && node.value) {
        node.value = this.eval(node.value)
    }

   //只有当这个元素有属性时才编译
     //或者其tagName包含一个连字符号(这意味着它可以
     //可能是一个自定义元素)
    if (node.hasAttributes() || node.tagName.indexOf('-') > -1) {

        // skip anything with v-pre
        if (utils.attr(node, 'pre') !== null) { // utils将处理v-pre,获取此属性值
            return
        }

        var i, l, j, k

      
//优先级指令列表
//需要按照特定的顺序进行检查
priorityDirectives = [
        'if',
        'repeat',
        'view',
        'component'
    ]
//检查优先级指令。
//如果其中任何一个存在,它将接管带有一个childVM的节点
//所以我们可以跳过其他
        for (i = 0, l = priorityDirectives.length; i < l; i++) {
            if (this.checkPriorityDir(priorityDirectives[i], node, root)) {
                return
            }
        }

        // 检查是否有transition和animation属性
        node.vue_trans  = utils.attr(node, 'transition')
        node.vue_anim   = utils.attr(node, 'animation')
        node.vue_effect = this.eval(utils.attr(node, 'effect'))

        var prefix = config.prefix + '-',
            params = this.options.paramAttributes,
            attr, attrname, isDirective, exp, directives, directive, dirname


          // v-with 在其他指令中有特别优先权
         //在计算的属性被评估之前它需要从父项中提取值,因为在这个阶段
         //计算的属性尚未设置它们的依赖关系。
        if (root) {
            var withExp = utils.attr(node, 'with')
            if (withExp) {
                directives = this.parseDirective('with', withExp, node, true)
                for (j = 0, k = directives.length; j < k; j++) {
                    this.bindDirective(directives[j], this.parent)
                }
            }
        }

        var attrs = slice.call(node.attributes)
        for (i = 0, l = attrs.length; i < l; i++) {

            attr = attrs[i]
            attrname = attr.name
            isDirective = false

            if (attrname.indexOf(prefix) === 0) {  //判断是不是v-的指令
                // a directive - split, parse and bind it.
                isDirective = true
                dirname = attrname.slice(prefix.length)
                // build with multiple: true
                directives = this.parseDirective(dirname, attr.value, node, true) // 如果是则走指令的解释通道
                // loop through clauses (separated by ",")
                // inside each attribute
                for (j = 0, k = directives.length; j < k; j++) {
                    this.bindDirective(directives[j])
                }
            } else if (config.interpolate) {
                // 非指令属性,检查插值标签
                //TextParser.parseAttr使用可能的插补标签解析属性值
                //返回一个指令友好的表达式 e.g.  a {{b}} c  =>  "a " + b + " c"
                exp = TextParser.parseAttr(attr.value) // attr.value为demo
                if (exp) {
                    directive = this.parseDirective('attr', exp, node)
                    directive.arg = attrname
                    if (params && params.indexOf(attrname) > -1) {
                        // a param attribute... we should use the parent binding
                        // to avoid circular updates like size={{size}}
                        this.bindDirective(directive, this.parent)
                    } else {
                        this.bindDirective(directive)
                    }
                }
            }

            if (isDirective && dirname !== 'cloak') {
                node.removeAttribute(attrname)
            }
        }

    }

    // 递归子元素 
    if (node.hasChildNodes()) {
        slice.call(node.childNodes).forEach(this.compile, this)
    }
}

2.解析文本元元素的情况

else if (nodeType === 3 && config.interpolate) {
        this.compileTextNode(node)
    }
  • compileTextNode()
// 以<b>{{test}}</b>中的{{test}}的文本节点为例
CompilerProto.compileTextNode = function (node) {

    var tokens = TextParser.parse(node.nodeValue) // node.nodeValue= {{test}},解析后的tokens=[{html:false,key:"test"}]
    if (!tokens) return
    var el, token, directive

    for (var i = 0, l = tokens.length; i < l; i++) {

        token = tokens[i] // tokens={html:false,key:"test"}
        directive = null

        if (token.key) { // a binding
            if (token.key.charAt(0) === '>') { // a partial
                el = document.createComment('ref')
                directive = this.parseDirective('partial', token.key.slice(1), el)
            } else {
                if (!token.html) { // text binding
                    el = document.createTextNode('')
                    directive = this.parseDirective('text', token.key, el) // 返回一个包含bind和update函数的对象
                } else { // html binding
                    el = document.createComment(config.prefix + '-html')
                    directive = this.parseDirective('html', token.key, el)
                }
            }
        } else { // a plain string
            el = document.createTextNode(token)
        }

        // insert node
        node.parentNode.insertBefore(el, node) // 将文本元素插入
        // bind directive
        this.bindDirective(directive) // 绑定指令。解析如下

    }
    node.parentNode.removeChild(node)// 删除原来的{{test}}
}

this.bindDirective(directive)

CompilerProto.bindDirective = function (directive, bindingOwner) {

    if (!directive) return

    // keep track of it so we can unbind() later
    this.dirs.push(directive)

    // 对于空或者文字指令我们仅仅调用bind方法
    if (directive.isEmpty || directive.isLiteral) {
        if (directive.bind) directive.bind()
        return
    }

    // otherwise, we got more work to do...
    var binding,
        compiler = bindingOwner || this,
        key      = directive.key

    if (directive.isExp) {
        // expression bindings are always created on current compiler
        binding = compiler.createBinding(key, directive)
    } else {
        //递归地定位哪个编译器拥有绑定
        while (compiler) {
            if (compiler.hasKey(key)) {
                break
            } else {
                compiler = compiler.parent
            }
        }
        compiler = compiler || this
        binding = compiler.bindings[key] || compiler.createBinding(key)
    }
    binding.dirs.push(directive)
    directive.binding = binding

    var value = binding.val()
    // invoke bind hook if exists
    if (directive.bind) {
        directive.bind(value)
    }
    //初始化value值
    directive.$update(value, true) // 这里实际上是调用了directives.text的update方法
}

directives.text

directives.text = {
    bind: function () {
        this.attr = this.el.nodeType === 3
            ? 'nodeValue'
            : 'textContent'
    },
    update: function (value) { 
        this.el[this.attr] = utils.guard(value) // 设置nodeValue值可以将文本元素直接设为value值
    }
}

相关文章

  • 泰斯拓

    TEST test Test TEST test test test test test test test

  • makedown test

    test test test test test test test ####### test test test...

  • 无标题文章

    test test test test test test test test

  • 2019-01-14

    test test test test test test test test

  • test2

    test test test test test test

  • 简书

    简书 test test test test test test

  • Test

    Test test Test Test Test

  • 无标题文章

    test test test test test

  • 此处为标题?

    测试test测试test测试test测试test测试test测试test测试test测试test测试test测试t...

  • Mardown

    Mardown test+test+test+test+test

网友评论

      本文标题:test

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