vue源码解析之(第二步 模板编译)

发布时间:2024年01月12日
#### 解析模板 转化为 字符串DOM结构的过程
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <div>{{name}}</div>
    <span>{{age}}</span>
  </div>
  <script src="./vue.js"></script>
  <script>
    const vm = new Vue({

        data(){
    
            return { 

                 name:'zs',

                 age:12,

                 addres:['北京市','昌平区','回龙观'],

                 hobby:['a',{b:1}],

             }
        },

        template:`<div>123</div>` // template渲染模板 

        el:'#app' // 将数据解析到el元素上

    })

    vm.$mount('#app') // 实现挂载
  </script>
</body>
</html>

下面我们主要用来解析模板,同时将DOM结构的模板通过outerHTML的方法,转义为字符串进行编译。
?

import {observe} from './observe/index' // 这里先引入后期再下面要创建的方法
 
export const initMiXin = (Vue){
    
    Vue.prototype._init(options) {  // 这里接收的是src/index.js中传递的参数
        
        const vm = this // 这里的this是Vue
    
        vm.$options = options // 把数据挂载在vm.$options的属性上面
 
        initState(vm) // 初始化数据
 
    } 
 
    
}
 
function initState(vm){
    
   const ops =  vm.$options
 
    if(ops.data){  // 如果有data这个属性
    
        initData(vm) // 初始化Data
 
    }

    // 这里增加处理模板的思路
    
    if(ops.el){  ++++++++++++ 处理el模板的数据
    
        vm.$mount(ops.el) // 将el进行挂载

    }
 
}


Vue.prototype.$mount = function (el) { +++++ 原型上创建$mount的方法 进行模板数据的处理
    
    const vm = this // this执行创建Vue构造函数的实例对象
    
    el= document.queryselector(el) // 获取元素

    let ops = vm.$options

    if(!ops.render) {  // 不存在render渲染模板

        let template;

        if(!ops.template && el){  // options参数中只存在 el 

            template = el.outerHTML // outerHTML 这个方法是将DOM节点转化为了字符串来显示
        }else { 

            if(el) {
        
                template = ops.template // 这是由template渲染的模板

            }

        }

        if(template) { 

            const render = compileToFunction(template) // 对字符串的模板进行编译

            ops.render = render // 往$options上面挂载一个render方法

         }
     }
      // 这里最终可以获得ops.render的方法
       
    

}
 
function initData(vm){ 
    
    let data = vm.$options.data 
 
    data = typeof data === 'function'? data() : data // 判断data类型如果是函数的话就执行
 
    vm._data= data //再往Vue上面挂载一个_data的属性
 
    observe(data) // 这里对数据进行劫持
}
####模板转化为ast语法树 虚拟DOM 真实DOM
// 标签名 a-aaa
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`;  
// 命名空间标签 aa:aa-xxx
const qnameCapture = `((?:${ncname}\\:)?${ncname})`;
// 开始标签-捕获标签名
const startTagOpen = new RegExp(`^<${qnameCapture}`); 
// 结束标签-匹配标签结尾的 </div>
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`);
// 匹配属性
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/; 
// 匹配标签结束的 >
const startTagClose = /^\s*(\/?)>/;
// 匹配 {{ }} 表达式
const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g;


// 在这里 我们可以接受到字符串的DOM结构 然后进行编译

export const compileToFunction(template){

    // 这里主要是转template转化为ast语法数

    let ast = parseHTML(template) // 通过parseHTML方法转为ast语法数

    // 然后生成render方法 返回值是虚拟DOM

}

// parseHTML方法创建ast语法树

function parseHTML(html) {

    // 第三步 创建删除html字符串的方法

    advance(num){
        
        html = html.substring(num) // 给删除<div字符串的DOM结构重新赋值

    }

    // 第二步 创建parseStartTag方法 

    parseStartTag(){
        
        const start = html.match(startTagOpen) // 查找标签

        if(start) {

            const match = { tagName:start[1], attrs:[] } // tagName标签名 attrs属性值
            
            // 这里得到的match是一个数组 ['<div','div'] 

            advance(start[0].length) // 调用删除的方法

            let attr, end;

            // 下面是继续遍历剩余字符串的DOM结构 符合的追加到match这个对象中去 end代表">"             
            // attr代表属性

            while(!(end=html.match(startTagClose))&&(attr = html.match(attribute))){
                
                advance(attr[0].length)
                
                match.attrs.push({name:attr[1],value:attr[3]||attr[4] || sttr[5]} )

            }

            if(end) {
                
                advance(end[0].length)
            }
            
            return match 

        }
        return false   // 如果不是开始标签就停止

    }

    // 第一步 循环html字符串DOM结构

    while(html){
    
        let textEnd =  html.indexof('<') // 如果索引是0 则说明是个标签

        if(textEnd == 0) { // 索引为0  说明开始标签为 <

            //  这里的返回值已经是 {tagName:'div', attrs:{id:app}}
            
            const startTagMatch =  parseStartTag()

            if(startTagMatch){continue} // 上面的结果执行完有返回值 然后继续下面的执行

             let endTagMatch = html.match(endTag) // 寻找结束标签

             if(endTagMatch){
                
                advance(endTagMatch[0].length) // 清除结束标签

                continue 
              }

             
        }

        if(textEnd > 0) { // 索引大于0 的

            let text = html.substring(0,textEnd) // 解析文本内容  中间内容为文本内容

            if(text){

                chars(text) // 处理文本标签**** 我们下面继续

                advance(text.length) // 清除文本内容

            }

        }
        
    }
}

?转化为ast语法树

// 此代码依旧在compile/index.js 文件中执行

function parseHTML(html){

    const ELEMENR_TYPE = 1

    const TEXT_TYPE = 3

    const stack = [] // 存放元素

    let currentParent; // 指向栈中的最后一个
    
    let root;

    // 最终转化为ast语法树

    function createASTElement(tag,attrs){

        return { 

            tag,

            type:ELEMENT_TYPE,

            children:[],

            attrs,

            parent:null
        }

    }

    function start(tag,attrs){ // 开始

        let node = createASTElement(tag,attrs) // 创建一个ast节点

        if(!root){

            root = node // 如果为空则为数的跟节点
        }

        if(currentparent){

            node.parent = currentparent

            currentparent.children.push(node) 

        }
        
        stack.push(node) // 存放元素里面
        
        currentParent = node  // 栈中的最后一个元素
    }


    function chars(text){ // 处理文本

        text = text.replace(/\s/g,'') // 去掉空格的

        text && currentParent.children.push({

            type:TEXT_TYPE,
            
            text,

            parent:currentParent

        })

    }


    function end(tag){ // 结束

        let node =  stack.pop() // 删除最后一个元素

        currentParent = stack[stack.length-1] // 最后一个元素

    }


    return root // 最后将ast语法树返回出去

    
}


?

文章来源:https://blog.csdn.net/h960822/article/details/135498708
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。