簡體   English   中英

使私有實例變量可以被匿名函數中包含的原型方法訪問

[英]Making private instance variable accessible to prototype methods enclosed in anonymous function

背景

我決定通過在JS中制作一個簡單的計算器應用程序來練習。 第一步是實現堆棧類。 然而,在使用顯示原型模式(?)實現數據封裝時遇到了一些問題。 以下是它現在的樣子:

堆棧“類”:

var Stack = (function () { 

    var Stack = function() {
        this.arr = []; // accessible to prototype methods but also to public
    };

    Stack.prototype = Object.prototype; // inherits from Object

    Stack.prototype.push = function(x) {
        this.arr.push(x);
    };

    Stack.prototype.pop = function() {
        return this.arr.length ? (this.arr.splice(this.arr.length - 1, 1))[0] : null;
    };

    Stack.prototype.size = function() {
        return this.arr.length;
    };

    Stack.prototype.empty = function() {
        return this.arr.length === 0;
    };

    return Stack;

})();

測試代碼:

var s1 = new Stack();
var s2 = new Stack();

for(var j = 1, k = 2; j < 10, k < 11; j++, k++) {

  s1.push(3*j);
  s2.push(4*k);

}

console.log("s1:");
while(!s1.empty()) console.log(s1.pop());
console.log("s2:");
while(!s2.empty()) console.log(s2.pop());

問題

唯一的問題是arr是可訪問的。 我想以某種方式隱藏arr變量。

嘗試解決方案

我的第一個想法是使它成為像Stack這樣的私有變量:

var Stack = (function () {

    var arr = []; // private, but shared by all instances

    var Stack = function() { };
    Stack.prototype = Object.prototype;

    Stack.prototype.push = function(x) {
        arr.push(x);
    };

    // etc.

})();

但是當然這種方法不起作用,因為每個實例都會共享 arr變量。 因此,它是創建私有變量的好方法,但不是私有實例變量。

我想到的第二種方式(真的很瘋狂,絕對不能提高可讀性)是使用隨機數來限制對數組變量的訪問,幾乎就像密碼一樣:

