链模式

本文共--字 阅读约--分钟 | 浏览: -- Last Updated: 2021-12-15

链模式(Chain of Responsibility):通过在对象方法中将当前对象返回,实现同一个对象多个方法的链式调用,从而简化该对象的多个方法的多次调用时,对该对象的多次引用。

在这个方面最佳的实践就是JQuery库。让我们探索它是如何实现的?

// 根据原型链访问其原型对象,以实现链调用
var $ = function (selector) { 
  return $.prototype.init(selector);
};

$.prototype = {
  init(selector) {
    this[0] = document.getElementById(selector);
    this.length = 1;
    // 不能直接返回元素,因为为了链式调用只能返回this,所以将获取到的元素存储在属性0上
    return this;
  },
  size() {
    return this.length;
  }
};

$('.className').size(); // 1 

但是,此时存在一个问题,就是当再次调用 $('.className2') 的时候,之前存储在this[0] 上的dom对象就会被替换。因为每次调用$()返回的都是$.prototype,并且它们的size方法也是共用的,所以可以直接使用new关键字去使得返回一个新的实例对象。

var $ = function (selector) { 
  return new $.prototype.init(selector);
};

$.prototype = {
  // 强化构造器
  constructor: $,
  // 如果使用简写的对象方法 init(){}  那么将无法被new调用
  init: function (selector) {
    console.log(this);
    this[0] = document.getElementById(selector);
    this.length = 1;
    return this;
  },
  size: function() {
    return this.length;
  }
};

// 使用new调用之后,将返回的实例对象的原型链指回
$.prototype.init.prototype = $.prototype;

var a = $('abc').size();

而关于链式调用,则是通过extend扩展多个方法到原型对象上并返回this来实现。

var $ = function (selector) { 
  return new $.prototype.init(selector);
};

$.prototype = {
  constructor: $,
  // 如果使用简写的对象方法 init(){}  那么将无法被new调用
  init: function (selector) {
    if (selector.startsWith('#')) {
      this[0] = document.getElementById(selector.slice(1));
      this.length = 1;
    } else {
      var doms = selector.startsWith('.') ? document.getElementsByClassName(selector.slice(1)) :  document.getElementsByTagName(selector);
      for (var i = 0; i < doms.length; i++) {
        this[i] = doms[i];
      }
      this.length = doms.length;
    }
    return this;
  },
  size: function() {
    return this.length;
  }
};

$.prototype.init.prototype = $.prototype;

// 方法扩展,可以扩展在$上,$.xx 也可以扩展到原型对象上 $('#id').xx
$.extend = $.prototype.extend = function() {
  // 如果只有一个参数,就当做是扩展$和$.prototype
  // 如果有多个参数,当做将后面的参数对象,扩展到第一个参数对象上
  var i = 1;
  var len = arguments.length;
  var target = arguments[0];

  if (i === len) {
    target = this;
    i--;
  }

  for (; i < len; i++) {
    for(var key in arguments[i]) {
      target[key] = arguments[i][key]
    }
  }
  return target;
}

var a = $.extend({ first: 1}, { second: 2})
console.log(a); // {first: 1, second: 2}

// 扩展到实例上 $.prototype
$.extend($.prototype, { version: '1.0.0'})
console.log($('demo').version); // 1.0.0

$.prototype.extend({ getVersion: function() { return this.version } }) // 只有1个参数
console.log($('demo').getVersion()) // 1.0.0

// 扩展到$上
$.extend($, { author: 'jQuery team' }); // jQuery team
console.log($.author);

$.extend({ nickname: '$' }) // 只有一个参数
console.log($.nickname); // $

// 添加方法以测试链式调用

$.prototype.extend({
  on: function(type, fn) {
    var i = this.length - 1;
    // 遍历所有元素添加事件
    for (; i >= 0; i--) {
      this[i].addEventListener(type, fn, false)
    }
    return this;
  },
  css: function() {
    var len = arguments.length;
    if (this.length < 1) { // 没有获取过元素
      return this;
    }

    if (len === 1) { // 只有一个参数
      if (typeof arguments[0] === 'string') {
         // 如果为字符串则作用为 获取第一个元素的css样式  $('#id').css('fontSize')
        return getComputedStyle(this[0], false)[arguments[0]]
      }

      if (typeof arguments[0] === 'object') {
        // 如果为对象,则设置第一个元素的多个样式  $('#id').css({ color: 'red', height: '30px' })
        for (var key in arguments[0]) {
          for (var j = this.length - 1; j >= 0; j--) { // 如果获取到的是一组元素就设置多个元素的多个样式
            this[j].style[key] = arguments[0][key]
          }
        }
      }
    } else if (len === 2) { // 两个参数则设置一个样式, $('#id').css('color', 'green')
      for (var j = this.length - 1; j >= 0; j--) {
        this[j].style[arguments[0]] = arguments[1];
      }
    }

    return this;
  }
})

console.log($('.my-div'))

$('.my-div').css('color', 'red').css({ height: '18px'}).on('click', function() {
  var h = $('.my-div').css('height');
  console.log(h);
})