WebComponent魔法堂:深究Custom Element 之 從過去看如今

前言

 提及Custom Element那必然會想起那個類似而又以失敗了結的HTML Component。HTML Component是在IE5開始引入的新技術,用於對原生元素做功能"加強",雖然僅僅被IE所支持,雖然IE10也開始放棄它了,雖然掌握了也用不上,但仍是不影響咱們以研究的心態去了解它的:)css

把玩HTML Component

 HTML Component簡稱HTC,它由定義和應用兩部分組成。定義部分寫在.htc文件中(MIME爲text/x-component),由HTC獨有標籤、JScript和全局對象(element,window等)組成;而應用部分則寫在html文件中,經過CSS的behavior規則附加到特定的元素上。html

定義部分

HTC獨有標籤
PUBLIC:COMPONENT, 根節點.
PUBLIC:PROPERTY, 定義元素公開自定義屬性/特性.
 屬性
NAME,html文件中使用的屬性名
INTERNALNAME,htc文件內使用的屬性名,默認與NAME一致
VALUE,屬性默認值
PUT,setter對應的函數名
GET,getter對應的函數名
PUBLIC:EVENT, 定義元素公開自定義事件.
 屬性
NAME,公開事件名稱,如onheadingchange
ID,htc內使用的事件名稱,如ohc.而後經過ohc.fire(createEventObject())來觸發事件
PUBLIC:ATTACH,訂閱事件
 屬性
EVENT,訂閱的事件名稱,如onheadingchange
ONEVENT,事件處理函數體,如headingchangehandler()
FOR,事件發生的宿主(element,document,window,默認是element)
PUBLIC:METHOD, 定義元素公開方法
 屬性
NAME,html文件中使用的方法名
INTERNALNAME,htc文件內使用的方法名,默認與NAME一致。在JScript中實現具體的方法體
PUBLIC:DEFAULTS,設置HTC默認配置
HTC生命週期事件
ondocumentready, 添加到DOM tree時觸發,在oncontentready後觸發
oncontentready, 添加到DOM tree時觸發
ondetach, 脫離DOM tree時觸發, 刷新頁面時也會觸發
oncontentsave, 當複製(ctrl-c)element內容時觸發
HTC全局對象
element, 所附加到的元素實例
runtimeStyle,所附加到的元素實例的style屬性
document,html的文檔對象
HTC全局函數
createEventObject(),建立事件對象
attachEvent(evtName, handler), 訂閱事件.注意:通常不建議使用attachEvent來訂閱事件,採用<PUBLIC:ATTACH>來訂閱事件,它會自動幫咱們執行detach操做,避免內存泄露.
detachEvent(evtName[, handler]), 取消訂閱事件git

應用部分

引入.htc
1.基本打開方式github

<style>
  css-selector{
    behavior: url(file.htc);
  }
</style>

2.打開多個app

<style>
  css-selector{
    behavior: url(file1.htc) url(file2.htc);
  }
</style>

 能夠看到是經過css-selector匹配元素而後將htc附加到元素上,感受是否是跟AngularJS經過屬性E指定附加元素的方式差很少呢!
3.自定義元素函數

<html xmlns:x>
    <head>
        <style>
            x\:alert{
                behavior: url(x-alert.htc);
            }
        </style>
    </head>
    <body>
        <x:alert></x:alert>
    </body>
</html>

 自定義元素則有些麻煩,就是要爲自定義元素指定命名空間x:alert,而後在html節點上列出命名空間xmlns:x。(可多個命名空間並存<html xmlns:x xmlns:y>)
 下面咱們來嘗試定義一個x:alert元素來看看具體怎麼玩吧!url

自定義x:alert元素

x-alert.htcspa

