详解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 相关文章推荐
浅谈JavaScript中面向对象技术的模拟
Sep 25 Javascript
JavaScript加密解密7种方法总结分析
Oct 07 Javascript
javascript常用方法、属性集合及NodeList 和 HTMLCollection 的浏览器差异
Dec 25 Javascript
js中escape对应的C#解码函数 UrlDecode
Dec 16 Javascript
toggle一个div显示或隐藏且可扩展成自定义下拉框
Sep 12 Javascript
js中iframe调用父页面的方法
Oct 30 Javascript
jquery中show()、hide()和toggle()用法实例
Jan 15 Javascript
javascript深拷贝和浅拷贝详解
Feb 14 Javascript
十大热门的JavaScript框架和库
Mar 21 Javascript
JavaScript实现求最大公共子串的方法
Feb 03 Javascript
基于Angular中ng-controller父子级嵌套的相关属性详解
Oct 08 Javascript
vue根据条件不同显示不同按钮的操作
Aug 04 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
php中计算中文字符串长度、截取中文字符串的函数代码
2011/08/09 PHP
基于PHPexecl类生成复杂的报表表头示例
2016/10/14 PHP
Laravel框架生命周期与原理分析
2018/06/12 PHP
JavaScript获取GridView选择的行内容
2009/04/14 Javascript
JavaScript 动态将数字金额转化为中文大写金额
2009/05/14 Javascript
深入理解JavaScript系列(7) S.O.L.I.D五大原则之开闭原则OCP
2012/01/15 Javascript
Seajs是什么及sea.js 由来,特点以及优势
2016/10/13 Javascript
详解ES6中的let命令
2020/04/05 Javascript
javascript中的深复制详解及实例分析
2016/12/29 Javascript
svg动画之动态描边效果
2017/02/22 Javascript
JavaScript数据结构之广义表的定义与表示方法详解
2017/04/12 Javascript
javascript将json格式数组下载为excel表格的方法
2017/12/22 Javascript
小程序实现多选框功能
2018/10/30 Javascript
Vue中消息横向滚动时setInterval清不掉的问题及解决方法
2019/08/23 Javascript
在vue中配置不同的代理同时访问不同的后台操作
2020/09/11 Javascript
JavaScript枚举选择jquery插件代码实例
2020/11/17 jQuery
使用Python的Django框架实现事务交易管理的教程
2015/04/20 Python
将Dataframe数据转化为ndarry数据的方法
2018/06/28 Python
使用PIL(Python-Imaging)反转图像的颜色方法
2019/01/24 Python
Python类中方法getitem和getattr详解
2019/08/30 Python
在tensorflow中设置使用某一块GPU、多GPU、CPU的操作
2020/02/07 Python
Tensorflow中的图(tf.Graph)和会话(tf.Session)的实现
2020/04/22 Python
Python实现LR1文法的完整实例代码
2020/10/25 Python
css3实现多个元素依次显示效果
2017/12/12 HTML / CSS
凯伦·米莲女装网上商店:Karen Millen
2017/11/07 全球购物
C语言中break与continue的区别
2012/07/12 面试题
SQL Server数据库笔试题和答案
2016/02/04 面试题
求职信怎么写
2014/05/23 职场文书
作风建设年活动实施方案
2014/10/24 职场文书
新郎答谢词
2015/01/04 职场文书
简历自我评价模板
2015/03/11 职场文书
运动会通讯稿50字
2015/07/20 职场文书
2015元旦感言
2015/12/09 职场文书
springboot+zookeeper实现分布式锁
2022/03/21 Java/Android
如何解决goland,idea全局搜索快捷键失效问题
2022/04/03 Golang
详解MySQL的内连接和外连接
2023/05/08 MySQL