JS 链模式

链式调用的优势

一行代码完成多个操作,代码简洁、易读,避免定义多个中间变量等。

js 复制代码
T('#target').css({'background-color': 'red'}).attr('class', 'demo').html('demo text show').on('click', function() { console.log('clicked') });

链式调用的实现思路

分析实现思路

从结果 T('#target').x().y().. 来看,T 应该是个函数,调用这个函数返回了一个包含所有属性和方法的对象 (调用任何方法也是)。所以一定有一个对象空间 存储和共享 所有的属性和方法。不禁想到了原型空间 prototype, prototype是对象的特殊属性,在js中 function 也是对象,所以返回自己的原型空间就可以了。

我们还需要创建一个属性 assistant(助手),来一同保存 T.prototype 这个指针

js 复制代码
T.assistant = T.prototype = {
    name: 'Tagawa',
    init: function() {
        ...
    },
    sayName: function() {
        return this.name;
    }
}
js 复制代码
function T(target) {
    return T.assistant.init(target)
}

初始化

T(arguments)来看,内部有一个 init 初始化的方法,假设这个方法只能通过 id 获取 dom元素

js 复制代码
T.assistant = T.prototype = {
  name: 'Tawaga',
  init: function(target) {
    return document.getElementById(target)
  }
}

const hello = T('hello')
console.log('hello: ', hello);

最重要的是它要返回当前对象(调用者)

从控制台可以看到,获取到了dom元素,但是 init 方法没有返回 this这才是重点 ,没有 this 就无法 链式调用 ,所以在 init 方法内把获取到的 dom 元素存储到新属性里 [0]

js 复制代码
init: function(target) {
    this[0] = document.getElementById(target);
    this.length = 1;
    return this;
},

但当你测试的时候,你发现第二次打印出来的 hello 元素,被覆盖成了 box

js 复制代码
const hello = T('hello')
console.log('hello: ', hello);
const box = T('box')
console.log('hello: ', hello);

因为你修改了共同的 this 对象中的 this[0]

实例化

解决这个问题要实例化 init

js 复制代码
const T = function(target) {
  return new T.assistant.init(target);
};

T.assistant = T.prototype = {
  constructor: T,
  name: 'Tawaga',
  init: function(target) {
    this[0] = document.getElementById(target);
    this.length = 1;
    return this;
  },
  sayName: function() {
    return this.name;
  }
}

当再打印测试时,你发现一切正常了

js 复制代码
const hello = T('hello')
console.log('hello: ', hello);
const box = T('box')
console.log('box: ', box);
console.log('hello: ', hello);

但是当你尝试 链式调用 的时候你发现又报错了

js 复制代码
console.log(T('hello').sayName());

那是因为,当 new T.assistant.init(target)后,init 方法内部 return 的 this,这个 thisinit 对象,此时的 init 相当于构造函数 ,然而不论是在 init方法内部,还是在 init.prototype 中都没有 sayName 这个方法,所以要把 T.prototype 的指针也给 init.prototype 一份

js 复制代码
T.assistant.init.prototype = T.assistant;

再测试一下,发现链式调用是ok的,控制台输出了预期的信息

用 extend 拓展

可拓展的能力是很强大的,T应该提供一个可供外部插入能力 的入口方法extend,用户可以根据自己的业务需求 渐进增强 T

js 复制代码
T.extend = T.assistant.extend = function() {
  let len = arguments.length, target = arguments[0];
  if (len === 1) {
    // 内部(插件)
    target = this;
  }
  // 外部(工具)
  for (let i = 0; i < len; i++) {
    for (let j in arguments[i]) {
      target[j] = arguments[i][j]
    }
  }
  return target;
}

当传入1个参数时,就是给 T 内部拓展属性(插件模式),当传入多个参数时,就是给第1个参数拓展属性(工具模式)

js 复制代码
// 内部插件拓展到T.assistant
T.assistant.extend({ innerHello: function() { return this.name }});
const out = T.extend({name: 'tgg'}, {age: 18}, {like: 'code'});

console.log('inner: ', T('#hello').innerHello());
console.log('out: ', out);

完整代码

js 复制代码
const T = function(target) {
  return new T.assistant.init(target);
}

T.assistant = T.prototype = {
  constructor: T,
  name: 'Tawaga',
  init: function(target, context) {
    this.length = 0;
    context = context ?? document;
    if (target.startsWith('#')) {
      const element = context.getElementById(target.slice(1));
      this[0] = element;
    } else {
      const elements = context.getElementsByTagName(target);
      for (let i = 0; i < elements.length; i++) {
        this[i] = elements[i];
      }
      this.length = elements.length;
    }
    this.target = target;
    this.context = context;
    return this;
  },
  sayName: function() {
    return this.name;
  },
  push: [].push,
  sort: [].sort,
  splice: [].splice
}
T.assistant.init.prototype = T.assistant;
T.extend = T.assistant.extend = function() {
  let len = arguments.length, target = arguments[0];
  if (len === 1) {
    // 内部(插件)
    target = this;
  }
  // 外部(工具)
  for (let i = 0; i < len; i++) {
    for (let j in arguments[i]) {
      target[j] = arguments[i][j]
    }
  }
  return target;
}

// 内部插件拓展到T.assistant
T.assistant.extend({ innerHello: function() { return this.name }});
const out = T.extend({name: 'tgg'}, {age: 18}, {like: 'code'});

console.log('inner: ', T('#hello').innerHello());
console.log('out: ', out);

实战

添加事件

js 复制代码
T.assistant.extend({
  // 添加事件
  on: (function() {
    // 标准浏览器DOM2级事件
    if(document.addEventListener) {
      return function(type, fn) {
        const i = this.length - 1;
        for (; i >= 0; i--) {
          this[i].addEventListener(type, fn, false);
        }
        // 返回原对象
        return this;
      }
    } else if (document.attachEvent) {
      return function(type, fn) {
        const i = this.length - 1;
        for (; i >= 0; i--) {
          this[i].addEvent('on' + type, fn);
        }
        return this;
      }
    // 不支持 DOM2 级事件浏览器添加事件
    } else {
      return function(type, fn) {
        const i = this.length - 1;
        for (; i >= 0; i--) {
          this[i]['on' + type] = fn;
        }
        return this;
      }
    }
  })()
})

callback篇头

js 复制代码
T('#target').css({'background-color': 'red'}).attr('class', 'demo').html('demo text show').on('click', function() { console.log('clicked') });
相关推荐
编程零零七2 小时前
Python数据分析工具(三):pymssql的用法
开发语言·前端·数据库·python·oracle·数据分析·pymssql
北岛寒沫3 小时前
JavaScript(JS)学习笔记 1(简单介绍 注释和输入输出语句 变量 数据类型 运算符 流程控制 数组)
javascript·笔记·学习
everyStudy3 小时前
JavaScript如何判断输入的是空格
开发语言·javascript·ecmascript
(⊙o⊙)~哦4 小时前
JavaScript substring() 方法
前端
无心使然云中漫步5 小时前
GIS OGC之WMTS地图服务,通过Capabilities XML描述文档,获取matrixIds,origin,计算resolutions
前端·javascript
Bug缔造者5 小时前
Element-ui el-table 全局表格排序
前端·javascript·vue.js
xnian_5 小时前
解决ruoyi-vue-pro-master框架引入报错,启动报错问题
前端·javascript·vue.js
麒麟而非淇淋6 小时前
AJAX 入门 day1
前端·javascript·ajax
2401_858120536 小时前
深入理解MATLAB中的事件处理机制
前端·javascript·matlab
阿树梢6 小时前
【Vue】VueRouter路由
前端·javascript·vue.js