JavaScript中的链式调用
链模式
链模式是一种链式调用的方式,准确来说不属于通常定义的设计模式范畴,但链式调用是一种非常有用的代码构建技巧。
描述
链式调用在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
参考
https://zhuanlan.zhihu.com/p/110512501
https://juejin.cn/post/6844904030221631495
https://segmentfault.com/a/1190000011863232
https://github.com/songjinzhong/JQuerySource
https://leohxj.gitbooks.io/front-end-database/content/jQuery/jQuery-source-code/index.html
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/%E5%8F%AF%E9%80%89%E9%93%BE
JavaScript中的链式调用的更多相关文章
- 编程中的链式调用:Scala示例
编程中的链式调用与Linux Shell 中的管道类似.Linux Shell 中的管道 ,会将管道连接的上一个程序的结果, 传递给管道连接的下一个程序作为参数进行处理,依次串联起N个实用程序形成流水 ...
- JavaScript设计模式-8.链式调用
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- 关于JavaScript中的setTimeout()链式调用和setInterval()探索
http://www.cnblogs.com/Wenwang/archive/2012/01/06/2314283.html http://www.cnblogs.com/yangjunhua/arc ...
- JavaScript实现链式调用
学习Jquery的时候,我们通常会看到链式调用的写法 $(window).addEvent('load', function(){ $('test').show().setStyle('color', ...
- javascript学习(10)——[知识储备]链式调用
上次我们简单的说了下单例的用法,这个也是在我们java中比较常见的设计模式. 今天简单说下链式调用,可能有很多人并没有听过链式调用,但是其实只要我简单的说下的话,你肯定基本上都在用,大家熟知的jQue ...
- JavaScript链式调用
1.什么是链式调用? 这个很容易理解,例如 $('text').setStyle('color', 'red').show(); 一般的函数调用和链式调用的区别:链式调用完方法后,return thi ...
- 浅析 JavaScript 链式调用
对$函数你已经很熟悉了.它通常返回一个html元素或一个html元素的集合,如下: function$(){ var elements = []; for(vari=0,len=arguments.l ...
- (原创)c++11中 function/lamda的链式调用
关于链式调用,比较典型的例子是c#中的linq,不过c#中的linq还只是一些特定函数的链式调用.c++中的链式调用更少见因为实现起来比较复杂.c++11支持了lamda和function,在一些延迟 ...
- 《javascript设计模式》笔记之第六章:方法的链式调用
这一章要实现的就是jQuery的那种链式调用,例子: $(this).setStyle('color', 'green').show(); 一:调用链的结构: 首先我们来看一下最简单的$()函数的实现 ...
随机推荐
- this.getResolve is not a function VUE中使用sass
1. 安装以下依赖 npm install node-sass --save-dev //安装node-sass npm install sass-loader --save-dev //安装sass ...
- D. Serval and Rooted Tree (樹狀DP)
Codeforce 1153D Serval and Rooted Tree (樹狀DP) 今天我們來看看CF1153D 題目連結 題目 給一棵數,假設有$k$個葉節點,我們可以給葉節點分配$1$~$ ...
- 随便聊一聊&最近做的项目
好久没有发文章了,我并非闲着,而是把原来发博文的形式转成了写稿. 今年是非常特殊的一年,长这么大,从来没有休过如此漫长的寒假,也从未在家进行过一个月以上的远程办公,新冠肺炎对各行各业都产生了巨大影响, ...
- Kubernetes Controller详解
运行容器化应用是Kubernetes最重要的核心功能.为满足不同的业务需要,Kubernetes提供了多种Controller,主要包括Deployment.DaemonSet.Job.CronJob ...
- 4G DTU数据传输终端的功能介绍
4G DTU是基于4G网络的自动化数据传输终端,是一种物联网无线数据传输设备,使用公用运营商的4G网络为用户提供无线远距离数据传输功能,使用工业级32位的高性能通信处理器和工业级无线模块,以嵌入式实时 ...
- nginx处理vue打包文件后的跨域问题
起因 在vue文件打包后,项目脱离了vue配置的反向代理配置,还是会报跨域的错误,或者直接打不开本地文件, 但是此刻我们想打开打包后的文件,测试一下文件有没有错误,因为经常会存在开发阶段没有问题,打包 ...
- FastThreadLocal 是什么鬼?吊打 ThreadLocal 的存在!!
ThreadLocal 大家都知道是线程本地变量,今天栈长再介绍一个神器:FastThreadLocal,从字面上看就是:Fast + ThreadLocal,一个快的 ThreadLocal?这到底 ...
- layui table中固定表头,弹框缩放之后,表头对不齐问题
新手一枚 直接上解决方案 在layui弹出成功后再渲染表格数据 具体操作就是在layer弹层完成之后的回调中渲染表格数据 layer.open({ type: 1, content: $(&quo ...
- Python3网络学习案例二:traceroute详解
1. 写在前面 本文是基于上一篇"ping详解"写的: 不同操作系统下的命令也不同,本文仅针对windows系统,命令为"tracert xxx",效果如下 2 ...
- Asp.Net Core学习笔记:入门篇
Asp.Net Core 学习 基于.Net Core 2.2版本的学习笔记. 常识 像Django那样自动检查代码更新,自动重载服务器(太方便了) dotnet watch run 托管设置 设置项 ...