Getters \ setters cho người giả


133

Tôi đã cố gắng để hiểu về getters và setters và nó không bị chìm. Tôi đã đọc Getters và Setters JavaScriptXác định Getters và Setters và chỉ không nhận được nó.

Ai đó có thể nói rõ:

  1. Những gì một getter và setter có nghĩa là để làm, và
  2. Cho một số ví dụ RẤT đơn giản?

12
Cá nhân tôi không biết làm thế nào bạn có thể nhận được lời giải thích rõ ràng hơn John ...
Jason Bunting

Trong thế giới OO, getter và setter giúp viết một lớp được gói gọn
trao đổi quá mức vào

Về cơ bản tôi thấy nó như thế này: bạn đang xác định tình trạng quá tải để nhận và thiết lập thuộc tính và những tình trạng quá tải này là các chức năng; nhưng, bạn không phải gọi họ. Bằng cách này, bạn có thể thay thế a = setValue(5);bằng a = 5;setValue()do đó sẽ được gọi dưới mui xe để làm bất cứ điều gì bạn thích.
Andrew

Câu trả lời:


102

Ngoài câu trả lời của @ millimoose , setters cũng có thể được sử dụng để cập nhật các giá trị khác.

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];
    }
};

Bây giờ, bạn có thể thiết lập fullName, firstlastsẽ được cập nhật và ngược lại.

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: Không, mặc dù, Internet Explorer 9 không hỗ trợ Object.definePropertychức năng mới hơn có thể xác định getters và setters.
Matthew Crumley

9
Có phải thực sự đau đớn khi MS không hỗ trợ JS chính xác và họ không làm cho ánh sáng bạc của họ chạy khắp mọi nơi, vì vậy tôi phải lập trình mọi thứ hai lần, một cho SL và một cho phần còn lại của thế giới :)
Akash Kava

2
@Martin: Bạn có thể đặt chúng ở chế độ riêng tư bằng cách sử dụng kỹ thuật tương tự như trong câu trả lời của John . Nếu bạn muốn sử dụng getters / setters thực, bạn sẽ phải sử dụng this.__defineGetter__hoặc Object.definePropertychức năng mới hơn .
Matthew Crumley

1
Chỉ có một vấn đề với cách tiếp cận được liệt kê ở trên, nếu bạn muốn thêm getters và setters cho lớp đã tồn tại, nó sẽ ghi đè lên các nguyên mẫu và các phương thức ban đầu sẽ không thể truy cập được.
xchg.ca

1
Cách tiếp cận này không ghi đè Name.prototype.constructor? Có vẻ như là một thay thế xấu cho câu trả lời của millimoose .
r0estir0bbe

62

Getters và Setters trong JavaScript

Tổng quat

Getter và setter trong JavaScript được sử dụng để xác định tính chất tính , hoặc accessors . Thuộc tính được tính là một thuộc tính sử dụng hàm để lấy hoặc đặt giá trị đối tượng. Lý thuyết cơ bản là làm một cái gì đó như thế này:

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'

Điều này hữu ích để tự động thực hiện những điều phía sau hậu trường khi một thuộc tính được truy cập, như giữ các số trong phạm vi, định dạng lại chuỗi, kích hoạt các sự kiện thay đổi giá trị, cập nhật dữ liệu quan hệ, cung cấp quyền truy cập vào các thuộc tính riêng tư, v.v.

Các ví dụ dưới đây cho thấy cú pháp cơ bản, mặc dù chúng chỉ đơn giản nhận và đặt giá trị đối tượng bên trong mà không làm gì đặc biệt. Trong trường hợp thực tế, bạn sẽ sửa đổi giá trị đầu vào và / hoặc đầu ra cho phù hợp với nhu cầu của bạn, như đã lưu ý ở trên.

lấy / đặt từ khóa

