koa2--delegates模組原始碼解讀

龍恩0707發表於2019-03-20

delegates模組是由TJ大神寫的,該模組的作用是將內部物件上的變數或函式委託到外部物件上。
然後我們就可以使用外部物件就能獲取內部物件上的變數或函式。delegates委託方式有如下:

getter: 外部物件可以通過該方法訪問內部物件的值。
setter:外部物件可以通過該方法設定內部物件的值。
access: 該方法包含getter和setter功能。
method: 該方法可以使外部物件直接呼叫內部物件的函式。

專案檔案如下結構:

|----- 專案
|  |-- delegates.js  # 委託代理的js
|  |-- index.js  # 入口檔案的js

1. getter (外部物件可以通過該方法訪問內部物件的值。)

使用方法demo如下(index.js):

const delegates = require('./delegates');

const obj = {
  xx: {
    name: 'kongzhi',
    age: 30,
    test: function() {
      console.log('xxxxxxx');
    }
  }
};

// 通過delegates將內部物件 xx 委託到外部物件obj上

var d = new delegates(obj, 'xx');
d.getter('name').getter('age').getter('test');

console.log(obj.name); // kongzhi
console.log(obj.age); // 30
obj.test(); // xxxxxxx

2. setter (外部物件可以通過該方法設定內部物件的值。)

使用方法的demo如下(程式碼在index.js內):

const delegates = require('./delegates');

const obj = {
  xx: {
    name: 'kongzhi',
    age: 30,
    test: function() {
      console.log('xxxxxxx');
    }
  }
};

// 通過delegates將內部物件 xx 委託到外部物件obj上

var d = new delegates(obj, 'xx');
d.setter('name').setter('age').setter('test');

// 使用setter後,就可以在obj物件上直接修改變數或函式的值了
obj.name = '123456';
obj.age = '31';
obj.test = function() {
  console.log('yyyy');
}

/*
 在外部物件obj修改完成後,我們再使用 外部物件[內部物件][變數] 
 這種方式獲取值, 就可以看到值更新了
*/
console.log(obj.xx.name); // 123456
console.log(obj.xx.age); // 31
obj.xx.test(); // yyyy

3. access (該方法包含getter和setter功能。)

使用方法的demo如下

const delegates = require('./delegates');

const obj = {
  xx: {
    name: 'kongzhi',
    age: 30,
    test: function() {
      console.log('xxxxxxx');
    }
  }
};

// 通過delegates將內部物件 xx 委託到外部物件obj上

var d = new delegates(obj, 'xx');
d.access('name').access('age').access('test');

// access 該方法既有setter功能,又有getter功能

// 1. 直接使用外部物件 obj, 來訪問內部物件中的屬性
console.log(obj.name); // kongzhi
console.log(obj.age); // 30
obj.test(); // xxxxxxx

// 2. 使用常規的方法獲取物件的內部的屬性
console.log(obj.xx.name); // kongzhi
console.log(obj.xx.age); // 30
obj.xx.test(); // xxxxxxx

// 3. 修改內部物件的屬性
obj.name = '2222';
console.log(obj.name); // 2222
console.log(obj.xx.name); // 2222

4. method (該方法可以使外部物件直接呼叫內部物件的函式。)

使用方法的demo如下:

const delegates = require('./delegates');

const obj = {
  xx: {
    name: 'kongzhi',
    age: 30,
    test: function() {
      console.log('xxxxxxx');
    }
  }
};

// 通過delegates將內部物件 xx 委託到外部物件obj上

var d = new delegates(obj, 'xx');
d.method('test');

obj.test(); // xxxxxxx

5. fluent

該方法的作用是,如果該方法傳了引數的話,那麼它的含義是修改該變數的值,如果沒有傳入引數的話,那麼
它的作用是獲取該引數的值。

注意:只針對變數有用,如果是函式的話,不建議使用;

如下程式碼demo所示:

const delegates = require('./delegates');

const obj = {
  xx: {
    name: 'kongzhi',
    age: 30,
    test: function() {
      console.log('xxxxxxx');
    }
  }
};

// 通過delegates將內部物件 xx 委託到外部物件obj上
var d = new delegates(obj, 'xx');
d.fluent('name').fluent('age');

// 無引數 獲取該物件的值
console.log(obj.name()); // kongzhi
console.log(obj.age()); // 30

