专业编程基础技术教程

网站首页 > 基础教程 正文

「3mins」四条写JS模块的最佳实践

ccvgpt 2024-11-26 00:48:58 基础教程 1 ℃

一 、 最好使用命名好的exports,而不是default

在写模块输出的时候,有时候我们会这样写:

// greeter.js
export default class Greeter {
  constructor(name) {
    this.name = name;
  }

  greet() {
    return `Hello, ${this.name}!`;
  }
}

引用处就可以这样写

「3mins」四条写JS模块的最佳实践

import Greeter from 'greeter.js'

这么写的弊端是,如果需要万一有一天这个方法要重构,需要重新改一个名字,变成SayHello,那么在使用处就需要你自己一处处去找来改这个名字。

但是如果你export出来的就是一个命名的函数

// greeter.js
export class Greeter {
  constructor(name) {
    this.name = name;
  }

  greet() {
    return `Hello, ${this.name}!`;
  }
}

这样引用处就会这样引用

import { Greeter } from 'greeter.js'

这样每当你去改Greeter的名字的时候,编辑器vscode就会帮你自动把引用的地方都改成SayHello。

二、不要在export的函数做复杂计算

不要在export的函数做复杂计算例如:解析json,发送http请求,读本地local Storage的数据等等。

比如:下面这个模块中,export一个configuration模块,里面去解析了一个大的JSON字符串。

// configuration.js
export const configuration = {
  // Bad
  data: JSON.parse(bigJsonString)
};

那么在引用的地方,import configuration的时候,这个解析json的东西就已经被执行了。所以可能会拖慢JS执行的速度,从而拖慢用户的可交互时间。

// Bad: parsing happens when the module is imported
import { configuration } from 'configuration';

export function AboutUs() {
  return <p>{configuration.data.siteName}</p>;
}

好的实践当然是等到用的时候再去执行这个复杂计算,所以我们来重构一下这个代码。

// configuration.js
let parsedData = null;

export const configuration = {
  // Good
  get data() {
    if (parsedData === null) {
      parsedData = JSON.parse(bigJsonString);
    }
    return parsedData;
  }
};

由于data已经被定义成一个getter,所以只会在用户真正用到configuration.data的时候才去解析大的json字符串。

// Good: JSON parsing doesn't happen when the module is imported
import { configuration } from 'configuration';

export function AboutUs() {
  // JSON parsing happens now
  return <p>{configuration.data.companyDescription}</p>;
}

在真正用到的时候才去执行这些复杂计算就可以防止有时候只是加载进来,却没有使用,造成计算浪费。而且还可以做网站的性能优化,在浏览器处于空闲时候才去执行这些复杂操作。

三、写高关联的模块

要求模块里的东西都是互相关联的,他们密切相关,公共合作处理同一个任务。

比如这个模块 formatDate就是高关联的,因为MONTHS,ensureDateInstance,formatDate相互关联,只做一个任务就是日期格式化。删除MONTHS或者ensureDateInstance都会使得最后export出去的formatDate无法正常工作。

// formatDate.js
const MONTHS = [
  'January', 'February', 'March','April', 'May',
  'June', 'July', 'August', 'September', 'October',
  'November', 'December'
];

function ensureDateInstance(date) {
  if (typeof date === 'string') {
    return new Date(date);
  }
  return date;
}

export function formatDate(date) {
  date = ensureDateInstance(date);
  const monthName = MONTHS[date.getMonth())];
  return `${monthName} ${date.getDate()}, ${date.getFullYear()}`;
}

在来一个反例:

// utils.js
import cookies from 'cookies';

export function getRandomInRange(start, end) {
  return start + Math.floor((end - start) * Math.random());
}

export function pluralize(itemName, count) {
  return count > 1 ? `${itemName}s` : itemName;
}

export function cookieExists(cookieName) {
  const cookiesObject = cookie.parse(document.cookie);
  return cookieName in cookiesObject;
}

在这里例子中,这三个函数都在做不同的任务。大家都跟彼此毫无联系,三个函数中即使少掉其中任意一个,对其他两个函数都没有什么大的影响,这就是低关联的表现。

低关联有个不好的地方就是有可能会引入进来这个函数根本用不到的模块。

比如,我要使用utils 里的pluralize

// ShoppingCartCount.jsx
import { pluralize } from 'utils';

export function ShoppingCartCount({ count }) {
  return (
    <div>
      Shopping cart has {count} {pluralize('product', count)}
    </div>
  );
}

但是由于utils里还引用了cookies,所以即使pluralize里根本没有用到这个模块,cookies也被引用了进来,造成冗余。

好的实践是将他们分开成高关联的模块:utils/random,utils/stringFormatandutils/cookies。

这样,当ShoppingCart 去引用utils/stringFormat的时候,就不会引入进来无用的模块。

// ShoppingCartCount.jsx
import { pluralize } from 'utils/stringFormat';

export function ShoppingCartCount({ count }) {
  // ...
}  

四、避免长的相对路径

import { compareDates } from '../../date/compare';
import { formatDate }   from '../../date/format';

../ 超过两个就比较难去找在哪个层级了。所以最好还是不要这样写, 直接写绝对路径。

import { compareDates } from 'utils/date/compare';
import { formatDate }   from 'utils/date/format';

这样不仅更易书写,同时也更好找这个引用模块的未知。

原文链接:

4 Best Practices to Write Quality JavaScript Modules?dmitripavlutin.com

Tags:

最近发表
标签列表