從JS繼承實現一個VueJs的render函數

市面上的主流框架,相信做爲一個前端搬磚人員,或多或少都會有所接觸到。如ReactJs、VueJs、AngularJs。那麼對於每一個框架的使用來講實際上是比較簡單的,還記得上大學時候,老師曾經說過:"技術就是窗戶紙,捅一捅就破了",也就是說,任何一門技術,只要深刻去研究,那麼它也再也不是很神祕的東西了。我我的在工做中用VueJs是比較多的,固然React也會,那今天就爲你們來實現一個Vuejs框架中的render函數

首先來看一段代碼:html

<div id="div1">
        <span>test</span>
        <Tab></Tab>
        <UserLogin></UserLogin>
    </div>

最終在頁面上的呈現是怎樣的呢?前端

圖片描述

毫無疑問,只看到了test這一段文本內容。由於html不認識Tab、UserLogin這兩個"異類"元素。那麼假如如今要實現的是,經過一個render方法:數組

render({
            root:'#div1',
            components:{
                Tab,UserLogin
            }
        })

將Tab、UserLogin這兩個組件的內容渲染出來,該去怎樣實現呢?這裏涉及到的知識點以下:數據結構

  • 類型判斷
  • DOM操做
  • Js的繼承、多態
  • 組件化思想

首先經過Js的繼承及組件化思想來定義兩個類Tab、UserLogin,它們都有一個自身的render方法(從父類Component)繼承而來並進行了重寫。直接上代碼:框架

Component類:函數

class Component{
        render(){
            throw new Error('render is required');
        }

    }

Tab類:組件化

class Tab extends Component{
        render(){
            let div1 = document.createElement('div');
            div1.innerHTML = '我是Tab組件';
            return div1;
        }
    }

UserLogin類:ui

class UserLogin extends Component{
        render(){
            let div2 = document.createElement('div');
            div2.innerHTML = "我是登陸組件"
            return div2
        }
    }

到這裏,相信你們學過ES6的,對這樣的代碼都是感受很熟悉的,重點是render函數究竟該怎樣去實現。再來看一下render函數的基本骨架:spa

render({
        root:'#div1',
        components:{
            Tab,UserLogin
        }
    })

render函數接收了一個參數(對象類型),裏面包括兩個屬性root(掛載的元素),以及components(帶渲染的組件類)。對於render的實現,先從root這個屬性入手。靈魂拷問,root屬性必定是某個元素的id嗎?對於一個函數的參數來講,使用者傳遞什麼類型都是能夠的,但只要符合規定的參數纔能有效,說那麼多其實就是須要對render函數對象參數的root屬性進行校驗。代碼以下:code

function render(opts){
        let root = null;
        if(typeof opts.root === "string"){
            root = document.querySelector(opts.root);
            if(!root){
                throw new Error(`can't found ${opts.root}`)
            }
        }else if(opts.root instanceof HTMLElement){
            root = opts.root
        }else{
            throw new Error(`root invalid`)
        }
    }

這裏的操做的目的就是爲了找到root這個(父)元素。

接下來就是針對參數對象的components屬性來進行處理了,也就是說須要找到全部自定義元素(Tab、UserLogin),又一次靈魂拷問,能夠經過父元素找到其包含的全部子元素,可是該怎樣去區分哪些元素是自定義的呢?先來看一下經過父元素找到全部子元素的代碼:

let elements = root.getElementsByTagName("*");

打印看看elements是怎樣的數據結構:

圖片描述

能夠看到,有一個是咱們很熟悉的自有元素span,還有兩個未知的元素tab、userlogin,這時你可能回想,將elements轉換爲數組、而後遍歷進行判斷是否有自定義元素,哎,其實這思路還不錯。看下代碼:

Array.from(elements).forEach((ele) =>{
       if(ele.tagName ==='tab'){
         //找到了自定義元素tab
       }
       if(ele.tagName ==='userlogin'){
         //找到了自定義元素userlogin  
       }     
  })

這樣行嗎?顯然是不行的。萬一將元素標籤結構改爲這樣呢

<div id="div1">
    <span>test</span>
    <Tab></Tab>
    <UserLogin></UserLogin>
    <List></List>
</div>

那是否是要寫不少個if判斷,顯示不對。咱們知道,一個html文檔的繼承結構大體以下:

圖片描述

對於上述的自有元素span,它繼承是HTMLElement,也就是說span元素的構造函數是HTMLSpanElement,那麼對於上述兩個未知元素,它們的構造函數是什麼呢?實際上是HTMLUnknownElement。這下就能夠經過構造函數類再結合參數對象中components屬性來找到未知元素了,代碼以下:

Array.from(elements).forEach((ele) =>{
            if(ele.constructor === HTMLUnknownElement){
                for(let compName in opts.components){
                    if(compName.toLowerCase() === ele.tagName.toLowerCase()){
                        let CmpCls = opts.components[compName];
                    }
                }
            }
        })

代碼中CmpCls其實就是咱們最初定義的兩個類Tab、UserLogin,而後經過實例化它們,並調用各自實例對象的render方法,再經過找到的未知元素ele來進行父元素(root)裏內容的替換渲染了。代碼以下:

Array.from(elements).forEach((ele) =>{
            if(ele.constructor === HTMLUnknownElement){
                for(let compName in opts.components){
                    if(compName.toLowerCase() === ele.tagName.toLowerCase()){
                        let CmpCls = opts.components[compName];
                        let cmp = new CmpCls();
                        let res = cmp.render();
                        ele.parentNode.replaceChild(res,ele);
                    }
                }
            }
        })

再看下頁面最終呈現的內容:

圖片描述

正確地將咱們自定義Tab、UserLogin兩個組件的內容渲染了出來。

最終完整代碼以下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>實現render函數</title>
</head>
<body>
    <div id="div1">
        <span>test</span>
        <Tab></Tab>
        <UserLogin></UserLogin>
    </div>
    <script>

        function render(opts){
            //1.找到root
            let root = null;
            if(typeof opts.root === "string"){
                root = document.querySelector(opts.root);
                if(!root){
                    throw new Error(`can't found ${opts.root}`)
                }
            }else if(opts.root instanceof HTMLElement){
                root = opts.root
            }else{
                throw new Error(`root invalid`)
            }

            //2.找出全部自定義的元素
            let elements = root.getElementsByTagName("*");
            Array.from(elements).forEach((ele) =>{
                if(ele.constructor === HTMLUnknownElement){
                    for(let compName in opts.components){
                        if(compName.toLowerCase() === ele.tagName.toLowerCase()){
                            let CmpCls = opts.components[compName];
                            let cmp = new CmpCls();
                            let res = cmp.render();
                            ele.parentNode.replaceChild(res,ele);
                        }
                    }
                }
            })

        }

        class Component{
            render(){
                throw new Error('render is required');
            }

        }

        class Tab extends Component{
            render(){
                let div1 = document.createElement('div');
                div1.innerHTML = '我是Tab組件';
                return div1;
            }
        }

        class UserLogin extends Component{
            render(){
                let div2 = document.createElement('div');
                div2.innerHTML = "我是登陸組件"
                return div2
            }
        }
        render({
            root:'#div1',
            components:{
                Tab,UserLogin
            }
        })
    </script>
    
</body>
</html>

就這樣,一個簡潔版的Vuejs render函數就實現了, 與Vuejs中的render函數相比,還差不少不少技術點未實現。但並不阻礙咱們來了解部分實現思想。

相關文章
相關標籤/搜索