详解JavaScript中的链式调用与可选链操作符
本文深入解析JavaScript中的链式调用技巧,涵盖this返回、对象返回及闭包实现方式,并以jQuery源码为例剖析其基于原型链的链式调用机制。同时介绍ES2020引入的可选链操作符?.,阐述其在安全访问深层属性时的语法与应用,帮助开发者提升代码简洁性与健壮性。
链式调用概述
链模式
链模式并非严格意义上的设计模式,而是一种高效的链式调用编码技巧。
描述
在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引入的可选链操作符,包括?.、??和??=。其中,?.允许安全地访问嵌套对象链深处的属性,无需手动验证链中每个引用是否有效。与传统的.操作符不同,当引用值为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中的链式调用
jQuery作为一个经典框架,其设计与逻辑至今仍具学习价值,尽管Vue、React等MVVM框架日益流行。本文以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通过$()调用时返回的是一个包含多个方法的对象,而非直接通过自身访问。我们可以借助另一个变量来访问该对象。
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对象上即可解决此问题。
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 链式调用的资料请关注本站其它相关文章!
JS语法错误会阻止加载吗?区分SyntaxError与资源加载失败
2026-11-05 16:08:46
JavaScript常见SyntaxError语法错误排查与修复方法
2026-11-05 13:58:44
JS中slice、substring和substr截取字符串的区别
2026-11-05 13:43:47
JS 转 TS 手动迁移最佳实践:从配置 tsconfig 到消除 any 类型
2026-11-05 11:53:32
为什么JS数组push后得到的是数字而不是数组?
2026-11-05 09:59:18
