美文网首页让前端飞Web前端之路
如何写出优雅耐看的JavaScript代码

如何写出优雅耐看的JavaScript代码

作者: alanwhy | 来源:发表于2019-09-26 12:55 被阅读0次

    变量

    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
    };
    
    function createMenu(config) {
      config = Object.assign({
        title: 'Foo',
        body: 'Bar',
        buttonText: 'Baz',
        cancellable: true
      }, config);
    
      // config 就变成了: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
      // ...
    }
    
    createMenu(menuConfig);
    
    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;
    }
    

    复杂条件判断

    1、if/else
    /**
     * 按钮点击事件
     * @param {number} status 活动状态:1 开团进行中 2 开团失败 3 商品售罄 4 开团成功 5 系统取消
     */
    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
    /**
     * 按钮点击事件
     * @param {number} status 活动状态:1 开团进行中 2 开团失败 3 商品售罄 4 开团成功 5 系统取消
     */
    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'],
    }
    /**
     * 按钮点击事件
     * @param {number} status 活动状态:1开团进行中 2开团失败 3 商品售罄 4 开团成功 5 系统取消
     */
    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']]
    ])
    /**
     * 按钮点击事件
     * @param {number} status 活动状态:1 开团进行中 2 开团失败 3 商品售罄 4 开团成功 5 系统取消
     */
    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();
    
    

    原文链接:# 如何写出优雅耐看的JavaScript代码

    相关文章

      网友评论

        本文标题:如何写出优雅耐看的JavaScript代码

        本文链接:https://www.haomeiwen.com/subject/nimguctx.html