閱讀751 返回首頁    go 技術社區[雲棲]


WebComponent魔法堂:深究Custom Element 之 從過去看現在

前言

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

把玩HTML Component

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

定義部分

 **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]), 取消訂閱事件

應用部分

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

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

2.打開多個

<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元素來看看具體怎麼玩吧!

自定義x:alert元素

x-alert.htc

<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 >\
                        <button type="button"  aria-label="Close">\
                          <span aria-hidden="true">&times;</span>\
                        </button>\
                        <div >${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.html

<html xmlns:x>
<head>
    <title></title>
    <style>
        x\:alert{
            behavior: url(x-alert.htc);
        }
    </style>
</head>
<body>
    <x:alert  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給我驚喜的地方吧!
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可謂是重中之重,後續繼續研究研究:)
 尊重原創,轉載請注明來自:https://www.cnblogs.com/fsjohnhuang/p/5987853.html ^_^肥仔John

感謝

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

最後更新:2017-05-05 11:31:39

  上一篇:go 打造高效前端工作環境-tmuxinator
  下一篇:go 前端魔法堂:解秘FOUC