在JavaScript对象中实现setter和getter

6

我希望在本地javascript变量上实现setter和getter。以下是一个示例函数:

function someThing() {
   var someLocalvariable = '';
}

// with this function I want to 
// return value of someLocalvariable
// also if it is possible to implement
// setter in this way. 
someThing.prototype.getLocalVar = function() {

}

我希望变量能够真正私有化。我不想使用类似于以下的语句: someThing.prototype.someLocalvariable =
或者其他的方法。
function someThing() {
   this.someLocalvariable = '';
}

或者像这样在 someThing() 中附加功能:
function someThing() {
      var someLocalvariable = '';
   this.getLocalvariable = function() {
      return someLocalvariable;
   }
}

我非常感激您提供的任何指导和帮助。

7个回答

14

你不希望做的最后一个例子不能工作(它有语法错误),已经被修复了,但我认为你可能想要通常的做法,那就是在构造函数内部创建getter和setter闭包(如下所示)。

不幸的是,如果你想要真正私有的变量,这几乎是你唯一的选择。没有其他方法可以获得真正私有的实例特定变量。但是,请参见下面的“hack”。

这是通常做法的正确版本(我认为你说你不想要,但为了完整起见):

function SomeThing() {
    var privateVar;

    this.setPrivateVar = function(val) {
        privateVar = val;
    };
    this.getPrivateVar = function() {
        return privateVar;
    };
}

// use:
var t = new Something();
t.setPrivateVar("foo");
console.log(t.getPrivateVar()); // "foo"

和大多数人一样,我第一次在道格拉斯·克罗克福德的网站上读到了这个模式。

这种方法确实有一个缺点:通过SomeThing构造函数创建的每个实例都有自己的两个函数。它们不能在实例之间共享。因此,如果你的应用程序中将有数百或数千个SomeThing实例,从内存角度考虑这是需要考虑的问题。如果只有几百个或更少的实例,那么可能并不重要。(这些数字是随意取的,你不应该信任它们,当/如果出现某种问题时,你必须审查代码的内存使用情况;但你明白我的意思。)

解决方法:如果你的实例已经具有某种唯一标识符作为公共数据(或者你愿意添加一个,同样它将是公共的),并且如果你愿意在实例的使用中增加相当多的复杂性,那么你可以拥有一个私有缓存,它仅保存你的代码可以访问的所有实例的数据,并通过对象的唯一标识符键入该缓存。像这样(在这个例子中,我分配了id值,但如果你已经有了唯一的ID,你也可以使用它):

var SomeThing = (function() {
    var cache = {}, idAllocator = 0;

    function SomeThing() {
        this.id = ++idAllocator; // The unique identifier, can be a string if desired
        cache[this.id] = {};
    }
    SomeThing.prototype.getPrivateVar = function() {
        var data = cache[this.id];
        return data && data.privateVar;
    };
    SomeThing.prototype.setPrivateVar = function(value) {
        cache[this.id].privateVar = value;
    };
    SomeThing.prototype.destroy = function() {
        delete cache[this.id];
    };

    return SomeThing;
})();

这是它的工作原理:所有函数都是针对外部作用域函数中的cache本地变量的闭包。我们使用对象的唯一ID进行索引,从而获得一个对象,在该对象上放置私有数据成员。当使用实例的代码完成后,该代码必须调用destroy(这是这种模式的主要缺点),以便我们通过删除id的属性来从cache中删除私有数据对象。
注意事项和成本:
- 你仍然有一个公共数据,即私有数据的关键(如上所述的id) - 使用SomeThing创建的实例的用户在完成使用后必须调用这些实例上的destroy。虽然这与JavaScript垃圾处理的方式相悖,但它是上述模式的要求,否则会导致垃圾在cache对象中积累。 - (我不担心这个)最终,如果你使用上述自动id值,如果你的应用程序创建和销毁了很多这些实例,你将耗尽它们。但JavaScript数字确实很高,如果这是一个问题,只需找到一个不同的方法来分配ID,而不是上述简单的始终增加系统。
我还没有在我的工作中使用上述模式,但我期望有用例涉及数千个SomeThing实例,因此不希望每个实例都有函数。
附注:在上面,我将someThing更改为SomeThing。在JavaScript中,标准惯例是普通函数的名称以小写字母开头,构造函数(用于new的函数)的名称以大写字母开头。由于SomeThing用于new,所以我将其大写。这只是约定,但它是一个极其流行的约定,并且当然在语言定义本身中使用(Date是构造函数,setHours是函数)。

