详解JavaScript中的链式调用


Posted in Javascript onNovember 27, 2020

链模式

链模式是一种链式调用的方式,准确来说不属于通常定义的设计模式范畴,但链式调用是一种非常有用的代码构建技巧。

描述

链式调用在JavaScript语言中很常见,如jQuery、Promise等,都是使用的链式调用,当我们在调用同一对象多次其属性或方法的时候,我们需要多次书写对象进行.或()操作,链式调用是一种简化此过程的一种编码方式,使代码简洁、易读。
链式调用通常有以下几种实现方式,但是本质上相似,都是通过返回对象供之后进行调用。

  • this的作用域链,jQuery的实现方式,通常链式调用都是采用这种方式。
  • 返回对象本身, 同this的区别就是显示返回链式对象。
  • 闭包返回对象的方式实现,这种方式与柯里化有相似之处。
var Person = function() {};
Person.prototype.setAge = function(age){
  this.age = age; 
  return this;
}
Person.prototype.setWeight = function(weight){
  this.weight = weight; 
  return this;
}
Person.prototype.get = function(){
  return `{age: ${this.age}, weight: ${this.weight}}`;
}

var person = new Person();
var des = person.setAge(10).setWeight(30).get();
console.log(des); // {age: 10, weight: 30}
var person = {
  age: null,
  weight: null,
  setAge: function(age){
    this.age = age; 
    return this;
  },
  setWeight: function(weight){
    this.weight = weight; 
    return this;
  },
  get: function(){
    return `{age: ${this.age}, weight: ${this.weight}}`;
  }
};
var des = person.setAge(10).setWeight(30).get();
console.log(des); // {age: 10, weight: 30}
function numsChain(num){
  var nums = num;
  function chain(num){
    nums = `${nums} -> ${num}`;
    return chain;
  }
  chain.get = () => nums;
  return chain;
}
var des = numsChain(1)(2)(3).get();
console.log(des); // 1 -> 2 -> 3

可选链操作符

说到链式调用,就有必要说一下JavaScript的可选链操作符,属于ES2020新特性运算符?.、??、??=,可选链操作符?.允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。?.操作符的功能类似于.链式操作符,不同之处在于在引用为空nullish即null或者undefined的情况下不会引起错误,该表达式短路返回值是undefined。与函数调用一起使用时,如果给定的函数不存在,则返回undefined。当尝试访问可能不存在的对象属性时,可选链操作符将会使表达式更短更简明。在探索一个对象的内容时,如果不能确定哪些属性必定存在,可选链操作符也是很有帮助的。

语法

obj?.prop
obj?.[expr]
arr?.[index]
func?.(args)

示例

const obj = {a: {}};
console.log(obj.a); // {}
console.log(obj.a.b); // undefined
// console.log(obj.a.b.c); // Uncaught TypeError: Cannot read property 'c' of undefined
console.log(obj && obj.a); // {}
console.log(obj && obj.a && obj.a.b && obj.a.b.c); // undefined
console.log(obj?.a?.b?.c); // undefined

const test = void 0;
const prop = "a";
console.log(test); // undefined
console.log(test?.a); // undefined
console.log(test?.[prop]); // undefined
console.log(test?.[0]); // undefined
console.log(test?.()); // undefined

jQuery中的链式调用

jQuery是一个高端而不失奢华的框架,其中有许多非常精彩的方法和逻辑,虽然现在非常流行于类似于Vue、React的MVVM模式的框架,但是jQuery的设计实在是棒,非常值得学习,在这里以最基础的实例化jQuery为例探查一下jQuery如何通过this实现的链式调用。
首先定义一个最基本的类,通过原型链去继承方法。

function _jQuery(){}
_jQuery.prototype = {
  constructor: _jQuery,
  length: 2,
  size: function(){
    return this.length;
  }
}

var instance = new _jQuery();
console.log(instance.size()); // 2
// _jQuery.size() // Uncaught TypeError: _jQuery.size is not a function
// _jQuery().size() / /Uncaught TypeError: Cannot read property 'size' of undefined

