+3

Giới Thiệu Proxies Objects Trong ES6

Một trong những tính năng mới được giới thiệu trong đặc tả ES6 cách đây không lâu. Đó là Proxy object. Hôm nay mình sẽ giới thiệu về tầm quan trọng của Proxy trong các ứng dụng thực tế và cũng hiểu được vì sao đó ra đời, nó sẽ giúp ích gì trong thế giới js đầy biến động này 😃. Mình sẽ giới thiệu khái niệm cơ bản và đưa ra những case study cụ thể để các bạn của rõ hơn về Proxy. Let's go!

Khái niệm

Đây là khái niệm về Proxy. Ở đây mình sẽ copy nguyên văn trong mà đặc tả ES6 mô tả về Proxy

The Proxy object is used to define custom behavior for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc)

Qua đây các bạn sẽ hiểu được là Proxy nó giúp chúng ta định nghĩa các hành vi tùy biến những hoạt động cơ bản của Object. Ví dụ những hoạt động cơ bản của object đó là: truy cập đến thuộc tính , thay đổi thuộc tính, khởi tạo đối tượng... của object. Thực là khó hiểu phải không? Đối với mình Proxy thực chất là Middleware của object mà thôi. Tại sao mình lại nói như vậy, các bạn chịu khó đọc hết bài này các bạn sẽ hiểu rõ hơn nha 😃

Proxies are Middleware for Javascript Objects

Ở khái niệm mình đã giới thiệu đến các bạn Proxy cho định nghĩa các hành vi tùy biến cho những hoạt động cơ bản của object. Mình sẽ lấy ví dụ hoạt động cơ bản của objectget giá trị của thuộc tính trong object.

const obj = {
   val: 10
};
console.log(obj.val)

Hoặc là set (tạo) mới thuộc tính cho object. set cũng là hoạt động cơ bản của object

const obj = {
   val: 10
};
obj.val2 = 20;

Ở đây mà chưa hề đề cập đến Proxy. Làm sao để tạo một Proxis (middleware) cho object đây.

Cú pháp

var p = new Proxy(target, handler);
  • handler — the placeholder object which contains the trap(s)
  • traps — the method(s) that provide property access
  • target — object which the proxy virtualizes

Ở trên các bạn sẽ thấy ES6 đặc tả là handler sẽ chứa các traps. Một câu hỏi đặt ra khi nào traps sẽ được kích hoạt. Các bạn đọc lại ở phần định nghĩa mình đã nói rất rõ về vấn đề này. Traps sẽ kích hoạt khi truy cập đến thuộc tính , thay đổi thuộc tính, khởi tạo đối tượng... của object. Trap lại một kĩ thuật trong lập trình khá là hay, mọi người có thể đọc thêm ở đây. Ví dụ để các bạn hiểu rõ hơn:

const handler = {
  get: function() {
    console.log('A value has been accessed');
  }
}

