详解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 相关文章推荐
js实现DIV的一些简单控制
Jun 04 Javascript
js jquery获取随机生成id的服务器控件的三种方法
Jul 11 Javascript
js冒泡法和数组转换成字符串示例代码
Aug 14 Javascript
javascript中返回顶部按钮的实现
May 05 Javascript
jquery插件jquery.confirm弹出确认消息
Dec 22 Javascript
基于jQuery实现点击最后一行实现行自增效果的表格
Jan 12 Javascript
Javascript实现图片懒加载插件的方法
Oct 20 Javascript
JS中作用域和变量提升(hoisting)的深入理解
Oct 31 Javascript
vue.js源代码core scedule.js学习笔记
Jul 03 Javascript
分析JS单线程异步io回调的特性
Dec 01 Javascript
微信小程序使用 vant Dialog组件的正确方式
Feb 21 Javascript
vue大型项目之分模块运行/打包的实现
Sep 21 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
MySQL修改密码方法总结
2008/03/25 PHP
php日历[测试通过]
2008/03/27 PHP
PHP 替换模板变量实现步骤
2009/08/24 PHP
php数组保存文本与文本反编成数组实例
2014/11/13 PHP
Thinkphp 框架扩展之标签库驱动原理与用法分析
2020/04/23 PHP
利用JS重写Cognos右键菜单的实现代码
2010/04/11 Javascript
javascript深入理解js闭包
2010/07/03 Javascript
jquery默认校验规则整理
2014/03/24 Javascript
JS获得选取checkbox整行数据的方法
2015/01/28 Javascript
jQuery插件EasyUI获取当前Tab中iframe窗体对象的方法
2016/08/05 Javascript
JS表单提交验证、input(type=number) 去三角 刷新验证码
2017/06/21 Javascript
select自定义小三角样式代码(实用总结)
2017/08/18 Javascript
js微信分享接口调用详解
2019/07/23 Javascript
基于Express框架使用POST传递Form数据
2019/08/10 Javascript
layui多图上传实现删除功能的例子
2019/09/23 Javascript
javascript设计模式 ? 组合模式原理与应用实例分析
2020/04/14 Javascript
python抓取网页图片并放到指定文件夹
2014/04/24 Python
python多线程threading.Lock锁用法实例
2014/11/01 Python
讲解Python中运算符使用时的优先级
2015/05/14 Python
python 采集中文乱码问题的完美解决方法
2016/09/27 Python
分析python请求数据
2018/08/19 Python
python如何求解两数的最大公约数
2018/09/27 Python
Python读取excel指定列生成指定sql脚本的方法
2018/11/28 Python
python原类、类的创建过程与方法详解
2019/07/19 Python
python Opencv计算图像相似度过程解析
2019/12/03 Python
django框架cookie和session用法实例详解
2019/12/10 Python
python网络爬虫实现发送短信验证码的方法
2021/02/25 Python
检测浏览器对HTML5和CSS3支持度的方法
2015/06/25 HTML / CSS
澳洲网红粉泥面膜:Sand & Sky
2019/08/13 全球购物
澳大利亚领先的亚麻品牌:Bed Threads
2019/12/16 全球购物
什么是Oracle的后台进程background processes?都有哪些后台进程?
2012/04/26 面试题
药学专业毕业生求职信
2013/10/20 职场文书
政工例会汇报材料
2014/08/26 职场文书
2014年党员自我评议对照检查材料
2014/09/20 职场文书
学习心理学的体会
2014/11/07 职场文书
Nginx HTTP跳转至HTTPS
2022/05/15 Servers