你好同学,我是沐爸,欢迎点赞、收藏和关注。个人知乎
不知你是否注意过,链式调用在编程中还是很普遍的,无论是原生JavaScript还是在一些框架和库中,都会看到它们的身影。今天我们一探究竟,看下链式调用是怎么实现的,以及在项目中的应用吧。
在JavaScript中,链式调用(Chaining)是一种非常流行的设计模式,它允许你以流畅的方式连续调用同一个对象的方法。链式调用通常是通过在每个方法的末尾返回对象本身(通常是**this**
关键字)来实现的。这样做的好处是代码更加简洁、易读,并且提高了代码的可维护性。
链式调用的实现
构造函数的实现
function Chain(value = 0) {
this.value = value
// 设置value的值并返回对象本身
this.setValue = function (value) {
this.value = value
return this
}
// 增加value的值并返回对象本身
this.addValue = function (value) {
this.value += value
return this
}
// 返回value的值
this.getValue = function () {
return this.value // 这里不返回this,因为getValue后通常不需要继续链式调用
}
}
// 使用
const chain = new Chain()
let res = chain.setValue(5).addValue(10).getValue()
console.log(res) // 15
在这个例子中,Chain
类的每个方法(除了getValue
,因为它不参与链式调用)在执行完自己的任务后,都返回了对象本身(this
),这允许你能够连续地调用这些方法。
原型的实现
使用原型prototye改写构造函数示例:
function Chain(value = 0) {
this.value = value
}
Chain.prototype.setValue = function (value) {
this.value = value
return this
}
Chain.prototype.addValue = function (value) {
this.value += value
return this
}
Chain.prototype.getValue = function () {
return this.value
}
// 使用
const chain = new Chain()
let res = chain.setValue(5).addValue(10).getValue()
console.log(res) // 15
Chain
函数定义了 value
属性,并使用 Chain.prototype
来定义 setValue
、addValue
和 getValue
方法。这些方法都是实例方法,它们修改或返回 value
属性的值,并返回 this
以支持链式调用。
类的实现
使用类class改写普通函数示例:
class Chain {
constructor(value = 0) {
this.value = value
}
setValue(value) {
this.value = value
return this
}
addValue(value) {
this.value += value
return this
}
getValue() {
return this.value
}
}
// 使用
const chain = new Chain()
let res = chain.setValue(5).addValue(10).getValue()
console.log(res) // 15
Chain
类有一个构造函数,用于初始化 value
属性。setValue
、addValue
和 getValue
方法都是实例方法,它们修改或返回 value
属性的值,并返回 this
以支持链式调用。
使用Proxy实现链式调用
var pipe = function (value) {
var funcStack = []
var oproxy = new Proxy(
{},
{
get: function (pipeObject, fnName) {
if (fnName === 'get') {
return funcStack.reduce(function (val, fn) {
return fn(val)
}, value)
}
funcStack.push(window[fnName])
return oproxy
}
}
)
return oproxy
}
var double = (n) => n * 2
var pow = (n) => n * n
var reverseInt = (n) => n.toString().split('').reverse().join('') | 0
pipe(3).double.pow.reverseInt.get // 63
上面的示例来自:https://es6.ruanyifeng.com/#docs/proxy
JS原生对象中的链式调用
数组的链式调用
在JavaScript中,数组(Array)原生支持链式调用,这主要得益于数组方法返回数组本身或者新的数组对象。数组的大部分方法都支持链式调用。
const arr = [1, 2, 3, 4, 5]
.filter((number) => number > 2) // 过滤出大于2的数
.map((number) => number * 2) // 将结果翻倍
.reduce((acc, number) => acc + number, 0) // 计算总和
console.log(arr) // 输出: 24
在这个示例中,我们首先使用.filter()
过滤数组,然后使用.map()
对结果进行映射,最后使用.reduce()
计算所有结果的总和。每个方法都返回一个新的数组或累加值,允许我们继续调用下一个数组方法,实现链式调用。
Promise的链式调用
const p = Promise.resolve(1) // 创建一个已解决的Promise
p.then((result) => {
console.log(result) // 输出: 1
return result + 1 // 返回一个新的Promise
})
.then((result) => {
console.log(result) // 输出: 2
return result * 2 // 返回一个新的Promise
})
.then((result) => {
console.log(result) // 输出: 4,链式调用结束
})
.catch((error) => {
console.error('发生错误:', error) // 错误处理
})
在这个示例中,每个.then()
方法调用后都返回一个新的Promise
,这个Promise
将接收上一个Promise
的结果作为输入。链中的每个.then()
方法都可以根据需要进行异步操作,并且它们的返回值可以被下一个.then()
捕获,.catch()
方法用来捕获链中任何地方发生的错误。
库和框架中的链式调用
链式调用在某些库和框架的API设计中非常常见。
jQuery
jQuery的DOM操作方法就大量使用了链式调用,使得操作DOM元素变得非常便捷和直观。
$('#myElement')
.removeClass('old-class')
.addClass('new-class')
.hide()
.fadeIn(1000);
在上面的jQuery示例中,addClass
、hide
和fadeIn
方法都返回了jQuery对象本身,允许这些方法被连续调用,形成链式调用。
addClass源码示例:
jQuery.fn.extend({
addClass: function (value) {
var classNames, cur, curValue, className, i, finalValue
if (isFunction(value)) {
return this.each(function (j) {
jQuery(this).addClass(value.call(this, j, getClass(this)))
})
}
classNames = classesToArray(value)
if (classNames.length) {
return this.each(function () {
curValue = getClass(this)
cur = this.nodeType === 1 && ' ' + stripAndCollapse(curValue) + ' '
if (cur) {
for (i = 0; i < classNames.length; i++) {
className = classNames[i]
if (cur.indexOf(' ' + className + ' ') < 0) {
cur += className + ' '
}
}
finalValue = stripAndCollapse(cur)
if (curValue !== finalValue) {
this.setAttribute('class', finalValue)
}
}
})
}
return this // 返回jQuery对象本身
}
})
Lodash
Lodash 的很多函数都支持链式调用,这意味着你可以将多个方法连续调用,而不需要创建临时变量。
数组示例:
const result = _([1, 2, 3, 4, 5])
.filter((n) => n > 2) // 过滤出大于 2 的数字
.map((n) => n * 2) // 将结果中的每个数字乘以 2
.value() // 获取最终结果
console.log(result) // [6, 8, 10]
对象示例:
const users = [
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 30 },
{ name: 'Carol', age: 22 }
]
const names = _(users)
.filter({ age: 30 }) // 过滤出年龄为 30 的用户
.map('name') // 取出这些用户的名字
.value() // 获取最终结果
console.log(names) // ['Bob']
字符串示例:
const namesString = _(['Alice', 'Bob', 'Carol'])
.join(' & ') // 将名字数组连接成一个字符串,元素之间用 " & " 分隔
.chain() // 开启链式调用
.trim() // 去除字符串两端的空白字符
.toUpperCase() // 将字符串转换为大写
.value() // 获取最终结果
console.log(namesString) // 输出: "ALICE & BOB & CAROL"
Axios
Axios 是一个基于 Promise 的 HTTP 客户端,用于浏览器和 Node.js 环境。Axios 提供了链式调用的能力,使得你可以连续地使用多个实例方法来处理 HTTP 请求。
axios
.get('https://api.example.com/data')
.then((response) => {
console.log(response.data) // 处理响应数据
})
.catch((error) => {
console.error('请求失败:', error) // 处理请求错误
})
Node.js
在Node.js中,链式调用通常与流、事件和异步操作相关。
Node.js的流API允许你以链式的方式连接数据读取和写入操作:
const fs = require('fs')
const zlib = require('zlib')
// 创建一个可读流
const rs = fs.createReadStream('input.txt')
// 使用 gzip 压缩数据
const gzip = zlib.createGzip()
// 创建一个写入流并压缩数据
const ws = fs.createWriteStream('input.txt.gz')
rs
.pipe(gzip) // 将读取的数据传递给 gzip 流
.pipe(ws) // 将 gzip 压缩后的数据写入文件
rs.on('error', (err) => {
console.error('读取错误:', err)
})
ws.on('error', (err) => {
console.error('写入错误:', err)
})
Node.js的EventEmitter
类允许你以链式的方式注册多个事件监听器:
const emitter = new EventEmitter()
emitter
.on('event1', () => {
console.log('事件1发生')
})
.on('event2', () => {
console.log('事件2发生')
})
.emit('event1') // 触发事件1
是不是像我说的?链式调用在前端编程中大量存在。希望对你有所帮助,下期再见!