JavaScript >> Javascript 文檔 >  >> JavaScript

試驗 ECMAScript 6 代理

ECMAScript 6,又名“Harmony”,引入了一種稱為代理的新型對象。代理是可以控制、消除或以其他方式更改其在常見情況下的默認行為的對象。這包括定義在 for-in 中使用對象時會發生什麼 看,當它的屬性與 delete 一起使用時 ,等等。

代理的行為是通過陷阱定義的,陷阱是“陷阱”特定行為的簡單函數,因此您可以做出適當的響應。有幾種不同的陷阱可用,一些是基本的,一些是衍生的。基本陷阱定義了低級行為,例如調用 Object.defineProperty() 時會發生什麼 在對像上,而派生陷阱定義稍高級別的行為,例如讀取和寫入屬性。建議始終實現基本陷阱,而派生陷阱被認為是可選的(當未定義派生陷阱時,默認實現使用基本陷阱來填補空白)。

我的實驗主要集中在派生的 get 陷阱。 get 陷阱定義從對像中讀取屬性時會發生什麼。想想 get 陷阱作為一個全局getter,為對像上的每個屬性調用。這讓我意識到我之前對專有 __noSuchMethod__() 的實驗 可能適用。經過一番修改,我最終得到了以下 HTML 編寫器原型:

/*
 * The constructor name I want is HTMLWriter.
 */
var HTMLWriter = (function(){

    /*
     * Lazily-incomplete list of HTML tags.
     */
    var tags = [
        "a", "abbr", "acronym", "address", "applet", "area",
        "b", "base", "basefont", "bdo", "big", "blockquote",
        "body", "br", "button",
        "caption", "center", "cite", "code", "col", "colgroup",
        "dd", "del", "dir", "div", "dfn", "dl", "dt",
        "em",
        "fieldset", "font", "form", "frame", "frameset",
        "h1", "h2", "h3", "h4", "h5", "h6", "head", "hr", "html",
        "i", "iframe", "img", "input", "ins", "isindex",
        "kbd",
        "label", "legend", "li", "link",
        "map", "menu", "meta",
        "noframes", "noscript",
        "object", "ol", "optgroup", "option",
        "p", "param", "pre",
        "q",
        "s", "samp", "script", "select", "small", "span", "strike",
        "strong", "style", "sub", "sup",
        "table", "tbody", "td", "textarea", "tfoot", "th", "thead",
        "title", "tr", "tt",
        "u", "ul",
        "var"
    ];

    /* 
     * Define an internal-only type. 
     */
    function InternalHTMLWriter(){
        this._work = [];
    }

    InternalHTMLWriter.prototype = {

        escape: function (text){
            return text.replace(/[>< "&#038;]/g, function(c){
                switch(c){
                    case ">": return "&gt;";
                    case "< ": return "&lt;";
                    case "\"": return "&quot;";
                    case "&#038;": return "&amp;";
                }
            });
        },

        startTag: function(tagName, attributes){
            this._work.push("<" + tagName);

            if (attributes){
                var name, value;
                for (name in attributes){
                    if (attributes.hasOwnProperty(name)){
                        value = this.escape(attributes[name]);
                        this._work.push(" " + name + "=\"" + value + "\"");
                    }
                }
            }

            this._work.push(">");
            return this;
        },

        text: function(text){
            this._work.push(this.escape(text));
            return this;
        },

        endTag: function(tagName){
            this._work.push("</" + tagName + ">");
            return this;
        },

        toString: function(){
            return this._work.join("");
        }

    };
    
    /*
     * Output a pseudo-constructor. It's not a real constructor,
     * since it just returns the proxy object, but I like the
     * "new" pattern vs. factory functions.
     */
    return function(){
        var writer = new InternalHTMLWriter(),    
            proxy = Proxy.create({

                /*
                 * Only really need getter, don't want anything else going on.
                 */
                get: function(receiver, name){
                    var tagName, 
                        closeTag = false;
                    
                    if (name in writer){
                        return writer[name];
                    } else {
                    
                        if (tags.indexOf(name) > -1){
                            tagName = name;
                        } else if (name.charAt(0) == "x" &#038;&#038; tags.indexOf(name.substring(1)) > -1){
                            tagName = name.substring(1);
                            closeTag = true;
                        }
                        
                        if (tagName){                
                            return function(){
                                if (!closeTag){
                                    writer.startTag(tagName, arguments[0]);
                                } else {
                                    writer.endTag(tagName);
                                }
                                return receiver;                
                            };
                        }
                    }
                }
            
            });
            
        return proxy;
    };
})();

