zgfang1993/blog

[javaScript] delegates源码解读

Opened this issue · 0 comments

  • 基本用法
  • 源码解读

基本用法:

将内部对象的变量或者函数绑定在暴露在外层的变量上,直接通过 delegates 方法进行如下委托,基本的委托方式包含:

  • getter:外部对象可以直接访问内部对象的值
  • setter:外部对象可以直接修改内部对象的值
  • access:包含 getter 与 setter 的功能
  • method:外部对象可以直接调用内部对象的函数
const delegates = require('delegates');

const animal = {
  cat: {
    name: 'cheese',
    age: 1.5,
    sex: 'man',
    love: 'fish',
    speek() {
      console.log('miao!');
    }
  },
}

// 将内部对象 cat 的属性、函数 => 委托至暴露在外的 animal 上
delegates(animal, 'cat')
  .getter('name')
  .setter('age')
  .access('sex')
  .method('speek')
  .fluent('love');

// 1. 访问内部对象属性
console.log(animal.name) // 'cheese'


// 2. 修改内部对象属性
animal.age = 2;
console.log(animal.cat.age) // 2


// 3. 同时访问和修改内部对象属性
console.log(animal.sex) // 'man'
animal.sex = 'xiaogongong';
console.log(animal.sex); // 'xiaogongong'


// 4. 调用内部对象函数
animal.speek(); // 'miao!'

// 5. 不传参数获取值,传参数修改值,可链式调用
console.log(animal.love()); // 'fish'
console.log(animal.love('mouse').love('maobohe').love()); // 'maobohe'

源码解读

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 = [];
}

methods | getters | setters | flaunts 均为数组,用来记录委托了哪些属性和函数。

如果 this 不是 Delegator 的实例的话,则调用 new Delegator(proto, target)。(避免在调用初始化函数时忘记写 new )。

Delegator(animal, 'cat')
//等价于👇
new Delegator(animal, 'cat')

getter

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;
};

核心:defineGetter

它可以在已存在的对象上添加可读属性, 其中第一个参数为属性名,第二个参数为函数,返回值为对应的属性值。

const obj = {};
obj.__defineGetter__('name', () => 'cheese');
console.log(obj.name); // 'cheese'

obj.name = 'baozi';
console.log(obj.name); // 'cheese' 没有被改名成功

注意:

现在已推荐使用_defineGetter_,建议通过 Object.defineProperty 实现同样功能,或者通过 get 操作符实现类似功能:

const obj = {};
Object.defineProperty(obj, 'name', {
  value: 'cheese',
});

Object.defineProperty(obj, 'sex', {
  get() {
    return 'man';
  }
});

const cat = {
  get name() {
    return 'baozi';
  }
};

setter

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;
};

核心:defineSetter

它可以在已存在的对象上添加可读属性, 其中第一个参数为属性名,第二个参数为函数,返回值为对应的属性值。

const obj = {};
obj.__defineSetter__('name', function(value) {
  this._name = value;
});

obj.name = 'baozi';
console.log(obj.name, obj._name); // undefined 'baozi'

注意:

现在已推荐使用_defineGetter_,建议通过 Object.defineProperty 实现同样功能,或者通过 set 操作符实现类似功能:

const obj = {};
Object.defineProperty(obj, 'name', {
  set(value) {
    this._name = value;
  }
});

const cat = {
  set(value) {
    this._name = value;
  }
};

method

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;
};

核心:apply

koa中应用

context.request/context.response 的许多属性都被委托在了 context上。

/**
 * Response delegation.
 */

delegate(proto, 'response')
  .method('attachment')
  .method('redirect')
  .method('remove')
  .method('vary')
  .method('set')
  .method('append')
  .method('flushHeaders')
  .access('status')
  .access('message')
  .access('body')
  .access('length')
  .access('type')
  .access('lastModified')
  .access('etag')
  .getter('headerSent')
  .getter('writable');

/**
 * Request delegation.
 */

delegate(proto, 'request')
  .method('acceptsLanguages')
  .method('acceptsEncodings')
  .method('acceptsCharsets')
  .method('accepts')
  .method('get')
  .method('is')
  .access('querystring')
  .access('idempotent')
  .access('socket')
  .access('search')
  .access('method')
  .access('query')
  .access('path')
  .access('url')
  .access('accept')
  .getter('origin')
  .getter('href')
  .getter('subdomains')
  .getter('protocol')
  .getter('host')
  .getter('hostname')
  .getter('URL')
  .getter('header')
  .getter('headers')
  .getter('secure')
  .getter('stale')
  .getter('fresh')
  .getter('ips')
  .getter('ip');

参考:https://juejin.im/post/5b9339136fb9a05d3634ba13