干净的代码,既在质量上较为可靠,也为后期维护、升级奠定了良好基础。
我们从以下几个方面进行探讨:
变量
1、变量命名
一般我们在定义变量是要使用有意义的词汇命令,要做到见面知义
- //bad code
- const yyyymmdstr = moment().format('YYYY/MM/DD');
- //better code
- const currentDate = moment().format('YYYY/MM/DD');
2、可描述
通过一个变量生成了一个新变量,也需要为这个新变量命名,也就是说每个变量当你看到他第一眼你就知道他是干什么的。
- //bad code
- const ADDRESS = 'One Infinite Loop, Cupertino 95014';
- const CITY_ZIP_CODE_REGEX = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
- saveCityZipCode(ADDRESS.match(CITY_ZIP_CODE_REGEX)[1], ADDRESS.match(CITY_ZIP_CODE_REGEX)[2]);
-
- //better code
- const ADDRESS = 'One Infinite Loop, Cupertino 95014';
- const CITY_ZIP_CODE_REGEX = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
- const [, city, zipCode] = ADDRESS.match(CITY_ZIP_CODE_REGEX) || [];
- saveCityZipCode(city, zipCode);
3、形参命名
在for、forEach、map的循环中我们在命名时要直接
- //bad code
- const locations = ['Austin', 'New York', 'San Francisco'];
- locations.map((l) => {
- doStuff();
- doSomeOtherStuff();
- // ...
- // ...
- // ...
- // 需要看其他代码才能确定 'l' 是干什么的。
- dispatch(l);
- });
-
- //better code
- const locations = ['Austin', 'New York', 'San Francisco'];
- locations.forEach((location) => {
- doStuff();
- doSomeOtherStuff();
- // ...
- // ...
- // ...
- dispatch(location);
- });
4、避免无意义的前缀
例如我们只创建一个对象是,没有必要再把每个对象的属性上再加上对象名
- //bad code
- const car = {
- carMake: 'Honda',
- carModel: 'Accord',
- carColor: 'Blue'
- };
-
- function paintCar(car) {
- car.carColor = 'Red';
- }
-
- //better code
- const car = {
- make: 'Honda',
- model: 'Accord',
- color: 'Blue'
- };
-
- function paintCar(car) {
- car.color = 'Red';
- }
5、默认值
- //bad code
- function createMicrobrewery(name) {
- const breweryName = name || 'Hipster Brew Co.';
- // ...
- }
-
- //better code
- function createMicrobrewery(name = 'Hipster Brew Co.') {
- // ...
- }
函数
1、参数
一般参数多的话要使用ES6的解构传参的方式
- //bad code
- function createMenu(title, body, buttonText, cancellable) {
- // ...
- }
-
- //better code
- function createMenu({ title, body, buttonText, cancellable }) {
- // ...
- }
-
- //better code
- createMenu({
- title: 'Foo',
- body: 'Bar',
- buttonText: 'Baz',
- cancellable: true
- });
2、单一化处理
一个方法里面最好只做一件事,不要过多的处理,这样代码的可读性非常高
- //bad code
- function emailClients(clients) {
- clients.forEach((client) => {
- const clientRecord = database.lookup(client);
- if (clientRecord.isActive()) {
- email(client);
- }
- });
- }
-
- //better code
- function emailActiveClients(clients) {
- clients
- .filter(isActiveClient)
- .forEach(email);
- }
- function isActiveClient(client) {
- const clientRecord = database.lookup(client);
- return clientRecord.isActive();
- }
3、对象设置默认属性
- //bad code
- const menuConfig = {
- title: null,
- body: 'Bar',
- buttonText: null,
- cancellable: true
- };
- function createMenu(config) {
- config.title = config.title || 'Foo';
- config.body = config.body || 'Bar';
- config.buttonText = config.buttonText || 'Baz';
- config.cancellable = config.cancellable !== undefined ? config.cancellable : true;
- }
- createMenu(menuConfig);
-
-
- //better code
- const menuConfig = {
- title: 'Order',
- // 'body' key 缺失
- buttonText: 'Send',
- cancellable: true
- };
4、避免副作用
函数接收一个值返回一个新值,除此之外的行为我们都称之为副作用,比如修改全局变量、对文件进行 IO 操作等。
当函数确实需要副作用时,比如对文件进行 IO 操作时,请不要用多个函数/类进行文件操作,有且仅用一个函数/类来处理。也就是说副作用需要在唯一的地方处理。
副作用的三大天坑:随意修改可变数据类型、随意分享没有数据结构的状态、没有在统一地方处理副作用。
- //bad code
- // 全局变量被一个函数引用
- // 现在这个变量从字符串变成了数组,如果有其他的函数引用,会发生无法预见的错误。
- var name = 'Ryan McDermott';
- function splitIntoFirstAndLastName() {
- name = name.split(' ');
- }
- splitIntoFirstAndLastName();
- console.log(name); // ['Ryan', 'McDermott'];
-
-
- //better code
- var name = 'Ryan McDermott';
- var newName = splitIntoFirstAndLastName(name)
-
- function splitIntoFirstAndLastName(name) {
- return name.split(' ');
- }
-
- console.log(name); // 'Ryan McDermott';
- console.log(newName); // ['Ryan', 'McDermott'];
在 JavaScript 中,基本类型通过赋值传递,对象和数组通过引用传递。以引用传递为例:
假如我们写一个购物车,通过 addItemToCart()方法添加商品到购物车,修改 购物车数组。此时调用 purchase()方法购买,由于引用传递,获取的 购物车数组正好是最新的数据。
看起来没问题对不对?
如果当用户点击购买时,网络出现故障, purchase()方法一直在重复调用,与此同时用户又添加了新的商品,这时网络又恢复了。那么 purchase()方法获取到 购物车数组就是错误的。
为了避免这种问题,我们需要在每次新增商品时,克隆 购物车数组并返回新的数组。
- //bad code
- const addItemToCart = (cart, item) => {
- cart.push({ item, date: Date.now() });
- };
-
- //better code
- const addItemToCart = (cart, item) => {
- return [...cart, {item, date: Date.now()}]
- };
5、全局方法
在 JavaScript 中,永远不要污染全局,会在生产环境中产生难以预料的 bug。举个例子,比如你在 Array.prototype上新增一个 diff方法来判断两个数组的不同。而你同事也打算做类似的事情,不过他的 diff方法是用来判断两个数组首位元素的不同。很明显你们方法会产生冲突,遇到这类问题我们可以用 ES2015/ES6 的语法来对 Array进行扩展。
- //bad code
- Array.prototype.diff = function diff(comparisonArray) {
- const hash = new Set(comparisonArray);
- return this.filter(elem => !hash.has(elem));
- };
-
- //better code
- class SuperArray extends Array {
- diff(comparisonArray) {
- const hash = new Set(comparisonArray);
- return this.filter(elem => !hash.has(elem));
- }
- }
6、避免类型检查
JavaScript 是无类型的,意味着你可以传任意类型参数,这种自由度很容易让人困扰,不自觉的就会去检查类型。仔细想想是你真的需要检查类型还是你的 API 设计有问题?
- //bad code
- function travelToTexas(vehicle) {
- if (vehicle instanceof Bicycle) {
- vehicle.pedal(this.currentLocation, new Location('texas'));
- } else if (vehicle instanceof Car) {
- vehicle.drive(this.currentLocation, new Location('texas'));
- }
- }
-
- //better code
- function travelToTexas(vehicle) {
- vehicle.move(this.currentLocation, new Location('texas'));
- }
如果你需要做静态类型检查,比如字符串、整数等,推荐使用 TypeScript,不然你的代码会变得又臭又长。
- //bad code
- function combine(val1, val2) {
- if (typeof val1 === 'number' && typeof val2 === 'number' ||
- typeof val1 === 'string' && typeof val2 === 'string') {
- return val1 + val2;
- }
-
- throw new Error('Must be of type String or Number');
- }
-
- //better code
- function combine(val1, val2) {
- return val1 + val2;
- }
复杂条件判断
我们编写js代码时经常遇到复杂逻辑判断的情况,通常大家可以用if/else或者switch来实现多个条件判断,但这样会有个问题,随着逻辑复杂度的增加,代码中的if/else/switch会变得越来越臃肿,越来越看不懂,那么如何更优雅的写判断逻辑
1、if/else
点击列表按钮事件
-
- const onButtonClick = (status)=>{
- if(status == 1){
- sendLog('processing')
- jumpTo('IndexPage')
- }else if(status == 2){
- sendLog('fail')
- jumpTo('FailPage')
- }else if(status == 3){
- sendLog('fail')
- jumpTo('FailPage')
- }else if(status == 4){
- sendLog('success')
- jumpTo('SuccessPage')
- }else if(status == 5){
- sendLog('cancel')
- jumpTo('CancelPage')
- }else {
- sendLog('other')
- jumpTo('Index')
- }
- }
从上面我们可以看到的是通过不同的状态来做不同的事情,代码看起来非常不好看,大家可以很轻易的提出这段代码的改写方案,switch出场:
2、switch/case
-
- const onButtonClick = (status)=>{
- switch (status){
- case 1:
- sendLog('processing')
- jumpTo('IndexPage')
- break
- case 2:
- case 3:
- sendLog('fail')
- jumpTo('FailPage')
- break
- case 4:
- sendLog('success')
- jumpTo('SuccessPage')
- break
- case 5:
- sendLog('cancel')
- jumpTo('CancelPage')
- break
- default:
- sendLog('other')
- jumpTo('Index')
- break
- }
- }
这样看起来比if/else清晰多了,细心的同学也发现了小技巧,case 2和case 3逻辑一样的时候,可以省去执行语句和break,则case 2的情况自动执行case 3的逻辑。
3、存放到Object
将判断条件作为对象的属性名,将处理逻辑作为对象的属性值,在按钮点击的时候,通过对象属性查找的方式来进行逻辑判断,这种写法特别适合一元条件判断的情况。
- const actions = {
- '1': ['processing','IndexPage'],
- '2': ['fail','FailPage'],
- '3': ['fail','FailPage'],
- '4': ['success','SuccessPage'],
- '5': ['cancel','CancelPage'],
- 'default': ['other','Index'],
- }
-
- const onButtonClick = (status)=>{
- let action = actions[status] || actions['default'],
- logName = action[0],
- pageName = action[1]
- sendLog(logName)
- jumpTo(pageName)
- }
4、存放到Map
- const actions = new Map([
- [1, ['processing','IndexPage']],
- [2, ['fail','FailPage']],
- [3, ['fail','FailPage']],
- [4, ['success','SuccessPage']],
- [5, ['cancel','CancelPage']],
- ['default', ['other','Index']]
- ])
-
- const onButtonClick = (status)=>{
- let action = actions.get(status) || actions.get('default')
- sendLog(action[0])
- jumpTo(action[1])
- }
这样写用到了es6里的Map对象,是不是更爽了?Map对象和Object对象有什么区别呢?
- 一个对象通常都有自己的原型,所以一个对象总有一个"prototype"键。
- 一个对象的键只能是字符串或者Symbols,但一个Map的键可以是任意值。
你可以通过size属性很容易地得到一个Map的键值对个数,而对象的键值对个数只能手动确认。
代码风格
常量大写
- //bad code
- const DAYS_IN_WEEK = 7;
- const daysInMonth = 30;
-
- const songs = ['Back In Black', 'Stairway to Heaven', 'Hey Jude'];
- const Artists = ['ACDC', 'Led Zeppelin', 'The Beatles'];
-
- function eraseDatabase() {}
- function restore_database() {}
-
- class animal {}
- class Alpaca {}
-
- //better code
- const DAYS_IN_WEEK = 7;
- const DAYS_IN_MONTH = 30;
-
- const SONGS = ['Back In Black', 'Stairway to Heaven', 'Hey Jude'];
- const ARTISTS = ['ACDC', 'Led Zeppelin', 'The Beatles'];
-
- function eraseDatabase() {}
- function restoreDatabase() {}
-
- class Animal {}
- class Alpaca {}
先声明后调用
- //bad code
- class PerformanceReview {
- constructor(employee) {
- this.employee = employee;
- }
-
- lookupPeers() {
- return db.lookup(this.employee, 'peers');
- }
-
- lookupManager() {
- return db.lookup(this.employee, 'manager');
- }
-
- getPeerReviews() {
- const peers = this.lookupPeers();
- // ...
- }
-
- perfReview() {
- this.getPeerReviews();
- this.getManagerReview();
- this.getSelfReview();
- }
-
- getManagerReview() {
- const manager = this.lookupManager();
- }
-
- getSelfReview() {
- // ...
- }
- }
-
- const review = new PerformanceReview(employee);
- review.perfReview();
-
- //better code
- class PerformanceReview {
- constructor(employee) {
- this.employee = employee;
- }
-
- perfReview() {
- this.getPeerReviews();
- this.getManagerReview();
- this.getSelfReview();
- }
-
- getPeerReviews() {
- const peers = this.lookupPeers();
- // ...
- }
-
- lookupPeers() {
- return db.lookup(this.employee, 'peers');
- }
-
- getManagerReview() {
- const manager = this.lookupManager();
- }
-
- lookupManager() {
- return db.lookup(this.employee, 'manager');
- }
-
- getSelfReview() {
- // ...
- }
- }
-
- const review = new PerformanceReview(employee);
- review.perfReview();