ECMAScript 5 hỗ trợ getsettừ khóa để xác định các thuộc tính được tính toán. Chúng hoạt động với tất cả các trình duyệt hiện đại trừ IE 8 trở xuống.

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

Getters và Setters tùy chỉnh

getsetkhông dành riêng các từ, vì vậy chúng có thể bị quá tải để tạo các chức năng thuộc tính được tính toán trên nhiều trình duyệt tùy chỉnh của riêng bạn. Điều này sẽ làm việc trong bất kỳ trình duyệt.

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' );

Hoặc đối với một cách tiếp cận nhỏ gọn hơn, một chức năng duy nhất có thể được sử dụng.

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' );

Tránh làm một cái gì đó như thế này, có thể dẫn đến phình mã.

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

Đối với các ví dụ trên, tên thuộc tính nội bộ được trừu tượng hóa bằng dấu gạch dưới để không khuyến khích người dùng thực hiện đơn giản foo.barso với foo.get( 'bar' )và nhận giá trị "chưa được xử lý". Bạn có thể sử dụng mã có điều kiện để làm những việc khác nhau tùy thuộc vào tên của tài sản được truy cập (thông qua nametham số).

Object.defineProperty ()

Sử dụng Object.defineProperty()là một cách khác để thêm getters và setters và có thể được sử dụng trên các đối tượng sau khi chúng được xác định. Nó cũng có thể được sử dụng để thiết lập các hành vi có thể cấu hình và vô số. Cú pháp này cũng hoạt động với IE 8, nhưng không may chỉ trên các đối tượng 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 __ ()

Cuối cùng, __defineGetter__()là một lựa chọn khác. Nó không được dùng nữa, nhưng vẫn được sử dụng rộng rãi trên web và do đó khó có thể biến mất bất cứ lúc nào sớm. Nó hoạt động trên tất cả các trình duyệt trừ IE 10 trở xuống. Mặc dù các tùy chọn khác cũng hoạt động tốt trên non-IE, vì vậy tùy chọn này không hữu ích.

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

Cũng đáng chú ý là trong các ví dụ sau, tên nội bộ phải khác với tên người truy cập để tránh đệ quy (nghĩa là foo.bargọi foo.get(bar)cuộc foo.bargọi gọi foo.get(bar)...).

Xem thêm

MDN nhận , thiết lập , Object.defineProperty () , __defineGetter __ () , __defineSetter __ () Hỗ trợ Getter
MSDN IE8


1
Trong cách tiếp cận nhỏ gọn hơn , this[ '_' + name ] = value;có thể this[ '_' + name ] = arguments[1];và sẽ không cần chỉ định valuearg.
Làm lại

1
Ví dụ: var foo = { bar : 123, get bar(){ return bar; }, set bar( value ){ this.bar = value; } }; foo.bar = 456; Tăng một ngoại lệ: Uncaught RangeError: Đã vượt quá kích thước ngăn xếp cuộc gọi tối đa tại thanh Object.set [dưới dạng thanh] (<nặc danh>: 4: 32) tại thanh Object.set [dưới dạng thanh] (<nặc danh>: 4: 32 ) tại thanh Object.set [dưới dạng thanh] (<nặc danh>: 4: 32) tại thanh Object.set [dưới dạng thanh] (<nặc danh>: 4: 32) tại thanh Object.set [dưới dạng thanh] (<nặc danh> : 4: 32) tại thanh Object.set [dưới dạng thanh] (<nặc danh>: 4: 32)
nevf

1
Tên set / get phải khác với tên thuộc tính. Vì vậy, thay vì bar: 123this.bar = valuevv thay đổi những điều này để _bar ví dụ. Xem: hongkiat.com/blog/getters-setters-javascript
nevf

@nevf Cảm ơn đã sửa! Có, thông thường với các thuộc tính được tính toán, nội bộ "thực" được đặt tên như _foohoặc mFoo. Nếu nó giống như getter / setter, nó sẽ gây ra một vòng lặp vô hạn do đệ quy và sau đó là Stack Overflow ™ ;-) bởi vì khi bạn nói a = b, nó gọi a.get (b) mà chính nó gọi là a = b , gọi a.get (b), ...
Beejor

