Cơ chế hoạt động của Two-way Data Binding trong Vue.js

Trong hệ sinh thái Vue.js, cơ chế ràng buộc dữ liệu hai chiều (Two-way Data Binding) là một trong những tính năng cốt lõi giúp đồng bộ hóa tự động giữa Model (dữ liệu) và View (giao diện). Khi dữ liệu thay đổi, giao diện sẽ cập nhật và ngược lại, khi người dùng tương tác trên giao diện, dữ liệu trong mã nguồn cũng được cập nhật tương ứng.

1. Bản chất của directive v-model

Thực tế, v-model không phải là một phép thuật đặc biệt mà là "syntax sugar" (cú pháp rút gọn) của việc kết hợp giữa v-bindv-on. Đối với một phần tử input thông thường, hai dòng mã sau đây có kết quả hoạt động tương đương:

<!-- Cách viết ngắn gọn với v-model -->
<input v-model="userName" />

<!-- Cách vận hành thực tế bên dưới -->
<input :value="userName" @input="userName = $event.target.value" />

Cơ chế này cũng cho phép chúng ta triển khai v-model trên các component tự định nghĩa bằng cách nhận vào một prop và phát ra một event để thông báo sự thay đổi:

Vue.component('custom-input', {
  props: ['value'],
  template: `
    <input 
      :value="value" 
      @input="$emit('input', $event.target.value)" 
    />
  `
});

2. Nguyên lý phản hồi thông qua Object.defineProperty

Vue 2 đạt được khả năng phản hồi dữ liệu bằng cách sử dụng phương thức Object.defineProperty() để can thiệp (hijack) vào quá trình truy cập và chỉnh sửa thuộc tính của đối tượng. Khi một đối tượng được đưa vào tùy chọn data, Vue sẽ duyệt qua tất cả các thuộc tính và chuyển đổi chúng thành các getter/setter.

function defineReactivity(data, key, val) {
    const dep = new Dependency();
    Object.defineProperty(data, key, {
        enumerable: true,
        configurable: true,
        get: function() {
            // Đăng ký Watcher khi dữ liệu được truy cập
            if (Dependency.target) {
                dep.addSubscriber(Dependency.target);
            }
            return val;
        },
        set: function(newVal) {
            if (newVal === val) return;
            val = newVal;
            // Thông báo cho tất cả Watcher khi dữ liệu thay đổi
            dep.notify();
        }
    });
}

3. Mô hình Observer - Watcher - Dep

Để hoàn thiện hệ thống phản hồi, Vue kết hợp mô hình Người xuất bản - Người đăng ký (Publish-Subscribe) với các thành phần chính:

  • Observer: Chịu trách nhiệm chuyển đổi các đối tượng dữ liệu thông thường thành dữ liệu có khả năng phản hồi (reactive).
  • Dep (Dependency): Đóng vai trò là trung tâm quản lý, lưu trữ danh sách các Watcher đang phụ thuộc vào một thuộc tính cụ thể.
  • Watcher: Là cầu nối giữa dữ liệu và logic cập nhật. Khi setter được kích hoạt, Dep sẽ gọi lệnh update() trên các Watcher liên quan.
  • Compiler: Phân tích các directive và template, khởi tạo Watcher để liên kết dữ liệu với các phần tử trên DOM.

4. Triển khai mã nguồn mô phỏng

Dưới đây là cách triển khai đơn giản các thành phần cốt lõi của hệ thống này:

Lớp Quản lý Phụ thuộc (Dep)

function Dependency() {
    this.subscribers = [];
}
Dependency.prototype = {
    addSubscriber: function(sub) {
        this.subscribers.push(sub);
    },
    notify: function() {
        this.subscribers.forEach(sub => sub.refresh());
    }
};
Dependency.target = null;

Lớp Theo dõi (Watcher)

function Watcher(vm, key, callback) {
    this.vm = vm;
    this.key = key;
    this.callback = callback;
    
    // Kích hoạt getter để đăng ký vào Dep
    Dependency.target = this;
    this.value = vm[key]; 
    Dependency.target = null;
}

Watcher.prototype = {
    refresh: function() {
        const newValue = this.vm[this.key];
        if (newValue !== this.value) {
            this.value = newValue;
            this.callback(newValue);
        }
    }
};

Bộ biên dịch cơ bản (Compiler)

Trong quá trình biên dịch, khi hệ thống phát hiện các directive như v-model hoặc cú pháp {{}}, nó sẽ thiết lập các trình lắng nghe sự kiện và khởi tạo Watcher để đảm bảo tính đồng bộ:

function compileTemplate(el, vm) {
    const nodes = el.childNodes;
    nodes.forEach(node => {
        if (node.nodeType === 1) { // Element node
            const attrs = node.attributes;
            Array.from(attrs).forEach(attr => {
                if (attr.nodeName === 'v-model') {
                    const fieldName = attr.nodeValue;
                    node.addEventListener('input', e => {
                        vm[fieldName] = e.target.value;
                    });
                    new Watcher(vm, fieldName, (val) => {
                        node.value = val;
                    });
                }
            });
        }
    });
}

Nhờ sự phối hợp nhịp nhàng giữa việc can thiệp dữ liệu (Data Hijacking) và mô hình Observer, Vue tạo ra một trải nghiệm lập trình mượt mà, nơi nhà phát triển chỉ cần tập trung vào việc quản lý trạng thái dữ liệu mà không cần can thiệp thủ công vào việc cập nhật DOM.

Thẻ: vuejs JavaScript data-binding frontend-development reactivity-system

Đăng vào ngày 19 tháng 8 lúc 05:44