const initialObj = {
  id: 1,![](https://images.viblo.asia/4490d0ed-55a4-4e02-bf61-63cee47dd5e6.png)
  name: 'Foo Bar'
}

const proxiedObj = new Proxy(initialObj, handler);

console.log(proxiedObj.name); 

Nếu không tạo Proxy khi gọi proxiedObj.name, kết quả mong đợi là 'Foo Bar'. Ở đây mình tạo Proxy thì kịch bản sẽ khác, hay nói cách khác là hành động khi get giá trị value có key là name đã custom logic hoặc nói cách dễ hơn đó là phải qua tầng middleware handler.get. Vậy khi thực hiện kết quả trên kết quả chúng ta sẽ trông như thế này. Khi chúng ta gọi hành động này proxiedObj.name, function get ở handler object đâu tiên sẽ in sẽ ***"A value has been accessed"***. Rồi sẽ truy cập đến thuộc tính name của object initialObj rồi sẽ in ra giá trị là ***"Foo Bar"***. Khi các bạn nhìn kĩ hình ảnh của trên nó in ra undefined. Vì sao lại như vậy nhỉ. Thật ra khi function không return về giá trị nào cả nên sẽ trả về undefined tại vì function get sẽ nhận 2 giá trị obj, prop. Obj == initialObj và prop == (name hoặc id).

const handler = {
  get: function(obj, prop) {
    console.log('A value has been accessed');
    return obj[prop]; // Return the value stored in the key being accessed
  }
}

const initialObj = {
  id: 1,
  name: 'Foo Bar'
}

const proxiedObj = new Proxy(initialObj, handler);

console.log(proxiedObj.name);

Kết quả sẽ như chúng ta mong đợi 😄 Giờ chúng ta thêm traps cho object initialObj là hành động set (thêm thuộc tính tuổi cho initialObj)


const handler = {
  get: function(obj, prop) {
    console.log('A value has been accessed');
    return obj[prop];
  },
  set: function(obj, prop, value) {
    console.log(`${prop} is being set to ${value}`);
  }
}

const initialObj = {
  id: 1,
  name: 'Foo Bar'
}

const proxiedObj = new Proxy(initialObj, handler);

proxiedObj.age = 24

Khi proxiedObj.age = 24 được thực thi truy cập đến thuộc tính age và gắn giá trị là 24 thì đồng nghĩa với việc function handler.set sẽ được kích hoạt đầu tiên và in ra Qua 2 ví dụ chúng ta đã hiểu mình phần nào đó về Proxy phải không nào, đơn giả chỉ cần khái báo những traps để thực hiện theo đúng ý đồ mà chúng ta mong muốn. Đây là list danh sách traps mà chúng ta cần phải nắm rõ để định nghĩa các trap theo đúng nhiệm vụ của nó

Ứng dụng thực tế

Ví dụ 1: Thiết lập private cho object

Ta có một object định nghĩa object person:

const person = {
   id: 1,
   name: 'Foo Bar'
}

Ta muốn thuộc tính id của person là private và không thể truy cập id của peson. Ta còn tạo một object Proxy có trapget và để kiểm tra khi truy cập đến id thì báo lỗi

const handler = {
  get: function(obj, prop) {
    if (prop === 'id') { // Check if the id is being accessed
      throw new Error('Cannot access private properties!'); // Throw an error
    } else {
      return obj[prop]; // If it's not the id property, return it as usual
    }
  }
}

const person = {
  id: 1,
  name: 'Foo Bar'
}

const proxiedPerson = new Proxy(person, handler);

console.log(proxiedPerson.id);

Ví dụ 2: Validation

Khi ứng dụng validation cho object thì chúng ta dùng trapset. Chúng ta có thể thêm phần validation tùy ý để hợp với mục đích dữ diệu của chúng ta được ràng buộc

const handler = {
  set: function(obj, prop, value) {
    if (typeof value !== 'string') {
      throw new Error('Only string values can be stored in this object!');
    } else {
      obj[prop] = value;  
    }
  }
}

const obj = {};

const proxiedObj = new Proxy(obj, handler);

console.log(proxiedObj); // This will log an empty object 
proxiedObj.name = 'Foo Bar'; // This should be allowed
console.log(proxiedObj); // This will log an object with the name property set

proxiedObj.age = 24; // This will throw an error.

Ví dụ 3: Extending constructor

Function proxy có thể dễ dàng mở rộng các dependences khi new một object

function extend(sup, base) {
  var descriptor = Object.getOwnPropertyDescriptor(
    base.prototype, 'constructor'
  );
  base.prototype = Object.create(sup.prototype);
  var handler = {
    construct: function(target, args) {
      var obj = Object.create(base.prototype);
      this.apply(target, obj, args);
      return obj;
    },
    apply: function(target, that, args) {
      sup.apply(that, args);
      base.apply(that, args);
    }
  };
  var proxy = new Proxy(base, handler);
  descriptor.value = proxy;
  Object.defineProperty(base.prototype, 'constructor', descriptor);
  return proxy;
}

var Person = function(name) {
  this.name = name;
};

var Boy = extend(Person, function(name, age) {
  this.age = age;
});

Boy.prototype.sex = 'M';

var Peter = new Boy('Peter', 13);
console.log(Peter.sex);  // "M"
console.log(Peter.name); // "Peter"
console.log(Peter.age);  // 13

Ví dụ 4: Thao tác với DOM

Đôi khi chúng muốn toggle ClassName = "aria-selected" của 2 phần tử DOM

let view = new Proxy({
  selected: null
},
{
  set: function(obj, prop, newval) {
    let oldval = obj[prop];

    if (prop === 'selected') {
      if (oldval) {
        oldval.setAttribute('aria-selected', 'false');
      }
      if (newval) {
        newval.setAttribute('aria-selected', 'true');
      }
    }

    // The default behavior to store the value
    obj[prop] = newval;

    // Indicate success
    return true;
  }
});

let i1 = view.selected = document.getElementById('item-1');
console.log(i1.getAttribute('aria-selected')); // 'true'

let i2 = view.selected = document.getElementById('item-2');
console.log(i1.getAttribute('aria-selected')); // 'false'
console.log(i2.getAttribute('aria-selected')); // 'true'

Tổng kết

Thông qua Proxy mà ES6 giới thiệu. Chúng ta đã thấy sức mạnh của Proxy qua những ứng dụng tế. Nó giúp chúng ta thay đổi cách tương tác thông thường với object của javascript. Trên đây chỉ là lý thuyết và những ứng dụng rất cơ bản. Còn việc áp dụng nó vào thực tế không hề dễ dàng như chúng ta tưởng tượng sẽ gặp rất nhiều khó khăn. Happy new year ❤️ Happy coding!


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.