通过定义一个类并且实现实例化之后,在实例之间可以共享原型上的方法,而直接通过_jQuery类直接去调用显然是不行的,抛出的第一种异常是因为在_jQuery类上不存在静态方法,第二种异常是因为_jQuery作为函数执行后未返回值,通过这里可以看出jQuery在通过$()方式调用的时候是返回了一个包含多个方法的对象的,而只是通过自己是访问不到的,我们就借助另一个变量去访问。

function _jQuery(){
  return _fn;
}
var _fn = _jQuery.prototype = {
  constructor: _jQuery,
  length: 2,
  size: function(){
    return this.length;
  }
}
console.log(_jQuery().size()); // 2

实际上jQuery为了减少变量的创建,直接将_fn看做了_jQuery的一个属性。

function _jQuery(){
  return _jQuery.fn;
}
_jQuery.fn = _jQuery.prototype = {
  constructor: _jQuery,
  length: 2,
  size: function(){
    return this.length;
  }
}
console.log(_jQuery().size()); // 2

到这里确实能够实现_jQuery()方式调用原型上的方法,但是在jQuery中$()的主要目标还是作为选择器用来选择元素,而现在返回的是一个_jQuery.fn对象,显然是达不到要求的,为了能够取得返回的元素,那就在原型上定义一个init方法去获取元素,这里为了省事直接使用了document.querySelector,实际上jQuery的选择器构建是很复杂的。

function _jQuery(selector){
  return _jQuery.fn.init(selector);
}
_jQuery.fn = _jQuery.prototype = {
  constructor: _jQuery,
  init: function(selector){
    return document.querySelector(selector);
  },
  length: 3,
  size: function(){
    return this.length;
  }
}
console.log(_jQuery("body")); // <body>...</body>

是似乎这样又把链式调用的this给漏掉了,这里就需要利用this的指向了,因为在调用时this总是指向调用他的对象,所以我们在这里将选择的元素挂载到this对象上即可。

function _jQuery(selector){
  return _jQuery.fn.init(selector);
}
_jQuery.fn = _jQuery.prototype = {
  constructor: _jQuery,
  init: function(selector){
    this[0] = document.querySelector(selector);
    this.length = 1;
    return this;
  },
  length: 3,
  size: function(){
    return this.length;
  }
}
var body = _jQuery("body");
console.log(body); // {0: body, length: 1, constructor: ƒ, init: ƒ, size: ƒ}
console.log(body.size()); // 1
console.log(_jQuery.fn); // {0: body, length: 1, constructor: ƒ, init: ƒ, size: ƒ}

但是此时又出现了一个问题,我们的选择器选择的元素是直接挂载到了_jQuery.fn上,这样的话由于原型是共享的,在之后的定义的选择器就会将前边定义的选择器覆盖掉,这样显然是不行的,于是我们使用new操作符新建一个对象。

function _jQuery(selector){
  return new _jQuery.fn.init(selector);
}
_jQuery.fn = _jQuery.prototype = {
  constructor: _jQuery,
  init: function(selector){
    this[0] = document.querySelector(selector);
    this.length = 1;
    return this;
  },
  length: 3,
  size: function(){
    return this.length;
  }
}
var body = _jQuery("body");
console.log(body); // init {0: body, length: 1}
// console.log(body.size()); // Uncaught TypeError: body.size is not a function

这样又出现了问题,当我们使用new实例化_jQuery.fn.init时返回的this指向的是_jQuery.fn.init的实例,我们就不能进行链式调用了,jQuery用了一个非常巧妙的方法解决了这个问题,直接将_jQuery.fn.init的原型指向_jQuery.prototype,虽然会有循环引用的问题,但是相对来说这一点性能消耗并不算什么,由此我们完成了jQuery选择器以及链式调用的实现。

function _jQuery(selector){
  return new _jQuery.fn.init(selector);
}
_jQuery.fn = _jQuery.prototype = {
  constructor: _jQuery,
  init: function(selector){
    this[0] = document.querySelector(selector);
    this.length = 1;
    return this;
  },
  length: 3,
  size: function(){
    return this.length;
  }
}
_jQuery.fn.init.prototype = _jQuery.fn;
var body = _jQuery("body");
console.log(body); // init {0: body, length: 1}
console.log(body.size()); // 1
console.log(_jQuery.fn.init.prototype.init.prototype.init.prototype === _jQuery.fn); // true

每日一题

https://github.com/WindrunnerMax/EveryDay

