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 分钟前
CSS 单位指南
前端·css
St2 分钟前
探索JavaScript原型链设计——详解prototype、__proto__及constructor三者之间的关系
前端·javascript
前端大白话2 分钟前
JavaScript中`Symbol.for()`和`Symbol()`的区别,在创建全局唯一的`Symbol`值时如何选择使用?
前端·javascript·设计模式
喵爱吃鱼2 分钟前
原来这就是react设计模式啊
前端·javascript·react.js
Synmbrf3 分钟前
说说平时开发注意事项
javascript·面试·代码规范
前端大白话4 分钟前
前端必看!90% 工程师踩过的状态管理坑,useReducer 如何一招化解?
前端·javascript·react.js
前端大白话4 分钟前
揭秘 HTML 可拖动元素及拖放功能:HTML5 API 大起底
前端·javascript·html
Moment21 分钟前
通过爬取 B 站热门视频来带你彻底了解 Playwright 🤷🏿‍♂️🤷🏿‍♂️🤷🏿‍♂️
前端·javascript·后端
CHQIUU26 分钟前
Java 设计模式心法之第25篇 - 中介者 (Mediator) - 用“中央协调”降低对象间耦合度
java·设计模式·中介者模式
Java~~37 分钟前
山东大学软件学院项目实训-基于大模型的模拟面试系统-前端美化滚动条问题
前端