// 有引數,就是修改對應的值
obj.name('11111')
obj.age(31)

console.log(obj.xx.name); // 11111
console.log(obj.xx.age); // 31

二:delegates模組原始碼如下:

/**
 * Expose `Delegator`.
 */

module.exports = Delegator;

/**
 * Initialize a delegator.
 *
 * @param {Object} proto
 * @param {String} target
 * @api public
 */
/*
 Delegator 函式接收二個引數,proto指是一個是外部物件,target指外部物件中的一個屬性,也就是內部物件。
 首先判斷this是否是Delegator的實列,如果不是實列的話,就直接使用 new 實列化一下。
 因此 const xx = Delegator(obj, 'xx') 或 const xx = new Delegator(obj, 'xx') 都是可以的。
 this.proto = proto; 外部物件儲存該實列this.proto 中。
 this.target = target; 和proto一樣。
 this.methods = [];
 this.getters = [];
 this.setters = [];
 this.fluents = [];
 如上四個陣列作用是 記錄委託了哪些屬性和函式。
*/
function Delegator(proto, target) {
  if (!(this instanceof Delegator)) return new Delegator(proto, target);
  this.proto = proto;
  this.target = target;
  this.methods = [];
  this.getters = [];
  this.setters = [];
  this.fluents = [];
}

/**
 * Delegate method `name`.
 *
 * @param {String} name
 * @return {Delegator} self
 * @api public
 */
/*
 method的作用是:該方法可以使外部物件直接呼叫內部物件的函式。如下demo:
 const obj = {
  xx: {
    name: 'kongzhi',
    age: 30,
    test: function() {
      console.log('xxxxxxx');
    }
  }
};
// 通過delegates將內部物件 xx 委託到外部物件obj上
var d = new delegates(obj, 'xx');
d.method('test');

obj.test(); // xxxxxxx

1. 首先我們呼叫 d.method('test'); 就把該test方法存入 this.methods陣列中。
2. 該方法返回了一個函式
obj['test'] = function() {
  return obj['xx']['test'].apply(obj['xx'], arguments);
}
3. 最後返回 this, 返回該例項化物件,目的是可以鏈式呼叫。
4. 因此就返回了第二步函式。因此當我們使用 obj.test() 的時候,就會自動呼叫該函式。然後
使用 apply方法自動執行 obj['xx']['test'].apply(obj['xx'], arguments); 
*/
Delegator.prototype.method = function(name){
  var proto = this.proto;
  var target = this.target;
  this.methods.push(name);

  proto[name] = function(){
    return this[target][name].apply(this[target], arguments);
  };

  return this;
};

/**
 * Delegator accessor `name`.
 *
 * @param {String} name
 * @return {Delegator} self
 * @api public
 */
/*
 該方法的作用是包含 getter的作用,同時也包含setter的作用,如demo如下:
 const obj = {
    xx: {
      name: 'kongzhi',
      age: 30,
      test: function() {
        console.log('xxxxxxx');
      }
    }
  };

  // 通過delegates將內部物件 xx 委託到外部物件obj上

  var d = new delegates(obj, 'xx');
  d.access('name').access('age').access('test');

  // access 該方法既有setter功能,又有getter功能

  // 1. 直接使用外部物件 obj, 來訪問內部物件中的屬性
  console.log(obj.name); // kongzhi
  console.log(obj.age); // 30
  obj.test(); // xxxxxxx

  // 2. 使用常規的方法獲取物件的內部的屬性
  console.log(obj.xx.name); // kongzhi
  console.log(obj.xx.age); // 30
  obj.xx.test(); // xxxxxxx

  // 3. 修改內部物件的屬性
  obj.name = '2222';
  console.log(obj.name); // 2222
  console.log(obj.xx.name); // 2222
*/
Delegator.prototype.access = function(name){
  return this.getter(name).setter(name);
};

/**
 * Delegator getter `name`.
 *
 * @param {String} name
 * @return {Delegator} self
 * @api public
 */