以上就是详解JavaScript中的链式调用的详细内容,更多关于JavaScript 链式调用的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
在chrome中window.onload事件的一些问题
Mar 01 Javascript
javascript页面上使用动态时间具体实现
Mar 18 Javascript
JavaScript 获取任一float型小数点后两位的小数
Jun 30 Javascript
angularjs学习笔记之简单介绍
Sep 26 Javascript
Easyui form combobox省市区三级联动
Jan 13 Javascript
Bootstrap Table使用整理(三)
Jun 09 Javascript
基于vue2.0实现简单轮播图
Nov 27 Javascript
React Native 使用Fetch发送网络请求的示例代码
Dec 02 Javascript
利用js给datalist或select动态添加option选项的方法
Jan 25 Javascript
layer.prompt使文本框为空的情况下也能点击确定的方法
Sep 24 Javascript
node.js使用yargs处理命令行参数操作示例
Feb 11 Javascript
react-router-dom 嵌套路由的实现
May 02 Javascript
在Vue中使用CSS3实现内容无缝滚动的示例代码
Nov 27 #Vue.js
vuex的数据渲染与修改浅析
Nov 26 #Vue.js
vue动态合并单元格并添加小计合计功能示例
Nov 26 #Vue.js
JavaScript用document.write()输出换行的示例代码
Nov 26 #Javascript
vue单元格多列合并的实现
Nov 26 #Vue.js
VUE项目实现主题切换的多种方法
Nov 26 #Vue.js
js实现弹窗猜数字游戏
Nov 26 #Javascript
You might like
什么是MVC,好东西啊
2007/05/03 PHP
php 文章采集正则代码
2009/12/28 PHP
将一维或多维的数组连接成一个字符串的php代码
2010/08/08 PHP
防止本地用户用fsockopen DDOS攻击对策
2011/11/02 PHP
Yii2简单实现多语言配置的方法
2016/07/23 PHP
当鼠标移动到图片上时跟随鼠标显示放大的图片效果
2013/06/06 Javascript
JavaScript基础篇之变量作用域、传值、传址的简单介绍与实例
2013/06/29 Javascript
jquerydom对象的事件隐藏显示和对象数组示例
2013/12/10 Javascript
jQuery取id有.的值的方法
2014/05/21 Javascript
详解JavaScript异步编程中jQuery的promise对象的作用
2016/05/03 Javascript
jQuery使用中可能被XSS攻击的一些危险环节提醒
2016/05/24 Javascript
详解Angular4中路由Router类的跳转navigate
2017/06/09 Javascript
基于easyui checkbox 的一些操作处理方法
2017/07/10 Javascript
深入理解Vue nextTick 机制
2018/04/28 Javascript
JS非行间样式获取函数的实例代码
2018/06/05 Javascript
微信小程序之自定义组件的实现代码(附源码)
2018/08/02 Javascript
详解async/await 异步应用的常用场景
2019/05/13 Javascript
微信小程序tab左右滑动切换功能的实现代码
2021/02/08 Javascript
windows下python模拟鼠标点击和键盘输示例
2014/02/28 Python
Python3实现生成随机密码的方法
2014/08/23 Python
Python计算字符宽度的方法
2016/06/14 Python
浅谈Python的list中的选取范围
2018/11/12 Python
Python可变和不可变、类的私有属性实例分析
2019/05/31 Python
使用python实现离散时间傅里叶变换的方法
2019/09/02 Python
python中的逆序遍历实例
2019/12/25 Python
python字典key不能是可以是啥类型
2020/08/04 Python
python zip()函数的使用示例
2020/09/23 Python
CSS3中线性颜色渐变的一些实现方法
2015/07/14 HTML / CSS
N:Philanthropy官网:美国洛杉矶基础款服装
2020/06/09 全球购物
物业经理自我鉴定
2014/03/03 职场文书
留学顾问岗位职责
2014/04/14 职场文书
2014年旅游局法制宣传日活动总结
2014/11/01 职场文书
2014年人民调解工作总结
2014/12/08 职场文书
遗嘱范文
2015/08/07 职场文书
用Python提取PDF表格的方法
2021/04/11 Python
mongodb数据库迁移变更的解决方案
2021/09/04 MongoDB