NỘI DUNG BÀI VIẾT
Trong OOP, getter
và setter
là các hàm hoặc phương thức được dùng để lấy ra hoặc thiết lập giá trị cho các biến. Khái niệm getter
và setter
rất phổ biến trong ngôn ngữ lập trình. Hầu hết các ngôn ngữ lập trình bậc cao đều có bộ cú pháp để thực hiện getter và setter, bao gồm cả JavaScript.
Trong bài viết này, chúng ta sẽ tìm hiểu getter
, setter
là gì và cách khởi tạo cũng như sử dụng chúng trong JavaScript.
getter và setter và việc đóng gói
getter
và setter
luôn được đề cập đến cùng với việc đóng gói. Chúng ta có thể hiêu việc đóng gói theo 2 cách :
- Thiết lập bộ 3
data
–getter
–setter
để truy cập và sửa đổi dữ liệu. Định nghĩa này rất hữu ích khi một số hành động kiểu như làvalidation
– phải được thực hiện trên dữ liệu trước khi lưu hoặc là xem nó.getter
vàsetter
cung cấp điều này. - Có một định nghĩa chặt chẽ hơn (theo mình thì nó cũng dễ hiểu và quen thuộc hơn với mọi người), theo đó thì đóng gói được thực hiện để ẩn dữ liệu, làm cho thành phần được đóng gói sẽ không thể bị truy cập, ngoại trừ việc thông qua các
getter
vàsetter
.
Khởi tạo getter và setter
1. Dưới dạng 1 phương thức
Vì getter
và setter
về cơ bản là những hàm để lấy ra hoặc thay đổi giá trị, có nhiều cách để khởi tạo vào sử dụng chúng. Ví dụ :
var obj = { foo: 'this is the value of foo', getFoo: function() { return this.foo; }, setFoo: function(val) { this.foo = val; } } console.log(obj.getFoo()); // "this is the value of foo" obj.setFoo('hello'); console.log(obj.getFoo()); // "hello"
Đây là cách đơn giản nhất để khởi tạo getter
và setter
. Có 1 thuộc tính foo
và 2 phương thức :
setFoo
để gán giá trị cho thuộc tínhfoo
.getFoo
để trả về giá trị của thuộc tínhfoo
2. Với từ khóa
Có một cách tốt hơn và chúng ta cũng hay dùng hơn đó là sử dụng từ khóa get
, set
.
Để khởi tạo một getter
thì bạn chỉ việc thêm từ khóa get
vào ngay trước khai báo hàm để biến hàm đó thành getter
; cũng tương tự với từ khóa set
trước hàm setter
. Cú pháp như sau :
var obj = { fooVal: 'this is the value of foo', get foo() { return this.fooVal; }, set foo(val) { this.fooVal = val; } } // getter console.log(obj.foo); // "this is the value of foo" // setter obj.foo = 'hello'; console.log(obj.foo); // "hello"
Lưu ý là dữ liệu chỉ có thể lưu trữ dưới một cái tên thuộc tính (ở ví dụ trên là fooVal
) .
Cách khai báo nào sẽ tốt hơn?
Trong 2 cách khai báo trên thì cách khai báo với từ khóa là tốt hơn, bạn có thể sử dụng toán tử gán để set
dữ liệu và toán tử .
để get
dữ liệu. Cách này cũng sẽ rất quen thuộc với bạn.
Chống ghi đè
Nếu vẫn phải sử dụng cách khai báo thứ nhất, hãy thay đổi các thuộc tính getter
và setter
trở thành read-only, bằng cách khởi tạo chúng bằng Object.defineProperties
.
Các thuộc tính được khai báo thông qua Object.defineProperties
, Object.defineProperty
và Reflect.defineProperty
tự động được cấu hình writable: false
.
Ví dụ :
/* Overwrite prevention */ var obj = { foo: 'this is the value of foo' }; Object.defineProperties(obj, { 'getFoo': { value: function () { return this.foo; } }, 'setFoo': { value: function (val) { this.foo = val; } } }); obj.getFoo = 66; // getFoo is not going to be overwritten! console.log(obj.getFoo()); // "this is the value of foo"
Hoạt động bên trong getter và setter
Một khi đã tạo ra getter
và setter
thì bạn có thể tiếp tục và thực hiện các thao tác trên dữ liệu trước khi thay đổi hoặc là trả về.
Trong ví dụ bên dưới, trong hàm getter
, dữ liệu được gắn thêm đằng sau một chuỗi trước khi trả về. Trong setter
thì thực hiện xác nhận dữ liệu có phải là number
hay không trước khi gán giá trị.
var obj = { n: 67, get id() { return 'The ID is: ' + this.n; }, set id(val) { if (typeof val === 'number') this.n = val; } } console.log(obj.id); // "The ID is: 67" obj.id = 893; console.log(obj.id); // "The ID is: 893" obj.id = 'hello'; console.log(obj.id); // "The ID is: 893"
Bảo vệ dữ liệu với getter và setter
Chúng ta cùng chuyển sang tìm hiểu cách ẩn dữ liệu khi nhìn từ bên ngoài vào với sự giúp đỡ của getter
và setter
.
Dữ liệu với getter và setter không được bảo vệ
Việc chúng ta khai báo các getter
và setter
không đồng nghĩa là dữ liệu chỉ có thể truy cập và thay đổi thông qua các phương thức đó. Dưới đây là một ví dụ dữ liệu được thay đổi trực tiếp :
var obj = { fooVal: 'this is the value of foo', get foo() { return this.fooVal; }, set foo(val) { this.fooVal = val; } } obj.fooVal = 'hello'; console.log(obj.foo); // "hello"
Ta thay đổi trực tiếp thuộc tính fooVal
mà không thông qua setter
, dữ liệu ban đầu chúng ta đặt trong obj
đã mất .
Để tránh việc đó, ta cần bảo vệ dữ liệu của mình, có thể là thêm giới hạn phạm vi mà dữ liệu của bạn sẵn dùng. Cùng tìm hiều kỹ hơn về phần này nhé.
1. Phạm vi block
Bên trong block thì dữ liệu sẽ được định nghĩa bằng từ khóa let
để hạn chế phạm vi dữ liệu.
Một block
có thể được tạo ra bằng cách đặt mã của bạn bên trong cặp dấu {}
(và khi dùng thế này thì nên có comment để mọi người hiểu)
/* BLOCK SCOPE, leave the braces alone! */ { let fooVal = 'this is the value of foo'; var obj = { get foo() { return fooVal; }, set foo(val) { fooVal = val } } } fooVal = 'hello'; // not going to affect the fooVal inside the block console.log(obj.foo); // "this is the value of foo"
Và bây giờ thì việc thay đổi hay là tạo mới fooVal
bên ngoài block kia sẽ không ảnh hưởng gì đến fooVal
được gọi trong getter
và setter
cả.
2. Phạm vi hàm
Một cách quen thuộc hơn để bảo về dữ liệu bằng phạm vi là dữ cho dữ liệu bên trong hàm và trả về một đối tượng với getter
và setter
trong hàm đó. Ví dụ :
function myobj(){ var fooVal = 'this is the value of foo'; return { get foo() { return fooVal; }, set foo(val) { fooVal = val } } } fooVal = 'hello'; // not going to affect our original fooVal var obj = myobj(); console.log(obj.foo); // "this is the value of foo"
Các đối tượng trả về bởi hàm myobj()
được lưu trong obj
, và sau đó obj
được dùng để gọi getter
, setter
.
3. Không sử dụng scope
Chúng ta cũng có cách khác bảo vệ dữ liệu để tránh việc bị ghi đè mà không sử dụng scope
. Logic ở đây là : làm thế nào bạn có thể thay đổi được một dữ liệu nếu bạn không biết những gì được gọi đến?
Hãy xem ví dụ dưới đây :
var obj = { s89274934764: 'this is the value of foo', get foo() { return this.s89274934764; }, set foo(val) { this.s89274934764 = val; } } console.log(obj.foo); // "this is the value of foo"
Với một tên biến như trên (hoặc là bạn cũng có thể dùng giá trị hoặc ký tự ngẫu nhiên) thì mục đích chính là để dữ cho dữ liệu vô hình nếu nhìn từ bên ngoài, để xem hoặc cập nhật thì bạn sẽ phải thông qua getter
và setter
.
Khi nào bạn nên sử dụng getter và setter
Nếu dữ liệu của các bạn có thể nhìn thấy từ bên ngoài là tốt, bạn vẫn cần sử dụng getter
và setter
, chỉ để đóng gói nó với code mà thực hiện một số hoạt động trên nó? Tôi trả lời là có.
Tham khảo:
https://viblo.asia/p/getter-va-setter-trong-javascript-3P0lPOX4Zox
https://www.hongkiat.com/blog/getters-setters-javascript/
Các bạn có thể tham khảo các bài viết hay về JavaScript tại đây.
Hãy tham gia nhóm Học lập trình để thảo luận thêm về các vấn đề cùng quan tâm.