前端开发中经常遇到需要根据不同的环境给变量赋不同值的情况,比如,我们需要在本地mock数据,以便独立于后端服务开发,针对本地mock数据有一套url;上线时,又需要更换成线上的url;如果还有测试环境,又需要另一套url;共需要3套url: 它们都有相同的变量名,但根据不同的环境会有不同的值。

怎么做呢?我们可以将不同环境的变量定义在json文件中,在build时根据不同的环境动态生成js配置文件,有这样的开源项目,可以直接使用。这里介绍另外一种方法,不需要依赖额外的包,那就是利用Proxy。

Proxy基本用法

Proxy可以在操作对象时提供自定义的动作,这些操作包括get, set, delete等。Proxy的基本语法是:

let p = new Proxy(target, handler);

其中target是要添加Proxy的对象,handler是一个对象,定义需要代理哪些操作,比如想代理get,那么可以这样定义一个handler:

let handler = {
  get (target, name) {
    return target[name] || 'default value'
  }
}

这里的get称为trap,除了get,还有set, deleteProperty, has…

一个例子

好,回到我们的应用场景。比如说我们有一个配置文件 (把常量定义在独立的配置文件中是个好习惯):

const _config = {
  setting1: 's1',
  dev: {
    myUrl: '/data/sample1.json'
  },
  stage: {
    myUrl: 'http://127.0.0.2/service/person'
  },
  prod: {
    myUrl: 'http://127.0.0.3/service/person'
  }
}
let config
...
export default config

我们分别为开发环境、测试环境、线上环境定义了相同的变量,但有不同的值,将下来我们需要在build的时候把环境值传进来,用webpack构建的话很容易做到,我们可以把环境名放在变量 process.env.NODE_ENV 中,那么我们可以这样定义一个Proxy:

  let handler = {
    get (target, name) {
      if (name in target) {
        return target[name]
      } else {
        let envConfig = target[process.env.NODE_ENV || 'prod']
        return envConfig[name]
      }
    }
  }
  config = new Proxy(_config, handler)

这样,当我们访问 config.myUrl 时,将会根据环境名返回正确的值。

兼容旧浏览器

Proxy是es6的新特性,遗憾的是,目前有相当一部分浏览器不支持,具体的支持情况可以查看[这里] (http://caniuse.com/#search=proxy)。更坏的消息是,我们常用的babel并不提供Proxy的polyfill,这种情况下我们只能用笨一点的办法:

// 判断浏览器是否支持Proxy
if (typeof Proxy !== 'function') {
  console.warn('Proxy is not supported')
  let envConfig = _config[process.env.NODE_ENV || 'production']
  config = Object.assign({}, _config, envConfig)
} else {
  let handler = {
    get (target, name) {
      if (name in target) {
        return target[name]
      } else {
        let envConfig = target[process.env.NODE_ENV || 'production']
        return envConfig[name]
      }
    }
  }
  config = new Proxy(_config, handler)
}

总结

本文探讨了一种新的方式来支持基于环境的变量定义,并不依赖第三方库,使用起来还是相当简单的。