這使用了與我之前的實驗相同的基本方法,即定義一個將屬性名稱解釋為 HTML 標記名稱的 getter。當屬性與 HTML 標記名稱匹配時,會返回一個調用 startTag() 的函數 方法,同樣以“x”開頭併後跟標籤名稱的屬性接收調用 endTag() 的函數 .所有其他方法都傳遞到內部 writer 對象。

InternalHTMLWriter 類型是在函數內部定義的,因此不能在外部訪問; HTMLWriter type 是使用此代碼的首選方式,使實現透明。每個調用 HTMLWriter 創建一個新的代理,該代理又引用了它自己的內部 writer 目的。基本用法是:

var w = new HTMLWriter();

w.html()
    .head().title().text("Example &#038; Test").xtitle().xhead()
    .body().text("Hello world!").xbody()
.xhtml();

console.log(w);

撇開醜陋的方法名稱不談,原型按您的預期工作。我真正喜歡這種模式的地方在於,可以通過修改 tags 輕鬆更新方法以支持新的 HTML 標記 數組。

我越想代理和 get 陷阱,我想出的想法越多。開發人員長期以來一直試圖找出從 Array 繼承的方法 創建自己的類似數組的結構,但由於許多問題,我們也無法實現。使用代理,實現類似數組的數據結構是微不足道的。

我決定要在 JavaScript 中創建一個堆棧實現,在它下面使用一個數組。我希望堆棧是老式的,只是 push() , pop() , 和 length 成員(不支持數字索引)。基本上,我只需要過濾 get 中正在訪問的成員 陷阱。結果如下:

var Stack = (function(){

    var stack = [],
        allowed = [ "push", "pop", "length" ];
    
    return Proxy.create({
        get: function(receiver, name){;
            if (allowed.indexOf(name) > -1){
                if(typeof stack[name] == "function"){
                    return stack[name].bind(stack);
                } else {
                    return stack[name];
                }
            } else {
                return undefined;
            }
        }
    
    });

});

var mystack = new Stack();

mystack.push("hi");
mystack.push("goodbye");

console.log(mystack.length);    //1

console.log(mystack[0]);        //undefined
console.log(mystack.pop());     //"goodbye"

在這裡,我使用的是私有 stack 我的堆棧的每個實例的數組。每個實例還具有一個返回並用作接口的代理。因此,我希望允許的每個方法最終都在數組上執行,而不是在代理對象本身上執行。

這種對象成員過濾模式允許我輕鬆啟用我想要的成員,同時禁用我不想要的成員。一個棘手的部分是確保方法綁定到正確的 this 價值。在我的第一次嘗試中,我只是從數組中返回了方法,但由於 this 導致出現多個錯誤 是代理對象而不是數組。我添加了使用 ECMAScript 5 bind() 確保this的方法 方法的值仍然正確,一切正常。

開始使用代理時的一些注意事項。首先,它目前僅在 Firefox 6+ 中受支持。其次,規範仍在不斷變化,語法、參數順序等可能會在未來發生變化。第三,我在這裡解釋的模式不是也不應該被視為使用代理的最佳實踐。這些只是我為了探索可能性而進行的一些實驗。代理尚未準備好用於生產用途,但對於實驗來說非常有趣。

更新(2011 年 9 月 18 日) :修復了代碼中的轉義問題。


Tutorial JavaScript 教程
  1. JavaScript 中的基本中間件模式

  2. 表單輸入類型的跨瀏覽器兼容性問題

  3. Google Analytics:如何在單頁應用程序中跟踪頁面?

  4. 使用側邊欄插件管理古騰堡中的 WordPress 元數據

  5. 檢查 JavaScript 數組中的重複字符串

  6. 10 驚人的 og:image 靈感🎨✨

  7. 介紹 SIMD.js

  1. 3.1 認真對待 Firebase V9 - 遷移到 ECMA 模塊

  2. 反應鉤子 |為什麼使用回調?

  3. 將 Django REST 與前端分離

  4. 什麼是 JavaScript 中的函數柯里化以及何時使用它

  5. 如何使用 AWS Lambda 將聯繫表添加到靜態網站

  6. 我學習 JavaScript 的花絮:CHALK 和 readlineSync

  7. Console.log(this) JavaScript |示例代碼

  1. JavaScript 用逗號將字符串分割成數組 |示例代碼

  2. 打字稿中的數字 - 用例子簡單解釋

  3. 什麼是 Docker?為 Node.js 應用程序創建容器

  4. 如何在您的 Web 應用程序中使用 jQuery 可排序 UI 組件