/*
 getter,該方法的作用是:外部物件可以通過該方法訪問內部物件的值。比如如下demo
 const obj = {
    xx: {
      name: 'kongzhi',
      age: 30,
      test: function() {
        console.log('xxxxxxx');
      }
    }
  };

  // 通過delegates將內部物件 xx 委託到外部物件obj上
  var d = new delegates(obj, 'xx');
  d.getter('name').getter('age').getter('test');

  console.log(obj.name); // kongzhi
  console.log(obj.age); // 30
  obj.test(); // xxxxxxx

  1. 該方法接收一個引數 name, 該引數是一個字串型別。
  2. 把該引數name值儲存到 this.getters陣列中。然後我們使用 __defineGetter__ 監聽物件屬性值的變化。
  想要理解 __defineGetter__ 作用,請看我這篇文章 (https://www.cnblogs.com/tugenhua0707/p/10324983.html#_labe1)
  如果獲取該物件值的話,就會自動呼叫 __defineGetter__ ,就能監聽到,因此就返回 this[target][name]; 即使:
  obj['xx']['name'];
*/
Delegator.prototype.getter = function(name){
  var proto = this.proto;
  var target = this.target;
  this.getters.push(name);

  proto.__defineGetter__(name, function(){
    return this[target][name];
  });

  return this;
};

/**
 * Delegator setter `name`.
 *
 * @param {String} name
 * @return {Delegator} self
 * @api public
 */
/*
 該方法的作用是:外部物件可以通過該方法設定內部物件的值。使用demo如下:
 const obj = {
    xx: {
      name: 'kongzhi',
      age: 30,
      test: function() {
        console.log('xxxxxxx');
      }
    }
  };

  // 通過delegates將內部物件 xx 委託到外部物件obj上

  var d = new delegates(obj, 'xx');
  d.setter('name').setter('age').setter('test');

  // 使用setter後,就可以在obj物件上直接修改變數或函式的值了
  obj.name = '123456';
  obj.age = '31';
  obj.test = function() {
    console.log('yyyy');
  }
  
  // 在外部物件obj修改完成後,我們再使用 外部物件[內部物件][變數] 這種方式獲取值, 就可以看到值更新了
  console.log(obj.xx.name); // 123456
  console.log(obj.xx.age); // 31
  obj.xx.test(); // yyyy

  1. 同樣的道理,使用 __defineSetter__方法來監聽物件值發生改變,如果物件值發生改變的話,就返回 
  this[target][name] = val; 把值賦值進去。最後返回this物件。
  */
Delegator.prototype.setter = function(name){
  var proto = this.proto;
  var target = this.target;
  this.setters.push(name);

  proto.__defineSetter__(name, function(val){
    return this[target][name] = val;
  });

  return this;
};

/**
 * Delegator fluent accessor
 *
 * @param {String} name
 * @return {Delegator} self
 * @api public
 */
/*
 該方法的作用是,如果該方法傳了引數的話,那麼它的含義是修改該變數的值,如果沒有傳入引數的話,那麼
 它的作用是獲取該引數的值。
 使用demo如下:
 const obj = {
    xx: {
      name: 'kongzhi',
      age: 30,
      test: function() {
        console.log('xxxxxxx');
      }
    }
  };

  // 通過delegates將內部物件 xx 委託到外部物件obj上
  var d = new delegates(obj, 'xx');
  d.fluent('name').fluent('age');

  // 無引數 獲取該物件的值
  console.log(obj.name()); // kongzhi
  console.log(obj.age()); // 30

  // 有引數,就是修改對應的值
  obj.name('11111')
  obj.age(31)

  console.log(obj.xx.name); // 11111
  console.log(obj.xx.age); // 31

  1. 當我像如上demo一樣,使用 d.fluent('name').fluent('age');後,會依次儲存到 this.flunts陣列中。
  2. 然後返回一個函式,如下程式碼:
  obj['name'] = function(val) {
    if ('undefined' != typeof val) {
      this[target][name] = val;
      return this;
    } else {
      return this[target][name];
    }
  }
  如果值沒有傳遞電話,就直接返回 this[target][name]; 即:obj['xx']['name'];
  如果傳遞了值的話,就把值賦值到物件裡面去,如程式碼:this[target][name] = val; 即:obj['xx']['name'] = val;
*/
Delegator.prototype.fluent = function (name) {
  var proto = this.proto;
  var target = this.target;
  this.fluents.push(name);

  proto[name] = function(val){
    if ('undefined' != typeof val) {
      this[target][name] = val;
      return this;
    } else {
      return this[target][name];
    }
  };

  return this;
};

相關文章