傻瓜的吸气器/安装器


133

我一直在努力使自己了解getter和setter,而不会陷入其中。我已经阅读了JavaScript Getters和Setters定义Getters和Setters,只是没有得到它。

有人可以明确指出:

  1. 吸气器和装夹器的作用是什么,以及
  2. 举几个非常简单的例子吗?

12
就个人而言,我不知道您能得到比约翰更清晰的解释……
杰森·邦廷

在OO世界中,getter和setter帮助编写一个正确封装的类
过度交换

基本上我是这样看的:您正在定义用于获取和设置属性的重载,这些重载是函数;但是,您不必致电给他们。这样,您就可以替换a = setValue(5);a = 5;setValue()从而可以在后台调用任何您喜欢的事情。
Andrew

Answers:


102

除了@millimoose的答案外,setter也可以用于更新其他值。

function Name(first, last) {
    this.first = first;
    this.last = last;
}

Name.prototype = {
    get fullName() {
        return this.first + " " + this.last;
    },

    set fullName(name) {
        var names = name.split(" ");
        this.first = names[0];
        this.last = names[1];
    }
};

现在,您可以设置fullName,并firstlast将被更新,反之亦然,副。

n = new Name('Claude', 'Monet')
n.first # "Claude"
n.last # "Monet"
n.fullName # "Claude Monet"
n.fullName = "Gustav Klimt"
n.first # "Gustav"
n.last # "Klimt"

2
@Akash:否,尽管Internet Explorer 9确实支持Object.defineProperty可以定义getter和setter 的较新功能。
马修·克鲁姆利

9
MS不能正确地支持JS真的很痛苦,并且它们不能使Silverlight到处运行,所以我必须对所有程序进行两次编程,一次对SL编程,对其他世界编程:)
Akash Kava

2
@Martin:您可以使用与John的答案相同的技术将它们设为私有。如果您想使用真正的getter / setter,则必须使用this.__defineGetter__或更新的Object.defineProperty函数。
马修·克鲁姆利

1
上面列出的方法只有一个问题,如果要为现有类添加getter和setter,它将覆盖原型,并且原始方法将不可访问。
xchg.ca 2011年

1
这种方法不会覆盖Name.prototype.constructor吗?似乎是对millimoose的Answer的不好选择。
r0estir0bbe '16

62

JavaScript中的Getter和Setter

总览

JavaScript中的getter和setter用于定义计算的属性访问器。计算属性是使用函数来获取或设置对象值的属性。基本理论正在做这样的事情:

var user = { /* ... object with getters and setters ... */ };
user.phone = '+1 (123) 456-7890'; // updates a database
console.log( user.areaCode ); // displays '123'
console.log( user.area ); // displays 'Anytown, USA'

这对于在访问属性时自动进行后台操作非常有用,例如将数字保留在范围内,重新格式化字符串,触发值已更改的事件,更新关系数据,提供对私有属性的访问等。

下面的示例显示了基本语法,尽管它们只是简单地获取和设置内部对象的值,而无需执行任何特殊操作。如上所述,在实际情况下,您将修改输入和/或输出值以适合您的需求。

获取/设置关键字

的ECMAScript 5支持getset关键字用于限定计算性能。它们可与IE 8及以下版本之外的所有现代浏览器一起使用。

var foo = {
    bar : 123,
    get bar(){ return bar; },
    set bar( value ){ this.bar = value; }
};
foo.bar = 456;
var gaz = foo.bar;

自定义获取器和设置器

get并且set不是保留字,因此可以重载它们以创建您自己的自定义跨浏览器计算属性功能。这可以在任何浏览器中使用。

var foo = {
    _bar : 123,
    get : function( name ){ return this[ '_' + name ]; },
    set : function( name, value ){ this[ '_' + name ] = value; }
};
foo.set( 'bar', 456 );
var gaz = foo.get( 'bar' );

或者,对于更紧凑的方法,可以使用单个功能。

var foo = {
    _bar : 123,
    value : function( name /*, value */ ){
        if( arguments.length < 2 ){ return this[ '_' + name ]; }
        this[ '_' + name ] = value;
    }
};
foo.value( 'bar', 456 );
var gaz = foo.value( 'bar' );

避免这样做,否则可能导致代码膨胀。

var foo = {
    _a : 123, _b : 456, _c : 789,
    getA : function(){ return this._a; },
    getB : ..., getC : ..., setA : ..., setB : ..., setC : ...
};

