Development模式是如何运作的?

更新日期: 2019-08-09阅读: 1.9k标签: 模式

如果您的JavaScript代码库非常复杂,那么您可能会想办法在开发模式和生产模式中捆绑和运行不同代码。

在开发模式和生产模式中捆绑并运行不同的代码是非常强大的。在开发模式中,react里有许多预警,可以帮助我们在导致bug之前找到问题。然而,检测此类错误所需的代码通常会增加bundle文件的大小,并使应用程序运行得更慢。

在开发模式中程序运行缓慢是可以接受的。事实上,在开发过程中减慢代码的运行速度甚至可能是有益的,因为它在一定程度上弥补了快速开发人员计算机和普通消费设备之间的差异。

在生产中,我们不想付出任何成本。因此,我们在生产中省略了这些检查。这是怎么回事?让我们来看看。

在开发中运行不同代码的确切方法取决于JavaScript构建管道(以及是否有)。Facebook是这样的:

if (__DEV__) {
  doSomethingDev();
} else {
  doSomethingProd();
}

这里,__DEV__不是一个真正的变量。它是一个常量,当浏览器的模块被拼接在一起时这个常量就被替换掉了。结果是这样的:


// In development:
if (true) {
  doSomethingDev(); // 
} else {
  doSomethingProd();
}

// In production:
if (false) {
  doSomethingDev();
} else {
  doSomethingProd(); // 
}

在生产中,我们还需要在代码上运行一个压缩器(例如,terser)。大多数JavaScript微引擎都会进行部分的死代码消除,例如删除if(false)分支。所以在生产中你只会看到:

// In production (after minification):
doSomethingProd();

(请注意,主流JavaScript工具如何有效地消除死码是有诸多限制的,但又这是一个单独的问题了。)

虽然您可能没有使用一个神奇的__DEV__常量,但是如果您使用一个流行的JavaScript捆绑器(如webpack),那么可能还有其他一些惯例可以遵循。例如,通常这样表示相同的模式:


if (process.env.NODE_ENV !== 'production') {
  doSomethingDev();
} else {
  doSomethingProd();
}

这正是使用捆绑器从npm导入时React和vue等库使用的模式。(单个文件(script)标签构建中将开发和生产版本作为单独的.js和.min.js文件提供。)

这一惯例最初来自Node.js。在Node.js中,有一个全局process变量将系统的环境变量作为process.env对象的属性公开。然而,当您在前端代码库中看到这个模式时,通常不涉及任何实际的process变量。

相反,整个process.env.NODE_ENV表达式在构建时被字符串文字替换,就像我们的神奇的__DEV__变量:

// In development:
if ('development' !== 'production') { // true
  doSomethingDev(); // 
} else {
  doSomethingProd();
}

// In production:
if ('production' !== 'production') { // false
  doSomethingDev();
} else {
  doSomethingProd(); // 
}

因为整个表达式是常量('production' !== 'production'确保为false),所以压缩器也可以删除其他分支。

// In production (after minification):
doSomethingProd();

这个麻烦就解决啦

注意,这对更复杂的表达式没用:

let mode = 'production';
if (mode !== 'production') {
  // not guaranteed to be eliminated
}

由于JavaScript语言的动态特性,JavaScript静态分析工具不是很智能。当他们看到像mode这样的变量而不是像false或'production' !== 'production'这样的静态表达式时,他们通常会放弃。

同样,JavaScript在我们使用顶级import语句时,死代码消除经常不能正常地跨模块边界运作:

// not guaranteed to be eliminated
import {someFunc} from 'some-module';

if (false) {
  someFunc();
}

因此,我们需要以一种非常机械的方式编写代码,使条件绝对静态,并确保要消除的所有代码都在其中。

要使所有这些正常运作,我们的bundler需要执行process.env.NODE_ENV替换,并需要知道希望在哪种模式中构建项目。

几年前,我们常常会忘记配置环境。所以我们经常会看到一个处于开发模式的项目部署到生产模式中。这很糟糕,因为这会使网站加载和运行速度变慢。

在过去两年中,情况有了显著的改善。例如,webpack添加了一个简单的mode选项,而不是手动配置process.env.NODE_ENV替换。React DevTools现在还会在带有开发模式的站点上显示一个红色图标,这使得用户更容易发现以及报告。(此处需翻译图片中的文字)(此页面正在使用React开发构建模式。打开开发工具,React键将会出现在右侧。注意:发构建模式并不适用于生产模式。确保在部署前使用生产构建模式 )


像 Create React App、Next/Nuxt、Vue CLI、Gatsby和其他一些固定设置,将开发构建和生产构建分离成两个单独的指令,这样就更不容易产生问题。(例如,npm start和npm run build)一般情况下我们只能部署生产构建,因此开发人员不会再犯这个错误。