58

Bạn sẽ sử dụng chúng chẳng hạn để thực hiện các thuộc tính được tính toán.

Ví dụ:

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)


Ok, tôi nghĩ rằng tôi đang bắt đầu để có được nó. Tôi đang cố gắng gán một getter cho thuộc tính độ dài của một đối tượng mảng nhưng gặp lỗi: "Khai báo lại độ dài var" Và mã trông như thế này: obj = []; obj .__ xác địnhGetter __ ('length', function () {return this.length;});

1
Đó là bởi vì các đối tượng Array đã có thuộc tính chiều dài dựng sẵn. Nếu quy định lại được cho phép, việc gọi độ dài mới sẽ lặp lại vô tận. Hãy thử gọi thuộc tính "my_length" hoặc một số như vậy.
millimoose

Để xác định cả hai getters trong một tuyên bố, sử dụng Object.defineProperties.
r0estir0bbe

Bạn không thể tạo {"area": ​​function () {return ...}}? chỉ cần gán nó làm tài sản đối tượng
RegarBoy

@developer Đó không phải là một getter Javascript như được định nghĩa bởi ngôn ngữ, đó chỉ là một chức năng. Bạn phải gọi nó để nhận giá trị, nó không quá tải quyền truy cập vào tài sản. Ngoài ra, có một vòng địa ngục đặc biệt dành riêng cho những người phát minh ra hệ thống đối tượng bị hỏng của chính họ trong JS thay vì xây dựng trên hệ thống mà nó đã có.
millimoose

16

Xin lỗi để làm sống lại một câu hỏi cũ, nhưng tôi nghĩ rằng tôi có thể đóng góp một vài ví dụ rất cơ bản và giải thích cho người giả. Không có câu trả lời nào khác được đăng như vậy minh họa cú pháp như hướng dẫn MDN ví dụ đầu tiên của , về cơ bản như người ta có thể nhận được.

Getter:

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

console.log(settings.fullname);

... Sẽ đăng nhập John Smith, tất nhiên. Một getter hoạt động giống như một thuộc tính đối tượng biến, nhưng cung cấp tính linh hoạt của hàm để tính giá trị trả về của nó khi đang bay. Về cơ bản, đây là một cách thú vị để tạo một hàm không yêu cầu () khi gọi.

Setter:

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);

... sẽ đăng nhập New Yorkvào bảng điều khiển. Giống như getters, setters được gọi với cùng một cú pháp như đặt giá trị của thuộc tính đối tượng, nhưng là một cách ưa thích khác để gọi một hàm không có ().

Xem jsfiddle này cho một ví dụ kỹ lưỡng hơn, có lẽ thực tế hơn. Việc chuyển các giá trị vào trình thiết lập của đối tượng sẽ kích hoạt việc tạo hoặc dân số của các mục đối tượng khác. Cụ thể, trong ví dụ jsfiddle, việc truyền một mảng các số sẽ nhắc setter tính giá trị trung bình, trung vị, chế độ và phạm vi; sau đó đặt thuộc tính đối tượng cho từng kết quả.


Tôi vẫn không hiểu lợi ích của việc sử dụng get và set so với việc tạo getMethod hoặc setMethod. Là lợi ích duy nhất mà bạn có thể gọi nó mà không có ()? Phải có một lý do khác để nó được thêm vào javascript.
Andreas

