详解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 相关文章推荐
jquery 学习之一 对象访问
Nov 23 Javascript
URL地址中的#符号使用说明
Feb 12 Javascript
浅析Js中的单引号与双引号问题
Nov 06 Javascript
JavaScript中操作字符串小结
May 04 Javascript
详细解读JavaScript编程中的Promise使用
Jul 27 Javascript
浏览器复制插件zeroclipboard使用指南
Mar 26 Javascript
vue.js实现简单轮播图效果
Oct 10 Javascript
Nuxt.js开启SSR渲染的教程详解
Nov 30 Javascript
jQuery操作选中select下拉框的值代码实例
Feb 07 jQuery
Node.js 在本地生成日志文件的方法
Feb 07 Javascript
eslint+prettier统一代码风格的实现方法
Jul 22 Javascript
angular4实现带搜索的下拉框
Mar 25 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
FirePHP 推荐一款PHP调试工具
2011/04/23 PHP
php使用file函数、fseek函数读取大文件效率对比分析
2016/11/04 PHP
php pdo操作数据库示例
2017/03/10 PHP
thinkphp 框架数据库切换实现方法分析
2020/05/18 PHP
用CSS+JS实现的进度条效果效果
2007/06/05 Javascript
jQuery-Tools-overlay 使用介绍
2012/07/14 Javascript
用jquery存取照片的具体实现方法
2013/06/30 Javascript
用js+iframe形成页面的一种遮罩效果的具体实现
2013/12/31 Javascript
动态加载JavaScript文件的两种方法
2016/04/22 Javascript
浅谈jQuery中ajaxPrefilter的应用
2016/08/01 Javascript
Angularjs 自定义服务的三种方式(推荐)
2016/08/02 Javascript
基于JavaScript实现本地图片预览
2017/02/08 Javascript
jQuery倒计时代码(超简单)
2017/02/27 Javascript
原生js 封装get ,post, delete 请求的实例
2017/08/11 Javascript
vue组件表单数据回显验证及提交的实例代码
2018/08/30 Javascript
如何在 JavaScript 中更好地利用数组
2018/09/27 Javascript
vue项目刷新当前页面的三种方法
2018/12/04 Javascript
vue仿淘宝滑动验证码功能(样式模仿)
2019/12/10 Javascript
H5实现手机拍照和选择上传功能
2019/12/18 Javascript
解决vue scoped scss 无效的问题
2020/09/04 Javascript
python中map()函数的使用方法示例
2017/09/29 Python
Python使用logging模块实现打印log到指定文件的方法
2018/09/05 Python
python中类的属性和方法介绍
2018/11/27 Python
python使用magic模块进行文件类型识别方法
2018/12/08 Python
对django中foreignkey的简单使用详解
2019/07/28 Python
Python Pandas list列表数据列拆分成多行的方法实现
2020/12/14 Python
CSS3区域模块region相关编写示例
2015/08/28 HTML / CSS
如何在Canvas中添加事件的方法示例
2019/05/21 HTML / CSS
利用Node实现HTML5离线存储的方法
2020/10/16 HTML / CSS
个性化皮包、小袋、生活配件:Mon Purse
2019/03/26 全球购物
应届生护士求职信
2013/11/01 职场文书
给物业的表扬信
2014/01/21 职场文书
教师党员公开承诺事项
2014/05/28 职场文书
课程设计感想范文
2015/08/11 职场文书
Python文件的操作示例的详细讲解
2021/04/08 Python
MySQL8.0无法启动3534的解决方法
2021/06/03 MySQL