总是有这样一种说法,即生产模式才应该被设置为默认的,而开发模式需要是手动切入。就我个人而言,我不认为这个论点有说服力。从开发模式的预警中获益最多的人通常是库的初学者。他们一般都不知道如何打开开发模式,并且会错过开发模式早就能给出的bug的高能预警。

是的,性能问题很糟糕。但向终端用户提供漏洞百出的体验也是如此。例如,React key预警有助于防止犯错,比如向错误的人发送消息或购买错误的产品。在禁用预警时进行开发对您和您的用户都会带来重大风险。如果默认情况下它是关闭的,那么当我们找到切换键并打开它时,我们将会面对过量的预警并需要清除。所以大多数人会把它切换回去。这就是为什么需要从一开始就打开它,而不是稍后才启用它。

最后,即使选择切入开发预警,并且开发人员知道早早的时候就要打开它们,我们又回到最初的问题。有人可能会在部署到生产环境中时忘记关闭它们!我们又回到了出发点。

就我个人而言,我相信能够显示和使用正确模式的工具取决于我们是在调试还是部署。几十年来,除了Web浏览器之外,几乎所有其他环境(无论是移动、桌面还是服务器)都有能够加载和区分开发和生产构建。也许是时候让JavaScript环境将这种区别视为头等需求了,而不是由库提出并依赖于临时约定。 
说了这么多的理论知识!让我们来看看实际操作:

if (process.env.NODE_ENV !== 'production') {
  doSomethingDev();
} else {
  doSomethingProd();
}

大家可能会好奇:如果前端代码中没有真正的process对象,为什么像React和Vue这样的库在npm构建中依赖它呢?(再次澄清一下:您可以在浏览器中加载的(script)标签,由React和Vue提供,不依赖于此。相反,我们必须自己在开发.js和生产.min.js之间作选择。下面部分提到的只是关于通过从npm导入它们将React或Vue与捆绑器一起使用。)


像编程中的许多东西一样,这种特定的惯例主要是历史原因。我们现在仍在使用它,因为现在它被不同的工具广泛采用。换成用其他东西是需要代价的,而且并没有太多意义。那背后的历史原因是什么呢?

在import和export语法标准化之前的很多年,存在着不止一种方式在竞争着来表达模块之间的关系。Node.js推广了require()和module.exports,称为CommonJS。早期在npm注册表上发布的代码是为Node.js编写的。Express是(并且可能现在仍然是?)Node.js最受欢迎的服务器端框架,它使用NODE_ENV环境变量来启用生产模式。其他一些npm包采用了相同的惯例。

像browserify这样的早期JavaScript捆绑包希望能够在前端项目中使用来自npm的代码。(是的,当时几乎没有人使用npm作为前端!大家能想象吗?)因此他们将Node.js生态系统中已经存在的相同惯例扩展到前端代码。

最初的“envify”转换是在2013年发布的。React是在那个时候开源的,而且在那个时代使用browserify的npm似乎是捆绑前端CommonJS代码的最佳解决方案。从一开始React就开始提供npm构建(包括(script)标记构建)。随着React的流行,使用CommonJS模块编写模块化JavaScript并通过npm发送前端代码的做法也开始流行。

React需要在生产模式中删除仅用于开发的代码。Browserify已经为这个问题提供了解决方案,因此React也采用了将process.env.NODE_ENV用于其npm构建的惯例。随着时间的推移,许多其他工具和库,包括webpack和Vue,都做了同样的事情。

到2019年,browserify已经失去了相当多的市场占有率。但是,在构建步骤中用'development'或'production'替换process.env.NODE_ENV仍是一种流行的惯例。(看看如何采用ES模块作为分发格式,而不仅仅是创作格式,会改变方程式,这很有意思。)

还有一件事情可能仍然让大家感到困惑,在GitHub上的React源代码中,我们看到__DEV__被用作魔术变量。但是在npm的React代码中,它使用process.env.NODE_ENV。这是怎么回事?
过去,我们在源代码中使用__DEV__来匹配Facebook源代码。很长一段时间,React被直接复制到Facebook代码库中,所以它需要遵循相同的规则。对于npm,我们有一个构建步骤,在发布之前用process.env.NODE_ENV !== 'production'直接替换__DEV__查验。

这有时会带来问题。有时,依赖于某些Node.js惯例的代码模式在npm上运行良好,但是在Facebook崩溃了,反之亦然。

自React 16以来,我们改变了我们的方法。相反,我们现在为每个环境编译一个捆绑包(包括(script)标签,npm和Facebook内部代码库)。因此,即使是针对npm的CommonJS代码也会提前被编译为分开开发和生产捆绑包。