对于上面的示例,内部属性名称用下划线抽象出来,以阻止用户简单地执行foo.barvs foo.get( 'bar' )和获取“未经烹煮”的值。您可以使用条件代码来执行不同的操作,具体取决于要访问的属性的名称(通过name参数)。

Object.defineProperty()

使用Object.defineProperty()是添加getter和setter的另一种方法,并且可以在定义对象后将其用于对象。它也可以用来设置可配置和可枚举的行为。此语法也可用于IE 8,但不幸的是仅适用于DOM对象。

var foo = { _bar : 123 };
Object.defineProperty( foo, 'bar', {
    get : function(){ return this._bar; },
    set : function( value ){ this._bar = value; }
} );
foo.bar = 456;
var gaz = foo.bar;

__defineGetter __()

最后,__defineGetter__()是另一种选择。它已过时,但仍在网络上广泛使用,因此不太可能很快消失。它适用于除IE 10及以下版本的所有浏览器。尽管其他选项在非IE上也能很好地工作,所以这个选项不是那么有用。

var foo = { _bar : 123; }
foo.__defineGetter__( 'bar', function(){ return this._bar; } );
foo.__defineSetter__( 'bar', function( value ){ this._bar = value; } );

另外值得注意的是,在后面的示例中,内部名称必须与访问者名称不同,以避免递归(即,foo.bar调用,foo.get(bar)调用,foo.bar调用foo.get(bar)...)。

也可以看看

MDN getsetObject.defineProperty()__ defineGetter __()__ defineSetter __()
MSDN IE8 Getter支持


1
更紧凑的方法中this[ '_' + name ] = value;可以this[ '_' + name ] = arguments[1];并且不需要指定valuearg。
Redhart

1
示例: var foo = { bar : 123, get bar(){ return bar; }, set bar( value ){ this.bar = value; } }; foo.bar = 456; 引发异常:未捕获RangeError:在Object.set bar [as bar](<anonymous>:4:32)处Object.set bar [as bar](<anonymous>:4:32)超出最大调用堆栈大小)在Object.set bar [as bar](<anonymous>:4:32)在Object.set bar [as bar](<anonymous>:4:32)at Object.set bar [as bar](<anonymous> :4:32)at Object.set bar [as bar](<anonymous>:4:32)
nevf

1
设置/获取名称必须与属性名称不同。因此,代替bar: 123this.bar = value等,将其更改_bar 为例如。请参阅:hongkiat.com/blog/getters-setters-javascript
nevf

@nevf感谢您的纠正!是的,通常具有计算属性的“真实”内部名称被称为like _foomFoo。如果它与getter / setter相同,则将由于递归而导致无限循环,然后将导致Stack Overflow™;-),因为当您说a = b时,它将调用a.get(b),后者本身将调用a = b ,它调用a.get(b),...
Beejor

58

例如,您将使用它们来实现计算属性。

例如:

function Circle(radius) {
    this.radius = radius;
}

Object.defineProperty(Circle.prototype, 'circumference', {
    get: function() { return 2*Math.PI*this.radius; }
});

Object.defineProperty(Circle.prototype, 'area', {
    get: function() { return Math.PI*this.radius*this.radius; }
});

c = new Circle(10);
console.log(c.area); // Should output 314.159
console.log(c.circumference); // Should output 62.832

(CodePen)


好的,我想我已经开始了解它了。我正在尝试为数组对象的length属性分配一个getter方法,但是却出现一个错误:“声明var length”,代码看起来像这样:obj = []; obj .__ defineGetter __('length',function(){return this.length;});

1
这是因为Array对象已经具有内置的length属性。如果允许重新声明,则调用新长度将无限递归。尝试调用属性“ my_length”或类似的名称。
millimoose

为了在一个语句中定义两个getter,请使用Object.defineProperties
r0estir0bbe

您不能只制作{“ area”:function(){return ...}}吗?只需将其分配为对象属性
RegarBoy '18

@developer这不是该语言定义的Javascript getter,而只是一个函数。您必须调用它来获取值,它不会过载对属性的访问。还有一个特殊的地狱圈子供那些在JS中发明自己的破碎对象系统而不是在已有的对象系统上构建的人们使用。
millimoose

16

很抱歉再次提出一个老问题,但我想我可能会提供几个非常基本的示例和伪造的解释。到目前为止,没有任何其他答案能像MDN指南的第一个示例那样说明语法,这几乎是一个基本知识。