1
他已经知道这个了,因为它是他不想做的例子之一。 - Vala
1
修订版非常彻底。答案很棒。 - Vala
@TJCrowder:我听说过反对第一个(即Crockford的)模式关于内存使用的论点 - 我认为这是有道理的 - 但从未看到有人发布测试。你能提供一个链接吗? - FK82
@FK82:不,我从未见过有东西进入分析。请注意,它将极其敏感于所使用的JavaScript引擎,可能是函数的大小等。在非常智能的引擎上,我期望开销很小:多个函数对象(不必太昂贵),但具有共享的基础代码。较少优化的引擎可能不会在不同的函数对象之间重用代码(对于引擎要符合规范,函数对象必须不同;而基础代码则是另一回事)。 - T.J. Crowder
@T.J. Crowder 很好的观点。实际上,我是想反过来考虑的。也就是说,在实例化父级“Function”时,原型中指定的“Function”将必须由JavaScript引擎实例化,因为它们各自打开了一个单独的执行上下文。不知道这是否准确,但我认为很可能是这种情况。 - FK82
显示剩余2条评论

4
在函数构造器中使用Object.defineProperty()来定义getter和setter,更多信息请点击此处
为了使某些值真正私有(对外不可见),可以使用闭包,更多信息请点击此处
在下面的示例中,我们为属性temperature定义了一个getter和setter,其中内部“私有”值存储在变量var temperature中。
由于它是一个闭包,因此var temperature永远不会从Archiver()外部可见/访问。
请注意,该模式仅适用于ES5,因为ES3不支持Object.defineProperty()。
function Archiver() {
    var temperature = null;
    var archive = [];

    Object.defineProperty(this, 'temperature', {
        get: function () {
            console.log('get!');
            return temperature;
        },
        set: function (value) {
            temperature = value;
            archive.push({ val: temperature });
        }
    });

    this.getArchive = function () {
        return archive;
    };
}

var arc = new Archiver();
arc.temperature; // 'get!'
arc.temperature = 11;
arc.temperature = 13;
arc.getArchive(); // [{ val: 11 }, { val: 13 }]

现在看来,这似乎是最好的解决方案,因为Object.DefineProperty存在,并且允许您像使用常规属性一样使用getter/setter。 - Scott Weaver
@sweaver2112,你可以在https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty上查看浏览器兼容性表。 - GibboK

3

类似这样的代码:

function Field(val){
    var value = val;
this.getValue = function(){ return value; };
this.setValue = function(val){ value = val; }; } var field = new Field("test"); field.value // => undefined field.setValue("test2") field.getValue()

参考链接:http://ejohn.org/blog/javascript-getters-and-setters/


2

http://meta.stackexchange.com/questions/8231/are-answers-that-just-contain-links-elsewhere-really-good-answers - T.J. Crowder

2

这是不可能的。如果您在someThing()中有一个局部变量,则附加到原型的函数无法读取其值(请记住,它是私有的)。您最后的示例是解决此问题的常规方法,为什么这对您来说还不够好?


0

尝试这两种方法来实现setter和getter

var address = {

            street : "No street",
            city : "No city",
            state : "No state",     

            get getAddress()
            {
                return (this.street+","+this.city+","+this.state);
            },

            set setAddress(theAddress)
            {
                var part = theAddress.toString().split(", ");
                this.street = part[0] || "";
                this.city = part[1] || "";
                this.state = part[2] || "";
            }
        };


        address.setAddress = "27 Sus Road, Pune, MH";
        console.log(address.getAddress);


        //Other setter and getter

        function Square(side)
        {
            this._side = side;


        };

        Square.prototype = {

            set setSide(side){ 
                this._side = side;
            },

            get getSide(){ 
                return this._side;
            },

            get getArea(){ 
                return (this._side * this._side);
            }               
        };


        var mySquare = new Square(10);

        mySquare.setSide = 15;

        console.log("Area of square is "+mySquare.getArea+" with side "+mySquare.getSide);

0

第一种方法

var address = {

            street : "No street",
            city : "No city",
            state : "No state",     

            get getAddress()
            {
                return (this.street+","+this.city+","+this.state);
            },

            set setAddress(theAddress)
            {
                var part = theAddress.toString().split(", ");
                this.street = part[0] || "";
                this.city = part[1] || "";
                this.state = part[2] || "";
            }
        };


        address.setAddress = "27 Sus Road, Pune, MH";
        console.log(address.getAddress);

第二种方法

function Square(side)
        {
            this._side = side;


        };

        Square.prototype = {

            set setSide(side){ 
                this._side = side;
            },

            get getSide(){ 
                return this._side;
            },

            get getArea(){ 
                return (this._side * this._side);
            }               
        };


        var mySquare = new Square(10);

        mySquare.setSide = 15;

        console.log("Area of square is "+mySquare.getArea+" with side "+mySquare.getSide);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接