@Andreas Getters và setters hoạt động giống như các thuộc tính khi được gọi, có thể giúp nói rõ mục đích dự định của chúng. Họ không mở khóa các khả năng còn thiếu, nhưng việc sử dụng chúng có thể giúp bạn sắp xếp suy nghĩ của mình. Đó là lợi ích thực sự. Như một ví dụ thực tế, tôi đã từng sử dụng một getter để mở rộng đối tượng Google Maps. Tôi cần tính toán góc cuộn camera để có thể xoay các ô bản đồ phẳng đến đường chân trời. Google thực hiện điều này tự động ở mặt sau ngay bây giờ; nhưng tại thời điểm đó nó hữu ích cho tôi để lấy lại maps.rollnhư một tài sản chứ không phải là maps.roll()val trả lại. Đó chỉ là một sở thích.
rojo

Vì vậy, nó chỉ là cú pháp đường để làm cho mã trông sạch hơn mà không có (). Tôi không thể hiểu tại sao bạn không thể lấy ví dụ của mình vớimaps.roll()
Andreas

@Andreas Ai nói tôi không thể? Như tôi nói, đó chỉ là một cách giúp tôi giữ suy nghĩ của mình ngăn nắp. Viết mã là một nghệ thuật. Bạn đừng hỏi Bob Ross tại sao anh ta phải sử dụng đất nung khi anh ta có thể sử dụng màu cam. Bạn có thể không thấy nhu cầu bây giờ, nhưng một ngày nào đó khi bạn quyết định bức tranh của mình cần một chút màu nâu đất, nó sẽ nằm trong bảng màu của bạn.
rojo

:) một điều mà tôi thấy rằng cú pháp get và set thực hiện, là tự động chạy nếu nó được sử dụng như một thuộc tính của một thuộc tính.
Andreas

11

Getters và setters thực sự chỉ có ý nghĩa khi bạn có thuộc tính riêng của các lớp. Vì Javascript không thực sự có các thuộc tính lớp riêng như bạn thường nghĩ từ Ngôn ngữ hướng đối tượng, nên có thể khó hiểu. Đây là một ví dụ về một đối tượng truy cập riêng. Điều thú vị về đối tượng này là "số đếm" bên trong không thể được truy cập từ bên ngoài đối tượng.

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());

Nếu bạn vẫn còn bối rối, hãy xem bài viết của Crockford về Thành viên tư nhân trong Javascript .


39
Tôi không đồng ý. Getters và setters cũng rất hữu ích để đóng gói thông tin mà định nghĩa của nó có thể không chỉ là một biến đơn giản. Nó có thể hữu ích nếu bạn cần thay đổi hành vi của một hệ thống trước đây đã sử dụng các thuộc tính đơn giản và những thứ khác có thể phụ thuộc vào. Hơn nữa, ví dụ của bạn chỉ thể hiện "giả getters" chỉ là các hàm. Các trình thu JavaScript thực sự xuất hiện dưới dạng các giá trị đơn giản (và được truy cập mà không có ký hiệu hàm), do đó sức mạnh thực sự của chúng.
devios1

Không chắc chắn nếu tôi sẽ gọi đó là mạnh mẽ. Một cái gì đó xuất hiện dưới dạng X nhưng thực sự là Y không nhất thiết phải rõ ràng. Tôi hoàn toàn KHÔNG mong đợi var baz = foo.barcó một tập hợp đầy đủ các hành vi ẩn đằng sau nó. Tôi sẽ hy vọng rằng từ foo.getBar(), tuy nhiên.
AgmLauncher

8

Tôi nghĩ rằng bài viết đầu tiên bạn liên kết đến nó khá rõ ràng:

Lợi thế rõ ràng để viết JavaScript theo cách này là bạn có thể sử dụng nó với các giá trị tối nghĩa mà bạn không muốn người dùng truy cập trực tiếp.

Mục tiêu ở đây là gói gọn và trừu tượng hóa các trường bằng cách chỉ cho phép truy cập vào chúng thông qua một get()hoặc set()phương thức. Bằng cách này, bạn có thể lưu trữ trường / dữ liệu bên trong theo bất kỳ cách nào bạn muốn, nhưng các thành phần bên ngoài chỉ cách xa giao diện đã xuất bản của bạn. Điều này cho phép bạn thực hiện các thay đổi bên trong mà không thay đổi giao diện bên ngoài, để thực hiện một số xác nhận hoặc kiểm tra lỗi trong set()phương thức, v.v.


