Khi bạn đang mơ thì người khác đang nỗ lực.

Mô phỏng hoạt động của thuộc tính computed trong Vue.js bằng Javascript

Trong bài viết giới thiệu về Các thuộc tính computed, watch trong Vue.js chúng ta đã được làm quen với thuộc tính computed. Nó giúp cho ứng dụng hoạt động hiệu quả do cache kết quả tính toán và trả về ngay tức thì nếu các thuộc tính mà nó giám sát không có thay đổi. Thuộc tính computed đặc biệt có hiệu quả lớn khi các tính toán phức tạp đòi hỏi nhiều tài nguyên. Bài viết này sẽ đi tìm hiểu cách thức hoạt động của thuộc tính computed trong Vue.js bằng các đoạn code viết thuần túy bằng ngôn ngữ Javascript.

Các thuộc tính của Javascript

Javascript có một tính năng là Object.defineProperty, chúng ta cùng xem đoạn mã sau:

var person = {};

Object.defineProperty (person, 'age', {
  get: function () {
    console.log ("Getting the age");
    return 25;
  }
});

console.log ("The age is ", person.age);

Khi bạn mở công cụ Console lên bạn sẽ thấy:

Getting the age
The age is 25

Cách thức truy xuất vào thuộc tính person.age giống như việc truy xuất vào một đối tượng, nhưng thực tế chúng ta chạy một hàm bên trong.

Khái niệm về giám sát Vue.js

Vue.js có một hàm khởi tạo cho phép trả về một đối tượng thông thường và được giám sát. Đây là các reactive property, dịch tạm là các thuộc tính "phản ứng" tức là khi các dữ liệu trong Model thay đổi nó sẽ cập nhật lên View, xem lại mô hình MVVM.

function defineReactive (obj, key, val) {
  Object.defineProperty (obj, key, {
    get: function () {
      return val;
    },
    set: function (newValue) {
      val = newValue;
    }
  })
};

// create an object
var person = {};

// add a reactive property called 'age' and 'country'
defineReactive (person, 'age', 25);
defineReactive (person, 'country', 'Brazil');

// now you can use `person.age` any way you please
if (person.age < 18) {
  console.log('thiếu nhi');
}
else {
  console.log('thanh niên');
}

// Set a value as well.
person.country = 'Russia';
console.log(person.country);

Giá trị ban đầu là 25 và Brazil, nhưng nó thay đổi khi bạn thiết lập giá trị cho person.country, bản chất person.country không chứa giá trị thực tế mà thay vào đó là một phương thức get để lấy giá trị. Như vậy, chúng ta đã giám sát được person, trong Vue.js cũng sử dụng cách thức như vậy. ## Định nghĩa thuộc tính computed

Chúng ta tạo một hàm định nghĩa thuộc tính computed có tên là defineComputed.

defineComputed (
  person, // the object to create computed property on
  'status', // the name of the computed property
  function () { // the function which actually computes the property
    console.log ("status getter called")
    if (person.age < 18) {
      return 'minor';
    }
    else {
      return 'adult';
    }
  },
  function (newValue) {
    // called when the computed value is updated
    console.log ("status has changed to", newValue)
  }
});

// We can use the computed property like a regular property
console.log ("The person's status is: ", person.status);

Chúng ta cùng thực hiện defineComputed, nó hỗ trợ gọi một hàm tính toán, không hỗ trợ updateCallback:

function defineComputed (obj, key, computeFunc, updateCallback) {
  Object.defineProperty (obj, key, {
    get: function () {
      // call the compute function and return the value
      return computeFunc ();
    },
    set: function () {
      // don't do anything. can't set computed funcs
    }
  })
}

Có một vài vấn đề như sau:

  • Nó sẽ chạy hàm tính toán mỗi khi thuộc tính được truy xuất
  • Nó không biết khi nào được cập nhật
// We want something like this to happen

person.age = 17;
// console: status has changed to: minor

person.age = 22;
// console: status has changed to: adult

Thêm các đối tượng theo vết các phụ thuộc

Chúng ta thêm vào một đối tượng toàn cục Dep

var Dep = {
  target: null
};

Đây là đối tượng theo vết các phụ thuộc và thay đổi lại hàm defineComputed như sau:

function defineComputed (obj, key, computeFunc, updateCallback) {
  var onDependencyUpdated = function () {
    // TODO
  }
  Object.defineProperty (obj, key, {
    get: function () {
      // Set the dependency target as this function
      Dep.target = onDependencyUpdated;
      var value = computeFunc ();
      Dep.target = null;
    },
    set: function () {
      // don't do anything. can't set computed funcs
    }
  })
}