吸气剂:

var settings = {
    firstname: 'John',
    lastname: 'Smith',
    get fullname() { return this.firstname + ' ' + this.lastname; }
};

console.log(settings.fullname);

...将记录John Smith,当然。一吸气表现得像可变对象的属性,但提供的功能,实时计算其返回值的灵活性。从根本上讲,这是创建调用时不需要()的函数的理想方法。

二传手:

var address = {
    set raw(what) {
        var loc = what.split(/\s*;\s*/),
        area = loc[1].split(/,?\s+(\w{2})\s+(?=\d{5})/);

        this.street = loc[0];
        this.city = area[0];
        this.state = area[1];
        this.zip = area[2];
    }
};

address.raw = '123 Lexington Ave; New York NY  10001';
console.log(address.city);

...将登录New York到控制台。像getter一样,setter的调用语法与设置对象属性值的语法相同,但这是调用不带()的函数的另一种奇特的方式。

有关更全面,更实际的示例,请参见此jsfiddle。将值传递到对象的设置器中会触发其他对象项的创建或填充。具体来说,在jsfiddle示例中,传递数字数组会提示设置器计算均值,中位数,众数和范围;然后为每个结果设置对象属性。


我仍然不了解使用get和set与创建getMethod或setMethod的好处。您可以不使用()调用它的唯一好处是吗?必须将其添加到javascript中的另一个原因。
Andreas

@Andreas Getter和setter在调用时的行为类似于属性,可以帮助阐明其预期目的。他们不会释放原本会缺少的能力,但是它们的使用可以帮助您组织思想。那才是真正的好处。作为一个实际示例,我曾经使用吸气剂来扩展Google Maps对象。我需要计算相机的侧倾角,以便可以将地图图块平移到地平线。Google现在会在后端自动执行此操作;但当时对我来说,将其maps.roll作为属性而不是maps.roll()return val 很有帮助。这只是一个偏爱。
rojo

因此,仅使用语法糖就能使代码看起来更干净,而无需使用()。我不明白为什么您不能以maps.roll()
Andreas

@安德烈亚斯谁说我不能?就像我说的那样,这只是帮助我保持思想井井有条的一种方法。编码是一门艺术。您不会问鲍勃·罗斯(Bob Ross),为什么他本来可以用橙子时却不得不用burn土。您现在可能看不到需求,但是有一天,当您决定绘画需要一点burn色时,它将出现在您的调色板上。
rojo,2016年

:)我看到get和set语法可以做的一件事是,如果将其用作属性的属性,则会自动运行。
Andreas

11

仅当您具有类的私有属性时,getter和setter才有意义。由于Java语言实际上并不像您通常从面向对象语言中所想到的那样具有私有类属性,因此可能很难理解。这是私有计数器对象的一个​​示例。关于这个对象的好处是,不能从对象外部访问内部变量“ count”。

var counter = function() {
    var count = 0;

    this.inc = function() {
        count++;
    };

    this.getCount = function() {
        return count;
    };
};

var i = new Counter();
i.inc();
i.inc();
// writes "2" to the document
document.write( i.getCount());

如果您仍然感到困惑,请参阅Crockford的Java私有成员文章。


39
我不同意。对于封装其定义可能不仅仅是简单变量的信息,获取器和设置器也非常有用。如果您需要更改以前使用简单属性以及可能依赖于其他属性的系统的行为,这将很方便。此外,您的示例仅演示了“伪获取器”,它们只是函数。真正的JavaScript getter 以简单值的形式出现(并且无需使用函数符号即可访问),因此它们的真正功能是。
devios1 2011年

不知道我是否会称其强大。看起来像X但实际上是Y的东西不一定很清楚。我绝对不会期望var baz = foo.bar背后有完整的隐藏行为。我想到的是从foo.getBar(),但是。
AgmLauncher '16

8

我认为您链接的第一篇文章很清楚地指出:

以这种方式编写JavaScript的明显优势在于,您可以使用它来掩盖您不希望用户直接访问的值。

这里的目标是通过仅允许通过get()set()方法访问字段来封装和抽象出这些字段。这样,您可以使用任意方式在内部存储字段/数据,但是外部组件仅不在您发布的界面之外。这使您可以在不更改外部接口的情况下进行内部更改,在set()方法内进行一些验证或错误检查等。


6