这意味着当React源代码说if (__DEV__)时,我们实际上为每个包生成了两个捆绑包。一个已经预编译了__DEV__ = true,另一个预编译了__DEV__ = false。npm上每个包的入口点“决定”要导出哪个包。
举个例子:

if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react.production.min.js');
} else {
module.exports = require('./cjs/react.development.js');
}

而且这是捆绑器将'development'或'production'插入字符串的唯一位置,这也是我们的压缩器将摆脱仅用于开发require的位置。

react.production.min.js 和 react.development.js都没有任何process.env.NODE_ENV 查验。这很棒,因为当实际在Node.js上运行时,访问process.env会有点慢。提前在两种模式下编译捆绑包还可以让我们更加一致地优化文件大小,无论您使用哪种捆绑器或压缩器。这就是它的工作原理!

我希望有一种更优秀的方式来做到这一点而不依赖于惯例,但我们现在就这样了。如果模式在所有JavaScript环境中都是一等的概念,并且如果浏览器有某种方式表明某些代码在开发模式下运行当它们其实不应该被运行的时候,那么它将是极好的。

另一方面,一个项目中的惯例如何能够在整个生态系统中传播是很有趣的。在2010年 EXPRESS_ENV 成为 NODE_ENV并在2013年扩展到前端。也许这个解决方案并不完美,但对于每个项目,采用它的成本要低于说服其他人做不同的事情的成本。这教授了关于采用自上而下和自下而上的宝贵经验。理解这种动态的运行方式可以区分成功的标准化尝试和失败。

分离开发模式和生产模式是一种非常有用的技巧。我建议在您的库和应用程序代码中使用它,用于那些在生产环境中执行开销太大,但在开发中执行却很有价值(而且常常很关键!)的检查。对于任何强大的特性,都有一些方法会误用它。

原文:https://overreacted.io/
译者:前端技术小哥 

 

链接: https://www.fly63.com/article/detial/4595

js设计模式之单例模式,javascript如何将一个对象设计成单例

单例模式是我们开发中一个非常典型的设计模式,js单例模式要保证全局只生成唯一实例,提供一个单一的访问入口,单例的对象不同于静态类,我们可以延迟单例对象的初始化,通常这种情况发生在我们需要等待加载创建单例的依赖。

前端设计模式:从js原始模式开始,去理解Js工厂模式和构造函数模式

工厂模式下的对象我们不能识别它的类型,由于typeof返回的都是object类型,不知道它是那个对象的实例。另外每次造人时都要创建一个独立的person的对象,会造成代码臃肿的情况。

JavaScript设计模式_js实现建造者模式

建造者模式:是将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。工厂类模式提供的是创建单个类的模式,而建造者模式则是将各种产品集中起来进行管理,用来创建复合对象

html和xhtml,DOCTYPE和DTD,标准模式和兼容模式

主要涉及知识点: HTML与XHTML,HTML与XHTML的区别,DOCTYPE与DTD的概念,DTD的分类以及DOCTYPE的声明方式,标准模式(Standard Mode)和兼容模式(Quircks Mode),标准模式(Standard Mode)和兼容模式(Quircks Mode)的区别

前端四种设计模式_JS常见的4种模式

JavaScript中常见的四种设计模式:工厂模式、单例模式、沙箱模式、发布者订阅模式

javascript 策略模式_理解js中的策略模式

javascript 策略模式的定义是:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。 策略模式利用组合,委托等技术和思想,有效的避免很多if条件语句,策略模式提供了开放-封闭原则,使代码更容易理解和扩展, 策略模式中的代码可以复用。

javascript观察者模式_深入理解js中的观察者模式

javascript观察者模式又叫发布订阅模式,观察者模式的好处:js观察者模式支持简单的广播通信,自动通知所有已经订阅过的对象。存在一种动态关联,增加了灵活性。目标对象与观察者之间的抽象耦合关系能够单独扩展以及重用。

Vue中如何使用方法、计算属性或观察者

熟悉 Vue 的都知道 方法methods、计算属性computed、观察者watcher 在 Vue 中有着非常重要的作用,有些时候我们实现一个功能的时候可以使用它们中任何一个都是可以的

我最喜欢的 JavaScript 设计模式

我觉得聊一下我爱用的 JavaScript 设计模式应该很有意思。我是一步一步才定下来的,经过一段时间从各种来源吸收和适应直到达到一个能提供我所需的灵活性的模式。让我给你看看概览,然后再来看它是怎么形成的

Flutter 设计模式 - 简单工厂

在围绕设计模式的话题中,工厂这个词频繁出现,从 简单工厂 模式到 工厂方法 模式,再到 抽象工厂 模式。工厂名称含义是制造产品的工业场所,应用在面向对象中,顺理成章地成为了比较典型的创建型模式

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!