Chúng ta quay lại với việc định nghĩa thuộc tính phản ứng

function defineReactive (obj, key, val) {
  // all computed properties that depend on this
  var deps = [];

  Object.defineProperty (obj, key, {
    get: function () {
      // Check if there's a computed property which 'invoked'
      // this getter. Also check that it's already not a dependency
      if (Dep.target && ) {
        // add the dependency
        deps.push (target);
      }

      return val;
    },
    set: function (newValue) {
      val = newValue;

      // notify all dependent computed properties
      deps.forEach ((changeFunction) => {
        // Request recalculation and update
        changeFunction ();
      });
    }
  })
};

Chúng ta có thể cập nhật hàm onDependencyUpdate trong thuộc tính computed để kích hoạt callback update.

var onDependencyUpdated = function () {
  // compute the value again
  var value = computeFunc ();
  updateCallback (value);
}

Tổng hợp lại

Chúng ta tổng hợp lại tất cả những gì đã mổ xẻ từ đầu bài viết để đi đến mã nguồn chung như sau:

// Singleton to track dependencies
var Dep = {
  // current target
  target: null
}

var trace = function (message) {
  // change to true to log trace messages
  if (false)
    console.log ("[ TRACE ] " + message);
}

function defineReactive (obj, key, val) {
  var deps = [];
  Object.defineProperty (obj, key, {
    get: function () {

      // Check if there is a target and it hasn't been linked 
      // as a dependency already
      if (Dep.target && deps.indexOf (Dep.target) == -1) {
        trace ("Adding target to deps for " + key)
        deps.push (Dep.target);
      }

      trace ("Getting value of " + key);
      return val;
    },
    set: function (newValue) {
      trace ("Setting value of " +  key + ". value: " + newValue);
      val = newValue;

      for (var i = 0; i < deps.length; i ++) {
        // call the target's callback
        deps[i]();
      }
    }
  })
};

function defineComputed (obj, key, computeFunc, callbackFunc) {
  var onDependencyUpdated = function () {
    trace ("Dependency updated for " + key + ". Recomputing.");
    var value = computeFunc ();
    callbackFunc (value);
  };

  Object.defineProperty (obj, key, {
    get: function () {
      trace("Getting computed property :" + key);

      // Set current update callback 
      Dep.target = onDependencyUpdated;

      // Compute the value
      var value = computeFunc ();

      // Reset the target so no more property adds this as dependency
      Dep.target = null;

      return value;
    },
    set: function () {
      console.warn ('nope!');
    }
  })
}

var person = {};
defineReactive (person, 'age', 16);
defineReactive (person, 'country', 'Brazil');

defineComputed (person, 'status', function () {
  if (person.age > 18) {
    return 'Adult'
  }
  else {
    return 'Minor'
  }
}, function (newValue) {
  console.log ("CHANGED!! The person's status is now: " + newValue)
});

console.log ("Current age: " + person.age)
console.log ("Current status: " + person.status)

// change age
console.log ("Changing age");
person.age = 22;

// change country. Note that status update doesn't trigger
// since status doesn't depend on country
console.log ("Changing country");
person.country = "Chile";

Ok, chúng ta sẽ cùng xem nó hoạt động như thế nào.

Bước 1: khi sử dụng person.status phương thức get() được gọi, nó sẽ thiết lập điểm callback trong Dep.target.

Bước 1

Bước 2: khi gọi get() của person.status nó sẽ gọi đến hàm tính toán, hàm này cần giá trị person.age do đó nó gọi đến get() của person.age.

Bước 2

Bước 3: Phương thức get() của person.age kiểm tra Dep xem target có giá trị chưa? và nó lưu xuống như là một phụ thuộc

Bước 3

Bước 4: Hàm tính toán nhận được giá trị mới và person.age biết phải thông báo cho person.status khi nó thay đổi

Bước 4

Lời kết

Thuộc tính computed sử dụng trong Vue.js rất tuyệt vời, đặc biệt cho những tính toán phức tạp đòi hỏi phải sử dụng bộ đệm giúp ứng dụng hoạt động trơn tru và sử dụng ít tài nguyên hơn. Các mô phỏng bằng Javascript trên đây giúp chúng ta có cái nhìn sâu hơn về thuộc tính computed này.

FirebirD

Đam mê Toán học, Lập trình. Sở thích chia sẻ kiến thức, Phim hài, Bóng đá, Cà phê sáng với bạn bè.

Xây dựng Component phân trang trong Laravel và Vue.js

Forum dạng SPA với Laravel và Vue.js - Phần 0: Giới thiệu

0 Bình luận trong "Mô phỏng hoạt động của thuộc tính computed trong Vue.js bằng Javascript"

Thêm bình luận