6

Mặc dù chúng ta thường thấy các đối tượng có thuộc tính công cộng mà không có bất kỳ kiểm soát truy cập nào, JavaScript cho phép chúng ta mô tả chính xác các thuộc tính. Trong thực tế, chúng ta có thể sử dụng các mô tả để kiểm soát cách truy cập một thuộc tính và logic nào chúng ta có thể áp dụng cho nó. Hãy xem xét ví dụ sau:

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"
};

Kết quả cuối cùng:

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

Có gì khó hiểu về nó ... getters là các chức năng được gọi khi bạn có một thuộc tính, setters, khi bạn đặt nó. ví dụ, nếu bạn làm

obj.prop = "abc";

Bạn đang thiết lập prop prop thuộc tính, nếu bạn đang sử dụng getters / setters, thì hàm setter sẽ được gọi, với "abc" làm đối số. Định nghĩa hàm setter bên trong đối tượng sẽ trông giống như thế này:

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

Tôi không chắc nó được triển khai tốt như thế nào trên các trình duyệt. Có vẻ như Firefox cũng có một cú pháp thay thế, với các phương thức đặc biệt ("ma thuật") được nhấn mạnh gấp đôi. Như thường lệ, Internet Explorer không hỗ trợ bất kỳ thứ gì trong số này.


2

Bạn có thể định nghĩa phương thức cá thể cho lớp js, thông qua nguyên mẫu của hàm tạo.

Sau đây là mã mẫu:

// 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

Và, điều này sẽ hoạt động cho bất kỳ trình duyệt nào, bạn cũng có thể chỉ cần sử dụng nodejs để chạy mã này.


1
Đây chỉ là tạo các phương thức getName và setName mới. Những điều này không liên quan để tạo tài sản!
uzay95

2

Tôi cũng hơi bối rối trước lời giải thích mà tôi đã đọc , bởi vì tôi đang cố gắng thêm một tài sản vào một nguyên mẫu hiện có mà tôi không viết, vì vậy việc thay thế nguyên mẫu có vẻ như là cách tiếp cận sai. Vì vậy, đối với hậu thế, đây là cách tôi đã thêm một lasttài sản vào Array:

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

Bao giờ cũng đẹp hơn một chức năng IMHO.


1

Nếu bạn đang đề cập đến khái niệm người truy cập, thì mục tiêu đơn giản là ẩn bộ nhớ bên dưới khỏi thao tác tùy ý. Cơ chế cực đoan nhất cho việc này là

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();

Nếu bạn đang đề cập đến tính năng getter / setter thực tế, vd. defineGetter/ defineSetter, hoặc { get Foo() { /* code */ } }, sau đó, đáng chú ý là trong hầu hết các động cơ hiện đại, việc sử dụng các thuộc tính đó sẽ chậm hơn nhiều so với trước đây. ví dụ. so sánh hiệu suất của

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

so với

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

-1

Tôi đã có một cái cho các bạn có thể hơi xấu xí, nhưng nó đã được thực hiện trên các nền tảng

function myFunc () {

var _myAttribute = "default";

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

theo cách này, khi bạn gọi

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

Nếu bạn thực sự muốn thêm gia vị .. bạn có thể chèn một loại kiểm tra:

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];

hoặc thậm chí điên rồ hơn với mã kiểm tra typeof nâng cao: mã type.of () tạiodingforums.com


điểm quan trọng là có thể thay đổi một thuộc tính thành thứ gì đó lạ hơn mà không cần thay đổi giao diện công cộng. Thêm một thẻ gọi () thay đổi nó.
Michael Scott Cuthbert
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.