var Stack = (function() {

    var pass = String(Math.floor(Math.pow(10, 15 * Math.random()));
    var arrKey = "arr" + pass;

    var Stack = function() {
        this[arrKey] = []; // private instance and accessible to prototypes, but too dirty
    };

    Stack.prototype = Object.prototype;

    Stack.prototype.push = function(x) {
        this[arrKey].push(x);
    };

    // etc.

})();

這個解決方案很有趣。 但顯然不是我想要做的。

最后一個想法,就是Crockford所做的 ,允許我創建一個私有實例成員,但是我無法告訴我這對於我定義的公共原型方法是否可見。

var Stack = (function() {

    var Stack = function() {
        var arr = []; // private instance member but not accessible to public methods
        this.push = function(x) { arr.push(x); }; // see note [1]
    }

})();

[1]這幾乎就在那里,但我不想在var Stack = function() {...}使用函數定義,因為每次創建實例時都會重新創建它們。 一個聰明的JS編譯器會意識到它們不依賴於任何條件並緩存函數代碼而不是this.push遍地重新創建this.push ,但如果我可以避免它,我寧願不依賴於推測性緩存。

問題

有沒有辦法創建一個可供原型方法訪問的私有實例成員? 通過某種方式利用封閉匿名函數創建的“影響泡沫”?

您可以使用為您創建實例的工廠函數:

function createStack() {
    var arr = [];

    function Stack() {

    };

    Stack.prototype = Object.prototype; // inherits from Object

    Stack.prototype.push = function(x) {
        arr.push(x);
    };

    Stack.prototype.pop = function() {
        return arr.length ? (this.arr.splice(this.arr.length - 1, 1))[0] : null;
    };

    Stack.prototype.size = function() {
        return arr.length;
    };

    Stack.prototype.empty = function() {
        return arr.length === 0;
    };

    return new Stack();

}

您將在每次執行工廠函數時定義類,但是您可以通過更改此函數來定義構造函數外部的大部分Stack ,就像不使用arr的部分可以進一步在原型鏈中一樣。 我個人現在使用Object.create而不是原型,我幾乎總是使用工廠函數來創建這些類型的對象的實例。

您可以做的另一件事是維護一個跟蹤實例的計數器並保持一個數組數組。

var Stack = (function() {

    var data = [];


    var Stack = function() {
        this.id = data.length;
        data[this.id] = [];
    };

    Stack.prototype = Object.prototype;

    Stack.prototype.push = function(x) {
        data[this.id].push(x);
    };

    // etc.

}());

現在您擁有隱藏數據多維數組,並且每個實例只在該數組中維護其索引。 現在你必須小心管理內存,這樣當你的實例不再被使用時你會刪除該數組中的內容。 除非您小心處理數據,否則我不建議這樣做。

真正的解決方案

編輯:事實證明這個解決方案基本上與這里描述的解決方案相同,首先由HMR在我上面的問題的評論中發布。 所以絕對不是新的,但它運作良好。

var Stack = (function Stack() {

    var key = {};

    var Stack = function() {
        var privateInstanceVars = {arr: []};
        this.getPrivateInstanceVars = function(k) {
            return k === key ? privateInstanceVars : undefined;
        };
    };

    Stack.prototype.push = function(el) {
        var privates = this.getPrivateInstanceVars(key);
        privates.arr.push(el);
    };

    Stack.prototype.pop = function() {
        var privates = this.getPrivateInstanceVars(key);
        return privates.arr.length ? privates.arr.splice(privates.arr.length - 1, 1)[0] : null;
    };

    Stack.prototype.empty = function() {
        var privates = this.getPrivateInstanceVars(key);
        return privates.arr.length === 0;
    };

    Stack.prototype.size = function() {
        var privates = this.getPrivateInstanceVars(key);
        return privates.arr.length;
    };

    Stack.prototype.toString = function() {
        var privates = this.getPrivateInstanceVars(key);
        return privates.arr.toString();
    };

    Stack.prototype.print = function() {
        var privates = this.getPrivateInstanceVars(key);
        console.log(privates.arr);
    }

    return Stack;

}());


// TEST

// works - they ARE separate now
var s1 = new Stack();
var s2 = new Stack();
s1.push("s1a");
s1.push("s1b");
s2.push("s2a");
s2.push("s2b");
s1.print(); // ["s1a", "s1b"]
s2.print(); // ["s2a", "s2b"]

// works!
Stack.prototype.push.call(s1, "s1c");
s1.print(); // ["s1a", "s1b", "s1c"]


// extending the Stack

var LimitedStack = function(maxSize) {

  Stack.apply(this, arguments);
  this.maxSize = maxSize;

}

LimitedStack.prototype = new Stack();
LimitedStack.prototype.constructor = LimitedStack;

LimitedStack.prototype.push = function() {

  if(this.size() < this.maxSize) {
    Stack.prototype.push.apply(this, arguments);
  } else {
    console.log("Maximum size of " + this.maxSize + " reached; cannot push.");
  }

  // note that the private variable arr is not directly accessible
  // to extending prototypes
  // this.getArr(key) // !! this will fail (key not defined)

};

var limstack = new LimitedStack(3);

limstack.push(1);
limstack.push(2);
limstack.push(3);
limstack.push(4); // Maximum size of 3 reached; cannot push
limstack.print(); // [1, 2, 3]

缺點:基本上沒有,除了記住一些額外的代碼

原始解決方案

(最初發布的第一種方法與下面的方法大不相同,但是通過一些粗心的編輯,我似乎已經失去了它。無論如何它都沒有用,所以沒有真正的傷害。)

這里使用每個實例創建一個新的對象/原型,但它借用了static privilegedInstanceMethods中的大部分代碼。 仍然失敗的是能夠做Stack.prototype.push.call(s1, val) ,但現在原型正在對象上設置,我想我們越來越近了。

var Stack = (function() {

    var privilegedInstanceMethods = {
        push: function(x) { 
            this.arr.push(x);
        },
        pop: function() {
            return this.arr.length ? this.arr.splice(this.arr.length - 1, 1)[0] : null;
        },
        size: function() {
            return this.arr.length;
        },
        empty: function() {
            return this.arr.length === 0;
        },
        print: function() {
            console.log(this.arr);
        },
    };

    var Stack_1 = function() {
        var Stack_2 = function() {
            var privateInstanceMembers = {arr: []};
            for (var k in privilegedInstanceMethods) {
                if (privilegedInstanceMethods.hasOwnProperty(k)) {
                    // this essentially recreates the class each time an object is created,
                    // but without recreating the majority of the function code
                    Stack_2.prototype[k] = privilegedInstanceMethods[k].bind(privateInstanceMembers);
                }
            }
        };
        return new Stack_2(); // this is key
    };

    // give Stack.prototype access to the methods as well.
    for(var k in privilegedInstanceMethods) {
        if(privilegedInstanceMethods.hasOwnProperty(k)) {
            Stack_1.prototype[k] = (function(k2) {
                return function() {
                    this[k2].apply(this, arguments);
                };
            }(k)); // necessary to prevent k from being same in all
        }
    }

    return Stack_1;

}());

測試:

// works - they ARE separate now
var s1 = new Stack();
var s2 = new Stack();
s1.push("s1a");
s1.push("s1b");
s2.push("s2a");
s2.push("s2b");
s1.print(); // ["s1a", "s1b"]
s2.print(); // ["s2a", "s2b"]

// works!
Stack.prototype.push.call(s1, "s1c");
s1.print(); // ["s1a", "s1b", "s1c"]

優點:

  • this.arr無法直接訪問
  • 方法代碼只定義一次,而不是每個實例
  • s1.push(x)工作, Stack.prototype.push.call(s1, x)

缺點:

  • bind調用在每個實例化上創建了四個新的包裝器函數(但是代碼比每次創建內部push / pop / empty / size函數要小得多)。
  • 代碼有點復雜

這里簡短的回答是,你不能在沒有犧牲一點的情況下擁有所有東西。

Stack感覺就像某種類型的struct ,或者至少是一種數據類型,它應該具有一種peek形式或讀取訪問形式。

陣列是否擴展,當然取決於您和您的解釋......

...但我的觀點是,對於像這樣的低級,簡單的事情,你的解決方案是兩件事之一:

function Stack () {
    this.arr = [];
    this.push = function (item) { this.arr.push(item); }
    // etc
}

要么

function Stack () {
    var arr = [];
    var stack = this;

    extend(stack, {
        _add  : function (item) { arr.push(item); },
        _read : function (i) { return arr[i || arr.length - 1]; },
        _remove : function () { return arr.pop(); },
        _clear  : function () { arr = []; }
    });
}

extend(Stack.prototype, {
    push : function (item) { this._add(item); },
    pop  : function () { return this._remove(); }
    // ...
});

extend這里只是,你可以寫一個簡單的函數,來復制對象的鍵- > VAL,到第一個對象(基本上,這樣我就不必繼續鍵入this.Class.prototype.

當然,有很多種方法可以用改進的樣式來編寫這些方法,這些方法都可以實現基本相同的功能。

這就是摩擦; 除非你使用全局注冊表,其中每個實例在構造時被賦予其自己唯一的Symbol(或unique-id),然后它用於注冊數組......當然,這意味着密鑰然后需要公開訪問(或具有公共訪問者 - 同樣的事情),您要么編寫基於實例的方法,使用原型方法編寫基於實例的訪問器,要么將所需內容放在公共范圍內。

將來,您將能夠做到這樣的事情:

var Stack = (function () {
    var registry = new WeakMap();

    function Stack () {
        var stack = this,
            arr   = [];

        registry[stack] = arr;
    }

    extend(Stack.prototype, {
        push (item) { registry[this].push(item); }
        pop () { return registry[this].pop(); }
    });

    return Stack;
}());

目前幾乎所有前沿的瀏覽器都支持這一點(減去方法的簡寫)。
但是有ES6 - > ES5編譯器(例如Traceur)。
我不認為Traceur支持WeakMaps,因為ES5實現需要很多環節或者工作代理,但Map可以工作(假設您自己處理GC)。

這讓我說從一個實用的角度來看,對於像Stack這樣小的類,如果你真的想讓數組保持內部,你可以給每個實例提供自己的方法。

對於其他無害的,微小的,低級別的類,隱藏數據可能毫無意義,因此所有這些都可能是公開的。

對於較大的類或高級類,在具有原型方法的實例上具有訪問器保持相對干凈; 特別是如果您使用DI來提供較低級別的功能,並且實例訪問器只是從依賴關系的接口橋接到您需要它們的形狀,對於您自己的接口。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM