JavaScript prend en charge les valeurs de paramètres par défaut depuis ES2015. Vous le savez. Je le sais. Ce que je ne savais pas, c'est que vous pouvez utiliser frère paramètres comme valeurs par défaut elles-mêmes. (Ou peut-être des « paramètres de position adjacents » ? Vous ne savez pas comment les appeler.)
function myFunc(arg1, arg2 = arg1) { console.log(arg1, arg2); } myFunc("arg1!"); // "arg1!" "arg1!"
Cela fonctionne également dans les constructeurs de classes – quelque chose que j'ai trouvé très utile pour rendre certains codes PicPerf.io plus testables. Il est courant de voir une simple injection de dépendances utilisée à cette fin. Explorons-le un peu.
En gardant le thème d'optimisation d'image, disons que vous avez une classe OptimizedImage. Fournissez une URL d'image à son constructeur et vous pourrez récupérer soit un tampon fraîchement optimisé de l'image, soit une version mise en cache.
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); // 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();
Le seul paramètre de constructeur utilisé en production est imageUrl, mais l'injection de CacheService et OptimizeService permet de réaliser des tests unitaires plus faciles avec des simulations :
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' ); });
Dans cet exemple, ces deux classes de service utilisent imageUrl uniquement lorsque des méthodes particulières sont invoquées. Mais imaginez s’ils exigeaient qu’il soit transmis à leurs propres constructeurs. Vous pourriez être tenté d'intégrer l'instanciation dans le constructeur d'OptimizedImage (j'étais) :
class OptimizedImage { constructor( imageUrl: string ) { this.imageUrl = imageUrl; this.cacheService = new CacheService(imageUrl); this.optimizeService = new OptimizeService(imageUrl); }
Cela fonctionnerait, mais désormais OptimizedImage est entièrement responsable de l'instanciation du service, et les tests deviennent également plus compliqués. Ce n'est pas si facile de transmettre des simulations pour des instances de service.
Vous pouvez contourner ce problème en transmettant des définitions de classes fictives, mais vous devrez alors créer des versions fictives de ces classes avec leurs propres constructeurs, ce qui rend les tests plus fastidieux. Heureusement, il existe une autre option : utilisez le paramètre imageUrl dans le reste de votre liste d'arguments.
Je ne savais même pas que cela était possible jusqu'à il y a peu de temps. Voici à quoi cela ressemblerait :
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); } }
Avec cette configuration, vous pouvez vous moquer de ces instances aussi facilement qu'avant, et le reste de la classe n'a même pas besoin de conserver une instance de imageUrl elle-même. L’instanciation, bien sûr, reste simple :
const instance = new OptimizedImage('https://macarthur.me/me.jpg'); const img = await instance.get();
La même approche de test reste également intacte :
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'); });
Rien de révolutionnaire ici – juste une petite fonctionnalité qui a rendu ma vie un peu plus agréable sur le plan ergonomique. J'espère rencontrer d'autres joyaux comme celui-ci à l'avenir.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!