尽管我们经常习惯于在没有任何访问控制的情况下查看具有公共属性的对象,但是JavaScript允许我们准确地描述属性。实际上,我们可以使用描述符来控制如何访问属性以及可以对其应用哪种逻辑。考虑以下示例:

var employee = {
    first: "Boris",
    last: "Sergeev",
    get fullName() {
        return this.first + " " + this.last;
    },
    set fullName(value) {
        var parts = value.toString().split(" ");
        this.first = parts[0] || "";
        this.last = parts[1] || "";
    },
    email: "boris.sergeev@example.com"
};

最终结果:

console.log(employee.fullName); //Boris Sergeev
employee.fullName = "Alex Makarenko";

console.log(employee.first);//Alex
console.log(employee.last);//Makarenko
console.log(employee.fullName);//Alex Makarenko

2

令人困惑的是... getter是在获取属性时调用的函数,setters是在设置属性时调用的。例如,如果你这样做

obj.prop = "abc";

您正在设置属性prop,如果您使用的是getters / setters,则将使用“ abc”作为参数来调用setter函数。理想情况下,对象内部的setter函数定义如下所示:

set prop(var) {
   // do stuff with var...
}

我不确定跨浏览器的实施情况如何。Firefox似乎还具有另一种语法,带有双下划线的特殊(“魔术”)方法。与往常一样,Internet Explorer不支持任何一个。


2

您可以通过构造函数的原型为js类定义实例方法。

以下是示例代码:

// BaseClass

var BaseClass = function(name) {
    // instance property
    this.name = name;
};

// instance method
BaseClass.prototype.getName = function() {
    return this.name;
};
BaseClass.prototype.setName = function(name) {
    return this.name = name;
};


// test - start
function test() {
    var b1 = new BaseClass("b1");
    var b2 = new BaseClass("b2");
    console.log(b1.getName());
    console.log(b2.getName());

    b1.setName("b1_new");
    console.log(b1.getName());
    console.log(b2.getName());
}

test();
// test - end

并且,这对于任何浏览器都适用,您也可以简单地使用nodejs来运行此代码。


1
这只是创建新的getName和setName方法。这些与创建属性无关!
uzay95 '17

2

对阅读说明也有些困惑,因为我试图向我没有编写的现有原型添加属性,因此替换原型似乎是错误的方法。因此,为了后代,这是我添加last属性的方式Array

Object.defineProperty(Array.prototype, "last", {
    get: function() { return this[this.length - 1] }
});

比添加功能恕我直言好一点。


1

如果您指的是访问器的概念,那么简单的目标是对底层存储进行任意操作。最极端的机制是

function Foo(someValue) {
    this.getValue = function() { return someValue; }
    return this;
}

var myFoo = new Foo(5);
/* We can read someValue through getValue(), but there is no mechanism
 * to modify it -- hurrah, we have achieved encapsulation!
 */
myFoo.getValue();

如果您指的是实际的JS getter / setter功能,例如。defineGetter/ defineSetter{ get Foo() { /* code */ } },那么值得注意的是,在大多数现代引擎中,这些属性的后续使用将比其他情况慢得多。例如。比较性能

var a = { getValue: function(){ return 5; }; }
for (var i = 0; i < 100000; i++)
    a.getValue();

var a = { get value(){ return 5; }; }
for (var i = 0; i < 100000; i++)
    a.value;

-1

我为你们准备了一个可能有点丑陋的工具,但确实可以跨平台完成

function myFunc () {

var _myAttribute = "default";

this.myAttribute = function() {
    if (arguments.length > 0) _myAttribute = arguments[0];
    return _myAttribute;
}
}

这样,当您打电话时

var test = new myFunc();
test.myAttribute(); //-> "default"
test.myAttribute("ok"); //-> "ok"
test.myAttribute(); //-> "ok"

如果您真的想让事情变得有趣..您可以插入typeof检查:

if (arguments.length > 0 && typeof arguments[0] == "boolean") _myAttribute = arguments[0];
if (arguments.length > 0 && typeof arguments[0] == "number") _myAttribute = arguments[0];
if (arguments.length > 0 && typeof arguments[0] == "string") _myAttribute = arguments[0];

或使用高级typeof检查甚至更疯狂:位于encodingforums.com的type.of()代码


关键是能够将属性更改为更高级的属性,而无需更改公共接口。添加call()标签会对其进行更改。
Michael Scott Cuthbert
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.