<PUBLIC:COMPONENT>
    <PUBLIC:ATTACH EVENT="oncontentready" ONEVENT="onattach()"></PUBLIC:ATTACH>
    <PUBLIC:ATTACH EVENT="ondetach" ONEVENT="ondetach()"></PUBLIC:ATTACH>

    <PUBLIC:METHOD NAME="close"></PUBLIC:METHOD>
    <PUBLIC:METHOD NAME="show"></PUBLIC:METHOD>

    <PUBLIC:PROPERTY NAME="heading" PUT="putHeading" SET="setHeading"></PUBLIC:PROPERTY>
    <PUBLIC:EVENT NAME="onheadingchange" ID="ohc"></PUBLIC:EVENT>
    <PUBLIC:ATTACH EVENT="onclick" ONEVENT="onclick()"></PUBLIC:ATTACH>

    <script language="JScript">
        /* 
         * private region
         */
        function toArray(arrayLike, sIdx, eIdx){
           return Array.prototype.slice.call(arrayLike, sIdx || 0, eIdx || arrayLike.length)
        }
        function curry(fn /*, ...args*/){
            var len = fn.length
              , args = toArray(arguments, 1)

            return len <= args.length 
                   ? fn.apply(null, args.slice(0, len)) 
                   : function next(args){
                        return function(){
                            var tmpArgs = args.concat(toArray(arguments))
                            return len <= tmpArgs.length ? fn.apply(null, tmpArgs.slice(0, len)) : next(tmpArgs)
                        }
                     }(args)
        }
        function compile(tpl, ctx){
            var k
            for (k in ctx){
                tpl = tpl.replace(RegExp('\$\{' + k + '\}'), ctx[k]
            }
            return tpl
        }

        // 元素內部結構
        var tpl = '<div class="alert alert-warning alert-dismissible fade in">\
                        <button type="button" class="close" aria-label="Close">\
                          <span aria-hidden="true">&times;</span>\
                        </button>\
                        <div class="content">${raw}</div>\
                      </div>'
        var getHtml = curry(compile, tpl)
        /* 
         * leftcycle region
         */
        var inited = 0, oHtml = ''
        function onattach(){
            if (inited) return

            oHtml = element.innerHTML
            var ctx = {
                raw: heading + oHtml
            }
            var html = genHtml(ctx)
            element.innerHTML = html

            runtimeStyle.display = 'block'
            runtimeStyle.border = 'solid 1px red'
        }
        function ondetach(){}
        /* 
         * public method region
         */
        function show(){
            runtimeStyle.display = 'block'
        }
        function close(){
            runtimeStyle.display = 'none'
        }
        /*
         * public property region
         */
        var heading = ''
        function putHeading(val){
            if (heading !== val){
                setTimeout(function(){
                    var evt = createEventObject()
                    evt.propertyName = 'heading'
                    ohc.fire(evt)
                }, 0)
            }
            heading = val
        }
        function getHeading(){
            return heading
        }

        /*
         * attach event region
         */
        function onclick(){
            if (/^\s*close\s*$/.test(event.srcElement.className)){
                close()
            }
        }
    </script>
</PUBLIC:COMPONENT>

引用x:alert

index.htmlprototype

<html xmlns:x>
<head>
    <title></title>
    <style>
        x\:alert{
            behavior: url(x-alert.htc);
        }
    </style>
</head>
<body>
    <x:alert id="a" heading="Hello world!"></x:alert>    
    <script language="JScript">
        var a = document.getElementById('a')
        a.onheadingchange = function(){
            alert(event.propertyName + ':' + a.heading)
        } 
        // a.show()
        // a.close()
        // document.body.appendChilid(document.createElement('x:alert'))
    </script>
</body>
</html>

感覺

 在寫HTC時我有種寫C的感受,先經過HTC獨有標籤聲明事件、屬性、方法等,而後經過JScript來提供具體實現,其實寫Angular2時也有這樣的感受,不過這裏的感受更強烈一些。
這裏先列出開發時HTC給我驚喜的地方吧!code

  1. htc文件內的JScript代碼做用域爲htc文件自己,並不污染html文件的腳本上下文;

  2. 帶屬性訪問器的自定義屬性大大提升咱們對自定義屬性的可控性;

而後就是槽點了

  1. htc行爲與元素綁定分離,好處是靈活,缺點是非自包含,每次引入都要應用者本身綁定一次太囉嗦了。我以爲Angular經過屬性E綁定元素既靈活又實現自包含纔是正路啊!

  2. API有bug。如ondocumentready事件說好了是html文檔加載完就會觸發,按理只會觸發一下,可實際上它總會在oncontentready事件後觸發,還有fireEvent的API根本就沒有,只能說繼承了IE一如既往的各類坑。

  3. 經過runtimeStyle來設置inline style,從而會丟失繼承、層疊等CSS特性的好處;

  4. 自定義元素內部結構會受到外部JS、CSS的影響,並非一個真正閉合的元素。

總結

 很抱歉本文的內容十分對不住標題所述,更全面的觀點請查看徐飛老師的《從HTML Components的衰落看Web Components的危機》。假如單獨看Custom Element,其實它跟HTML Component無異,都沒有完整的解決自定義元素/組件的問題,但WebComponent除了Custom Element,還有另外3個好夥伴(Shadow DOM,template,html imports)來一塊兒爲自定義元素提供完整的解決方案,其中Shadow DOM可謂是重中之重,後續繼續研究研究:)
 尊重原創,轉載請註明來自:http://www.cnblogs.com/fsjohn... ^_^肥仔John

感謝

《從HTML Components的衰落看Web Components的危機》
HTC Reference
Using HTML Components to Implement DHTML Behaviors in Script

相關文章
相關標籤/搜索