详解JavaScript中的链式调用与可选链操作符

作者:小括号

时间:2026-10-24

浏览:96

本文深入解析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 链式调用的资料请关注本站其它相关文章!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章