Getter và Setter trong JavaScript

Getter và Setter trong JavaScript

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 gettersetter 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 gettervà 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ính foo.
  • getFoo để trả về giá trị của thuộc tính foo

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 getset.

Để 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.definePropertiesObject.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 gettersetter.

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.

Leave a Reply

Your email address will not be published. Required fields are marked *