• 如何在函数中使用兄弟参数作为默认值
  • 发布于 1周前
  • 33 热度
    0 评论
JavaScript 自 ES2015 起就支持默认参数值。你知道这一点。我也知道。我不知道的是,你可以使用之前的同级参数作为默认值。(或者叫 "相邻位置参数"?)
function 
myFunc(arg1, arg2 = arg1) {
  
console.log(arg1, arg2);
}
myFunc("arg1!");
// "arg1!" "arg1!"
MDN 甚至指出了这一点(我是在发表这篇文章后才发现的),并演示了该功能如何帮助处理一些不寻常的函数签名。

场景
继续以图像优化为主题,假设你有一个 OptimizedImage 类。向它的构造函数提供一个图片 URL,你就可以获取图片的最新优化缓冲区或缓存版本。
class 
OptimizedImage 
{
constructor(
    imageUrl: string,
    cacheService = new CacheService(),
    optimizeService = new OptimizeService()
  ) {
    
this.imageUrl = imageUrl;
this.cacheService = cacheService;
this.optimizeService = optimizeService;
  }
async 
get() {
const cached = this.cacheService.get(this.imageUrl);
// Return the previously optimized image. 
if (cached) return cached;
const optimizedImage = await 
this.optimizeService
      .optimize(this.imageUrl);
 // 堆代码 duidaima.com
// Cache the optimized image for next time.
return 
this.cacheService.put(this.imageUrl, optimizedImage);
  }
}
const instance = new OptimizedImage('<https://macarthur.me/me.jpg>');
const imgBuffer = await instance.get();
生产中使用的构造函数参数只有 imageUrl ,但注入 CacheService 和 OptimizeService 可以更方便地使用模拟进行单元测试:
import { it, expect, vi } from 
'vitest';
import { OptimizedImage } from 
'./main';
it('returns freshly optimized image', async 
function () {
const fakeImageBuffer = new 
ArrayBuffer('image!');
  
const mockCacheService = {
get: (url) => 
null,
put: vi.fn().mockResolvedValue(fakeImageBuffer),
  };
const mockOptimizeService = {
optimize: (url) => fakeImageBuffer,
  };
const optimizedImage = new OptimizedImage(
'<https://test.jpg>',
    mockCacheService,
    mockOptimizeService
  );
const result = await optimizedImage.get();
  expect(result).toEqual(fakeImageBuffer);
  expect(mockCacheService.put).toHaveBeenCalledWith(
'<https://test.jpg>', 
'optimized image'
  );
});
使问题更加复杂
在该示例中,这两个服务类只有在调用特定方法时才会使用 imageUrl 。但试想一下,如果它们要求将 imageUrl 传递到自己的构造函数中,那又会怎样呢?你可能会倾向于将实例化引入 OptimizedImage 的构造函数(我就是这样做的):
class 
OptimizedImage 
{
constructor(
    imageUrl: string
  ) {
this.imageUrl = imageUrl;
this.cacheService = new CacheService(imageUrl);
this.optimizeService = new OptimizeService(imageUrl);
  }
这也行得通,但现在 OptimizedImage 完全负责服务实例化,测试也变得更加麻烦。为服务实例传递模拟数据也不是那么容易。你可以通过传递模拟类定义来解决这个问题,但这样一来,你就需要为这些类创建具有自己构造函数的 mock 版本,从而使测试变得更加繁琐。幸运的是,还有另一种选择:在参数列表的其余部分使用 imageUrl 参数。

共享兄弟参数
直到不久前,我才意识到这是可能的。这就是它的样子:
export 
class 
OptimizedImage 
{
constructor(
    imageUrl: string,
    // Use the same `imageUrl` in both dependencies.
    cacheService = new CacheService(imageUrl),
    optimizeService = new OptimizeService(imageUrl)
  ) {
    
this.cacheService = cacheService;
    
this.optimizeService = optimizeService;
  }
async 
get() {
const cached = this.cacheService.get();
// Return the previously optimized image.
if (cached) return cached;
const optimizedImage = await 
this.optimizeService.optimize();

// Cache the optimized image for next time.
    
return 
this.cacheService.put(optimizedImage);
  }
}
有了这个设置,你就可以像以前一样轻松地模拟这些实例,而类的其他部分甚至不需要持有 imageUrl 的实例。当然,实例化仍然很简单:
const instance = new OptimizedImage('<https://macarthur.me/me.jpg>');
const img = await instance.get();
测试方法也保持不变:
import { it, expect, vi } from 
'vitest';
import { OptimizedImage } from 
'./main';

it('returns freshly optimized image', async 
function () {
  
const mockCacheService = {
    
get: () => 
null,
    
put: vi.fn().mockResolvedValue('optimized image'),
  };

  
const mockOptimizeService = {
    
optimize: () => 
'optimized image',
  };

  
const optimizedImage = new OptimizedImage(
    
'<https://test.jpg>',
    mockCacheService,
    mockOptimizeService
  );

const result = await optimizedImage.get();
  expect(result).toEqual('optimized image');
  expect(mockCacheService.put).toHaveBeenCalledWith('optimized image');
});
这里没有什么革命性的东西--只是一种使特定问题的解决更符合人体工程学的功能。

其他案例
除了这种情况,我还没有遇到过这种功能特别方便的用例。不过,也许会有一些机会来加强你的工作保障,并树立你不败的聪明才智。
考虑用 .reduce() 计算一个数组的和:
const numbers = [1, 2, 3, 4, 5];
const total = numbers.reduce((acc, value) => {
return acc + value;
});
console.log(total); // 15
这个过程的 "业务" 存在于函数体中。不过,你也可以把它作为默认参数塞进函数签名中,让函数体只返回结果。
const total = numbers.reduce(
(acc, value, _index, _array, result = acc + value) => {
    
return result;
  }
);
而且,它还可以使用隐式 return 来显得更加引人注目:
const total = numbers.reduce((acc, value, _index, _array, result = acc + value) => result);
看起来很奇怪。从我自己的经验来看,用处不大,但还是很有趣。
用户评论