最近文章

为什么React的render方法会执行两次?

最近在写React组件的时候,发现了一个奇怪的问题,就是组件的render方法总是执行两次。import Card from './src/Card'; import FriendProfile from './src/FriendProfile'; export default function App() { console.log('render App'); return (
标签:

webpack初步理解

Webpack 是一个非常强大且有趣的工具,它被视为当今许多 Web 开发人员用来构建其应用程序的基础组件。然而,许多人会认为使用它是一个相当大的挑战,主要是因为它的复杂性。 webpack bundle过程图表modules模块是一个文件的升级版本。一个模块,一旦创建并且构建之后,除了包含原始的文件代码之外,还有一些其他有意义的信息:比如模块使用的加载器,模块的依赖项,模块的导出(如果
标签:

CSS——Flexbox布局另类理解

Flexbox是一种非常强大的布局模式。当我们真正理解它们如何工作时,我们可以构建自动响应的动态布局,并且根据需要重新排列元素。看下面一个例子:新建一个dynamic-layout.html文件<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta nam
标签:

CSS——理解布局算法

大家在学习CSS的时候,是不是常常有一些灵感浮现的时刻,或者是看到别人只是用简单的CSS就能做出如此炫酷的效果。其实我也有类似的经历,我那时候总是关注折写出来的CSS的属性和值有哪些情况,它们分别的效果是什么?比如说z-index:10肯定在z-index:5之上; justify-content:center就是在flex布局的时候,让元素框居中;我琢磨着如果能够将CSS的属性和值学习得越多,那
标签:

CSS-Layout Ribbon

最近在设计页面的时候,用到了css布局中的一个Ribbon布局,样子如下图所示:刚开始打算用这个样式作为文章的标题,后来暂时没有使用,但是在学习这个布局的时候,积累了很多css的知识,今天在此记录一下,方便以后查找温习。首先,我贴出这部分代码的dom结构:<!DOCTYPE html> <html> <head> <meta charset="UT
标签:

javascript——嵌套函数作用域

javascript作用域我们知道,js中有三个作用域,分别是block scope(块作用域),function scope(函数作用域),globle scope(全局作用域);今天我们来看看什么是嵌套函数作用域嵌套函数作用域let a = 10; function outer() { let b = 20; function inner() { let c = 30; conso
标签:

JUnit——Ignore测试

有时我们的代码在运行测试用例时没有完全准备好。结果,测试用例失败。 @Ignore 注释在这种情况下会有所帮助。带有@Ignore 注解的测试方法将不会被执行。如果一个测试类被@Ignore注解,那么它的任何测试方法都不会被执行。创建测试用例类创建一个 java 测试类,例如 TestJunit.java。将测试方法 testPrintMessage() 或 testSalutationMessa
标签:

JUnit——Suite测试

Test Suite用于捆绑一些单元测试用例并将它们一起运行。在 JUnit 中,@RunWith 和@Suite 注释都用于运行suite test。本文以具有两个测试类 TestJunit1 和 TestJunit2 的示例为例,它们使用测试套件一起运行。创建一个要测试的 java 类,比如 MessageUtil.java/* * This class prints the given m
标签:

JUnit执行过程

本文解释了JUnit中方法的执行过程,定义了方法调用的顺序。下面举例说明 JUnit 测试 API 方法的执行过程。创建一个名为 ExecutionProcedureJunit.java 的 java 类文件import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.
标签:

Assertion使用

Assertion所有断言都在 Assert 类中public class Assert extends java.lang.Object此类提供一组断言方法,可用于编写测试。仅记录失败的断言。 Assert类的一些重要方法如下Sr.No.Methods & Description1void assertEquals(boolean expected, boolean actual)Che
标签:

Promise.all()使用场景

Promise.all()使用场景在上一节我们介绍了Promise.all()典型的两类使用场景,今天我们再看看另一类:创建人为的延迟Promise.all() 的一个不太常见的场景是当你想延迟某件发生太快的事情。 这更有可能发生在浏览器而不是服务器端,您有时需要在用户操作和响应之间稍作延迟。 例如,您可能希望在从服务器获取数据时显示加载指示器,但如果响应太快,用户可能看不到加载微调器,因此不知道
标签:

JUnit第一个测试

在本文,我们将看到一个完整的 JUnit 测试示例,它使用 POJO 类、业务逻辑类和一个测试类,它将由测试运行器运行。提醒一句,看完之后回答一个小问题,本文中的被测试类是哪一个?创建 EmployeeDetails.java,它是一个POJO类public class EmployeeDetails { private String name; private double mon
标签:

JUnit常用API

JUnit 中最重要的包是 junit.framework,它包含了所有的核心类。一些重要的类如下:Sr.No.Class NameFunctionality1AssertA set of assert methods.2TestCaseA test case defines the fixture to run multiple tests.3TestResultA TestResult col
标签:

JUnit测试@DataJpaTest与@ComponentScan冲突

今天在写JUnit测试的时候,发现一个bug,首先将测试代码贴出来:@DataJpaTest @RunWith(SpringRunner.class) //@ContextConfiguration(classes= RepositoryTest.class) public class SubscriptionRepositoryTest{ }SpringBoot的启动类如下:@SpringBoo
标签:

JUnit测试框架介绍

在上一篇文章中我们对JUnit进行了初步学习,今天我们继续学习JUnit这个测试框架。JUnit 是一个回归测试框架,开发人员用来在 Java 中实现单元测试,加快编程速度,提高代码质量。 JUnit Framework 可以很容易地与以下任何一个集成EclipseAntMavenJUnit 测试框架的特点JUnit 测试框架提供以下重要特性FixturesTest suitesTest runn
标签:

JUnit初步理解

JUnit 是 Java 编程语言的单元测试框架。 JUnit 在测试驱动开发的开发中一直很重要,它是一组统称为 xUnit 的单元测试框架之一,它起源于 JUnit。本文介绍了在使用 Java 时在项目单元测试中使用 JUnit。学习完成本系列文章后,您将获得有关使用 JUnit 测试框架的足够知识,您可以将自己提升到新的水平。测试是检查应用程序功能以确保其按要求运行的过程。单元测试出现在开发人
标签:

JUnit基本使用

前两篇文章主要对JUnit做了一些简单的功能性的介绍,今天我们开始学习基本的使用。现在让我们有一个基本示例来演示使用 JUnit 的逐步过程。创建一个类创建一个要测试的 java 类,比如 MessageUtil.java/* * This class prints the given message on console. */ public class MessageUtil { p
标签:

JUnit如何测试Repository层

JUnit是一个优秀的java单元测试框架,今天我们用例子来说明如何使用JUnit来测试Repository层。我们先来看看被测试对象:@Repository public interface UserCreditRepository extends JpaRepository<UserCredit, Long> { Optional<UserCredit> fi
标签:

BabelJS - 概述

BabelJS - 概述BabelJS 是一个 JavaScript 转译器,它将新功能转译成旧标准。有了BabelJS,这些新功能可以在新旧浏览器上自由运行。澳大利亚开发人员 Sebastian McKenzie 创立了 BabelJS。为什么选择 BabelJS?JavaScript 是浏览器可以理解的语言。我们使用不同的浏览器来运行我们的应用程序——Chrome、Firefox、Intern
标签:

Babel初步认识

Babel初步认识BabelJS 是一个 JavaScript 转译器,它将新功能转译成旧标准。有了BabelJS,这些功能可以在新旧浏览器上兼容运行。Babeljs 以插件、预设、polyfill 等形式提供了广泛的功能。简而言之,Babeljs 是一个工具集,其中包含所有可用的工具,它可以帮助开发人员使用 ECMA Script 中可用的所有当前功能,而不必担心浏览器将如何支持它。首先,我们需
标签:

什么是控制反转(IOC)

什么是控制反转(IOC)控制反转 (IoC) 和依赖注入 (DI) 是用于解耦类依赖关系的编程模式。在本文中,我们将讨论 Spring Framework 的 IoC 和 DI。为简单起见,假设类 Vehicle 依赖于类 Engine,这意味着没有 Engine 的 Vehicle 没有意义。我们需要确保通过保持代码松散耦合来满足这种依赖关系。所以我们使用一个特殊的类来确保对象的创建以正确的顺序
标签:

在 Spring AOP 中使用 @Order 通知排序

在 Spring AOP 中使用 @Order 通知排序简介在本文中,您将了解在 Spring AOP 中使用 @Order 的多个 Advice 排序。通常,不止一条建议应用于单个匹配的连接点,并且您希望控制它们的执行顺序。Spring 允许您使用 org.springframework.core.Ordered 接口或@Order 注解设置优先级。通知优先规则在两个 @Order(n) 注释中
标签:

BabelJS - 使用 Babel 和 JSX

BabelJS - 使用 Babel 和 JSX在本文中,我们将了解如何使用 JSX 和 babel。在我们深入细节之前,让我们了解一下 JSX 是什么。什么是 JSX?JSX 是一个 JavaScript 代码,其中结合了 xml 语法。 JSX 标签具有标签名称、属性和子标签,使其看起来像 xml。React 使用 JSX 进行模板化,而不是常规的 JavaScript。没有必要使用它,但是,
标签:

Spring AOP 中的环绕通知 – @Around

Spring AOP 中的环绕通知 – @Around简介在本文中,您将了解 Spring AOP 中围绕匹配的 Joinpoint 执行运行的环绕建议。使用 @Around 注解声明环绕通知。您已经了解了上一篇文章中列出的 5 种通知类型,如下所示。Before advice – @BeforeAfter returning – @AfterReturni
标签:

在 Spring AOP 中的After advice - @After

在 Spring AOP 中的After advice - @After简介在本文中,您将了解 Spring AOP 中的 After 通知,它在匹配的 Joinpoint 退出执行后运行。After 通知是使用 @After 注释声明的。它也被称为 After finally 通知,因为无论是否成功退出或由于异常而中断,它都会被调用。您已经了解了上一篇文章中列出的 5 种建议类型,如下所示。Be
标签:

BabelJS - 使用 Babel 和 Webpack

BabelJS - 使用 Babel 和 WebpackWebpack 是一个模块打包器,它将所有具有依赖关系的模块——js、样式、图像等打包成静态资产 .js、.css、.jpg、.png 等。Webpack 带有预设,有助于编译成需要的形式。例如,有助于以 react 形式获得最终输出的 react 预设,有助于在 ES5 或 6 或 7 中编译代码的 es2015 或 env 预设等。我们在
标签:

BabelJS - Babel 预设

BabelJS - Babel 预设Babel presets是 babel-transpiler 的配置详细信息,告诉它以指定的模式进行转换。以下是我们将在本章中讨论的一些最受欢迎的预设:ES2015EnvReact我们需要使用具有我们希望转换代码的环境的预设。或者例如,es2015 preset 会将代码转换为 es5。带有值 env 的预设也将转换为 es5。它还具有附加功能,即选项。如果您
标签:

揭秘 webpack 的“导入”功能:使用动态参数

揭秘 webpack 的“导入”功能:使用动态参数在本文中,我们将学习揭开 webpack 的“导入”功能迷雾:使用动态参数。虽然是 webpack 的一个热门卖点功能,但是导入功能有很多隐藏的细节和特性,很多开发者可能都不知道。例如,导入函数可以接受动态表达式,能够实现一些众所周知的特性,比如延迟加载等。您可以将动态表达式视为不是原始字符串(例如 import('./path/to/file.j
标签:

After Throwing advice in Spring AOP – @AfterThrowing

After Throwing advice in Spring AOP – @AfterThrowing简介匹配的方法通过抛出异常完成/中止其执行后调用抛出通知之后。您已经知道,这种匹配方法的名称是 Joinpoint。您可以使用 @AfterThrowing 注释声明 After throwing advice。您已经了解了上一篇文章中列出的 5 种建议类型,如下所示。Before advice
标签:

在 Spring AOP 中返回通知后 – @AfterReturning

在 Spring AOP 中返回通知后 – @AfterReturning简介在匹配的方法完成执行并返回一个值后,返回通知被调用。您已经知道,这种匹配方法的名称是 Joinpoint。您可以使用 @AfterReturning 注释声明 After 返回通知。您已经了解了上一篇文章中列出的 5 种建议类型,如下所示。Before advice – @BeforeAfter returni
标签:

BabelJS - Babel CLI

BabelJS - Babel CLIBabelJS 带有一个内置的命令行界面,其中 JavaScript 代码可以使用的命令轻松编译为相应的 ECMA 脚本。我们将在本文中讨论这些命令的使用。首先,我们将为我们的项目安装 babel-cli。我们将使用 babeljs 来编译代码。为您的项目创建一个文件夹以使用 babel-cli。npm init为项目创建的 Package.json让我们运行
标签:

BabelJS - Babel Polyfill

BabelJS - Babel PolyfillBabel Polyfill 为 Web 浏览器添加了对功能的支持,这些功能不可用。 Babel 将代码从最近的 ecma 版本编译为我们想要的版本。它根据预设更改语法,但不能对使用的对象或方法做任何事情。为了向后兼容,我们必须为这些功能使用 polyfill。可以polyfilled的功能以下是在旧浏览器中使用时需要 polyfill 支持的功能列
标签:

在 Spring AOP 中组合和重用切入点表达式

在 Spring AOP 中组合和重用切入点表达式在本文中,您将学习在Spring AOP 中组合和重用多个 Pointcut 表达式。我们在上一篇文章中只讨论了它的一些知识。组合意味着使用两个或多个由逻辑运算符分隔的表达式 - &&、||和 !我已经包含了高级示例以及一个完整的示例,以使您对这个主题有很好的理解。1、组合切入点表达式您可以使用 AND – &&,
标签:

BabelJS - Babel 插件

BabelJS - Babel 插件BabelJS 是一个 javascript 编译器,它可以根据可用的预设和插件更改代码的语法。 babel编译的流程涉及到以下3个部分:parsingtransformingprinting给 babel 的代码原样返回,只是语法改变了。我们已经看到将预设添加到 .babelrc 文件中以将代码从 es6 编译到 es5,反之亦然。预设只不过是一组插件。如果在
标签:

Webpack 或 Browserify & Gulp:哪个更好?

Webpack 或 Browserify & Gulp:哪个更好?随着 Web 应用程序变得越来越复杂,使您的 Web 应用程序可扩展变得至关重要。过去编写ad-hot时候, JavaScript 和 jQuery 就足够了,而如今构建 Web 应用程序需要更高程度的纪律原则和正式的软件开发实践,例如:1、单元测试以确保对代码的修改不会破坏现有功能。2、Linting 以确保一致的编码风格
标签:

BabelJS - 将 ES8 功能转换为 ES5

BabelJS - 将 ES8 功能转换为 ES5字符串填充是添加到 javascript 中的新 ES8 功能。我们将研究一个简单的示例,它将使用 babel 将字符串填充转换为 ES5。String Padding字符串填充根据指定的长度从左侧添加另一个字符串。字符串填充的语法如下所示:str.padStart(length, string); str.padEnd(length, strin
标签:

BabelJS - 将 ES7 功能转换为 ES5

BabelJS - 将 ES7 功能转换为 ES5在本文中,我们将学习如何将 ES7 功能转换为 ES5。ECMA Script 7 添加了以下新功能Async-AwaitExponentiation OperatorArray.prototype.includes()我们将使用 babeljs 将它们编译为 ES5。根据您的项目要求,也可以在任何 ecma 版本中编译代码,即 ES7 到 ES6
标签:

BabelJS - 将 ES6 模块转换为 ES5

BabelJS - 将 ES6 模块转换为 ES5在本文中,我们将看到如何使用 Babel 将 ES6 模块转换为 ES5。Modules考虑需要重用部分 JavaScript 代码的场景。 ES6 用模块的概念来拯救你。一个模块只不过是写在一个文件中的一段 JavaScript 代码。模块中的函数或变量不可用,除非模块文件导出它们。简单来说,这些模块帮助您在模块中编写代码,并且只公开应该由代码的
标签:

BabelJS - 将 ES6 功能转换为 ES5

BabelJS - 将 ES6 功能转换为 ES5在本文以及接下来的文章中,我们将看到 ES6 中添加的特性。我们还将学习如何使用 BabelJS 将功能编译到 ES5。以下是我们将在本章中讨论的各种 ES6 特性Let + ConstArrow FunctionsClassesPromisesGeneratorsDestructuringIteratorsTemplate LiteralstEn

Spring AOP 中的切入点表达式

Spring AOP 中的切入点表达式简介在本文中,您将了解用于将通知与目标连接点匹配的切入点表达式。 Spring AOP 使用 AspectJ 切入点表达式。您学习过切入点定义。 Spring AOP 仅支持方法执行连接点,因此您可以将切入点视为匹配 Spring bean 上方法的执行。切入点:它是一个确定连接点的谓词表达式,因此允许我们控制通知的执行。简单来说,切入点表达式就像一个正则表达
标签:

为什么React的render方法会执行两次?

更新于 2023.04.27 2分钟阅读 0 评论 15 推荐

    React笔记本

    作者: 静下心来
  1. 为什么React的render方法会执行两次? Page 1

最近在写React组件的时候,发现了一个奇怪的问题,就是组件的render方法总是执行两次。

import Card from './src/Card';
import FriendProfile from './src/FriendProfile';

export default function App() {
  console.log('render App');
  return (
    ....
  );
}

这段代码在执行的时候,render App总是会执行两次。

经过网上搜索,发现了原因是:

在运行App的时候使用了严格模式(strict mode)。如果不使用这样的运行模式,实际上是不会执行两次的。

同时需要记住的一点就是,React在执行两次渲染的过程中,也会执行React组件的生命周期函数,比如会先卸载组件,然后再加载组件,因此React的两个生命周期函数componentDidMount():以及componentWillUnmount(): 都会执行一次。

我们可以利用执行两次的机会,检查我们组件的函数是否包含副作用,因此可以起到减少bug的作用。

至于为什么要使用严格模式,官方的说法是,这种模式一般在我们开发过程中使用,产品部署以后就会忽略,至于开发阶段使用的原因是:

检测渲染阶段那些存在的不期望的副作用的函数,方便开发者找出bug

好了,这里记录一下遇到的小问题和解决方法。

webpack初步理解

Webpack 是一个非常强大且有趣的工具,它被视为当今许多 Web 开发人员用来构建其应用程序的基础组件。然而,许多人会认为使用它是一个相当大的挑战,主要是因为它的复杂性

 

webpack bundle过程图表

modules

模块是一个文件的升级版本。一个模块,一旦创建并且构建之后,除了包含原始的文件代码之外,还有一些其他有意义的信息:比如模块使用的加载器,模块的依赖项,模块的导出(如果存在的话)以及模块的hash值等等。

The  entry  object

Entry对象(也可以称为入口对象)记住一点:enrty对象中的每一项都是模块树中的根模块。模块树,因为根模块可能需要一些其他模块(又称为依赖项),这些模块(依赖项也是模块)可能也需要其他模块等等,因此您可以在更高的级别上理解如何构建这棵模块树。所有这些模块树都存储联结在一个 ModuleGraph(模块图) 中。

另外一点我们需要知道的是:webpack 是建立在许多插件之上的。尽管bundle过程已经构建起来,但是可以嵌入很多方法来添加自定义逻辑。webpack的扩展通过hooks来实现。比如,你可以在模块图已经构建之后,或者为chunk生成新的assets 时或者模块将要构建前(加载器运行时、解析源代码时),添加一些自定义逻辑等等。hooks非常有趣,可以为许多与 webpack 定制相关的问题提供解决方案。大多数时候,hooks是根据它们的功能分组的,每一个插件都有明确定义的功能。例如,有一个插件负责处理 import() 函数(负责解析注释和参数)它被称为 ImportParserPlugin,它所做的只是在 AST 解析期间遇到 import() 调用时添加一个hook。

同时不足为奇的是,有几个负责处理entry对象的插件。有一个 EntryOptionPlugin,它实际上接受entry对象为参数并为入口对象中的每个项目(entry item)创建一个 EntryPlugin。这部分很重要:入口对象的每一项都将产生一棵模块树(所有这些模块树都是彼此分离的)。基本上,EntryPlugin 开启每一棵模块树的创建过程,每个模块树都会将信息添加到同一个地方,即 ModuleGraph。因此,我们会说 EntryPlugin 开启了这个复杂的处理过程。

入口对象插件处理

结合初始图片来看,我们可以知道,EntryPlugin 也是创建 EntryDependency 的地方。基于上图,让我们通过自己实现EntryOptionsPlugin来进一步了解EntryOptionsPlugin的重要性:

class CustomEntryOptionPlugin {
  // This is the standard way of creating plugins.
  // It's either this, or a simple function, but we're using this approach
  // in order to be on par with how most of the plugins are created.
  apply(compiler) {
    // Recall that hooks offer us the possibility to intervene in the
    // bundling process.
    // With the help of the `entryOption` hook, we're adding the logic
    // that will basically mean the start of the bundling process. As in,
    // the `entryObject` argument will hold the `entry` object from the
    // configuration file and we'll be using it to set up the creation of
    // module trees.
    compiler.hooks.entryOption.tap('CustomEntryOptionPlugin', entryObject => {
      // The `EntryOption` class will handle the creation of a module tree.
      const EntryOption = class {
        constructor (options) {
          this.options = options;
        };

        // Since this is still a plugin, we're abiding by the standard.
        apply(compiler) {
          // The `start` hook marks the start of the bundling process.
          // It will be called **after** `hooks.entryOption` is called.
          compiler.hooks.start('EntryOption', ({ createModuleTree }) => {
            // Creating new tree of modules, based on the configuration of this plugin.
            // The `options` contain the name of the entry(which essentially is the name of the chunk)
            // and the file name.
            // The `EntryDependency` encapsulates these options and also provides way to
            // create modules(because it maps to a `NormalModuleFactory`, which produces `NormalModule`s).
            // After calling `createModuleTree`, the source code of the file will be found,
            // then a module instance will be created and then webpack will get its AST, which 
            // will be further used in the bundling process.
            createModuleTree(new EntryDependency(this.options));
          });
        };
      };

      // For each item in the `entryObject` we're preparing
      // the creation of a module tree. Remember that each
      // module tree is independent of others.
			// The `entryObject` could be something like this: `{ a: './a.js' }`
      for (const name in entryObject) {
        const fileName = entryObject[name];
        // We're fundamentally saying: `ok webpack, when the bundling process starts,
        // be ready to create a module tree for this entry`.
        new EntryOption({ name, fileName }).apply(compiler);
      };
    });
  }
};

在本节的最后一部分,我们将稍微扩展介绍什么是依赖(Dependency),因为我们将在本文中进一步使用它。您现在可能想知道 EntryDependency 是什么以及为什么需要它。从我的角度来看,当创建新模块时,它可以归结为一种智能抽象。简单地说,依赖就是实际模块实例的准备阶段(初级阶段)。例如,甚至entry对象的项目从webpack 的视角来看也是依赖项,它们指明要创建模块实例的最低限度要求:它的路径(例如 ./a.js、./b.js)。没有依赖项就无法创建模块,因为依赖项包含模块的请求以及其他重要信息,即可以找到模块的源文件路径(例如'./a.js')。依赖项还指示如何构造该模块,它怎样使用一个模块工厂(module factory)来完成模块构建。模块工厂知道如何从原始状态(字符串源代码)转化到一些可由webpack使用的具体实体。EntryDependency 实际上是 ModuleDependency 的一种类型,这意味着它肯定会保持模块的请求,并且它指向的模块工厂是 NormalModuleFactory。然后,NormalModuleFactory 确切地知道要做什么才能从一条路径创建对 webpack 有意义的东西。另一种思考方式是,一个模块起初只是一个简单的路径(在入口对象中或导入语句的一部分中),然后它成为一个依赖项,最后成为一个模块。 这是一种可视化的方法:

因此,在创建模块树的根模块时,最开始会使用EntryDependency。

对于其他的模块,它们是其他类型的依赖项。例如,如果您使用import语句,如 import defaultFn from './a.js' ,那么将有一个HarmonyImportSideEffectDependency保存模块的请求(在本例中为 './a.js')并映射到NormalModuleFactory。因此,文件“a.js”将会是一个新模块,希望现在您可以理解依赖项所起的重要作用。它们本质上是指导 webpack 如何创建模块。我们将在本文后面揭示有关依赖项的更多信息。

快速回顾一下我们在本节中学到的内容:

1、对于 entry 对象中的每一项,都会有一个 EntryPlugin 实例,在该实例中创建了一个 EntryDependency。这个 EntryDependency 保存模块的请求(即文件的路径),并且还提供了一种通过映射到模块工厂(即 NormalModuleFactory)来充分利用该请求。模块工厂知道如何仅从文件路径创建对 webpack 有用的实体。

2、再次,依赖关系对于创建模块至关重要,因为它包含重要信息,例如模块的请求以及如何处理该请求。有几种类型的依赖关系,并非所有类型的依赖都对创建新模块有用。从每个 EntryPlugin 实例出发并在新创建的 EntryDependency 的帮助下,将创建一个模块树。模块树建立在模块及其依赖关系之上,这些依赖项也是模块,也可以有依赖关系。

现在,让我们通过了解有关 ModuleGraph 的更多信息来继续我们的学习之旅。

 

理解 ModuleGraph

ModuleGraph 是一种跟踪已经构建好的模块的方法。它特别依靠依赖关系,因为它们提供了连接 2 个不同模块的方法。例如:

// a.js
import defaultBFn from './b.js';
// b.js
export default function () { console.log('Hello from B!'); }

这里我们有 2 个文件,所以有 2 个模块。文件 a 需要文件 b 中的某些内容,因此在 a 中存在由 import 语句建立的依赖关系。就 ModuleGraph 而言,依赖项定义了一种连接 2 个模块的方式。甚至上一节中的 EntryDependency 也连接了 2 个模块:图的根模块,我们将其称为空模块,以及与入口文件关联的模块。上面的代码片段可以可视化如下:

 

阐明简单模块(即 NormalModule 实例)和属于 ModuleGraph 的模块之间的区别很重要。ModuleGraph 的节点称为 ModuleGraphModule(这里是不是应该称呼:模块图模块),它只是一个修饰的 NormalModule 实例。ModuleGraph 借助具有以下签名的映射跟踪这些装饰模块:

 Map<Module, ModuleGraphModule>

这些方面是有必要提及的,因为如果只有 NormalModule 实例,那么您对它们无能为力,它们不知道如何相互通信。ModuleGraph 赋予这些裸模块意义,通过在上述映射的帮助下互连它们,该映射为每个 NormalModule 分配一个 ModuleGraphModule。这将在构建 ModuleGraph 部分很有意义,我们将使用 ModuleGraph 及其内部映射来遍历模块图。。我们将属于 ModuleGraph 的模块简称为模块,因为区别仅包含几个附加属性。

对于属于 ModuleGraph 的节点,是有少有的几个定义好的东西:传入连接和传出连接。Connection是 ModuleGraph 的另一个小实体,它包含有意义的信息,例如:源模块、目标模块和连接前面提到的 2 个模块的依赖项。具体来说,根据上图,新建了一个连接:

// This is based on the diagram and the snippet from above.
Connection: {
	originModule: A,
	destinationModule: B,
	dependency: ImportDependency
}

并且上面的连接将被添加到 A.outgoingConnections 集合和 B.incomingConnections 集合中。

这些是 ModuleGraph 的基本概念。正如上一节中已经提到的,从entry创建的所有模块树都会将有意义的信息输出到同一个地方,即 ModuleGraph。这是因为所有这些模块树最终都会与空模块(ModuleGraph 的根模块)相连。通过 EntryDependency 和从入口文件创建的模块建立与空模块的连接。这是我对 ModuleGraph 的看法:

如您所见,空模块与从entry对象中的项目生成的每个模块树的根模块都有一个连接。图中的每条边代表 2 个模块之间的连接,每个连接都包含有关源节点、目标节点和依赖项的信息(这非正式地回答了为什么这 2 个模块连接的问题?)。

现在我们对 ModuleGraph 有点熟悉了,让我们看看它是如何构建的。

构建 ModuleGraph

正如我们在上一节中看到的,ModuleGraph 以一个空模块开始,其直接后代是模块树的根模块,这些模块树是从entry对象项构建的。因此,为了了解 ModuleGraph 是如何构建的,我们将研究单个模块树的构建过程。

第一个模块的创建

我们将从一个非常简单的entry对象开始:

entry: {
	a: './a.js',
}

根据第一部分所说的,在某些时候,我们最终会得到一个请求为“./a.js”的EntryDependency。这个 EntryDependency 提供了一种从该请求创建有意义的东西的方法,因为它映射到一个模块工厂,即 NormalModuleFactory。这是我们在第一部分中没提到的地方。

该过程的下一步是 NormalModuleFactory 起作用的地方。NormalModuleFactory,如果它成功完成它的任务,将创建一个 NormalModule为了确保没有不确定性,NormalModule 只是文件源代码的反序列化版本,它只不过是一个原始字符串。原始字符串不会带来太多价值,因此 webpack 不能用它做太多事情。NormalModule 还将源代码存储为字符串,但同时,它还将包含其他有意义的信息和功能,例如:应用到它的加载器,构建模块的逻辑,生成运行时代码的逻辑,它的哈希值等等。换句话说,从 webpack 的角度来看,NormalModule 是一个简单原始文件的有用版本

为了让 NormalModuleFactory 输出一个 NormalModule,它必须经过一些步骤。创建模块后还有一些事情要做,例如构建模块并处理其依赖项(如果有的话)。

这又是我们一直在关注的图表,现在专注于构建 ModuleGraph 部分:

NormalModuleFactory 通过调用它的 create 方法开始它的魔力。然后,process过程开始。这里是请求(文件的路径)被解析的地方,以及该类型文件的加载器。请注意,在此步骤中,将仅确定加载程序的文件路径,尚未调用加载程序。

模块构建处理

解析完所有必要的文件路径后,NormalModule创建完成。但是,在这一点上,该模块不是很有价值。构建模块后会出现很多相关信息。 NormalModule 的构建过程包括以下几个步骤:

  • 首先,将在原始源代码上调用加载器;如果有多个加载器,那么一个加载器的输出可能是另一个加载器的输入(在配置文件中提供加载器的顺序很重要);
  • 其次,通过所有加载器运行后的结果字符串将用 acorn(一个 JavaScript 解析器)解析,从而产生给定文件的 AST;
  • 最后分析AST;分析是必要的,因为在这个阶段会确定当前模块的依赖关系(例如其他模块),webpack 可以检测到它的神奇功能(例如 require.context、module.hot)等;AST 分析发生在 JavascriptParser 中,如果您单击链接,您应该会看到那里处理了很多案例;这部分过程是最重要的部分,因为捆绑过程中接下来的很多事情都取决于这部分;

通过生成的 AST 发现依赖关系

一种思考处理过程的方法,无需过多详细介绍,如下所示:

其中 moduleInstance 是指从 index.js 文件创建的 NormalModule。红色的 dep 指的是从第一个 import 语句创建的依赖项,蓝色的 dep 指的是第二个 import 语句。这只是查看事物的一种简化方式。实际上,如前所述,依赖项是在获得 AST 之后添加的。

现在已经检查了 AST,是时候继续构建我们在本节开头谈到的模块树的过程了。下一步是处理在上一步中找到的依赖项。如果我们按照上图,index 模块有两个依赖,它们也是模块,即 math.js 和 utils.js。但在依赖项成为实际模块之前,我们只有index模块,其 module.dependencies 有 2 个值,其中包含模块请求(文件路径)、导入说明符(例如 sum、greet)等信息。为了将它们变成模块,我们需要使用这些依赖关系映射到的 ModuleFactory 并重复上述相同的步骤(重复在本节开头的图中用虚线箭头表示)。在处理完当前模块的依赖关系之后,这些依赖关系可能也有依赖关系,并且这个过程一直持续到没有更多的依赖关系为止。这就是模块树的构建方式,当然还要确保正确设置父模块和子模块之间的连接。

根据我们到目前为止所获得的知识,我们自己实际试验 ModuleGraph 将是一个很好的练习。为此,让我们看看一种实现自定义插件的方法,该插件将允许我们遍历 ModuleGraph。这是描述模块如何相互依赖的图表:

为了确保图中的所有内容都可以理解,a.js 文件导入 b.js 文件,该文件同时导入 b1.js 和 c.js,然后 c.js 导入 c1.j 和 d.js,最后,d .js 导入 d1.js。最后,ROOT 指的是空模块,它是 ModuleGraph 的根。入口选项仅包含一个值 a.js:

// webpack.config.js
const config = {
  entry: path.resolve(__dirname, './src/a.js'),
	/* ... */
};

现在让我们看看我们的自定义插件是什么样子的:

// The way we're adding logic to the existing webpack hooks
// is by using the `tap` method, which has this signature:
// `tap(string, callback)`
// where `string` is mainly for debugging purposes, indicating
// the source where the custom logic has been added from.
// The `callback`'s argument depend on the hook on which we're adding custom functionality.

class UnderstandingModuleGraphPlugin {
  apply(compiler) {
    const className = this.constructor.name;
    // Onto the `compilation` object: it is where most of the *state* of
    // the bundling process is kept. It contains information such as the module graph,
    // the chunk graph, the created chunks, the created modules, the generated assets
    // and much more.
    compiler.hooks.compilation.tap(className, (compilation) => {
      // The `finishModules` is called after *all* the modules(including
      // their dependencies and the dependencies' dependencies and so forth)
      // have been built.
      compilation.hooks.finishModules.tap(className, (modules) => {
        // `modules` is the set which contains all the built modules.
        // These are simple `NormalModule` instances. Once again, a `NormalModule`
        // is produced by the `NormalModuleFactory`.
        // console.log(modules);

        // Retrieving the **module map**(Map<Module, ModuleGraphModule>).
        // It contains all the information we need in order to traverse the graph.
        const {
          moduleGraph: { _moduleMap: moduleMap },
        } = compilation;

        // Let's traverse the module graph in a DFS fashion.
        const dfs = () => {
          // Recall that the root module of the `ModuleGraph` is the
          // *null module*.
          const root = null;

          const visited = new Map();

          const traverse = (crtNode) => {
            if (visited.get(crtNode)) {
              return;
            }
            visited.set(crtNode, true);

            console.log(
              crtNode?.resource ? path.basename(crtNode?.resource) : 'ROOT'
            );

            // Getting the associated `ModuleGraphModule`, which only has some extra
            // properties besides a `NormalModule` that we can use to traverse the graph further.
            const correspondingGraphModule = moduleMap.get(crtNode);

            // A `Connection`'s `originModule` is the where the arrow starts
            // and a `Connection`'s `module` is there the arrow ends.
            // So, the `module` of a `Connection` is a child node.
            // Here you can find more about the graph's connection: https://github.com/webpack/webpack/blob/main/lib/ModuleGraphConnection.js#L53.
            // `correspondingGraphModule.outgoingConnections` is either a Set or undefined(in case the node has no children).
            // We're using `new Set` because a module can be reference the same module through multiple connections.
            // For instance, an `import foo from 'file.js'` will result in 2 connections: one for a simple import
            // and one for the `foo` default specifier. This is an implementation detail which you shouldn't worry about.
            const children = new Set(
              Array.from(
                correspondingGraphModule.outgoingConnections || [],
                (c) => c.module
              )
            );
            for (const c of children) {
              traverse(c);
            }
          };

          // Starting the traversal.
          traverse(root);
        };

        dfs();
      });
    });
  }
}

根据模块层次结构,运行 build 命令后,我们应该得到以下输出:

a.js
b.js
b1.js
c.js
c1.js
d.js
d1.js

现在已经构建了 ModuleGraph,希望您已经掌握了它,是时候了解接下来会发生什么了。根据主图,下一步将是创建块,所以让我们开始吧。但在此之前,有必要澄清一些重要的概念,例如 Chunk、ChunkGroup 和 EntryPoint。

 

澄清 Chunk,ChunkGroup,EntryPoint

现在我们对什么是模块有了一些了解,我们将在此基础上解释本节标题中提到的概念。为了再次快速解释什么是模块,只要知道模块是文件的升级版本就足够了。一个模块,一旦创建和构建,除了原始源代码之外,还包含许多有意义的信息,例如:使用的加载器、它的依赖项、它的导出(如果有的话)、它的哈希等等。

一个chunk封装一个或多个模块。乍一看,可能会认为entry文件的数量(一个entry文件=入口对象的一项)与生成的块的数量成正比。这个陈述部分正确,因为entry对象可能只有一个项目但是chunks的数量可能大于一个。确实,对于每个entry item,在 dist 目录中都会有一个相应的块,但是可以隐式创建其他块,例如在使用 import() 函数时。但是不管是怎么创建的,每个chunk都会在dist目录下有一个对应的文件。我们将在构建 ChunkGraph 部分对此进行展开说明,我们将阐明哪些模块属于一个块,哪些不属于?

一个 ChunkGroup 包含一个或多个chunk。一个 ChunkGroup可以是另一个 ChunkGroup的父级或子级。例如,当使用动态导入时,对于每个使用的 import() 函数,都会创建一个 ChunkGroup,其父级将是一个现有的 ChunkGroup,它包含使用 import() 函数的文件(即模块)。在构建 ChunkGraph 部分可以看到这一事实的可视化。

EntryPoint 是一种 ChunkGroup,它是为entry对象中的每个项目创建的。chunk属于entrypoint这一事实对渲染过程有影响,因为我们将在以后的文章中更清楚地说明这一点。

鉴于我们对这些概念比较熟悉,让我们继续了解 ChunkGraph

构建ChunkGraph

回想一下,到目前为止,我们所拥有的只是一个 ModuleGraph,我们在上一节中讨论过。但是,ModuleGraph 只是bundling过程的必要部分。必须利用它才能使代码拆分等功能成为可能。

在bundling过程的这一点上,对于来自entry对象的每个项目,都会有一个entrypoint。由于它是 ChunkGroup 的一种,因此它至少会包含一个 chunk。所以,如果 entry 对象有 3 个 item,就会有 3 个 EntryPoint 实例,每个实例都有一个 chunk,也叫 entrypoint chunk,名字就是 entry item key 的值。与入口文件关联的模块称为入口模块,它们中的每一个都将属于它们的 entrypoint chunk。它们很重要,因为它们是 ChunkGraph 构建过程的起点。请注意,一个块可以有多个入口模块:

// webpack.config.js
entry: {
  foo: ['./a.js', './b.js'],
}

在上面的示例中,将有一个名为 foo 的块(项目的键)将有 2 个入口模块:一个与 a.js 文件关联,另一个与 b.js 文件关联。当然,该块将属于基于entry item创建的 EntryPoint 实例。

在详细介绍之前,让我们举一个例子,我们将在此基础上讨论构建过程:

entry: {
    foo: [path.join(__dirname, 'src', 'a.js'), path.join(__dirname, 'src', 'a1.js')],
    bar: path.join(__dirname, 'src', 'c.js'),
}

此示例将包含前面提到的内容:ChunkGroups(以及因此动态导入)、chunks和entrypoint的父子关系。

ChunkGraph 以递归方式构建。它首先将所有入口模块添加到队列中。然后,当一个入口模块被处理时,这意味着它的依赖项(也是模块)将被检查,并且每个依赖项也将被添加到队列中。这不断重复,直到队列变空。这部分过程是访问模块的地方。然而,这只是第一部分。回想一下,ChunkGroups 可以是其他 ChunkGroups 的父/子。这些连接在第二部分中得到解决。例如,如前所述,动态导入(即 import() 函数)将产生一个新的子 ChunkGroup。用 webpack 的说法,import() 表达式定义了一个异步的依赖块。从我的角度来看,它被称为块,因为首先想到的是包含其他对象的东西。在 import('./foo.js').then(module => ...) 的情况下,很明显我们的意图是异步加载一些东西,很明显,为了使用模块变量,在实际模块可用之前,必须解析 foo(包括 foo 本身)的所有依赖项(即模块)。我们将在以后的文章中彻底讨论 import() 函数的工作原理以及它的特殊性(例如魔术注释和其他选项)。

如果这激发了您的好奇心,那么这里就是在 AST 分析期间创建块的位置。

总结 ChunkGraph 构建过程的源代码可以在这里找到。

现在,让我们看一下根据我们上面的配置创建的 ChunkGraph 的图表:

该图说明了 ChunkGraph 的一个非常简化的版本,但它应该足以突出显示结果Chunk和 ChunkGroup 之间的关系。我们可以看到 4 个块,所以会有 4 个输出文件。 foo 块将有 4 个模块,其中 2 个是入口模块。bar chunk 将只有 1 个入口模块,而另一个可以被视为普通模块。我们还可以注意到,每个 import() 表达式都会产生一个新的 ChunkGroup(其父级是 bar EntryPoint),其中涉及一个新的 chunk。

 

生成文件的内容是根据 ChunkGraph 确定的,所以这就是为什么它对整个打包过程非常重要。我们将在下一节简要讨论块资产(即生成的文件)。

在探索我们将使用 ChunkGraph 的实际示例之前,重要的是要提及它的一些特殊性。与 ModuleGraph 类似,属于 ChunkGraph 的节点称为 ChunkGraphChunk(读作属于 ChunkGraph 的块),它只是一个装饰块,这意味着它作为一些额外的属性,例如作为块的一部分的模块,块的入口模块等。就像 ModuleGraph 一样,ChunkGraph 借助具有以下签名的映射使用附加属性跟踪这些块:WeakMap<Chunk, ChunkGraphChunk>。与 ModuleGraph 的 map 相比,由 ChunkGraph 维护的这个 map 不包含有关 chunk 之间连接的信息。相反,所有必要的信息(例如它所属的 ChunkGroups)都保存在块本身中。请记住,Chunk在 ChunkGroups 中组合在一起,并且这些ChunkGroup之间可以存在父子关系(正如我们在上图中看到的那样)。模块不是这样,因为模块可以相互依赖,但是没有严格的父模块概念。

现在让我们尝试在自定义插件中使用 ChunkGraph,以便更好地理解它。请注意,我们正在考虑的这个例子是上图描述的例子:

const path = require('path');

// We're printing this way in order to highlight the parent-child
// relationships between `ChunkGroup`s.
const printWithLeftPadding = (message, paddingLength) => console.log(message.padStart(message.length + paddingLength));

class UnderstandingChunkGraphPlugin {
  apply (compiler) {
    const className = this.constructor.name;
    compiler.hooks.compilation.tap(className, compilation => {
      // The `afterChunks` hook is called after the `ChunkGraph` has been built.
      compilation.hooks.afterChunks.tap(className, chunks => {
        // `chunks` is a set of all created chunks. The chunks are added into
        // this set based on the order in which they are created.
        // console.log(chunks);
        
        // As we've said earlier in the article, the `compilation` object
        // contains the state of the bundling process. Here we can also find
        // all the `ChunkGroup`s(including the `Entrypoint` instances) that have been created.
        // console.log(compilation.chunkGroups);
        
        // An `EntryPoint` is a type of `ChunkGroup` which is created for each
        // item in the `entry` object. In our current example, there are 2.
        // So, in order to traverse the `ChunkGraph`, we will have to start
        // from the `EntryPoints`, which are stored in the `compilation` object.
        // More about the `entrypoints` map(<string, Entrypoint>): https://github.com/webpack/webpack/blob/main/lib/Compilation.js#L956-L957
        const { entrypoints } = compilation;
        
        // More about the `chunkMap`(<Chunk, ChunkGraphChunk>): https://github.com/webpack/webpack/blob/main/lib/ChunkGraph.js#L226-L227
        const { chunkGraph: { _chunks: chunkMap } } = compilation;
        
        const printChunkGroupsInformation = (chunkGroup, paddingLength) => {
          printWithLeftPadding(`Current ChunkGroup's name: ${chunkGroup.name};`, paddingLength);
          printWithLeftPadding(`Is current ChunkGroup an EntryPoint? - ${chunkGroup.constructor.name === 'Entrypoint'}`, paddingLength);
          
          // `chunkGroup.chunks` - a `ChunkGroup` can contain one or mode chunks.
          const allModulesInChunkGroup = chunkGroup.chunks
            .flatMap(c => {
              // Using the information stored in the `ChunkGraph`
              // in order to get the modules contained by a single chunk.
              const associatedGraphChunk = chunkMap.get(c);
              
              // This includes the *entry modules* as well.
              // Using the spread operator because `.modules` is a Set in this case.
              return [...associatedGraphChunk.modules];
            })
            // The resource of a module is an absolute path and
            // we're only interested in the file name associated with
            // our module.
            .map(module => path.basename(module.resource));
          printWithLeftPadding(`The modules that belong to this chunk group: ${allModulesInChunkGroup.join(', ')}`, paddingLength);
          
          console.log('\n');
          
          // A `ChunkGroup` can have children `ChunkGroup`s.
          [...chunkGroup._children].forEach(childChunkGroup => printChunkGroupsInformation(childChunkGroup, paddingLength + 3));
        };
        
		// Traversing the `ChunkGraph` in a DFS manner.
        for (const [entryPointName, entryPoint] of entrypoints) {
          printChunkGroupsInformation(entryPoint, 0);
        }
      });
    });
  }
}; 

这是您应该看到的输出:

Current ChunkGroup's name: foo;
Is current ChunkGroup an EntryPoint? - true
The modules that belong to this chunk group: a.js, b.js, a1.js, b1.js

Current ChunkGroup's name: bar;
Is current ChunkGroup an EntryPoint? - true
The modules that belong to this chunk group: c.js, common.js


    Current ChunkGroup's name: c1;
    Is current ChunkGroup an EntryPoint? - false
    The modules that belong to this chunk group: c1.js

    Current ChunkGroup's name: c2;
    Is current ChunkGroup an EntryPoint? - false
    The modules that belong to this chunk group: c2.js

我们使用缩进来区分父子关系。我们还可以注意到输出与图表一致,因此我们可以确定遍历的正确性。

生成Chunk Assets

值得一提的是,生成的文件不仅仅是原始文件的复制粘贴版本,因为为了实现其功能,webpack 需要添加一些自定义代码,以使一切按预期工作。

这就引出了 webpack 如何知道要生成什么代码的问题。好吧,这一切都从最基本(也是最有用的)层开始:模块。一个模块可以导出成员,导入其他成员,使用动态导入,使用 webpack 特定的功能(例如 require.resolve)等。根据模块的源代码,webpack 可以确定生成哪些代码以实现所需的功能。这发现在 AST 分析期间开始,在此发现依赖项。尽管到目前为止我们一直在交替使用依赖项和模块,但事情在幕后有点复杂。

例如,一个简单的 import { aFunction } from './foo' 将产生 2 个依赖项(一个用于 import 语句本身,另一个用于说明符,即 aFunction),将从中创建单个模块。另一个例子是 import() 函数。正如前面部分中提到的,这将导致异步的依赖块,其中一个依赖项是 ImportDependency,它特定于动态导入。这些依赖关系是必不可少的,因为它们带有一些关于应该生成什么代码的提示。例如, ImportDependency 知道要告诉 webpack 什么才能异步获取导入的模块并使用其导出的成员。这些提示可以称为运行时要求。例如,如果模块导出了它的一些成员,就会有一些依赖(回想一下我们现在不是指模块),即 HarmonyExportSpecifierDependency,它将通知 webpack 它需要处理导出成员的逻辑。

总而言之,一个模块将附带其运行时要求,这取决于该模块在其源代码中使用的内容。块的运行时要求将是属于该块的所有模块的所有运行时要求的集合。现在 webpack 知道了一个块的所有需求,它将能够正确地生成运行时代码。这也称为渲染过程,我们将在专门的文章中详细讨论。现在,了解渲染过程严重依赖 ChunkGraph 就足够了,因为它包含ChunkGroup(即 ChunkGroup、EntryPoint),其中包含Chunk,其中包含Module,以细粒度的方式,包含有关将由 webpack 生成的运行时代码的信息和提示。

CSS——Flexbox布局另类理解

发布于 2022.12.25 45分钟阅读 0 评论 5 推荐

    作者:

Flexbox是一种非常强大的布局模式。当我们真正理解它们如何工作时,我们可以构建自动响应的动态布局,并且根据需要重新排列元素。

看下面一个例子:

新建一个dynamic-layout.html文件

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <link rel="stylesheet" href="dynamic-layout.css"/>
  <style>
	  form {
		display: flex;
		align-items: flex-end;
		flex-wrap: wrap;
		gap: 8px;
	  }
	  .name {
		flex-grow: 1;
		flex-basis: 120px;
	  }
	  .email {
		flex-grow: 3;
		flex-basis: 170px;
	  }
	  button {
		flex-grow: 1;
		flex-basis: 70px;
	  }
  </style>
  <title>dynamic-layout</title>
</head>
<body>
	<form>
	  <label class="name" for="name-field">
		Name:
		<input id="name-field" />
	  </label>
	  <label class="email" for="email-field">
		Email:
		<input id="email-field" type="email" />
	  </label>
	  <button>
		Submit
	  </button>
	</form>
</body>
</html>

新建一个dynamic-layout.css文件

*, *:before, *:after {
    box-sizing: border-box;
    line-height: 1.5;
    line-height: calc(1em + 0.5rem);
    -webkit-font-smoothing: antialiased;
    font-family: Wotfard;
}

/* Cosmetic styles */
form {
  padding: 8px;
  border: 1px solid hsl(0deg 0% 50%);
}

label {
  font-weight: 500;
}
input {
  display: block;
  width: 100%;
  height: 2.5rem;
  margin-top: 4px;
}
button {
  height: 2.5rem;
}

如果将页面的宽度由大到小不断调整,会依次出现以上四种不同的布局;也许大部分人说,这有啥难的,不就是媒体查询吗?可是你注意到没,在CSS代码中一点媒体查询的痕迹也没有,完全是这个demo的作者出于对Flexbox布局模式的深入理解,今天我们就来学习对Flexbox布局算法的深入理解,这样你也能学会不用媒体查询也能动态布局啦。

在这篇文章中,我想提高您对Flexbox布局的心智模型。通过了解这些属性中的每一个,我们将建立对 Flexbox 算法如何工作的感觉。无论您是 CSS 初学者,还是已经使用 Flexbox 多年,我敢打赌您会学到很多东西!

Flexbox简介

CSS由许多不同的布局算法组成,官方称为“布局模式”。每种布局模式在CSS 中有自己的子语言。默认布局模式是 Flow 布局,但我们可以通过更改父容器上的 display 属性来选择使用 Flexbox:

看下面的例子:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <link rel="stylesheet" href="default-flexbox.css"/>
  <style>
	.wrapper{
		
	}
	.wrapper-item {
		background:hsl(210deg,8%,50%);
		font-size: 1rem;
		margin: 2px;
		padding: 10px 16px;
		transform: none;
		transform-origin: 50% 50% 0px;
		color:#fff;
	}
  </style>
  <title>default-to-flexbox</title>
</head>
<body>
	<div class="wrapper">
		<div class="wrapper-item">hello</div>
		<div class="wrapper-item">to</div>
		<div class="wrapper-item">the</div>
		<div class="wrapper-item">world</div>
	</div>
</body>
</html>

当我们将display设置为 flex 时,我们创建了一个“flex 格式化上下文”。这意味着,默认情况下,所有子项都将根据 Flexbox 布局算法进行定位。

每个布局算法都是为解决特定问题而设计的。默认的“Flow”布局旨在创建数字文档;它本质上是 Microsoft Word 布局算法。标题和段落作为块垂直堆叠,而文本、链接和图像等内容则不显眼地位于这些块中。

那么,Flexbox解决了什么问题呢?Flexbox 就是将一组项目排列成一行或一列,并让我们对这些项目的分布对齐方式进行大量控制。顾名思义,Flexbox 就是关于灵活性的。我们可以控制项目是增长还是收缩额外空间的分配方式等等。

它过时了吗? 

你可能想知道:既然 CSS Grid 在现代浏览器中得到了很好的支持,那么 Flexbox 是不是已经过时了?

CSS Grid 是一种美妙的布局模式,但它解决的问题与 Flexbox 不同。我们应该学习这两种布局模式,并使用正确的工具来完成工作。

当涉及到在垂直或水平列表中排列项目的动态、流畅的 UI 时,Flexbox 仍然占据主导地位。

老实说,作为一个同时熟悉 CSS Grid 和 Flexbox 的人,我仍然发现自己经常接触 Flexbox!

Flex direction

如前所述,Flexbox 就是控制行或列中元素的分布。默认情况下,项目将并排堆叠成一行,但我们可以使用 flex-direction 属性翻转到一列:

使用 flex-direction: row,主轴从左到右水平运行。当我们翻转到 flex-direction: column 时,主轴从上到下垂直运行。

在 Flexbox 中,一切都基于主轴。该算法不关心垂直/水平,甚至行/列。所有的规则都是围绕这个主轴和垂直运行的交叉轴构建的。

这很酷。当我们学习了 Flexbox 的规则后,我们可以从水平布局无缝切换到垂直布局。所有的规则都是围绕这个主轴和垂直运行的交叉轴构建的。

默认情况下,子项目将根据以下 2 条规则定位:

  1. 主轴:子项目将在容器的开头聚集在一起。
  2. 交叉轴:子项目会伸展开来填满整个容器。

看看这个规则的可视化展示

在 Flexbox 中,我们决定主轴是水平运行还是垂直运行。这是所有 Flexbox布局计算都与之关联的基础。

Alignment

我们可以使用 justify-content 属性更改子项目沿主轴的分布方式:

说到主轴,我们一般不会从对齐单个子项目的角度来考虑。相反,这完全取决于群体(全体子项目)的分布。我们可以将所有子项目集中在一个特定的位置(使用 flex-start、center 和 flex-end),或者我们可以将它们分开(使用 space-between、space-around 和 space-evenly)。看下面的图片:

对于交叉轴,情况有些不同。我们使用 align-items 属性:

我们看看不同属性的情况:

很有趣……对于 align-items,我们有一些与 justify-content 相同的选项,但没有完美的重叠。

它们为什么不共享相同的选项?我们很快就会揭开这个谜团,但首先,我需要再分享一个对齐属性:align-self。

justify-content align-items 不同,align-self 应用于子元素,而不是容器。它允许我们更改特定子项沿交叉轴的对齐方式

大家可以去尝试一下,这个属性的值和align-items的值是一模一样,一个是对所有元素,一个是对特定子元素。

为什么单个子元素没有justify-self要理解为什么,我们需要更深入地研究 Flexbox 算法。

Content 与 items

根据我们目前所学的内容,Flexbox 似乎看起来非常随意。为什么是 justify-contentalign-items,而不是 justify-itemsalign-content

同时,为什么有一个align-self,而没有一个justify-self??

这些问题触及了关于 Flexbox 最重要却最容易被误解的事情之一。为了帮助解释,我想用一个比喻。

在 Flexbox 中,项目沿主轴分布。默认情况下,它们并排排列整齐。我可以画一条水平直线,把所有的子项目都串起来,就像烤肉串一样?:

但是,交叉轴不同。一条直线只会与其中一个子项目相交。

它不像烤肉串,更像是一群鸡尾酒香肠?

这里有一个显着的区别。使用鸡尾酒香肠,每个项目都可以沿着它的棒移动而不会干扰任何其他项目。

相比之下,我们的主轴串在每个兄弟项目上,单个项目不能沿着它的棒移动而不撞到它的兄弟项目!

这就是是主轴/交叉轴之间的根本区别。当我们谈论交叉轴对齐时,每个项目都可以做任何它想做的事情。但是,在主轴上,我们只能考虑如何分配组。

这就是为什么没有justify-self 的原因。假设中间项目设置 justify-self: flex-start 意味着什么?前面那里已经有另一块(另一个子项目)了!

考虑到所有这些情境,我们对我们一直在谈论的所有 4 个术语给出一个正确的定义:

  • justify — 沿主轴定位某物。
  • align — 沿交叉轴定位某物。
  • content——一组可以分布的“东西”。
  • items — 可以单独定位的单个项目。

因此:我们有 justify-content 来控制组沿主轴的分布,我们有 align-items 来沿交叉轴单独定位每个项目。这是我们使用 Flexbox 管理布局的两个主要属性。

没有 justify-items 的原因与没有 justify-self 的原因相同;当谈到主轴时,我们必须将项目视为一个组,作为可以分布的内容。

align-content呢?实际上,这确实存在于 Flexbox 中!我们稍后会在讨论 flex-wrap 属性时介绍它。

算法的输入

我们倾向于将 CSS 语言视为属性的集合,但我认为这是错误的思维模式。正如我们上一篇所见,width 属性的行为因所使用的布局模式而异!

相反,我喜欢将 CSS 视为布局模式的集合。每种布局模式都是一种算法,可以实现或重新定义每个 CSS 属性。我们为我们的 CSS 声明(键/值对)提供一个算法,算法决定如何使用它们。

换句话说,我们编写的 CSS 是这些算法的输入,就像传递给函数的参数一样。如果我们想真正对 CSS 感到舒服自在,仅仅学习属性是不够的;我们必须了解算法如何使用这些属性。

Growing and shrinking

我们已经看到 Flexbox 算法具有一些内置的灵活性,具有推荐的大小。但要真正了解 Flexbox 的特点,我们需要讨论 3 个属性:flex-grow、flex-shrink 和 flex-basis

flex-basis

我承认:很长一段时间,我都没有真正理解 flex-basis 到底是怎么回事。 🤗

简单来说:在 Flex 行中,flex-basis 与宽度起相同的作用。在 Flex 列中,flex-basis 与高度起相同的作用。

正如我们所了解的,Flexbox 中的所有内容都与主轴/交叉轴挂钩。例如,justify-content 会沿主轴分布子项,无论主轴水平还是垂直,它的工作方式都完全相同。

但是,宽度和高度不遵循此规则!宽度将始终影响水平尺寸。当我们将 flex-direction 从行翻转到列时,它不会突然变成高度。

因此,Flexbox 作者创建了一个通用的“大小”属性,称为 flex-basis。它就像宽度或高度,但与其他一切一样与主轴挂钩。它允许我们在主轴方向上设置元素的推荐大小,无论是水平还是垂直。

看下面的例子:

就像我们看到的宽度一样,flex-basis 更像是一个建议,而不是硬性约束(黄色区域在flex-basis为250时,实际宽度没有250,而是一个弹性的建议值)。在某个时刻,没有足够的空间让所有元素都按指定的大小放置,因此它们必须妥协以避免溢出。

不完全一样

通常,我们可以在 Flex 行中交替使用 width 和 flex-basis,但也有一些例外。例如,width 属性对图像等替换元素的影响不同于 flex-basis。此外,宽度可以将项目缩小到其最小尺寸以下,而 flex-basis 不能。

flex-grow

默认情况下,Flex 上下文中的元素将沿着主轴缩小到它们的最小舒适尺寸。这通常会产生额外的空间。我们可以指定如何使用 flex-grow 属性消耗该空间:看下图

flex-grow 的默认值为 0,这意味着增长是可选的。如果我们想让孩子吞噬容器中的任何额外空间,我们需要明确地告诉它。

如果多个子项目设置 flex-grow 怎么办?在这种情况下,额外的空间在子项目之间分配,根据他们的 flex-grow 值按比例分配。

flex-shrink

到目前为止,在我们看到的大多数示例中,我们都有额外的空间可以使用。但是,如果我们的子项目对于他们的容器来说太大了怎么办?

有趣,对吧?两个项目都会收缩,但它们会按比例收缩。第一个子项目的宽度始终是第二个子项目的 2 倍。

友情提示,flex-basis 与 width 的作用相同。我们将使用 flex-basis,因为它是常规的,但如果我们使用 width,我们会得到完全相同的结果!

flex-basis 和 width 设置元素的推荐大小。 Flexbox 算法可能会将元素缩小到低于此所需大小,但默认情况下,它们将始终一起缩放,并保持两个元素之间的比例。

现在,如果我们不希望我们的元素按比例缩小怎么办?这就是 flex-shrink 属性的用武之地。

好吧,所以:我们有两个子项目,每个子项目的推荐大小都是 250px。容器需要至少 500 像素宽才能容纳这些处于推荐大小的子项目。

假设我们将容器缩小到 400 像素。好吧,我们不能把 500 像素的内容塞进 400 像素的包里!我们有 100px 的赤字。我们的元素将需要放弃 100px 的总尺寸,以便它们适合。

flex-shrink 属性让我们决定如何支付余额。

就像 flex-grow 一样,它是一个比率。默认情况下,两个子项目都有 flex-shrink: 1,所以每个子项目支付 ½ 的余额。他们每个人放弃 50px,他们的实际大小从 250px 缩小到 200px。

现在,假设我们将第一个子节点设置为 flex-shrink: 3:

我们的总赤字为 100px。通常,每个子项目会支付 ½,但因为我们已经修改了 flex-shrink,所以第一个元素最终支付 ¾ (75px),第二个元素支付 ¼ (25px)。

请注意,绝对值并不重要,重要的是比率。如果两个子项目都有 flex-shrink: 1,每个子项目将支付总赤字的 ½。如果两个子项目都被调高到 flex-shrink: 1000,每个子项目将支付总赤字的 1000/2000。无论哪种方式,结果都是一样的。

收缩和比例

在我们一直在查看的示例中,两个 Flex 子项具有相同的推荐大小 (250px)。在弄清楚如何缩小它们时,我们可以专门使用 flex-shrink 来计算它。

不过,正如我们之前看到的,缩小算法也会尝试保持兄弟姐妹之间的比例。如果第一个孩子是第二个孩子的 2 倍,它会收缩得更厉害。

因此,完整的计算涉及查看每个子项的相对 flex-shrink 及其相对大小。

不久前,我对 flex-shrink 有了一个顿悟:我们可以将其视为 flex-grow 的“逆”。它们是同一枚硬币的两面:

  • flex-grow 控制当项目小于容器时如何分配额外空间。
  • flex-shrink 控制当项目大于容器时如何删除空间。

这意味着一次只能激活其中一个属性。如果有额外的空间,则 flex-shrink 无效,因为项目不需要收缩。如果子项目们对于他们的容器来说太大了, flex-grow 就没有效果,因为没有额外的空间可以分配。

我喜欢将其视为两个独立的领域。你要么在地球上,要么在颠倒的地方?每个世界都有自己的规则。

Preventing shrinking

有时,我们不希望我们的一些 Flex 子项收缩。

我一直在使用 SVG 图标和形状注意到这一点。让我们看一个简化的例子:

当容器变窄时,我们的两个圆圈会被压扁成椭圆形。如果我们希望它们保持循环怎么办?

我们可以通过设置 flex-shrink: 0 来做到这一点:

当我们将 flex-shrink 设置为 0 时,我们实际上完全“选择退出”收缩过程。 Flexbox 算法会将 flex-basis(或宽度)视为硬性最小限制。

看下面的例子:

新建一个shrink-prevent.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <link rel="stylesheet" href="shrink-prevent.css"/>
  <style>
	/* This is the key property: */
	  .item.ball {
		flex-shrink: 0;
	  }
  </style>
  <title>shrink-prevent-flexbox</title>
</head>
<body>
	<div class="wrapper">
	  <div class="item ball"></div>
	  <div class="item stretch"></div>
	  <div class="item ball"></div>
	</div>
</body>
</html>

新建一个shrink-prevent.css

body {
  background: hsl(210deg, 30%, 12%);
  color: hsl(0deg 0% 100%);
}
.wrapper {
  display: flex;
  gap: 8px;
}
.item {
  height: 32px;
  border: 2px solid hsl(210deg 8% 50%);
  background: hsl(210deg 15% 20%);
}
.item.stretch {
  /*
    Because this item is empty, it
    has a default hypothetical width
     of 0px. This isn't realistic,
     though; in a typical case,
     there would be content in here!
     And so we set a width of 300px
     to simulate it containing stuff.
  */
  width: 300px;
  flex-grow: 1;
  border-radius: 16px;
}
.item.ball {
  width: 32px;
  /*
    NOTE: We could prevent the circle
    from squishing into an oval by
    setting border-radius: 16px
    instead. I'm using percentages
    because it makes for a better
    demo. But even with a pixel-based
    radius, we still need
    flex-shrink: 0 to prevent the
    item from shrinking (give it a
    shot and see the difference!).
  */
  border-radius: 50%;
}

相信通过这个例子大家能理解怎么阻止收缩啦!

The minimum size gotcha

我们还需要在这里讨论一件事,这非常重要。这可能是整篇文章中最有用的东西!

假设我们正在为电子商务商店构建一个流畅的搜索表单:

当容器收缩到一定程度以下时,内容就会溢出!

但为什么?? flex-shrink 的默认值为 1,我们没有删除它,所以搜索输入应该可以根据需要缩小!为什么它拒绝收缩?

事情是这样的:除了推荐的大小,Flexbox 算法还关心另一个重要的大小:最小大小。

Flexbox 算法拒绝将子项目缩小到其最小尺寸以下。无论我们将 flex-shrink 调到多高,内容都会溢出而不是进一步缩小!

文本输入的默认最小尺寸为 170px-200px(因浏览器而异)。这就是我们在上面遇到的限制。

在其他情况下,限制因素可能是元素的内容。例如,尝试调整此容器的大小:

对于包含文本的元素,最小宽度是最长牢不可破的字符串的长度。

好消息是:我们可以使用 min-width 属性重新定义最小尺寸。

通过直接在 Flex 子元素上设置 min-width: 0px,我们告诉 Flexbox 算法覆盖“内置”最小宽度。因为我们将它设置为 0px,元素可以根据需要缩小。同样的技巧可以在具有 min-height 属性的 Flex 列中使用(尽管问题似乎并不经常出现)。

提醒一句:非必要不要修改这个属性的值,因为可能造成访问性问题。

Gaps

近年来最大的 Flexbox 中改进之一是 gap 属性:

gap 允许我们沿着主轴在每个 Flex 子元素之间创建空间。这对于导航标题之类的东西非常有用:

gap 是 Fl​​exbox 语言的一个相对较新的补充,但自 2021 年初以来,它已在所有现代浏览器中实现。

Auto margins

我想分享另一个与间距相关的技巧。它在 Flexbox 的早期就已经存在,但它相对晦涩难懂,当我第一次发现它时,它让我大吃一惊。

margin 属性用于在特定元素周围添加空间。在某些布局模式中,例如 Flow 和 Positioned,它甚至可以用于使元素居中,margin: auto。

Flexbox 中的Auto margin更有趣:

早些时候,我们看到了 flex-grow 属性如何吞噬任何额外的空间,并将其应用于子项目。自动边距会吞噬额外的空间,并将其应用于元素的边距。它使我们能够精确控制在何处分配额外空间。

常见的标题布局在一侧具有徽标,在另一侧具有一些导航链接。以下是我们如何使用自动边距构建此布局:

新建auto-margin.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <link rel="stylesheet" href="auto-margin.css"/>
  <style>
	ul {
		display: flex;
		gap: 12px;
	  }
	  li.logo {
		margin-right: auto;
	  }
  </style>
  <title>auto-margin-flexbox</title>
</head>
<body>
	<nav>
	  <ul>
		<li class="logo">
		  <a href="/">
			Corpatech
		  </a>
		</li>
		<li>
		  <a href="">
			Mission
		  </a>
		</li>
		<li>
		  <a href="">
			Contact
		  </a>
		</li>
	  </ul>
	</nav>
</body>
</html>

新建auto-margin.css

body {
  padding: 0;
}
nav {
  padding: 12px;
  border-bottom: 1px dotted
    hsl(0deg 0% 0% / 0.2);
}
ul {
  list-style-type: none;
  align-items: baseline;
  padding: 0px;
  margin: 0;
}
ul a {
  color: inherit;
  text-decoration: none;
  font-size: 0.875rem;
}
.logo a {
  font-size: 1.125rem;
  font-weight: 500;
}

Corpatech 徽标是列表中的第一个列表项。通过给它 margin-right: auto,我们收集了所有额外的空间,并将其强制放在第一项和第二项之间。

我们有很多其他方法可以解决这个问题:我们可以将导航链接分组到它们自己的 Flex 容器中,或者我们可以使用 flex-grow 增加第一个列表项。但就个人而言,我喜欢自动边距解决方案。我们将额外的空间视为一种资源,并决定它应该放在哪里。

Wrapping

到目前为止,我们已经介绍了很多东西。我还想分享一件重要的事情。

到目前为止,我们所有的项目都并排放置在一行/一列中。 flex-wrap 属性允许我们改变它。

大多数时候,当我们在二维空间工作时,我们会想要使用 CSS Grid,但是 Flexbox + flex-wrap 肯定有它的用处!这个特殊的例子展示了“解构的煎饼”布局,其中 3 个项目在中型屏幕上堆叠成一个倒金字塔。

当我们设置 flex-wrap: wrap 时,项目不会缩小到低于其假设大小。至少,当换行到下一行/列是一个选项时不会!

可是等等!我们的烤肉串/鸡尾酒香肠比喻怎么样?

使用 flex-wrap: wrap,我们不再有一条可以串起每个项目的主轴线。实际上,每一行都充当了自己的迷你弹性容器。每行都有自己的串,而不是 1 个大串:

到目前为止,我们学到的所有规则在这个缩小的范围内继续适用。例如,justify-content 会将两块分布在每根棍子上。

但是嗯...既然我们有多行,align-items是如何工作的?交叉轴现在可以与多个项目相交!

花点时间考虑一下。当我们改变这个属性时,你认为会发生什么?一旦你有了答案(或至少是一个想法),看看它是否正确:

每一行都是它自己的迷你 Flexbox 环境。 align-items 将在环绕每一行的不可见框中向上或向下移动每个项目。

但是如果我们想自己对齐行呢?我们可以使用 align-content 属性来做到这一点:

总结一下这里发生的事情:

  • flex-wrap: wrap 给了我们两行东西。
  • 在每一行中,align-items 允许我们将每个子项目向上或向下滑动
  • 但是,缩小后,我们在单个 Flex 上下文中有这两行!交叉轴现在将与两行相交,而不是一行。因此,我们不能单独移动行,我们需要将它们作为一个组进行分配。
  • 使用我们上面的定义,我们处理的是内容,而不是项目。但我们还在谈论交叉轴!所以我们想要的属性是 align-content。

CSS——理解布局算法

发布于 2023.01.30 9分钟阅读 0 评论 5 推荐

    作者:

大家在学习CSS的时候,是不是常常有一些灵感浮现的时刻,或者是看到别人只是用简单的CSS就能做出如此炫酷的效果。其实我也有类似的经历,我那时候总是关注折写出来的CSS的属性和值有哪些情况,它们分别的效果是什么?比如说z-index:10肯定在z-index:5之上; justify-content:center就是在flex布局的时候,让元素框居中;我琢磨着如果能够将CSS的属性和值学习得越多,那么就能够更加深刻的理解这一门语言。

随着时间的推移,我对CSS的关键认知有了变化:CSS不仅仅是一系列属性的集合,还是各种相互关联的布局算法的集合;每个算法都是一个复杂的系统,且有自身的规则和隐秘机制。

了解特定属性和值的作用是不够的;还应该理解布局算法是怎样工作的?这些算法怎样使用我们提供的属性来进行布局。

您也许也遇到过这样的情况,写了以前多次写过的CSS属性,但是却得到了不是预期输出的布局效果,这样的不安会让我们觉得特别沮丧。因此CSS会让我们心理上觉得不一致和不稳定。同时又陷入深深的疑问:为什么相同的输入会产生不同的输出?

发生这种情况是因为这些属性和值工作在一个复杂的系统中,一些微妙的上下文变化改变了属性的行为方式。我们对CSS的认知模型不完整,导致产生了各种各样的惊吓。

当我开始深入理解CSS的布局算法的时候,一切变得豁然开朗。困扰多年的迷惑也云开雾散。我开始认识到CSS是一门强大语言,也开始享受编写的乐趣。

本文将用一个全新的视角来帮助理解CSS背后发生的事情;同时用这个视角来解惑那些惊人的疑团。

布局算法

什么是布局算法呢?您可能已经非常熟悉它们了,布局算法包括:

  • Flexbox
  • Positioned
  • Grid
  • Table
  • Flow

技术上它们也被成为布局模式,而不叫布局算法;我这里称呼布局算法是希望能够显得高大上一点,欢迎拍砖。

浏览器在渲染HTML页面的时候,每个元素都会使用一个主要的布局算法来计算元素的页面布局。我们可以使用特定的CSS声明来选择不同的布局算法。举个例子:position:absolute会将元素切换为Positioned布局;默认是Flow布局。

我们先看一个例子。有下面的CSS代码

.box {
  z-index: 10;
}

我们的首要工作是确定将使用哪种布局算法来渲染.box元素。根据提供的CSS,我们可以确定的是使用Flow布局算法。

Flow是网络上最通用的布局算法。它诞生于网络被视为一系列超连接文档,就像是世界上最大的档案库一样。它和微软的Word文字处理软件中使用的布局算法类似。

Flow也是非表格HTML元素的默认布局算法,除非你明确指定一种布局算法,否则就是Flow布局。

z-index属性用于控制堆叠顺序,如果它们发生重叠,哪一个显示在“顶部”。但是这里有一个事实是:在Flow布局中根本没有实现这个属性。Flow就是创建文档样式的布局,相信您没有看到过允许元素重叠的文字处理软件。

网络上有一种这样的说法:

如果不将position设置为“relative”或“absolute”之类的值,则不能使用 z-index,因为 z-index 属性取决于position属性。

以上说法不完全错,但是却有一点微妙的误解。更准确的说法是z-index属性没有在Flow布局中实现,如果想让这个属性起作用,那么需要选择一个实现了此属性的不同布局算法。

您可能觉得在此我有点小题大做,但是这个小误解会产生大困惑。举例如下:

新建一个html文件

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <link rel="stylesheet" href="layout.css"/>
  <style>
	  .row {
		display: flex;
		gap: 16px;
	  }
	  .raised.item {
		z-index: 2;
		background: hotpink;
	  }
  </style>
  <title>layout-algorithm</title>
</head>
<body>
	<ul class="row">
	  <li class="item"></li>
	  <li class="raised item"></li>
	  <li class="item"></li>
	</ul>
</body>
</html>

新建一个layout.css文件

.row {
  list-style-type: none;
  padding: 16px;
}
.item {
  width: 50px;
  height: 50px;
  border: 2px solid;
  border-radius: 4px;
  background: white;
}
.raised.item {
  margin-top: 8px;
  margin-right: -32px;
}

效果如图所示:

在这个例子中,我们使用Flexbox布局算法安排了3 个兄弟元素。中间的元素设置的z-index属性,可以看到能正常工作,显示在最顶端。如果将这个属性删掉,可以看到效果如下:

可以看到这个时候元素不再出现在顶层。

为什么会这样?我们没有在任何地方设置position: relative。按照前面所说,这个属性能够正常工作是因为Flexbox布局算法实现了z-index这个属性。当语言作者设计Flexbox布局算法时,他们决定实现 z-index 属性来控制堆叠顺序,就像在Positioned布局算法中一样。

这是关键的心智模式(不会觉得不安了吧)转变。CSS 属性本身是没有意义的(一定要结合具体的布局算法才有意义)。由布局算法来定义它们的作用,以及它们在计算中的工作方式。

需要明确的是,有一些 CSS 属性在所有布局算法中都是一样的。 color: red 无论如何都会产生红色文本。但是每个布局算法都可以覆盖任何属性的默认行为。许多属性没有任何默认行为。

有一个让人吃惊的例子,您通过width属性的实现方式因为不同算法而有所区别吗?看下面的例子:

新建一个width.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <link rel="stylesheet" href="width.css"/>
  <style>
	  .flex-wrapper {
		display: flex;
	  }
	  .item {
		width: 2000px;
	  }
  </style>
  <title>width-different-with-algorithm</title>
</head>
<body>
	<div class="item"></div>

	<div class="flex-wrapper">
	  <div class="item"></div>
	</div>
</body>
</html>

再新建一个width.css

.row {
  list-style-type: none;
  padding: 16px;
}
.item {
  height: 50px;
  border: 2px solid;
  border-radius: 4px;
  background: hotpink;
  margin: 16px;
}

效果如下:

第一个.item元素有一个宽度为2000px的属性,这个元素使用的是Flow布局算法来渲染,因此它实际占据了2000px的空间宽度;在Flow布局算法中,宽度是硬性规则,没有含糊。

第二个.item元素在一个flex容器中渲染,因此采用的布局算法是Flexbox;而在Flexbox布局算法中,宽度这是一个建议值,没有强制硬性规定。

Flexbox规范称宽度为推荐大小。它是元素在容器中的实际大小,而没有任何约束或者强制一定是多少。在一个足够大的容器中,这个大小可能是2000px;但是在一个比较小的容器中,元素的大小会收缩,以便适应容器的大小。

在这里,视角(不同的布局算法)非常重要。当涉及到Flexbox布局时,宽度并没有什么特别的警告。这是 Flexbox布局算法实现 width属性不同于 Flow布局算法而已。 

您可以这里理解这种机制:

我们编写的CSS属性是输入,就像传递给函数的参数一样。选择如何处理这些输入取决于布局算法。如果我们想深入理解CSS,就需要了解布局算法的工作原理;仅了解属性是远远不够的。

识别布局算法

CSS没有一个布局算法的属性,比如layout-mode;有几个属性可以调整所使用的布局算法,这让布局算法会变得非常棘手!

在某些情况下,应用于元素的 CSS 属性将选择特定的布局模式。看下面的代码

.help-widget {
  /* Uses Positioned layout, because of this declaration: */
  position: fixed;
  right: 0;
  bottom: 0;
}
.floated {
  /* Uses Float layout, because of this declaration: */
  float: left;
  margin-right: 32px;
}

在其他情况下,我们需要查看父级元素使用的CSS属性。例如:

<style>
  .row {
    display: flex;
  }
</style>
<ul class="row">
  <li class="item"></li>
  <li class="item"></li>
  <li class="item"></li>
</ul>

当我们应用display: flex时,我们实际上并没有为.row元素使用Flexbox布局算法;相反,我们说它的子元素应该使用 Flexbox 布局来定位。

用技术话语来说,display: flex创建了一个 flex格式化上下文。所有直接子级都将应用此上下文,这意味着子元素将使用 Flexbox 布局而不是默认的 Flow 布局。

display: flex也会把一个内联元素,比如 <span>变成块级元素,所以它确实对父元素的布局有一些影响。但它不会改变使用的布局算法。

布局算法变体

一些布局算法可以分割成多个变体。

例如,当我们使用Positioned布局时,它指的是几种不同的“定位方案”:

  • Relative
  • Absolute
  • Fixed
  • Sticky

每个变体有点像它自己的迷你布局算法,尽管它们确实共享一些共同点(例如,它们都可以使用 z-index 属性)。

同样,在 Flow 布局中,元素可以是块状或内联的。稍后我们将详细讨论 Flow 布局。

算法冲突

当多个布局算法应用在一个元素上会发生什么呢?这是一个有意思的话题。看个例子

<style>
  .row {
    display: flex;
  }
  .primary.item {
    position: absolute;
  }
</style>
<ul class="row">
  <li class="item"></li>
  <li class="primary item"></li>
  <li class="item"></li>
</ul>

所有三个列表项都是Flex 容器中的子项,因此它们应该根据 Flexbox 进行定位。但是那个中间的子项目通过设置 position: absolute 选择了 Positioned 布局。

据我了解,元素将使用主布局模式呈现。这有点像特异性:某些布局模式比其他布局模式具有更高的优先级。

我不知道确切的层次结构,但Positioned布局往往胜过一切。因此,在这个例子中,中间的子项目将使用 Positioned 布局,而不是 Flexbox。

因此,Flexbox 的计算结果就好像只有两个子项目,而不是三个。就 Flexbox 算法而言,那个中间子项目不存在;它对算法完全没有影响。

一般来说,冲突通常是非常明显的/有意的。但是,如果您发现某个元素的行为方式与您期望的不同,则值得尝试确定它使用的是哪种布局算法。答案可能会让你大吃一惊!

相对定位

这里有一个难题,如果每一个元素都使用单一布局算法渲染,那么我们应该如何理解相对定义呢?

具有 position: relative 的元素使用 Positioned 布局清晰呈现。它可以使用独有的定位布局属性,如 top 或 left。然而,它也可以参与 Flexbox / Grid 布局!

这个问题比较复杂,超出了本文讨论的范畴,但是可以快速理解一下

每个元素都在特定的格式化上下文中渲染,由布局算法决定是否参与其中。通常,Positioned 布局算法会忽略此类上下文,但它会排除相对定位的例外情况。

当在 Flexbox 上下文中渲染相对定位的元素时,Positioned 布局算法将允许它参与。一旦使用该上下文确定了它的大小/位置,它就会应用 Positioned 布局内容(例如,使用 top 或 left 调整位置)。

你可以把它想成有点像组合。 Positioned 布局算法将为相对定位的元素组合 Flexbox 布局算法。

内联魔法空间

我们首先来看一个经典的令人困惑的CSS问题,看看布局算法是怎么样帮助我们理解并解决这个问题的。

新建一个inline-magic-space.html的文件

<!DOCTYPE html>
<html lang="en" >
<head>
  <meta charset="UTF-8">
  <title>inline magic space</title>
  <link rel="stylesheet" href="normalize.min.css">
  <link rel="stylesheet" href="inline-magic-space.css">
</head>
<body>
<div class="photo-wrapper">
  <img
    class="cat-photo"
    alt="A basketful of cats"
    src="cats.jpg"
  />
</div>
</body>
</html>

新建一个inline-magic-space.css的文件

.photo-wrapper {
  border: 1px solid;
}

.cat-photo {
  width: 250px;
  max-width: 100%;
}

效果如下所示:

为什么图像下面有一些额外的空白呢?

如果试着调试一下,你就会发现,图像的宽度和高度都是250px,但是外面的包装器元素.photo-wrapper的高度实际上会多几个像素,为什么会这样呢?

如果您熟悉盒模型,您就会知道可以使用填充、边框和边距来分隔元素。您可能认为图像上有一些边距,或者容器上有一些填充?

在这个例子下,这些属性均没有作用。这就是为什么多年来,我一直私下将其称为“内联魔法空间”。它不是由通常的罪魁祸首引起的。

要了解这里发生了什么,我们必须更深入地研究 Flow 布局。

Flow布局算法

如前文所述,Flow布局是为文档设计的,类似于文字处理软件。

文档具有以下特点:

  • 单个字符组合成单词和句子。当没有足够的水平空间时,这些元素内联、并排和换行。
  • 段落被视为块,如标题或图像。块将垂直堆叠,一个在另一个之上,从上到下。

Flow布局就是基于这种结构。单个元素可以排列为行内元素(并排,如段落中的单词),或排列为块元素(从上到下堆叠的大块砖):

大多数 HTML 元素都带有合理的默认值。 <p> 和 <h1> 被认为是块级元素,而 <span> 和 <strong> 被认为是内联元素。

行内元素用于段落中间,而不是布局的一部分。例如,也许我们想在句子中间添加一个小图标。

为了确保行内元素不会对周围文本的易读性产生负面影响,添加了一些额外的垂直空间。

那么,回到我们的谜团:为什么我们的图像有一些额外的空间像素?因为图片默认是行内元素!

Flow 布局算法将此图像视为段落中的字符,并在下方添加一点空间以确保它不会令人不舒服地靠近(理论上的)下一行文本中的字符。

默认情况下,内联元素是“基线”对齐的。这意味着图像的底部将与文本所在的不可见水平线对齐。这就是图像下方有一些空间的原因——该空间用于下行,例如字母 j 和 p。

所以它不是边距、填充或边框……它是 Flow 布局应用于内联元素的固有空间位。

解决方案

有多种方法可以解决此问题。也许最简单的方法是在 Flow 布局中将此图像视为一个块:

.cat-photo {
    display: block;
}

或者,因为这种行为是 Flow 布局所独有的,我们可以转向不同的布局算法:

/*
  We flip its *parent* to Flex, so
  that the child will use Flexbox
  instead of Flow:
*/
.photo-wrapper {
    display: flex;
}

最后,我们还可以通过使用line-height将额外空间缩小为 0 来解决这个问题:

.photo-wrapper {
  line-height: 0;
}

此解决方案通过将其设置为 0 来删除所有额外的行间距。这会使多行文本完全不可读,但由于此容器不包含文本,因此这不是问题。

我建议使用前面两种解决方案中的一种。提出这个纯粹是因为它很有趣(并且因为它证明了问题是由于行间距造成的!)。

行高和可访问性

当我们谈论行高时:您知道“无样式”的 HTML 实际上被认为是不可访问的,因为行靠得太近了吗?当行间距不够大时,患有阅读障碍的人很难解析文本。

大多数浏览器的默认行高为 1.1 到 1.2,但根据 WCAG 指南,我们应该将正文的行高至少设置为 1.5。

好啦。本文主要为了给大家建立起一种对CSS的稳定和一致感,每次遇到输出和预期不一致的时候,思考一下当前的元素采用的是什么布局算法,然后再结合具体的属性进行分析,写得越多,积累得越多,并且思考得越多,您就会对CSS更加自信。

 

CSS-Layout Ribbon

更新于 2022.12.16 14分钟阅读 0 评论 5 推荐

    作者:

最近在设计页面的时候,用到了css布局中的一个Ribbon布局,样子如下图所示:

刚开始打算用这个样式作为文章的标题,后来暂时没有使用,但是在学习这个布局的时候,积累了很多css的知识,今天在此记录一下,方便以后查找温习。

首先,我贴出这部分代码的dom结构:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8"/>
    <title>Ribbons设计 - 实例</title>
    <link rel="stylesheet" type="text/css" href="./ribbon.css" />
</head>

<body>
	<div class="ribbon">
		<!-- The left side -->
		<div class="ribbon__side ribbon__side--l"></div>

		<!-- The left triangle displayed below the content -->
		<div class="ribbon__triangle ribbon__triangle--l"></div>

		<!-- The right triangle displayed below the content -->
		<div class="ribbon__triangle ribbon__triangle--r"></div>

		<!-- The right side -->
		<div class="ribbon__side ribbon__side--r"></div>

		<!-- The content -->
		<div class="ribbon__content"></div>
	</div>
</body>
</html>

然后再贴出对应的ribbon.css的代码

.ribbon {
    /* Center the content */
    align-items: center;
    display: flex;
    justify-content: center;

    /* Size */
    height: 2rem;

    /* Use to position the corners */
    position: relative;
	
	width: 6rem;
	margin : 6rem auto;
}

.ribbon__content {
    /* Background color */
    background-color: #9ca3af;
    z-index: 1;

    height: 100%;
    width: 100%;
}
.ribbon__side {
    bottom: -0.5rem;
    position: absolute;

    /* Displayed under the ribbon */
    z-index: 1;

    /* Background */
    border: 1rem solid #d1d5db;
}

.ribbon__side--l {
    /* Position */
    left: -1.5rem;
    border-color: #d1d5db #d1d5db #d1d5db transparent;
}

.ribbon__side--r {
    /* Position */
    right: -1.5rem;
    border-color: #d1d5db transparent #d1d5db #d1d5db;
}

.ribbon__triangle {
    position: absolute;
    top: 100%;

    border: 0.5rem solid transparent;
    border-bottom-width: 0;
    border-top-color: #1c1919;
	z-index:2;
}

.ribbon__triangle--l {
    border-right-width: 0;
    left: 0;
}

.ribbon__triangle--r {
    border-left-width: 0;
    right: 0;
}

注释部分解释了各部分代码在实现这个样式的时候起到的作用。我们来具体分析和研究一下:

从这个ribbon的dom结构可以看出,它有最外部的容易.ribbony以及内部的五个部分组成,分别是左右的支出去的两部分结构,内容区下面的三角形以及具体的内容区组成。

首先看看这两个支出去的部分是怎么实现的呢?

它们的代码如下:

.ribbon__side {
    bottom: -0.5rem;
    position: absolute;

    /* Displayed under the ribbon */
    z-index: 1;

    /* Background */
    border: 1rem solid #d1d5db;
}

.ribbon__side--l {
    /* Position */
    left: -1.5rem;
    border-color: #d1d5db #d1d5db #d1d5db transparent;
}

.ribbon__side--r {
    /* Position */
    right: -1.5rem;
    border-color: #d1d5db transparent #d1d5db #d1d5db;
}

我们知道父元素是相对定位(position:relative),而这里的两个元素采用了绝对定位,而共同点是bottom:0.5rem;然后一个向左,一个向右,分别都是-1.5rem;最后都设置border为1rem;并且border有三个方向有颜色,而一个方向透明,显然,如果是左边支出的那部分,显然是左边的border-color为透明的颜色,按照上右下左的原理,border-color: #d1d5db #d1d5db #d1d5db transparent;的最左边的颜色为透明。同样的到来,右边支出的部分应该是右方的颜色为透明,同理按照上右下左的原理,border-color: #d1d5db transparent #d1d5db #d1d5db;最右边的颜色为透明。

至于这里为什么这样设置border就会出现这样的形状呢?可以参考这篇文章(css绘制三角形原理-border)

简单说明一下:

.t2 {
  width: 50px;
  height: 50px;
  border: 2px solid orange;
}

我们使用border的时候一般只是小小的一段,大家可能会觉得border是有四个小矩形拼接而成;然而实际上呢?请看下面的例子:

.t2 {
  width: 100px;
  height: 100px;
  border: 40px solid;
  border-color: orange blue red green;
}

我们可以看到明显的border的四条边不是长方形,而是梯形,很像相框的四条边

我们再思考一下极端的情况,我将这个dom的width和height都设置为0;

.t2 {
  width: 0;
  height: 0;
  border: 40px solid;
  border-color: orange blue red green;
}

大家看到了没,实际上是不是就是四个不同颜色的小三角形拼接而成的,如果你想实现其他的效果,是不是只需要让其中的某些三角形的颜色透明就OK啦。

好啦,我上面的例子实际上是不是就是让左边的三角形透明就达到效果啦。

这里有个问题,为什么在ribbon的css中没有设置宽度和高度;其实是这样的,没有设置宽度和高度的时候,默认是auto,并且这个dom中也没有文字其他的,它的实际宽度和高度其实就是0;

如果大家感兴趣的话,可以在<div class="ribbon__side ribbon__side--l"></div>这个dom中添加一些内容,此时这个dom结构会变大,而图形也将不再是原来的结构,如图所示:

这时,如果将此dom结构的宽度和高度设置为0;

.ribbon__side {
    bottom: -0.5rem;
    position: absolute;

    /* Displayed under the ribbon */
    z-index: 1;

    /* Background */
    border: 1rem solid #d1d5db;
	width: 0;
	height:0; 
}

此时图形恢复原样,但是内容溢出;因此如果你想在这个里面显示一些内容,最好将容器设置大一点,这样内容才不会溢出。

有了这个使用border来构建三角形的基础知识,那么在这个ribbon结构中最下面的两个黑色的三角形,不用我解释您也能轻松理解啦。

如果有不理解的地方,可以参考这篇文章,也可以留言讨论学习。

 

javascript——嵌套函数作用域

更新于 2022.12.16 4分钟阅读 0 评论 5 推荐

    作者:

javascript作用域

我们知道,js中有三个作用域,分别是block scope(块作用域),function scope(函数作用域),globle scope(全局作用域);

今天我们来看看什么是嵌套函数作用域

嵌套函数作用域

let a = 10;
function outer() {
	let b = 20;
	function inner() {
		let c = 30;
		console.log(a,b,c);
	}
	inner();
}
outer();
// 10 20 30

我们来分析一下,在outer()函数内部定义了一个 inner()函数。在执行outer()函数的时候,会执行inner()函数,这个时候在打印a变量的时候,js引擎会在当前的函数作用域(inner函数)中查找是否有a变量,如果没有,引擎会继续在上级函数(outer函数)中查找,此时任然没有,那么就在全局作用域查找,此时有值,因此就打印出a的值,如果此时还没有值,就打印出undefined。同样的道理对于b和c都是如此。

 

闭包(closure)

我们先看看闭包的定义:

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function's scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.

这个定义记住关键一点,它是在运行时产生的,一定是在函数创建时刻才有闭包

function outer() {
	let counter = 0;
	function inner() {
		counter++;
		console.log(counter);
	}
	inner();
}
outer();
outer();
// 1 1 
function outer() {
	let counter = 0;
	function inner() {
		counter++;
		console.log(counter);
	}
	return inner;
}
let fn = outer();
fn();
fn();
// 1 2 

理解了上面两个例子的区别,您就理解了闭包,

1、闭包一定是在函数创建时候生成的。

2、闭包可以产生私有变量。

JUnit——Ignore测试

有时我们的代码在运行测试用例时没有完全准备好。结果,测试用例失败。 @Ignore 注释在这种情况下会有所帮助。

带有@Ignore 注解的测试方法将不会被执行。

如果一个测试类被@Ignore注解,那么它的任何测试方法都不会被执行。

创建测试用例类

创建一个 java 测试类,例如 TestJunit.java

将测试方法 testPrintMessage()testSalutationMessage() 添加到测试类。

在方法 testPrintMessage() 中添加一个注解 @Ignore

import org.junit.Test;
import org.junit.Ignore;
import static org.junit.Assert.assertEquals;

public class TestJunit {

   String message = "Robert";	
   MessageUtils messageUtil = new MessageUtils(message);
   
   @Ignore
   @Test
   public void testPrintMessage() {
      System.out.println("Inside testPrintMessage()");
      message = "Robert";
      assertEquals(message,messageUtil.printMessage());
   }

   @Test
   public void testSalutationMessage() {
      System.out.println("Inside testSalutationMessage()");
      message = "Hi!" + "Robert";
      assertEquals(message,messageUtil.salutationMessage());
   }
}

创建测试运行器类

import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;

public class TestRunner {
   public static void main(String[] args) {
      Result result = JUnitCore.runClasses(TestJunit.class);

      for (Failure failure : result.getFailures()) {
         System.out.println(failure.toString());
      }
		
      System.out.println(result.wasSuccessful());
   }
}  	

输出如下:

Inside testSalutationMessage()
Hi!Robert
true

可以看到在方法上加了@Ignore的测试用例没有执行

下面我们看看@Ignore注释用在一个类上的情况,更新测试用例类如下:

import org.junit.Test;
import org.junit.Ignore;
import static org.junit.Assert.assertEquals;

@Ignore
public class TestJunit {

   String message = "Robert";	
   MessageUtils messageUtil = new MessageUtils(message);
     
   @Test
   public void testPrintMessage() {
      System.out.println("Inside testPrintMessage()");
      message = "Robert";
      assertEquals(message,messageUtil.printMessage());
   }

   @Test
   public void testSalutationMessage() {
      System.out.println("Inside testSalutationMessage()");
      message = "Hi!" + "Robert";
      assertEquals(message,messageUtil.salutationMessage());
   }
	
}

再次运行一下,可以知道一个测试类也没有运行,只是输出了一个true

true

简单吧

 

JUnit——Suite测试

Test Suite用于捆绑一些单元测试用例并将它们一起运行。在 JUnit 中,@RunWith 和@Suite 注释都用于运行suite test。本文以具有两个测试类 TestJunit1 和 TestJunit2 的示例为例,它们使用测试套件一起运行。

创建一个要测试的 java 类,比如 MessageUtil.java

/*
 * This class prints the given message on console.
 */

public class MessageUtil {

   private String message;

   //Constructor
   //@param message to be printed
   public MessageUtil(String message){
      this.message = message; 
   }

   // prints the message
   public String printMessage(){
      System.out.println(message);
      return message;
   }   

   // add "Hi!" to the message
   public String salutationMessage(){
      message = "Hi!" + message;
      System.out.println(message);
      return message;
   }   
}  

创建一个名为 TestJunit1.java 的 java 类文件

import org.junit.Test;
import org.junit.Ignore;
import static org.junit.Assert.assertEquals;

public class TestJunit1 {

   String message = "Robert";	
   MessageUtil messageUtil = new MessageUtil(message);
   
   @Test
   public void testPrintMessage() {	
      System.out.println("Inside testPrintMessage()");    
      assertEquals(message, messageUtil.printMessage());     
   }
}

创建一个名为 TestJunit2.java 的 java 类文件

import org.junit.Test;
import org.junit.Ignore;
import static org.junit.Assert.assertEquals;

public class TestJunit2 {

   String message = "Robert";	
   MessageUtil messageUtil = new MessageUtil(message);
 
   @Test
   public void testSalutationMessage() {
      System.out.println("Inside testSalutationMessage()");
      message = "Hi!" + "Robert";
      assertEquals(message,messageUtil.salutationMessage());
   }
}

创建测试套件类

创建一个java类。 

在类上附加 @RunWith(Suite.class) 注解。 

使用 @Suite.SuiteClasses 注释添加对 JUnit 测试类的引用。

创建一个名为 TestSuite.java 的 java 类文件

import org.junit.runner.RunWith;
import org.junit.runners.Suite;

@RunWith(Suite.class)

@Suite.SuiteClasses({
   TestJunit1.class,
   TestJunit2.class
})

public class JunitTestSuite {   
}  	

创建测试运行器类

创建一个名为 TestRunner.java 的 java 类文件

import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;

public class TestRunner {
   public static void main(String[] args) {
      Result result = JUnitCore.runClasses(JunitTestSuite.class);

      for (Failure failure : result.getFailures()) {
         System.out.println(failure.toString());
      }
		
      System.out.println(result.wasSuccessful());
   }
}  

输出如下:

Inside testPrintMessage()
Robert
Inside testSalutationMessage()
Hi Robert
true

JUnit执行过程

本文解释了JUnit中方法的执行过程,定义了方法调用的顺序。下面举例说明 JUnit 测试 API 方法的执行过程。

创建一个名为 ExecutionProcedureJunit.java 的 java 类文件

import org.junit.After;
import org.junit.AfterClass;

import org.junit.Before;
import org.junit.BeforeClass;

import org.junit.Ignore;
import org.junit.Test;

public class ExecutionProcedureJunit {
	
   //execute only once, in the starting 
   @BeforeClass
   public static void beforeClass() {
      System.out.println("in before class");
   }

   //execute only once, in the end
   @AfterClass
   public static void  afterClass() {
      System.out.println("in after class");
   }

   //execute for each test, before executing test
   @Before
   public void before() {
      System.out.println("in before");
   }
	
   //execute for each test, after executing test
   @After
   public void after() {
      System.out.println("in after");
   }
	
   //test case 1
   @Test
   public void testCase1() {
      System.out.println("in test case 1");
   }

   //test case 2
   @Test
   public void testCase2() {
      System.out.println("in test case 2");
   }
}

接下来,创建一个名为 TestRunner.java 的 java 类文件

import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;

public class TestRunner {
   public static void main(String[] args) {
      Result result = JUnitCore.runClasses(ExecutionProcedureJunit.class);

      for (Failure failure : result.getFailures()) {
         System.out.println(failure.toString());
      }
		
      System.out.println(result.wasSuccessful());
   }
} 

输出如下:

in before class
in before
in test case 1
in after
in before
in test case 2
in after
in after class

查看上面的输出。执行过程如下

首先,beforeClass() 方法只执行一次。 

afterClass() 方法只执行一次。

before() 方法针对每个测试用例执行,但在执行测试用例之前。 

after() 方法针对每个测试用例执行,但在测试用例执行之后。 

在 before() 和 after() 之间,执行每个测试用例。

其实,通过JUnit的执行注解还是很容易理解这些执行过程的,最重要的是要学会怎么使用的问题。

Assertion使用

Assertion

所有断言都在 Assert 类中

public class Assert extends java.lang.Object

此类提供一组断言方法,可用于编写测试。仅记录失败的断言。 Assert类的一些重要方法如下

Sr.No.Methods & Description
1

void assertEquals(boolean expected, boolean actual)

Checks that two primitives/objects are equal.

2

void assertTrue(boolean condition)

Checks that a condition is true.

3

void assertFalse(boolean condition)

Checks that a condition is false.

4

void assertNotNull(Object object)

Checks that an object isn't null.

5

void assertNull(Object object)

Checks that an object is null.

6

void assertSame(object1, object2)

The assertSame() method tests if two object references point to the same object.

7

void assertNotSame(object1, object2)

The assertNotSame() method tests if two object references do not point to the same object.

8

void assertArrayEquals(expectedArray, resultArray);

The assertArrayEquals() method will test whether two arrays are equal to each other.

让我们在一个例子中使用上面提到的一些方法。创建一个名为 TestAssertions.java 的 java 类文件

import org.junit.Test;
import static org.junit.Assert.*;

public class TestAssertions {

   @Test
   public void testAssertions() {
      //test data
      String str1 = new String ("abc");
      String str2 = new String ("abc");
      String str3 = null;
      String str4 = "abc";
      String str5 = "abc";
		
      int val1 = 5;
      int val2 = 6;

      String[] expectedArray = {"one", "two", "three"};
      String[] resultArray =  {"one", "two", "three"};

      //Check that two objects are equal
      assertEquals(str1, str2);

      //Check that a condition is true
      assertTrue (val1 < val2);

      //Check that a condition is false
      assertFalse(val1 > val2);

      //Check that an object isn't null
      assertNotNull(str1);

      //Check that an object is null
      assertNull(str3);

      //Check if two object references point to the same object
      assertSame(str4,str5);

      //Check if two object references not point to the same object
      assertNotSame(str1,str3);

      //Check whether two arrays are equal to each other.
      assertArrayEquals(expectedArray, resultArray);
   }
}

接下来,创建一个名为 TestRunner.java 的 java 类文件

import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;

public class TestRunner2 {
   public static void main(String[] args) {
      Result result = JUnitCore.runClasses(TestAssertions.class);
		
      for (Failure failure : result.getFailures()) {
         System.out.println(failure.toString());
      }
		
      System.out.println(result.wasSuccessful());
   }
} 

Annotation

注释就像元标记,您可以将其添加到代码中,并将它们应用于方法或类中。 JUnit 中的这些注释提供了有关测试方法的以下信息

哪些方法将在测试方法之前和之后运行。 

哪些方法在所有方法之前和之后运行。

以及在执行过程中将忽略哪些方法或类。

下表提供了注释列表及其在 JUnit 中的含义

Sr.No.Annotation & Description
1

@Test

The Test annotation tells JUnit that the public void method to which it is attached can be run as a test case.

2

@Before

Several tests need similar objects created before they can run. Annotating a public void method with @Before causes that method to be run before each Test method.

3

@After

If you allocate external resources in a Before method, you need to release them after the test runs. Annotating a public void method with @After causes that method to be run after the Test method.

4

@BeforeClass

Annotating a public static void method with @BeforeClass causes it to be run once before any of the test methods in the class.

5

@AfterClass

This will perform the method after all tests have finished. This can be used to perform clean-up activities.

6

@Ignore

The Ignore annotation is used to ignore the test and that test will not be executed.

创建一个名为 JunitAnnotation.java 的 java 类文件

import org.junit.After;
import org.junit.AfterClass;

import org.junit.Before;
import org.junit.BeforeClass;

import org.junit.Ignore;
import org.junit.Test;

public class JunitAnnotation {
	
   //execute before class
   @BeforeClass
   public static void beforeClass() {
      System.out.println("in before class");
   }

   //execute after class
   @AfterClass
   public static void  afterClass() {
      System.out.println("in after class");
   }

   //execute before test
   @Before
   public void before() {
      System.out.println("in before");
   }
	
   //execute after test
   @After
   public void after() {
      System.out.println("in after");
   }
	
   //test case
   @Test
   public void test() {
      System.out.println("in test");
   }
	
   //test case ignore and will not execute
   @Ignore
   public void ignoreTest() {
      System.out.println("in ignore test");
   }
}

接下来,创建一个名为 TestRunner.java 的 java 类文件

import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;

public class TestRunner {
   public static void main(String[] args) {
      Result result = JUnitCore.runClasses(JunitAnnotation.class);
		
      for (Failure failure : result.getFailures()) {
         System.out.println(failure.toString());
      }
		
      System.out.println(result.wasSuccessful());
   }
} 

输出如下:

in before class
in before
in test
in after
in after class
true

Promise.all()使用场景

更新于 2023.03.13 0分钟阅读 0 评论 5 推荐

    作者:

Promise.all()使用场景

在上一节我们介绍了Promise.all()典型的两类使用场景,今天我们再看看另一类:

创建人为的延迟

Promise.all() 的一个不太常见的场景是当你想延迟某件发生太快的事情。 这更有可能发生在浏览器而不是服务器端,您有时需要在用户操作和响应之间稍作延迟。 例如,您可能希望在从服务器获取数据时显示加载指示器,但如果响应太快,用户可能看不到加载微调器,因此不知道屏幕上的数据是最新的。 在这种情况下,您可以引入人为的延迟,如下所示:

const API_BASE = "https://jsonplaceholder.typicode.com";
const appElement = document.getElementById("app");
  
function delay(milliseconds) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve();
    }, milliseconds);
  });
}

function fetchUserData(userId) {

  appElement.classList.add("loading");

  const urls = [
    `${API_BASE}/users/${userId}/posts`,
    `${API_BASE}/users/${userId}/albums`
  ];

  return Promise.all([
    ...urls.map(url => fetch(url)),
    delay(1500)
  ]).then(results => {

    // strip off the undefined result from delay()
    return results.slice(0, results.length - 1);
  });
}

fetchUserData(1).then(responses => {
  return Promise.all(
    responses.map(
      response => {
        if (response.ok) {
          return response.json();
        } else {
          return Promise.reject(
            new Error(`Unexpected status code: ${response.status} ${response.statusText} for ${response.url}`)
          );
        }
      }
    )
  );
}).then(([posts, albums]) => {

  // process your data as necessary
  console.log(posts);
  console.log(albums);

}).finally(() => {
  appElement.classList.remove("loading");
}).catch(reason => console.error(reason.message));

此代码通过在每个 fetch() 调用中引入延迟来构建前面的示例。 delay() 函数返回一个promise,该promise会在指定的毫秒数过去后resolve。 它通过使用本机 setTimeout() 函数并传递调用 resolve() 的回调函数。请注意,在这种情况下不需要向 resolve() 传递任何值,因为没有相关数据。

您也可以直接将 resolve 作为第一个参数传递给 setTimeout(); 但是,一些 JavaScript 运行时将参数传递给超时回调。 为了在运行时之间获得最佳兼容性,最好从另一个函数内部调用 resolve()。

fetchUserData() 函数启动指定用户 ID 的 Web 服务请求。与上一节的示例一样,Promise.all() 用于监控多个 fetch() 请求,但 在这个例子中,传递给 Promise.all() 的数组中还包含对 delay() 的调用。当返回的promise fullfiled时,fullfilment handler接收所有结果的数组,包括作为最后一个数组元素的未定义。在从 fetchUserData() 返回之前,最后一个元素被删除,因此调用 fetchUserData() 的代码根本不需要知道 delay() 调用。 CSS 类加载被添加到 DOM 中的应用程序元素中,以指示正在检索数据,然后在收到响应时由settlement handler删除。

您刚刚学习了使用 Promise.all() 的用例最佳解决方案。但如果你想要即使一个promise rejected,您的操作还会继续吗?这就是 Promise.allSettled() 是更好的选择。

JUnit第一个测试

在本文,我们将看到一个完整的 JUnit 测试示例,它使用 POJO 类、业务逻辑类和一个测试类,它将由测试运行器运行。提醒一句,看完之后回答一个小问题,本文中的被测试类是哪一个?

创建 EmployeeDetails.java,它是一个POJO类

public class EmployeeDetails {

   private String name;
   private double monthlySalary;
   private int age;
   
   /**
   * @return the name
   */
	
   public String getName() {
      return name;
   }
	
   /**
   * @param name the name to set
   */
	
   public void setName(String name) {
      this.name = name;
   }
	
   /**
   * @return the monthlySalary
   */
	
   public double getMonthlySalary() {
      return monthlySalary;
   }
	
   /**
   * @param monthlySalary the monthlySalary to set
   */
	
   public void setMonthlySalary(double monthlySalary) {
      this.monthlySalary = monthlySalary;
   }
	
   /**
   * @return the age
   */
   public int getAge() {
      return age;
   }
	
   /**
   * @param age the age to set
   */
   public void setAge(int age) {
      this.age = age;
   }
}

EmployeeDetails 类用于 - 

获取/设置员工姓名的值。 

获取/设置员工的月薪值。 

获取/设置员工年龄的值。

创建一个名为 EmpBusinessLogic.java 的文件,它是一个业务逻辑类。

public class EmpBusinessLogic {
   // Calculate the yearly salary of employee
   public double calculateYearlySalary(EmployeeDetails employeeDetails) {
      double yearlySalary = 0;
      yearlySalary = employeeDetails.getMonthlySalary() * 12;
      return yearlySalary;
   }
	
   // Calculate the appraisal amount of employee
   public double calculateAppraisal(EmployeeDetails employeeDetails) {
      double appraisal = 0;
		
      if(employeeDetails.getMonthlySalary() < 10000){
         appraisal = 500;
      }else{
         appraisal = 1000;
      }
		
      return appraisal;
   }
}

EmpBusinessLogic 类用于计算 - 

雇员的年薪。 

雇员的评估金额。

创建一个名为 TestEmployeeDetails.java 的文件,其中包含要测试的测试用例。

public class TestEmployeeDetails {
   EmpBusinessLogic empBusinessLogic = new EmpBusinessLogic();
   EmployeeDetails employee = new EmployeeDetails();

   //test to check appraisal
   @Test
   public void testCalculateAppriasal() {
      employee.setName("Rajeev");
      employee.setAge(25);
      employee.setMonthlySalary(8000);
		
      double appraisal = empBusinessLogic.calculateAppraisal(employee);
      assertEquals(500, appraisal, 0.0);
   }

   // test to check yearly salary
   @Test
   public void testCalculateYearlySalary() {
      employee.setName("Rajeev");
      employee.setAge(25);
      employee.setMonthlySalary(8000);
		
      double salary = empBusinessLogic.calculateYearlySalary(employee);
      assertEquals(96000, salary, 0.0);
   }
}

TestEmployeeDetails 类用于测试 EmpBusinessLogic 类的方法。

它 测试员工的年薪。 

测试员工的考核金额。

接下来,创建一个名为 TestRunner.java 的 java 类文件,它用来执行测试

import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;

public class TestRunner {
   public static void main(String[] args) {
      Result result = JUnitCore.runClasses(TestEmployeeDetails.class);
		
      for (Failure failure : result.getFailures()) {
         System.out.println(failure.toString());
      }
		
      System.out.println(result.wasSuccessful());
   }
} 

运行之后验证结果:true

我们来分析一下,

第一个问题,EmployeeDetails这个POJO类是否需要测试?在这里,我提出一点,只要是源代码,只要包含一些不是标准的业务逻辑,就需要测试;在这里,这个类只包含一些基本的设置/获取标准方法,因此可以不用测试。

第二个问题,这里的被测试类是哪个?显然是EmpBusinessLogic 类,而且这个类包含了两个业务逻辑方法,因此测试类中一定有两个测试用例来单独测试这两个方法?

第三个问题,这里的测试用例是否完整呢?我的理解是不是那么完整,比如第一个testCalculateAppriasal方法中,没有覆盖到返回1000的情况,同时也没有覆盖到正向测试的反例负向测试,以及异常出现的情况。实际上负向测试或者异常的情况不是那么容易发现,能覆盖前两种情况已经足够。

第四个问题,你为啥不用IDE进行测试呢?我的看法是给各一个看这篇文章的人介绍一下JUnitCore.runClasses这个工具类的使用;让大家知道JUnit还可以这样来写。哈哈

欢迎留言讨论。

 

JUnit常用API

JUnit 中最重要的包是 junit.framework,它包含了所有的核心类。一些重要的类如下:

Sr.No.Class NameFunctionality
1AssertA set of assert methods.
2TestCaseA test case defines the fixture to run multiple tests.
3TestResultA TestResult collects the results of executing a test case.
4TestSuiteA TestSuite is a composite of tests.

Assert Class

以下是 org.junit.Assert 类的声明

public class Assert extends java.lang.Object

此类提供了一组对编写测试有用的断言方法。仅记录失败的断言。 Assert类的一些重要方法如下:

Sr.No.Methods & Description
1

void assertEquals(boolean expected, boolean actual)

Checks that two primitives/objects are equal.

2

void assertFalse(boolean condition)

Checks that a condition is false.

3

void assertNotNull(Object object)

Checks that an object isn't null.

4

void assertNull(Object object)

Checks that an object is null.

5

void assertTrue(boolean condition)

Checks that a condition is true.

6

void fail()

Fails a test with no message.

让我们在一个例子中使用上面提到的一些方法。创建一个名为 TestJunit1.java 的 java 类文件

import org.junit.Test;
import static org.junit.Assert.*;

public class TestJunit1 {
   @Test
   public void testAdd() {
      //test data
      int num = 5;
      String temp = null;
      String str = "Junit is working fine";

      //check for equality
      assertEquals("Junit is working fine", str);
      
      //check for false condition
      assertFalse(num > 6);

      //check for not null value
      assertNotNull(temp);
   }
}

接下来,创建一个名为 TestRunner1.java 的 java 类文件

import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;

public class TestRunner1 {
   public static void main(String[] args) {
      Result result = JUnitCore.runClasses(TestJunit1.class);
		
      for (Failure failure : result.getFailures()) {
         System.out.println(failure.toString());
      }
		
      System.out.println(result.wasSuccessful());
   }
}  

验证输出: true

TestCase Class

以下是 org.junit.TestCase 类的声明

public abstract class TestCase extends Assert implements Test

测试用例定义了运行多个测试的夹具。 TestCase类的一些重要方法如下

Sr.No.Methods & Description
1

int countTestCases()

Counts the number of test cases executed by run(TestResult result).

2

TestResult createResult()

Creates a default TestResult object.

3

String getName()

Gets the name of a TestCase.

4

TestResult run()

A convenience method to run this test, collecting the results with a default TestResult object.

5

void run(TestResult result)

Runs the test case and collects the results in TestResult.

6

void setName(String name)

Sets the name of a TestCase.

7

void setUp()

Sets up the fixture, for example, open a network connection.

8

void tearDown()

Tears down the fixture, for example, close a network connection.

9

String toString()

Returns a string representation of the test case.

让我们在一个例子中使用上面提到的一些方法。创建一个名为 TestJunit2.java 的 java 类文件

import junit.framework.TestCase;
import org.junit.Before;
import org.junit.Test;

public class TestJunit2 extends TestCase  {
   protected double fValue1;
   protected double fValue2;
   
   @Before 
   public void setUp() {
      fValue1 = 2.0;
      fValue2 = 3.0;
   }
	
   @Test
   public void testAdd() {
      //count the number of test cases
      System.out.println("No of Test Case = "+ this.countTestCases());
		
      //test getName 
      String name = this.getName();
      System.out.println("Test Case Name = "+ name);

      //test setName
      this.setName("testNewAdd");
      String newName = this.getName();
      System.out.println("Updated Test Case Name = "+ newName);
   }
	
   //tearDown used to close the connection or clean up activities
   public void tearDown(  ) {
   }
}

接下来,创建一个名为 TestRunner2.java 的 java 类文件

import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;

public class TestRunner2 {
   public static void main(String[] args) {
      Result result = JUnitCore.runClasses(TestJunit2.class);
		
      for (Failure failure : result.getFailures()) {
         System.out.println(failure.toString());
      }
		
      System.out.println(result.wasSuccessful());
   }
} 

验证输出

No of Test Case = 1
Test Case Name = testAdd
Updated Test Case Name = testNewAdd
true

TestResult Class

以下是 org.junit.TestResult 类的声明

public class TestResult extends Object

TestResult 收集执行测试用例的结果。它是收集参数模式的一个实例。测试框架区分失败和错误。预期会发生故障并使用断言进行检查。错误是预料之外的问题,例如 ArrayIndexOutOfBoundsException。TestResult类的一些重要方法如下:

Sr.No.Methods & Description
1

void addError(Test test, Throwable t)

Adds an error to the list of errors.

2

void addFailure(Test test, AssertionFailedError t)

Adds a failure to the list of failures.

3

void endTest(Test test)

Informs the result that a test was completed.

4

int errorCount()

Gets the number of detected errors.

5

Enumeration<TestFailure> errors()

Returns an Enumeration for the errors.

6

int failureCount()

Gets the number of detected failures.

7

void run(TestCase test)

Runs a TestCase.

8

int runCount()

Gets the number of run tests.

9

void startTest(Test test)

Informs the result that a test will be started.

10

void stop()

Marks that the test run should stop.

创建一个名为 TestJunit3.java 的 java 类文件

import org.junit.Test;
import junit.framework.AssertionFailedError;
import junit.framework.TestResult;

public class TestJunit3 extends TestResult {
   // add the error
   public synchronized void addError(Test test, Throwable t) {
      super.addError((junit.framework.Test) test, t);
   }

   // add the failure
   public synchronized void addFailure(Test test, AssertionFailedError t) {
      super.addFailure((junit.framework.Test) test, t);
   }
	
   @Test
   public void testAdd() {
      // add any test
   }
   
   // Marks that the test run should stop.
   public synchronized void stop() {
      //stop the test here
   }
}

接下来,创建一个名为 TestRunner3.java 的 java 类文件

import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;

public class TestRunner3 {
   public static void main(String[] args) {
      Result result = JUnitCore.runClasses(TestJunit3.class);

      for (Failure failure : result.getFailures()) {
         System.out.println(failure.toString());
      }
		
      System.out.println(result.wasSuccessful());
   }
}  

TestSuite Class

以下是 org.junit.TestSuite 类的声明:

public class TestSuite extends Object implements Test

TestSuite 是测试的组合。它运行一组测试用例。 TestSuite类的一些重要方法如下

Sr.No.Methods & Description
1

void addTest(Test test)

Adds a test to the suite.

2

void addTestSuite(Class<? extends TestCase> testClass)

Adds the tests from the given class to the suite.

3

int countTestCases()

Counts the number of test cases that will be run by this test.

4

String getName()

Returns the name of the suite.

5

void run(TestResult result)

Runs the tests and collects their result in a TestResult.

6

void setName(String name)

Sets the name of the suite.

7

Test testAt(int index)

Returns the test at the given index.

8

int testCount()

Returns the number of tests in this suite.

9

static Test warning(String message)

Returns a test which will fail and log a warning message.

创建一个名为 JunitTestSuite.java 的 java 类文件

import junit.framework.*;

public class JunitTestSuite {
   public static void main(String[] a) {
      // add the test's in the suite
      TestSuite suite = new TestSuite(TestJunit1.class, TestJunit2.class, TestJunit3.class );
      TestResult result = new TestResult();
      suite.run(result);
      System.out.println("Number of test cases = " + result.runCount());
   }
}

验证输出

No of Test Case = 1
Test Case Name = testAdd
Updated Test Case Name = testNewAdd
Number of test cases = 3

总结

简单介绍了JUnit框架中四个最重要的API,同时需要理解的是,这些API体现了JUnit中最重要的四个基本概念:测试用例,测试集,测试断言和测试结果。以后的所有的一些高级概念都是这些基本概念的组合和深化。

 

 

 

 

JUnit测试@DataJpaTest与@ComponentScan冲突

今天在写JUnit测试的时候,发现一个bug,首先将测试代码贴出来:

@DataJpaTest
@RunWith(SpringRunner.class)
//@ContextConfiguration(classes= RepositoryTest.class)
public class SubscriptionRepositoryTest{
}

SpringBoot的启动类如下:

@SpringBootApplication
@EnableJpaRepositories
@ComponentScan("io.majing")
@EnableAsync
public class UserCenterApp{
}

执行的时候报了一个错误:

Error creating bean with name 'userCreditPublisher' defined in file 
[C:\devnote\backend\devnote\user-center\target\classes\io\majing\usercenter\message\UserCreditPublisher.class]: 
Unsatisfied dependency expressed through constructor parameter 0; 
nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.amqp.core.AmqpTemplate' available: 
expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

在这样的情况下,如果将启动类中的注解@ComponentScan("io.majing")注释掉;然后测试正常通过;

问题分析:

我们知道,@DataJpaTest这个注解只会初始化@Repository注解的类,但是为什么在添加了@ComponentScan后,SpringBoot会初始化@Service;而去掉注解之后就不会初始化@Service呢;通过网上查询后知道,@DataJpaTest@ComponentScan有冲突;因此对应有两种方案:

1、去掉启动类的注解@ComponentScan

2、重新增加一个启动类

@SpringBootConfiguration
@EnableAutoConfiguration
public class RepositoryTest {
    public static void main(String[] args) {
        SpringApplication.run(RepositoryTest.class, args);
    }
}

并且在测试类中将启动类修改成新增加的这个类:

@ContextConfiguration(classes= RepositoryTest.class)
public class SubscriptionRepositoryTest {
}

好了,测试通过;

JUnit测试框架介绍

在上一篇文章中我们对JUnit进行了初步学习,今天我们继续学习JUnit这个测试框架。

JUnit 是一个回归测试框架,开发人员用来在 Java 中实现单元测试,加快编程速度,提高代码质量。 JUnit Framework 可以很容易地与以下任何一个集成

  • Eclipse
  • Ant
  • Maven

JUnit 测试框架的特点

JUnit 测试框架提供以下重要特性

  • Fixtures
  • Test suites
  • Test runners
  • JUnit classes

下面我们分别介绍以上功能

Fixtures

Fixtures 是一组对象的固定状态,用作运行测试的基线。测试夹具的目的是确保有一个众所周知的固定环境来运行测试,以便结果可重复。这包括

1、setUp() 方法,在每次测试调用之前运行。

2、tearDown() 方法,在每个测试方法之后运行。

举个例子:

import junit.framework.*;

public class JavaTest extends TestCase {
   protected int value1, value2;
   
   // assigning the values
   protected void setUp(){
      value1 = 3;
      value2 = 3;
   }

   // test method to add two values
   public void testAdd(){
      double result = value1 + value2;
      assertTrue(result == 6);
   }
}

这里需要注意的是,如果你想实现Fixtures的具体方法,请务必继承TestCase这个类。

Test Suites

测试套件捆绑了几个单元测试用例并将它们一起运行。在 JUnit 中,@RunWith 和 @Suite 注解都用于运行套件测试。下面给出了一个使用 TestJunit1 和 TestJunit2 测试类的示例。

还是用例子来说明:

import org.junit.runner.RunWith;
import org.junit.runners.Suite;

//JUnit Suite Test
@RunWith(Suite.class)

@Suite.SuiteClasses({ 
   TestJunit1.class ,TestJunit2.class
})

public class JunitTestSuite {
}
import org.junit.Test;
import org.junit.Ignore;
import static org.junit.Assert.assertEquals;

public class TestJunit1 {

   String message = "Robert";	
   MessageUtil messageUtil = new MessageUtil(message);
   
   @Test
   public void testPrintMessage() {	
      System.out.println("Inside testPrintMessage()");    
      assertEquals(message, messageUtil.printMessage());     
   }
}
import org.junit.Test;
import org.junit.Ignore;
import static org.junit.Assert.assertEquals;

public class TestJunit2 {

   String message = "Robert";	
   MessageUtil messageUtil = new MessageUtil(message);
 
   @Test
   public void testSalutationMessage() {
      System.out.println("Inside testSalutationMessage()");
      message = "Hi!" + "Robert";
      assertEquals(message,messageUtil.salutationMessage());
   }
}

通过这个例子,我想大家应该对测试套件(Test Suites)有了基本的理解了吧。这个知识点很重要,我们后续还会学习。好了,下一个

Test Runners

试运行器用于执行测试用例。这是一个假设测试类 TestJunit 已经存在的示例。

import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;

public class TestRunner {
   public static void main(String[] args) {
      Result result = JUnitCore.runClasses(TestJunit.class);
		
      for (Failure failure : result.getFailures()) {
         System.out.println(failure.toString());
      }
		
      System.out.println(result.wasSuccessful());
   }
}

JUnit Classes

Unit 类是重要的类,用于编写和测试 JUnit。一些重要的类是

1、Assert - 包含一组断言方法。 

2、TestCase - 包含定义夹具以运行多个测试的测试用例。 

3、TestResult - 包含收集执行测试用例结果的方法。

JUnit设计目标:

1、框架必须帮助我们编写有用的测试。

2、框架必须帮助我们创建具有长久价值的测试。

3、框架必须帮助我们通过复用代码来降低编写测试的成本。 

总结:

JUnit的单元测试框架包含几个重要的概念,首先你要有TestCase(测试用例),而测试用例包含基本的Assert(d断言),Fixtures(setUp,tearDown);有了TestCase之后,可以将这些TestCase组合成TestSuites;有了测试用例和测试套件后,需要一个测试运行器来执行这些测试;

我们后面的学习就是对以上概念的深入学习。

JUnit初步理解

JUnit 是 Java 编程语言的单元测试框架。 JUnit 在测试驱动开发的开发中一直很重要,它是一组统称为 xUnit 的单元测试框架之一,它起源于 JUnit。

本文介绍了在使用 Java 时在项目单元测试中使用 JUnit。学习完成本系列文章后,您将获得有关使用 JUnit 测试框架的足够知识,您可以将自己提升到新的水平。

测试是检查应用程序功能以确保其按要求运行的过程。单元测试出现在开发人员级别;它是对单个实体(类或方法)的测试。单元测试在帮助软件公司向客户交付优质产品方面发挥着关键作用。

单元测试可以通过两种方式完成——手动测试和自动化测试。

Manual TestingAutomated Testing
Executing a test cases manually without any tool support is known as manual testing.Taking tool support and executing the test cases by using an automation tool is known as automation testing.
Time-consuming and tedious − Since test cases are executed by human resources, it is very slow and tedious.Fast − Automation runs test cases significantly faster than human resources.
Huge investment in human resources − As test cases need to be executed manually, more testers are required in manual testing.Less investment in human resources − Test cases are executed using automation tools, so less number of testers are required in automation testing.
Less reliable − Manual testing is less reliable, as it has to account for human errors.More reliable − Automation tests are precise and reliable.
Non-programmable − No programming can be done to write sophisticated tests to fetch hidden information.Programmable − Testers can program sophisticated tests to bring out hidden information.

JUnit是什么?

JUnit 是 Java 编程语言的单元测试框架。它在测试驱动开发中发挥着至关重要的作用,是一系列单元测试框架,统称为 xUnit。

JUnit提倡“先测试后编码”的思想,强调为一段代码设置测试数据,可以先测试后实现。这种方法就像“测试一点,编码一点,测试一点,编码一点”。它提高了程序员的工作效率和程序代码的稳定性,从而减少了程序员的压力和调试时间。

JUnit的特点

1、JUnit 是一个开源框架,用于编写和运行测试。

2、提供注释以识别测试方法。

3、为测试预期结果提供断言。

4、为运行测试提供测试运行器。

5、JUnit 测试允许您更快地编写代码,从而提高质量。

6、JUnit 非常简单。它不那么复杂,花费的时间也更少。

7、JUnit 测试可以自动运行,它们会检查自己的结果并提供即时反馈。无需手动梳理测试结果报告。

8、JUnit 测试可以组织成包含测试用例甚至其他测试套件的测试套件。

9如果测试运行顺利,JUnit 会在一个绿色条中显示测试进度,而当测试失败时,它会变为红色。

什么是单元测试用例?

单元测试用例是代码的一部分,它确保另一部分代码(方法)按预期工作。为了快速达到预期的结果,需要一个测试框架。 JUnit 是一个完美的 Java 编程语言单元测试框架。

正式的书面单元测试用例的特点是已知输入和预期输出,这是在执行测试之前制定的。已知输入应测试前置条件,预期输出应测试后置条件。

每个需求必须至少有两个单元测试用例——一个正面测试和一个负面测试。如果一个需求有子需求,那么每个子需求必须至少有两个测试用例,分别是肯定的和否定的。

 

单元测试基本原则

1、每个单元测试都必须独立于其它所有单元测试而运行。

2、框架应该以单个测试为单元来检测和报告错误。

3、应该易于定义要运行哪些单元测试。

JUnit基本使用

前两篇文章主要对JUnit做了一些简单的功能性的介绍,今天我们开始学习基本的使用。
现在让我们有一个基本示例来演示使用 JUnit 的逐步过程。

创建一个类

创建一个要测试的 java 类,比如 MessageUtil.java

/*
* This class prints the given message on console.
*/

public class MessageUtil {

   private String message;

   //Constructor
   //@param message to be printed
	
   public MessageUtil(String message){
      this.message = message;
   }
      
   // prints the message
   public String printMessage(){
      System.out.println(message);
      return message;
   }   
} 

创建测试用例类

创建一个 java 测试类,比如 TestJunit.java。 

将测试方法 testPrintMessage() 添加到您的测试类。 

将 Annotaion @Test 添加到方法 testPrintMessage()。 

使用 JUnit 的 assertEquals API 实现测试条件并检查条件。

创建一个java类文件名TestJunit.java

import org.junit.Test;
import static org.junit.Assert.assertEquals;

public class TestJunit {
	
   String message = "Hello World";	
   MessageUtil messageUtil = new MessageUtil(message);

   @Test
   public void testPrintMessage() {
      assertEquals(message,messageUtil.printMessage());
   }
}

创建测试运行器类

创建一个 TestRunner java 类。 

使用 JUnit 的 JUnitCore 类的 runClasses 方法运行上面创建的测试类的测试用例。 

获取在 Result Object 中运行的测试用例的结果。

使用 Result 对象的 getFailures() 方法获取失败。 

使用 Result 对象的 wasSuccessful() 方法获取成功结果。

创建一个名为 TestRunner.java 的 java 类文件

import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;

public class TestRunner {
   public static void main(String[] args) {
      Result result = JUnitCore.runClasses(TestJunit.class);
		
      for (Failure failure : result.getFailures()) {
         System.out.println(failure.toString());
      }
		
      System.out.println(result.wasSuccessful());
   }
}  	

验证输出

Hello World
true

 

 

JUnit如何测试Repository层

JUnit是一个优秀的java单元测试框架,今天我们用例子来说明如何使用JUnit来测试Repository层。

我们先来看看被测试对象:

@Repository
public interface UserCreditRepository extends JpaRepository<UserCredit, Long> {

    Optional<UserCredit> findByUserId(UserId userId);
}

这个Repository接口是根据用户id来查询对象。

显然我们要测试的方法是根据用户id来查询对象,因此,我们需要

1、先创建表,

2、插入一些数据,

3、执行测试

4、删除数据

spring:
  datasource:
    url: jdbc:h2:~/credit
    driverClassName: org.h2.Driver
    username: sa
    password: password
  jpa:
    database-platform: org.hibernate.dialect.H2Dialect
    hibernate:
      ddl-auto: update
    properties:
      hibernate:
        format_sql: true
    show_sql: true
  h2:
    console:
      enabled: true
      path: /h2-console
      settings:
        trace: false
        web-allow-others: false

有了以上配置,我们在执行测试的时候会创建相应的表结构。

然后我们再一个测试类中执行以上三步:

@DataJpaTest
@RunWith(SpringRunner.class)
public class UserCreditJpaRepositoryTest {

    @Autowired
    private DataSource dataSource;
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Autowired
    private EntityManager entityManager;
    @Autowired
    private UserCreditRepository userCreditRepository;

    private UserCredit initUserCredit;

    private UserCredit initWithStaticUserCredit;

    private UserCredit initWithDynamicUserCredit;

    @Before
    @Transactional
    public void initClass() {
        initUserCredit = UserCredit.newUserCredit(1000L);
        userCreditRepository.save(initUserCredit);

        initWithStaticUserCredit = UserCredit.newUserCredit(1001L);
        UserStaticCredit userStaticCredit = new UserStaticCredit(1001L,UserCreditType.USER_REGISTER);
        initWithStaticUserCredit.addUserStaticCredit(userStaticCredit);
        userCreditRepository.save(initWithStaticUserCredit);

        initWithDynamicUserCredit = UserCredit.newUserCredit(1002L);
        UserDynamicCredit userDynamicCredit = new UserDynamicCredit(1002L,UserCreditType.USER_PUBLISH);
        userDynamicCredit.buildTargetId(1000L);
        initWithDynamicUserCredit.addUserDynamicCredit(userDynamicCredit);
        userCreditRepository.save(initWithDynamicUserCredit);
    }

    @Test
    @Transactional
    public void testQueryByUserId() {

        UserId userId = new UserId(1001L);
        UserCredit userCredit = userCreditRepository.findByUserId(userId).get();
        Assert.assertEquals(userCredit.getUserId().id(),1001L);
        Assert.assertEquals(userCredit.getUserDynamicCredits().size(),0);
        Assert.assertEquals(userCredit.getUserStaticCredits().size(),1);

        userId = new UserId(1002L);
        UserCredit userCredit1 = userCreditRepository.findByUserId(userId).get();
        Assert.assertEquals(userCredit1.getUserId().id(),1002L);
        Assert.assertEquals(userCredit1.getUserDynamicCredits().size(),1);
        Assert.assertEquals(userCredit1.getUserStaticCredits().size(),0);

        userId = new UserId(1000L);
        UserCredit userCredit2 = userCreditRepository.findByUserId(userId).get();
        Assert.assertEquals(userCredit2.getUserId().id(),1000L);
        Assert.assertEquals(userCredit2.getUserDynamicCredits().size(),0);
        Assert.assertEquals(userCredit2.getUserStaticCredits().size(),0);
    }

    @After
    @Transactional
    public void removeClass(){
        System.out.println("id1:"+initUserCredit.getId());
        System.out.println("id2:"+initWithStaticUserCredit.getId());
        System.out.println("id3:"+initWithDynamicUserCredit.getId());
        userCreditRepository.delete(initUserCredit);
        userCreditRepository.delete(initWithStaticUserCredit);
        userCreditRepository.delete(initWithDynamicUserCredit);
    }

    @Test
    public void injectedComponentsAreNotNull(){
        Assert.assertNotNull(dataSource);
        Assert.assertNotNull(jdbcTemplate);
        Assert.assertNotNull(entityManager);
        Assert.assertNotNull(userCreditRepository);
    }
}

我们在@Before中插入一些不同的数据,然后在@After中执行数据的删除,同时在testQueryByUserId方法中执行不同的查询测试验证;

注意,我们在测试类中增加了一个注解@DataJpaTest这个注解可以验证injectedComponentsAreNotNull方法中的四个对象是否已经准备好进行测试。具体的业务逻辑就不用介绍啦。

欢迎各位进行讨论。

 

BabelJS - 概述

更新于 2022.10.28 9分钟阅读 3 评论 5 推荐

    作者:

BabelJS - 概述

BabelJS 是一个 JavaScript 转译器,它将新功能转译成旧标准。有了BabelJS,这些新功能可以在新旧浏览器上自由运行。澳大利亚开发人员 Sebastian McKenzie 创立了 BabelJS。

为什么选择 BabelJS?

JavaScript 是浏览器可以理解的语言。我们使用不同的浏览器来运行我们的应用程序——Chrome、Firefox、Internet Explorer、Microsoft Edge、Opera、UC 浏览器等。ECMA Script 是 JavaScript 语言规范; ECMA Script 2015 ES6 是稳定版本,可在所有新旧浏览器中正常运行。

在 ES5 之后,我们有了 ES6、ES7 和 ES8。 ES6 发布了许多新功能,但并非所有浏览器都完全支持。这同样适用于 ES7、ES8 和 ESNext(ECMA 脚本的下一个版本)。现在不确定何时所有浏览器都可以与所有已发布的 ES 版本兼容。

如果我们计划使用 ES6 或 ES7 或 ES8 功能来编写我们的代码,由于缺乏对新更改的支持,它往往会在一些旧浏览器中中断。因此,如果我们想在我们的代码中使用 ECMA Script 的新特性并希望在所有可能的浏览器上运行它,我们需要一个工具来在 ES5 中编译我们的最终代码。

Babel 也是如此,它被称为转译器,可以转译我们想要的 ECMA 脚本版本中的代码。它具有预设和插件等功能,可以配置我们需要转译代码的 ECMA 版本。使用 Babel,开发人员可以使用 JavaScript 中的新功能编写代码。用户可以得到使用 Babel 转译的代码;这些代码以后可以在任何浏览器中使用而不会出现任何问题。

下表列出了 ES6、ES7 和 ES8 中可用的功能

FeaturesECMA Script version
Let + ConstES6
Arrow FunctionsES6
ClassesES6
PromisesES6
GeneratorsES6
IteratorsES6
ModulesES6
DestructuringES6
Template LiteralsES6
Enhanced ObjectES6
Default, Rest & Spread PropertiesES6
Async - AwaitES7
Exponentiation OperatorES7
Array.prototype.includes()ES7
String PaddingES8

BabelJS 管理以下两部分

  • transpiling
  • polyfilling

 

什么是 Babel-Transpiler?

Babel-transpiler 将现代 JavaScript 的语法转换为旧浏览器可以轻松理解的形式。例如,箭头函数、const、let 类将被转换为函数、var 等。这里的语法,即箭头函数被转换为普通函数,在两种情况下都保持功能相同。

什么是 Babel-polyfill?

avaScript 中添加了一些新功能,例如 promise、map 和 include。这些特征可以用在数组上;同样,当使用 babel 进行转译时,也不会被转换。如果新特性是一个方法或对象,我们需要使用 Babel-polyfill 和转译来使其在旧浏览器上工作。

这是 JavaScript 中可用的 ECMA 脚本功能列表,可以转译和polyfill

  • Classes
  • Decorators
  • Const
  • Modules
  • Destructing
  • Default parameters
  • Computed property names
  • Object rest/spread
  • Async functions
  • Arrow functions
  • Rest parameters
  • Spread
  • Template Literals

可以polyfill的 ECMA 脚本功能

  • Promises
  • Map
  • Set
  • Symbol
  • Weakmap
  • Weakset
  • includess
  • Array.from, Array.of,Array#find,Array.buffer, Array#findIndex
  • Object.assign,Object.entries,Object.values

BabelJS 的特点

在本节中,我们将了解 BabelJS 的不同特性。以下是 BabelJS 最重要的核心特性

Babel-Plugins

Plugins 和 Presets 是 Babel 转换代码的配置细节。 Babel 支持许多插件,如果我们知道代码将在其中执行的环境,它们可以单独使用。

Babel-Presets

Babel 预设是一组插件,即 babel-transpiler 的配置详细信息,指示 Babel 在特定模式下进行转换。我们需要使用预设,它具有我们想要转换代码的环境。例如,es2015 preset 会将代码转换为 es5。

Babel-Polyfills

有一些特性,如方法和对象,不能被转译。在这种情况下,我们可以使用 babel-polyfill 来促进在任何浏览器中使用功能。让我们考虑一下承诺的例子;为了使该功能在较旧的浏览器中工作,我们需要使用 polyfill。

Babel-cli 附带了一堆命令,可以在命令行上轻松编译代码。它还具有插件和预设等功能,可与命令一起使用,从而可以轻松地一次性转换代码。

使用 BabelJS 的优势

在本节中,我们将了解与使用 BabelJS 相关的不同优势

BabelJS 为 JavaScript 新添加的所有功能提供向后兼容性,并且可以在任何浏览器中使用。

BabelJS 能够转译以采用下一个即将推出的 JavaScript 版本 - ES6、ES7、ESNext 等。

BabelJS 可以与 gulp、webpack、flow、react、typescript 等一起使用,使其非常强大,可以与大型项目一起使用,使开发人员的生活变得轻松。

BabelJS 还可以与 react JSX 语法一起使用,并且可以以 JSX 形式编译。

BabelJS 支持插件、polyfills、babel-cli,这使得处理大型项目变得容易。

使用 BabelJS 的缺点

在本节中,我们将了解使用 BabelJS 的不同缺点

BabelJS 代码在转译时会更改语法,这使得代码在生产中发布时难以理解。

与原始代码相比,转译的代码更大。

并非所有 ES6/7/8 或即将推出的新功能都可以转译,我们必须使用 polyfill 才能在旧浏览器上运行。

Babel初步认识

更新于 2022.10.21 4分钟阅读 5 评论 5 推荐

    作者:

Babel初步认识

BabelJS 是一个 JavaScript 转译器,它将新功能转译成旧标准。有了BabelJS,这些功能可以在新旧浏览器上兼容运行。Babeljs 以插件、预设、polyfill 等形式提供了广泛的功能。

简而言之,Babeljs 是一个工具集,其中包含所有可用的工具,它可以帮助开发人员使用 ECMA Script 中可用的所有当前功能,而不必担心浏览器将如何支持它。

首先,我们需要理解的是为什么会出现Babel,以及Babel解决了什么问题?

我们知道,现代浏览器上的标准开发语言毫无疑问就是javascript,而js又是一门不断发展变化的语言,这门语言的标准不断的更新完善,但是实现这些标准的浏览器厂商在实现这些标准的进度上有的快有的慢,不一定能那么及时的支持新的标准。但是总的来说,大部门浏览器都支持es2015。因此Babel为了解决大部门浏览器的兼容性问题,所以开发了Babel相关的工具来将新的js标准代码转化为es2015的代码,这样代码就能更好的在各种不同的浏览器上运行。

 

Babel是怎么解决这个问题的呢?

这里我们还是来说说具体的情况,用例子来说明能更好的理解Babel各个功能的作用,首先,我们知道在新版的js标准中有箭头函数这个语法:

const fn = ()=> {
  return 1024;
}

我们思考一下,如果你要将这个箭头函数转化成几乎大部分浏览器都可以执行的代码,应该怎么做呢?是不是应该想办法转化成es2015可以运行的函数:

var fn = function() {
  return 1024;
}

Babel在这里就是采用的预设的方式,将箭头函数转为成es2015对应的函数。具体的实现可以看源代码,这里我们只需要知道为什么会有Babel的预设这个功能就好了

再来看看下一个例子:

在es6中我们有一个新的语法叫做Promise,这个是为了解决我们的回调函数层次太多而提供的API,Babel怎么才能让这个功能在大部分浏览器上运行呢?

显然,es2015没有这个功能,如果我们自己来实现的话,那就是提供一个这样的功能,然后将这个功能绑定到浏览器的全局对象不就OK了吗?这个叫做啥?是不是应该叫做polyfill。用中文来解释就是你没有这个功能,我给你提供一个。理解了这个就容易理解为什么Babel中有很多polyfill。这个的好处是让老的浏览器也能用新的标准实现,不好的地方就是让老的浏览器的代码不那么好看。

最后一个例子我们再谈谈为啥会有插件?

我们知道插件的主要作用是增强功能,比如babel本身并不支持jsx语法的处理,我能否通过插件的机制将jsx语法转化成浏览器能够认识的语法呢?这其实也是一种转化,Babel是一个转化的工具,如果猜测没有错误的话,那么Babel转化jsx很有可能就是通过插件来实现的。

有了以上的分析和猜测,我想大家对babel的主要作用应该有可一定的认识,以后在使用的时候基于以上认知去选择的时候,往往能够给大家提供指路的作用。

什么是控制反转(IOC)

什么是控制反转(IOC)

控制反转 (IoC) 和依赖注入 (DI) 是用于解耦类依赖关系的编程模式。在本文中,我们将讨论 Spring Framework 的 IoC 和 DI。

为简单起见,假设类 Vehicle 依赖于类 Engine,这意味着没有 EngineVehicle 没有意义。我们需要确保通过保持代码松散耦合来满足这种依赖关系。所以我们使用一个特殊的类来确保对象的创建以正确的顺序完成并且依赖关系得到照顾。这个特殊的类有时被称为容器或工厂。

控制反转是一种通过将对象创建转移到容器或框架来确保解耦的技术。该框架还允许我们根据开发人员的需要自定义 Objection 创建并添加自定义行为。之所以称为反转,是因为该过程是反向的,其中 bean 创建和依赖由容器而不是 bean 本身控制。

IOC的优势:

1、解耦对象创建过程及其执行过程。

2、有助于运行时多态性。

3、更好的代码可维护性。

4、由于接口和实现之间的松散耦合,易于代码测试。

实现 IoC 的机制:

IoC 可以通过各种编程技术和模式来实现,例如策略设计模式、服务定位器模式、工厂模式和依赖注入。

 

上一节我们已经用代码介绍了依赖注入,今天我们用另一个例子来说明:

传统的耦合代码:

//Traditional tightly coupled code.
public class Store {
  private Item item;
  public Store() {
    this.item = new Item();
  }
}

依赖注入代码:

public class Store {
  private Item item;
  public Store(Item item) {
    this.item = item;
  }
}

注意 – 我们需要通过元数据配置 IoC 容器,以便容器拥有创建 Bean 所需的信息。

接下来,我们将探索 Spring Framework IOC Container。让我们开始实际的 Spring 编码。

Spring 框架 IoC 容器

控制反转容器是 Spring Framework 的核心。 Spring 生态系统中的所有其他库/模块都建立在此之上。要使用 Spring IoC,请确保 pom.xml 中有 spring-context 依赖项。

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.2.2.RELEASE</version>
</dependency>

在 Spring 框架中

1、ApplicationContext 接口代表 Spring IoC 容器。

2、相同的接口还负责 Bean 的实例化、配置和管理。

3、bean 配置元数据可以以 XML、Java 注释或 Java 代码的形式提供。

4、Spring 提供了几种 ApplicationContext 接口的实现。例如,要在独立应用程序中使用基于 XML 的元数据配置,它具有 ClassPathXmlApplicationContext 和 FileSystemXmlApplicationContext。同样,如果您在基于独立注释的配置中工作,则使用 AnnotationConfigApplicationContext。

Spring 中的元数据可以通过 3 种方式进行配置

1、使用基于纯 XML 的配置

2、使用基于注释的配置。这种方法利用注释和基于 XML 的配置。

3100% 基于 Java 的配置。这种方法还利用了注释。

 

Spring中依赖注入的三种方式

请记住,Spring 中的依赖注入可以通过三种方式完成,通过构造函数、设置器或字段。我们将使用基于 Java 的配置以及基于 XML 的配置来学习这些 DI 方法中的每一个。

基于构造函数的依赖注入

在基于构造函数的 DI 中,IoC 容器将调用构造函数来注入其必要的依赖项。元数据配置可以在 java 配置文件或 XML bean-config 文件中提供。

用于构造函数注入的基于 XML 的元数据配置

下面的代码显示了使用基于 XML 的配置来执行基于构造函数的依赖注入。您不必接触 java POJO 类。所有配置都在 bean-config.xml 文件中完成。

Spring 中的 bean 的默认作用域为 Singleton。由于我们希望每次要求 spring 返回一个新 bean 时都创建一个新实例,因此将 bean 范围指定为原型范围 =“prototype”。如您在 bean-config.xml 文件中所见,构造函数 DI 是使用构造函数参数完成的。

正如您在 TestXMLConstructorInjection 类中看到的那样,ApplicationContext 实例是使用 ClassPathXmlApplicationContext 类创建的。

Item.java

package basic.ioc.constructor.xml;
import java.util.StringJoiner;
public class Item {
  private Long id;
  private String name;
  public Item() {
  }
  public Item(Long id, String name) {
    this.id = id;
    this.name = name;
  }
  public Long getId() {
    return id;
  }
  public void setId(Long id) {
    this.id = id;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  @Override
  public String toString() {
    return new StringJoiner(", ", Item.class.getSimpleName() + "[", "]")
        .add("id=" + id)
        .add("name='" + name + "'")
        .toString();
  }
}

Store.java

package basic.ioc.constructor.xml;
import java.util.StringJoiner;
public class Store {
  private String id;
  private Item item;
  public Store() {
  }
  public Store(String id, Item item) {
    this.id = id;
    this.item = item;
  }
  public String getId() {
    return id;
  }
  public void setId(String id) {
    this.id = id;
  }
  public Item getItem() {
    return item;
  }
  public void setItem(Item item) {
    this.item = item;
  }
  @Override
  public String toString() {
    return new StringJoiner(", ", Store.class.getSimpleName() + "[", "]")
        .add("id=" + id)
        .add("item=" + item)
        .toString();
  }
}

bean-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
  <bean id="item" scope="prototype" class="basic.ioc.constructor.xml.Item"/>
  <bean id="store" scope="prototype" class="basic.ioc.constructor.xml.Store">
    <constructor-arg type="java.lang.String" value="#{ T(java.util.UUID).randomUUID().toString() }"/>
    <constructor-arg type="basic.ioc.constructor.xml.Item" ref="item"/>
  </bean>
</beans>

TestXMLConstructorInjection.java

package basic.ioc.constructor.xml;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestXMLConstructorInjection {
  public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("bean-config.xml");
    System.out.println(context.getBean(Store.class));
  }
}

输出:

Store[id=ad25498e-9538-422f-99a4-eb65d112d6fd, item=Item[id=null, name='null']]

用于构造函数注入的基于 Java 的元数据配置

就像 XML bean 配置文件一样,我们创建一个 java 类 BasicIocConfig 用于保存元数据配置。在实际场景中,您可以拥有多个这样的配置类。

1、@Configuration 注解表明该类是一个 bean 定义配置文件。

2、@Bean 注解指示 bean 名称,用于创建 bean 的方法。

3、默认的 bean 作用域是单例的,为了得到原型作用域,所以我们使用了@Scope 注解。

4、最后,AnnotationConfigApplicationContext 实现类用于创建ApplicationContext 的实例。

BasicIocConfig.java

package basic.ioc.constructor.annotation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import java.util.UUID;
@Configuration
public class BasicIocConfig {
  @Bean
  @Scope("prototype")
  public Item item() {
    return new Item();
  }
  @Bean
  @Scope("prototype")
  public Store store() {
    return new Store(
        UUID.randomUUID().toString(), this.item()
    );
  }
}

TestInjection.java

package basic.ioc.constructor.annotation;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestInjection {
  public static void main(String[] args) {
    ApplicationContext context = new AnnotationConfigApplicationContext(BasicIocConfig.class);
    Store store = context.getBean(Store.class);
    System.out.println(store);
  }
}

Store.java 和 Item.java与上面一致。

输出如下:

Store[id=c960066d-5142-49f5-83bd-1d53012449ef, item=Item[id=null, name='null']]

注意:建议使用带有注释的基于 Java 的配置,而不是基于 XML 的配置。

还有另外两种注入方式以后再介绍。

在 Spring AOP 中使用 @Order 通知排序

在 Spring AOP 中使用 @Order 通知排序

简介

在本文中,您将了解在 Spring AOP 中使用 @Order 的多个 Advice 排序。通常,不止一条建议应用于单个匹配的连接点,并且您希望控制它们的执行顺序。Spring 允许您使用 org.springframework.core.Ordered 接口或@Order 注解设置优先级。

通知优先规则

在两个 @Order(n) 注释中,具有较低 n 值的注释具有较高的优先级。例如。 @Order(0) 的优先级值高于@Order(1)。

您只能在通知类上使用@Order,而不能在通知方法级别上使用。

因此,当两条通知写在不同的类中时,您可以指定执行顺序。如果两个通知都写在一个类中,您将无法控制执行顺序。

最高优先级的通知首先在进入的过程中运行。例如在两条@Before 建议中,优先级较高的一条首先运行。

最低优先级的通知在退出时首先运行。例如。给定两条@After 建议,优先级较低的一条首先运行。

根据 Spring 官方文档,应用到同一个 Joinpoint 的多个通知的执行顺序是未定义的,除非您指定优先级。这与您是在单个 Aspect 中声明多条通知还是在多个 Aspect 中声明无关。

通知顺序示例

我将使用一个简单的例子来演示@Order注解的使用。或者,您可以扩展 org.springframework.core.Ordered。

在之前的文章中,我已经介绍了创建 Spring AOP 项目的详细分步方法。因此,让我们直接看代码并了解 Advice Ordering 的工作原理。

步骤一 使用示例类创建一个 maven 项目

使用下面提到的以下依赖项创建一个 Maven 项目。创建示例 Dao、Service 和 config 类。我们将在下一步中创建方面类。

UserRepository

package com.jbd.saop.order.dao;
import org.springframework.stereotype.Repository;
//A very stupid demo repository
@Repository
public class UserRepository {
  //Add a user
  public UserRepository add(String username, String password) {
    if(username == null) {
      throw new RuntimeException("username is null", new NullPointerException());
    }
    if (password == null) {
      throw new RuntimeException("password is null", new NullPointerException());
    }
    System.out.println("New user added: " + username);
    return this;
  }
  //Update an user
  public UserRepository update(String username, String email) {
    System.out.println("Update email: " + email);
    return this;
  }
  //Delete an user
  public boolean delete(String username){
    if (username == null) {
      throw new RuntimeException("username is null", new NullPointerException());
    }
    System.out.println("User deleted: " + username);
    return true;
  }
}

EmailRepository

package com.jbd.saop.order.dao;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Repository;
@Repository
public class EmailRepository {
  @Async
  public void sendRegistrationEmail(String email){
    System.out.println("Registration email will be sent to: " + email);
  }
}

ApplicationConfig

package com.jbd.saop.order;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.jbd.saop.order")
public class ApplicationConfig {
}

UserService

package com.jbd.saop.order.service;
import com.jbd.saop.order.dao.EmailRepository;
import com.jbd.saop.order.dao.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
  @Autowired
  private UserRepository userRepository;
  @Autowired
  private EmailRepository emailRepository;
  public void registerUser(String username, String email, String password) {
    userRepository.add(username, password);
    userRepository.update(username, email);
    emailRepository.sendRegistrationEmail(email);
  }
}

pom.xml

<dependencies>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-core</artifactId>
          <version>5.2.2.RELEASE</version>
        </dependency>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-context</artifactId>
          <version>5.2.2.RELEASE</version>
        </dependency>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-aop</artifactId>
          <version>5.2.2.RELEASE</version>
        </dependency>
        <dependency>
          <groupId>org.aspectj</groupId>
          <artifactId>aspectjweaver</artifactId>
          <version>1.9.5</version>
        </dependency>
    <dependencies>
   <!-- Test Dependencies-->
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-api</artifactId>
      <version>5.5.2</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-engine</artifactId>
      <version>5.5.2</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.2.2.RELEASE</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
...
<!-- Important for Jupiter-engine -->
<build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>3.0.0-M3</version>
      </plugin>
    </plugins>
  </build>
</project>

步骤 2 创建 Aspect 类

当通知的代码片段位于不同的类中时,我只能控制执行顺序。因此,让我们创建如下 3 个通知类和常见的切入点。注意,所有的 @Order 注释都被注释掉了。

ZeroAdvice

package com.jbd.saop.order.advice;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
@Aspect
@Configuration
//@Order(0)
public class ZeroAdvice {
  @Before("CommonPointcut.anyDaoMethod()")
  public void beforeAdvice(JoinPoint joinPoint) {
    System.out.println("\n======= Inside @Before() - 0 =======");
    System.out.println(joinPoint.getSignature().toShortString());
  }
  @After("CommonPointcut.anyDaoMethod()")
  public void afterAdvice(JoinPoint joinPoint) {
    System.out.println("\n======= Inside @After() - 0 =======");
    System.out.println(joinPoint.getSignature().toShortString());
  }
  @AfterReturning(
    pointcut = "CommonPointcut.anyDaoMethod()",
    returning = "result")
  public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
    System.out.println("\n======= Inside @AfterReturning() - 0 =======");
    System.out.println(joinPoint.getSignature().toShortString());
    System.out.println("result: " + result);
  }
}

SecondAdvice

package com.jbd.saop.order.advice;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
@Aspect
@Configuration
//@Order(1)
public class SecondAdvice {
  @Before("CommonPointcut.anyDaoMethod()")
  public void beforeAdvice(JoinPoint joinPoint) {
    System.out.println("\n======= Inside @Before() - 1 =======");
    System.out.println(joinPoint.getSignature().toShortString());
  }
  @After("CommonPointcut.anyDaoMethod()")
  public void afterAdvice(JoinPoint joinPoint) {
    System.out.println("\n======= Inside @After() - 1 =======");
    System.out.println(joinPoint.getSignature().toShortString());
  }
}

ThirdAdvice

package com.jbd.saop.order.advice;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
@Configuration
@Aspect
//@Order(2)
public class ThirdAdvice {
  @Around("CommonPointcut.anyDaoMethod()")
  public Object aroundAdvice(ProceedingJoinPoint pJoinPoint) throws Throwable {
    System.out.println("\n======= Inside @Around() - 2 =======");
    System.out.println(pJoinPoint.getSignature().toShortString());
    Object result = pJoinPoint.proceed();
    System.out.println("\n======= Inside @Around() - 2 =======");
    return result;
  }
}

CommonPointcut

package com.jbd.saop.order.advice;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class CommonPointcut {
  @Pointcut("execution(* com.jbd.saop.order.dao.*.*(..))")
  public void anyDaoMethod() {}
}

步骤3 运行测试用例并观察输出

使用以下测试代码创建一个测试类并运行它。

package com.jbd.saop.order;
import com.jbd.saop.order.dao.UserRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
@SpringJUnitConfig(ApplicationConfig.class)
public class TestAdviceOrdering {
  @Autowired
  private UserRepository userRepository;
  @Test
  public void testAddUser() {
    userRepository.add("abc", "abc@123");
  }
}

输出

======= Inside @Before() - 1 =======
UserRepository.add(..)

======= Inside @Around() - 2 =======
UserRepository.add(..)

======= Inside @Before() - 0 =======
UserRepository.add(..)
New user added: abc

======= Inside @After() - 0 =======
UserRepository.add(..)

======= Inside @AfterReturning() - 0 =======
UserRepository.add(..)
result: com.jbd.saop.order.dao.UserRepository@2b52c0d6

======= Inside @Around() - 2 =======

======= Inside @After() - 1 =======
UserRepository.add(..)

在案例中,调用不同建议的顺序可能会有所不同。正式执行的顺序是未定义的,但是,看起来执行是按字母顺序发生的。

步骤4 应用@Order 并重新运行测试用例

这就是我们所期待的,现在取消注释掉通知类中的所有 @Order 注释并重新运行测试用例以观察输出。

ZeroAdvice.java

@Aspect
@Configuration
@Order(0)
public class ZeroAdvice {
....
}

SecondAdvice.java

@Aspect
@Configuration
@Order(1)
public class SecondAdvice {
....
}

ThirdAdvice.java

@Configuration
@Aspect
@Order(2)
public class ThirdAdvice {
....
}

输出

======= Inside @Before() - 0 =======
UserRepository.add(..)

======= Inside @Before() - 1 =======
UserRepository.add(..)

======= Inside @Around() - 2 =======
UserRepository.add(..)
New user added: abc

======= Inside @Around() - 2 =======

======= Inside @After() - 1 =======
UserRepository.add(..)

======= Inside @After() - 0 =======
UserRepository.add(..)

======= Inside @AfterReturning() - 0 =======
UserRepository.add(..)
result: com.jbd.saop.order.dao.UserRepository@7de0c6ae

总结

您可以使用 @Order 注释来控制通知执行的顺序,当它们被分成多个类时。如果它们是在一个类中编写的,您应该考虑将它们组合起来。

BabelJS - 使用 Babel 和 JSX

发布于 2022.09.19 12分钟阅读 0 评论 5 推荐

    作者:

BabelJS - 使用 Babel 和 JSX

在本文中,我们将了解如何使用 JSX 和 babel。在我们深入细节之前,让我们了解一下 JSX 是什么。

什么是 JSX?

JSX 是一个 JavaScript 代码,其中结合了 xml 语法。 JSX 标签具有标签名称、属性和子标签,使其看起来像 xml。

React 使用 JSX 进行模板化,而不是常规的 JavaScript。没有必要使用它,但是,以下是它附带的一些优点。

它更快,因为它在将代码编译为 JavaScript 时执行优化。

它也是类型安全的,并且大多数错误都可以在编译期间被捕获。

如果您熟悉 HTML,则可以更轻松、更快速地编写模板。

我们在项目设置中使用了 babel 6。如果你想切换到 babel 7,请使用 @babel/babel-package-name 安装所需的 babel 包。

我们将创建项目设置并使用 webpack 编译 jsx,并使用 Babel 对普通 JavaScript 做出反应。 要开始项目设置,请运行下面给出的命令来安装 babel、react 和 webpack。

npm init

现在,我们将安装我们需要使用的必要包——babel、webpack 和 jsx

npm install --save-dev webpack
npm install --save-dev webpack-cli
npm install --save-dev webpack-dev-server
npm install --save-dev babel-core
npm install --save-dev babel-loader
npm install --save-dev babel-preset-es2015
npm install --save-dev babel-preset-react
npm install --save-dev react
npm install --save-dev react-dom

这是安装后的package.json

现在将创建一个 webpack.config.js 文件,该文件将包含捆绑 js 文件并使用 babel 将其编译为 es5 的所有详细信息。

要使用服务器运行 webpack,有一个叫做 webpack-server 的东西。我们添加了名为publish的命令;该命令将启动 webpack-dev-server 并更新最终文件的存储路径。现在我们要用来更新最终文件的路径是 /dev 文件夹。

要使用 webpack,我们需要运行以下命令

npm run publish

我们将创建 webpack.config.js 文件,其中包含 webpack 工作的配置细节。 

文件中的详细信息如下:

var path = require('path');

module.exports = {
   entry: {
      app: './src/main.js'
   },
   output: {
      path: path.resolve(__dirname, 'dev'),
      filename: 'main_bundle.js'
   },
   mode:'development',
   module: {
      rules: [
         {
            test:/\.(js|jsx)$/,
            include: path.resolve(__dirname, 'src'),
            loader: 'babel-loader',
            query: {
               presets: ['es2015','react']
            }
         }
      ]
   }
};

文件结构如上图所示。它从路径开始,它给出了当前路径的详细信息。

var path = require('path'); //gives the current path

接下来是 module.exports 对象,它具有属性 entry、output 和 module。 入口是起点。这里我们需要给出我们要编译的主要js文件。

entry: {
   app: './src/main.js'
},

path.resolve(_dirname, ‘src/main.js’) - 将在目录中查找 src 文件夹,并在该文件夹中查找 main.js。

output: {
   path: path.resolve(__dirname, 'dev'),
   filename: 'main_bundle.js'
},

输出是具有路径和文件名详细信息的对象。路径将保存编译文件将保存的文件夹,文件名将告诉您的 .html 文件中要使用的最终文件的名称。

module: {
   rules: [
      {
         test:/\.(js|jsx)$/,
         include: path.resolve(__dirname, 'src'),
         loader: 'babel-loader',
         query: {
            presets: ['es2015','react']
         }
      }
   ]
}

module 是具有规则详细信息的对象,具有测试、包含、加载器、查询等属性。

test 将保存所有以 .js 和 .jsx 结尾的 js 文件的详细信息。它具有在给定入口点末尾查找 .js 和 .jsx 的模式。

Include 告诉用于查找文件的文件夹。

Loader 使用 babel-loader 来编译代码。

Query 具有属性预设,即值为 env 的数组 - es5 或 es6 或 es7。我们使用了 es2015 并将 react 作为预设。

创建文件夹 src/。在其中添加 main.js 和 App.jsx。

App.jsx

import React from 'react';

class App extends React.Component {
   render() {
         var style = {
         color: 'red',
         fontSize: 50
      };
      return (
         <div style={style}>
            Hello World!!!
         </div>
      );
   }
}
export default App;

main.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.jsx';

ReactDOM.render(, document.getElementById('app'));

运行以下命令捆绑 .js 文件并使用预设 es2015 转换它并做出反应。

npm run pack

将 dev 文件夹中的 main_bundle.js 添加到 index.html

<!DOCTYPE html>
<html lang = "en">
   <head>
      <meta charset = "UTF-8">
      <title>React App</title>
   </head>
   <body>
      <div id = "app"></div>
      <script src = "dev/main_bundle.js"></script>
   </body>
</html>
npm run publish

Spring AOP 中的环绕通知 – @Around

Spring AOP 中的环绕通知 – @Around

简介

在本文中,您将了解 Spring AOP 中围绕匹配的 Joinpoint 执行运行的环绕建议。使用 @Around 注解声明环绕通知。

您已经了解了上一篇文章中列出的 5 种通知类型,如下所示。

环绕通知执行 - @Around

使用 @Around 注释声明环绕通知。它包含在匹配方法(JoinPoint)之前和之后执行的代码

通知方法的第一个参数必须是 ProceedingJoinPoint 类型。

在通知正文中,对 ProceedingJoinPoint 调用 proceed() 会导致执行底层方法。

在大多数情况下,从通知方法返回一个值很重要,因为匹配的 JoinPoint 可以有一个返回类型。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;
@Aspect
public class AroundExample {
    @Around("execution (* com.jbd.myapp.service.*.*(..))")
    public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
        // Custom code before method execution
       Object retVal = pjp.proceed(); //method execution
        // Custom code after method execution
        return retVal; //exit the advice
    }
}

环绕通知的例子——@Around

问题陈述——我们将使用之前示例中的相同 UserRepository 并添加一个新功能来跟踪方法完成其执行所花费的时间。使用 @ExecutionTime 注释的任何方法都将在控制台中记录总执行时间。 @ExecutionTime 是一个自定义注解。

步骤 1:添加依赖项

创建一个maven项目,在pom.xml中添加如下依赖

<dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>5.2.2.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.2.RELEASE</version>
    </dependency>
    <!-- Step-1 Add the dependencies -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>5.2.2.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
     <version>1.9.5</version>
    </dependency>
<dependencies>

步骤 2:创建示例类

我们将创建带有@ExecutionTime注释的方法。所以我们创建了示例代码——UserRepository、配置类和注释,如下所示。正如我们之前讨论过的,注解@EnableAspectJAutoProxy 用于启用Spring-AOP。

UserRepository.java

package com.jbd.saop.around.dao;
import com.jbd.saop.around.ExecutionTime;
import org.springframework.stereotype.Repository;
//A very stupid demo repository
@Repository
public class UserRepository {
  //Add a user
  @ExecutionTime
  public UserRepository add(String username) throws InterruptedException {
    Thread.sleep(100);
    if(username == null) {
      throw new RuntimeException("username is null", new NullPointerException());
    }
    System.out.println("New user added: " + username);
    return this;
  }
  //Update an user
  public UserRepository update(String username, String email) {
    System.out.println("Update email: " + email);
    return this;
  }
  //Delete an user
  @ExecutionTime
  public boolean delete(String username){
    if (username == null) {
      throw new RuntimeException("username is null", new NullPointerException());
    }
    System.out.println("User deleted: " + username);
    return true;
  }
}

@ExecutionTime

package com.jbd.saop.around;
import java.lang.annotation.*;
/**
 * Tracks the execution time.
 */
//Allowed to use only on methods
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExecutionTime {
}

ApplicationConfig.java

package com.jbd.saop.around;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan("com.jbd.saop.around")
@EnableAspectJAutoProxy
public class ApplicationConfig {
}

步骤 3:创建切入点表达式和通知

切入点表达式匹配所有使用@ExecutionTime 注释的方法。如果任何方法抛出 RuntimeException,它将被记录。创建方面类,如下所示。

package com.jbd.saop.around.advice;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.context.annotation.Configuration;
import java.time.LocalTime;
@Configuration
@Aspect
public class ExecutionTimeLogger {
  @Around("@annotation(com.jbd.saop.around.ExecutionTime)")
  public Object logExecTime(ProceedingJoinPoint pJoinPoint){
    System.out.println("Before method: "
        + pJoinPoint.getSignature().toShortString());
    long beforeTime = System.currentTimeMillis();
    Object result = null;
    try {
       result = pJoinPoint.proceed();//Important
      //If method throws Exception or any error occurs
    } catch (Throwable throwable) {
      throwable.printStackTrace();
    }
    long afterTime = System.currentTimeMillis();
    System.out.println("Time taken to execute: "
        + (afterTime - beforeTime) + "ms");
    return result;
  }
}

第 4 步:测试示例

最后,我们将测试该示例以查看其输出。在运行测试用例之前,在 pom.xml 中添加测试依赖项。

<dependencies>
  ... 
   <!-- Test Dependencies-->
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-api</artifactId>
      <version>5.5.2</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-engine</artifactId>
      <version>5.5.2</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.2.2.RELEASE</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
...
<!-- Important for Jupiter-engine -->
<build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>3.0.0-M3</version>
      </plugin>
    </plugins>
  </build>
</project>

添加测试依赖项后,在 src\test 中创建 Test 类。

package com.jbd.saop.around;
import com.jbd.saop.around.dao.UserRepository;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
@SpringJUnitConfig(ApplicationConfig.class)
public class TestAroundAdvice {
  @Autowired
  private UserRepository userRepository;
  @Test
  public void testNull(){
    Assertions.assertNotNull(userRepository);
  }
  @Test
  public void testAddUser() throws InterruptedException {
    userRepository.add("sample_username");
  }
  @Test
  public void testAddUserFailure() throws InterruptedException {
    userRepository.add(null);
  }
}

测试用例应通过控制台中的以下输出。顺序可能不同。我建议分别运行每个测试用例并检查输出。

Before method: UserRepository.add(..)
New user added: sample_username
Time taken to execute: 124ms

Before method: UserRepository.add(..)
java.lang.RuntimeException: username is null
	at com.jbd.saop.around.dao.UserRepository.add(UserRepository.java:14)
	at com.jbd.saop.around.dao.UserRepository$$FastClassBySpringCGLIB$$dcb57c42.invoke()
...
Caused by: java.lang.NullPointerException
	... 86 more
Time taken to execute: 120ms

如您所见,Spring AOP 中的 around 建议非常强大。您可以更好地控制匹配的方法执行,尤其是由于 ProceedingJoinPoint。永远记得在通知方法中调用proceed()并返回Object值。

在 Spring AOP 中的After advice - @After

在 Spring AOP 中的After advice - @After

简介

在本文中,您将了解 Spring AOP 中的 After 通知,它在匹配的 Joinpoint 退出执行后运行。After 通知是使用 @After 注释声明的。它也被称为 After finally 通知,因为无论是否成功退出或由于异常而中断,它都会被调用。

您已经了解了上一篇文章中列出的 5 种建议类型,如下所示。

After (finally) advice execution – @After

After 通知也称为 After finally 通知,因为通知被执行,方法要么成功退出,要么通过抛出异常终止其执行。因此,After 通知必须准备好处理正常以及异常返回条件。一个简单的例子如下所示。

@After("execution(* com.jbd.saop.after.dao.*.*(..))")
  public void logResults(JoinPoint joinPoint){
    System.out.println("\nAfter advice executed: "
        + joinPoint.getSignature().toShortString());
  }

它通常用于释放资源和类似目的。

After throwing 通知示例 – @AfterThrowing

问题陈述——假设我们有一个用户管理应用程序。我们想要记录方法抛出的所有异常。我们将使用上一篇文章中的相同示例,并将空值传递给 add() 和 delete() 方法。

步骤 1:添加依赖项

创建一个maven项目,在pom.xml中添加如下依赖

<dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>5.2.2.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.2.RELEASE</version>
    </dependency>
    <!-- Step-1 Add the dependencies -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>5.2.2.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
     <version>1.9.5</version>
    </dependency>
<dependencies>

步骤 2:创建用户存储库

我们将切面应用到 DAO 类。所以我们创建了示例代码——UserRepository 和 Java 配置来注册这个类,如下所示。正如我们之前讨论过的,注解@EnableAspectJAutoProxy用于启用Spring-AOP。

UserRepository.java

package com.jbd.saop.after.dao;
import org.springframework.stereotype.Repository;
//A very stupid demo repository
@Repository
public class UserRepository {
  //Add a user
  public UserRepository add(String username){
    if(username == null) {
      throw new RuntimeException("username is null", new NullPointerException());
    }
    System.out.println("New user added: " + username);
    return this;
  }
  //Update an user
  public UserRepository update(String username, String email) {
    System.out.println("Update email: " + email);
    return this;
  }
  //Delete an user
  public boolean delete(String username){
    if (username == null) {
      throw new RuntimeException("username is null", new NullPointerException());
    }
    System.out.println("User deleted: " + username);
    return true;
  }
}

ApplicationConfig.java

package com.jbd.saop.after;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.jbd.saop.after")
public class ApplicationConfig {
}

步骤 3:创建切入点表达式和通知

切入点表达式匹配 Dao 类的所有方法。如果任何 Dao 类方法抛出 RuntimeException,它将与两个通知匹配。但是,如果任何方法成功退出,则只会执行 logResults()。

package com.jbd.saop.after.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Service;
@Service
@Aspect
public class AllAfterAspects {
  @Pointcut("execution(* com.jbd.saop.after.dao.*.*(..))")
  private void allDaoMethods(){}
  @AfterThrowing(
      pointcut = "allDaoMethods()",
      throwing = "exception")
  public void logDaoExceptions(JoinPoint joinPoint, RuntimeException exception) {
    System.out.println("After Exception executed: "
        + joinPoint.getSignature().toShortString());
    System.out.println("Exception details: " + exception);
  }
  @After("allDaoMethods()")
  public void logResults(JoinPoint joinPoint){
    System.out.println("\nAfter advice executed: "
        + joinPoint.getSignature().toShortString());
  }
}

第 4 步:测试示例

最后,我们将测试该示例以查看其输出。在运行测试用例之前,在 pom.xml 中添加测试依赖项。

<dependencies>
  ... 
   <!-- Test Dependencies-->
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-api</artifactId>
      <version>5.5.2</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-engine</artifactId>
      <version>5.5.2</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.2.2.RELEASE</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
...
<!-- Important for Jupiter-engine -->
<build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>3.0.0-M3</version>
      </plugin>
    </plugins>
  </build>
</project>

永远记得在使用 Junit-jupiter-* 时将 maven-surefire-plugin 升级到 3.0.0-M3 或更高版本。

添加测试依赖项后,在 src\test 中创建 Test 类。

package com.jbd.saop.after;
import com.jbd.saop.after.dao.UserRepository;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
@SpringJUnitConfig(ApplicationConfig.class)
public class TestAfterAdvice {
  @Autowired
  private UserRepository userRepository;
  @Test
  public void testAfterThrows() {
    //Assert exception
    Assertions.assertThrows(RuntimeException.class, () -> {
      userRepository.add(null);
      userRepository.delete(null);
    });
  }
  @Test
  public void testAfter() {
    //Assert not exception
    Assertions.assertDoesNotThrow(() -> {
      userRepository.delete("alexa");
    });
  }
}

测试用例应通过控制台中的以下输出。顺序可能不同。

输出

After advice executed: UserRepository.add(..)
After Exception executed: UserRepository.add(..)
Exception details: java.lang.RuntimeException: username is null

User deleted: alexa
After advice executed: UserRepository.delete(..)

正如您在输出中看到的,当 add() 方法抛出 RuntimeException 时,会调用两个建议(@After 和 @AfterThrowing)。只有 @After 建议在 delete() 完成执行后运行。

我希望能让你对 Spring AOP 中的 @After – After Advice 有一个很好的理解。在实际用例中,您可能希望使用它来执行资源释放类型的工作。

BabelJS - 使用 Babel 和 Webpack

发布于 2022.09.15 21分钟阅读 0 评论 5 推荐

    作者:

BabelJS - 使用 Babel 和 Webpack

Webpack 是一个模块打包器,它将所有具有依赖关系的模块——js、样式、图像等打包成静态资产 .js、.css、.jpg、.png 等。Webpack 带有预设,有助于编译成需要的形式。例如,有助于以 react 形式获得最终输出的 react 预设,有助于在 ES5 或 6 或 7 中编译代码的 es2015 或 env 预设等。我们在项目设置中使用了 babel 6。如果您想切换到 babel7,请使用 @babel/babel-package-name 安装所需的 babel 包。

在这里,我们将讨论使用 babel 和 webpack 进行项目设置。创建一个名为的文件夹并在 Visual Studio IDE 中打开它。

要创建项目设置,请运行 npm init babelwebpack,如下所示

这是在 npm init 之后创建的 package.json

现在,我们将安装使用 babel 和 webpack 所需的必要包。

npm install --save-dev webpack
npm install --save-dev webpack-dev-server
npm install --save-dev babel-core
npm install --save-dev babel-loader
npm install --save-dev babel-preset-env

这是安装后的 package.json

现在,我们将创建一个 webpack.config.js 文件,其中包含捆绑 js 文件的所有详细信息。这些文件将使用 babel 编译成 es5。

要使用服务器运行 webpack,我们使用 webpack-server。以下是添加到它的详细信息

我们添加了发布命令,它将启动 webpack-dev-server 并更新最终文件的存储路径。现在我们要用来更新最终文件的路径是 /dev 文件夹。

要使用 webpack,我们需要运行以下命令

npm run publish

首先我们需要创建 webpack.config.js 文件。这些将具有 webpack 工作的配置详细信息。 文件中的详细信息如下

var path = require('path');

module.exports = {
   entry: {
      app: './src/main.js'
   },
   output: {
      path: path.resolve(__dirname, 'dev'),
      filename: 'main_bundle.js'
   },
   mode:'development',
   module: {
      rules: [
         {
            test: /\.js$/,
            include: path.resolve(__dirname, 'src'),
            loader: 'babel-loader',
            query: {
               presets: ['env']
            }
         }
      ]
   }
};

文件结构如上图所示。它从路径开始,它给出了当前路径的详细信息。

var path = require('path'); //gives the current path

接下来是 module.exports 对象,它具有属性 entry、output 和 module。入口是起点。在这里,我们需要给出需要编译的主要js文件。

entry: {
   app: './src/main.js'
},

path.resolve(_dirname, ‘src/main.js’) - 将在目录中查找 src 文件夹,并在该文件夹中查找 main.js。

output: {
   path: path.resolve(__dirname, 'dev'),
   filename: 'main_bundle.js'
},

输出是具有路径和文件名详细信息的对象。路径将保存编译文件将保存的文件夹,文件名将告诉您.html文件中要使用的最终文件的名称。

module: {
   rules: [
      {
         test: /\.js$/,
         include: path.resolve(__dirname, 'src'),
         loader: 'babel-loader',
         query: {
            presets: ['env']
         }
      }
   ]
}

模块是一个包含规则细节的对象。它具有以下属性

  • test
  • include
  • loader
  • query

test 将保存所有以 .js 结尾的 js 文件的详细信息。它具有模式,它将在给定入口点的末尾查找 .js。

include 指示要查看的文件上正在使用的文件夹。

Loader 使用 babel-loader 来编译代码。

Query 有属性预设,它是一个值为 env 的数组 - es5 或 es6 或 es7。

在其中创建文件夹 src 和 main.js;用 ES6 编写你的 js 代码。稍后,运行命令以查看它使用 webpack 和 babel 编译为 es5。

src/main.js

let add = (a,b) => {
   return a+b;
};
let c = add(10, 20);
console.log(c);

运行命令

npm run pack

编译后的文件如下

dev/main_bundle.js

!function(e) {
   var t = {};
   function r(n) {
      if(t[n])return t[n].exports;var o = t[n] = {i:n,l:!1,exports:{}};
      return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports
   }
   r.m = e,r.c = t,r.d = function(e,t,n) {
      r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})
   },
   r.r = function(e) {
      "undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})
   },
   r.t = function(e,t) {
      if(1&t&&(e = r(e)),8&t)return e;
      if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;
      var n = Object.create(null);
      if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t) {return e[t]}.bind(null,o));
      return n
   },
   r.n = function(e) {
      var t = e&&e.__esModule?function() {return e.default}:function() {return e};
      return r.d(t,"a",t),t
   },
   r.o = function(e,t) {return Object.prototype.hasOwnProperty.call(e,t)},
   r.p = "",r(r.s = 0)
}([function(e,t,r) {"use strict";var n = function(e,t) {return e+t}(10,20);console.log(n)}]);
!function(e) {
   var t = {};
   function r(n) {
      if(t[n])return t[n].exports;
      var o = t[n] = {i:n,l:!1,exports:{}};
      return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports
   }
   r.m = e,r.c = t,r.d = function(e,t,n) {
      r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})
   },
   r.r = function(e) {
      "undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})
   },
   r.t = function(e,t) {
      if(1&t&&(e=r(e)),
      8&t)return e;
      if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;
      var n = Object.create(null);
      if(
         r.r(n),
         Object.defineProperty(n,"default",{enumerable:!0,value:e}),
         2&t&&"string"!=typeof e
      )
      for(var o in e)r.d(n,o,function(t) {return e[t]}.bind(null,o));
      return n
   },
   r.n = function(e) {
      var t = e&&e.__esModule?function() {return e.default}:function() {return e};
      return r.d(t,"a",t),t
   },
   r.o = function(e,t) {
      return Object.prototype.hasOwnProperty.call(e,t)
   },
   r.p = "",r(r.s = 0)
}([function(e,t,r) {
   "use strict";
   var n = function(e,t) {return e+t}(10,20);
   console.log(n)
}]);

代码编译如上所示。 Webpack 添加了一些内部需要的代码,最后可以看到来自 main.js 的代码。我们已经打印了上面显示的值。

在.html文件中添加最终的js文件如下

<html>
   <head></head>
   <body>
      <script type="text/javascript" src="dev/main_bundle.js"></script>
   </body>
</html>

运行命令

npm run publish

要检查输出,我们可以在

http://localhost:8080/

我们得到如上所示的控制台值。现在让我们尝试使用 webpack 和 babel 编译成单个文件。

我们将使用 webpack 将多个 js 文件打包成一个文件。 Babel 将用于将 es6 代码编译为 es5。 现在,我们在 src/ 文件夹中有 2 个 js 文件 - main.js 和 Person.js,如下所示

person.js

export class Person {
   constructor(fname, lname, age, address) {
      this.fname = fname;
      this.lname = lname;
      this.age = age;
      this.address = address;
   }

   get fullname() {
      return this.fname +"-"+this.lname;
   }
}

我们使用 export 来使用 Person 类的详细信息。

main.js

import {Person} from './person'
var a = new Person("Siya", "Kapoor", "15", "Mumbai");
var persondet = a.fullname;
console.log(persondet);

在 main.js 中,我们从文件路径中导入了 Person。

注意 - 我们不必包含 person.js,只需包含文件名。我们已经创建了一个 Person 类的对象,并如上所示对细节进行了控制台操作。

Webpack 会将 person.js 和 main.js 合并,并在 dev/main_bundle.js 中更新为一个文件。运行命令 npm run publish 在浏览器中检查输出

BabelJS - Babel 预设

发布于 2022.09.14 8分钟阅读 0 评论 5 推荐

    作者:

BabelJS - Babel 预设

Babel presets是 babel-transpiler 的配置详细信息,告诉它以指定的模式进行转换。以下是我们将在本章中讨论的一些最受欢迎的预设:

  • ES2015
  • Env
  • React

我们需要使用具有我们希望转换代码的环境的预设。或者例如,es2015 preset 会将代码转换为 es5。带有值 env 的预设也将转换为 es5。它还具有附加功能,即选项。如果您希望最新版本的浏览器支持该功能,只有当这些浏览器不支持该功能时,babel 才会转换代码。使用 Preset react,Babel 将在何时做出反应时转译代码。

要使用预设,我们需要在项目根文件夹中创建 .babelrc 文件。为了显示工作,我们将创建一个项目设置,如下所示。

npm init

我们必须安装所需的 babel 预设,如下所示以及 babel cli、babel core 等。

Babel 6 packages

npm install babel-cli babel-core babel-preset-es2015 --save-dev

Babel 7 Packages

npm install @babel/cli @babel/core @babel/preset-env --save-dev

注意 - babel-preset-es2015 从 babel 7 开始被弃用。

es2015 or @babel/env

在项目根目录下创建 .babelrc 文件(babel 6)

在 .babelrc 中,预设是 es2015。这是向 babel 编译器的指示,我们希望将代码转换为 es2015。

对于 babel 7,我们需要使用预设如下

{
   "presets":["@babel/env"]
}

这是安装后的package.json

由于我们在本地安装了 babel,所以我们在 package.json 的 scripts 部分添加了 babel 命令。

让我们用一个简单的例子来检查使用预设 es2015 的转译。

main.js

let arrow = () => {
   return "this is es6 arrow function";
}

转译为 es5,如下图所示。

npx babel main.js --out-file main_es5.js

main_es5.js

"use strict";

var arrow = function arrow() {
   return "this is es6 arrow function";
};

Env

使用 Env 预设,您可以指定最终代码要转译到的环境。 我们将使用上面创建的相同项目设置,并将预设从 es2015 更改为 env,如下所示。

另外,我们需要安装 babel-preset-env。我们将执行下面给出的命令来安装它。

npm install babel-preset-env --save-dev

我们将再次编译 main.js 并查看输出。

main.js

let arrow = () => {
   return "this is es6 arrow function";
}
npx babel main.js --out-file main_env.js

main_env.js

"use strict";

var arrow = function arrow() {
   return "this is es6 arrow function";
};

我们已经看到转译后的代码是 es5。如果我们知道我们的代码将在其中执行的环境,我们可以使用这个预设来指定它。例如,如果我们将浏览器指定为 chrome 和 firefox 的最后 1 个版本,如下所示。

npx babel main.js --out-file main_env.js

main_env.js

"use strict";

let arrow = () => {
   return "this is es6 arrow function";
};

我们现在得到了箭头函数的语法。它不会被转译成 ES5 语法。这是因为我们希望我们的代码支持的环境已经支持箭头功能。

Babel 负责使用 babel-preset-env 编译基于环境的代码。我们也可以基于nodejs环境进行目标编译如下图

代码的最终编译如下图所示。

npx babel main.js --out-file main_env.js

main_env.js

"use strict";

let arrow = () => {
   return "this is es6 arrow function";
};

Babel 按照当前版本的 nodejs 编译代码。

React Preset

我们在使用 Reactjs 时可以使用 react preset。我们将处理一个简单的示例,并使用 react preset 来查看输出。

要使用预设,我们需要安装 babel-preset-react (babel 6) 如下

npm install --save-dev babel-preset-react

对于 babel 7,如下

npm install --save-dev @babel/preset-react

babel6 对 .babelrc 的改动如下

对于babel 7

{
   "presets": ["@babel/preset-react"]
}

main.js

<h1>Hello, world!</h1>
npx babel main.js --out-file main_env.js

main_env.js

React.createElement(
   "h1",
   null,
   "Hello, world!"
);

main.js 中的代码使用预设:react 转换为 reactjs 语法。

揭秘 webpack 的“导入”功能:使用动态参数

揭秘 webpack 的“导入”功能:使用动态参数

在本文中,我们将学习揭开 webpack 的“导入”功能迷雾:使用动态参数。

虽然是 webpack 的一个热门卖点功能,但是导入功能有很多隐藏的细节和特性,很多开发者可能都不知道。例如,导入函数可以接受动态表达式,能够实现一些众所周知的特性,比如延迟加载等。您可以将动态表达式视为不是原始字符串(例如 import('./path/to/file.js'))的任何内容。动态表达式的一些示例可能是:import('./animals/' + 'cat' + '.js')import('./animals/' + animalName + '.js'),其中 animalName 是在运行时或编译时才有具体的值。在本文中,我们将深入探讨导入功能的动态表达式的概念,并希望在最后,您将更加熟悉此 webpack 功能提供的可能性范围。

本文没有特殊的先决条件,除了基本了解导入函数在其参数为静态时的行为(即它创建一个新块)。还有一篇名为An in-depth perspective on webpack's bundling process的文章解释了ModulesChunks等概念,但应该不会过多影响对本文的理解。

让我们开始吧!

The implications of dynamic arguments

尽管在编译时该值是未知的,但通过使用带有动态参数的 import() 函数,我们仍然可以实现延迟加载。与 SystemJS 不同,webpack 不能在运行时加载任意模块,因此在运行时知道值这一事实将限制 webpack 确保参数可以解析为的所有可能值都被考虑在内。我们将在以下部分中了解它的含义,我们将在其中检查import函数可以接受的自定义参数值。

现在,我们将专注于 import 的参数。以下所有部分都将基于相同的示例,其中有一个名为 animals的目录,其中有与animal相对应的文件:

├── animals
│   ├── cat.js
│   ├── dog.js
│   ├── fish.js
│   └── lion.js
├── index.js

每个示例都使用如下import函数:import('./animals/${fileName}.js')。就 ./animals/${fileName}.js 段而言,每个 ${fileName} 指的是一个动态部分,默认情况下会被替换为 /.*/ (你可以把它想象成一个 glob 模式)。给定的表达式可以有多个动态部分。提供的参数最终将生成一个 RegExp 对象,该对象将用于确定应考虑哪些文件。遍历从提供的路径的第一个静态部分开始(在本例中为 ./animals),在每一步中,它将从当前目录读取文件,并针对它们测试 RegExp 对象。它还可以遍历嵌套目录(这是默认行为),一旦正确发现文件,webpack 将根据选择的模式继续。在此示例中,生成的 RegExp 对象将是 /^\\.\\/.*\\.js$/ 并且它将针对所有位于 animals/ 目录中的文件进行测试(例如 regExp.test('. /cat.js'))。

重要的是要提到遍历和文件查找是在编译时完成的。

作为旁注,我们可以在配置文件中选择动态部分的替换以及是否应遍历嵌套目录:

// wepback.config.js
module: {
    parser: {
      javascript: {
        wrappedContextRegExp: /.*/,
		wrappedContextRecursive: true
      }
    }
  }

因此,wrappedContextRecursive 指定是否应该遍历嵌套目录(例如,是否考虑到 animal/aquatic/ 中的文件)并且使用 WrappedContextRegExp 我们可以告诉 webpack 用什么来替换表达式的动态部分。

基于默认配置,我们的初始表达式 ./animals/${fileName}.js 将导致 ./animals/.*.js(loosely)。

在接下来的部分中,我们将探讨一旦计算出这些文件会发生什么。

让我们开始吧!

延迟模式

这是默认模式,这意味着您不必明确指定它。假设有一个如下所示的目录结构:

├── animals
│   ├── cat.js
│   ├── dog.js
│   ├── fish.js
│   └── lion.js
└── index.js

通过在我们的应用程序代码中使用import功能:

// index.js

// In this example, the page shows an `input` tag and a button.
// The user is supposed to type an animal name and when the button is pressed,
// the chunk whose name corresponds to the animal name will be loaded.

let fileName;

// Here the animal name is written by the user.
document.querySelector('input').addEventListener('input', ev => {
  fileName = ev.target.value;
});

// And here the chunk is loaded. Notice how the chunk depends on the animal name
// written by the user.
document.getElementById('demo').addEventListener('click', () => {
  import(/* webpackChunkName: 'animal' */ `./animals/${fileName}.js`)
    .then(m => {
      console.warn('CHUNK LOADED!', m);
      m.default();
    })
    .catch(console.warn);
});

webpack 将为animal目录中的每个文件生成一个块。这是lazy选项的行为。在这个例子中发生的事情是,用户将在输入中键入动物的名称,当单击按钮时,将加载与该名称对应的块。如果在animal目录中找不到动物的名称,则会抛出错误。你现在可能会想:如果webpack创建多个chunk,最终只有一个chunk匹配路径,这不是浪费资源吗?好吧,实际上并非如此,因为所有这些可能的块只在服务器上保存文件,除非浏览器需要它们(例如,当 import() 的路径与现有文件路径匹配时),否则它们不会发送到浏览器。

与在编译时知道路径的静态导入情况一样(例如 import('./animals/cat.js)),当只创建一个块时,当导入的路径是动态时,加载的块将被缓存 ,因此在多次需要相同块的情况下不会浪费任何重要资源。

准确地说,webpack 将加载的块存储在一个映射中,这样如果请求的块已经被加载,它将立即从映射中检索。映射的键是块的 ID,值取决于块的状态:0(加载块时)、promise(当前加载块时)和undefined(没有从任何地方请求过块时) )。

我们可以从这个图中注意到已经创建的 4 个块(animal目录中的每个文件一个),以及主要的父块(称为索引)。拥有(根)父块至关重要,因为它包含在应用程序中获取和集成其他子块所需的逻辑。

webpack 在内部处理这种行为的方式是通过一个映射,其中键是文件名(在这种情况下,键是animal目录中的文件名),值是数组(正如我们将看到的,数组的模式将是{ filename:[moduleId,chunkId] })。这种类型的数组包含对 webpack 非常有用的信息,例如:chunk id(它将在 HTTP 请求中请求相应 JS 文件使用)、module id(以便它知道在一旦chunk完成加载时,需要哪个模块),最后是模块的导出类型(webpack 使用它是为了在使用 ES 模块以外的其他类型的模块时实现兼容性)。无论我们使用何种模式,都会使用这种用于跟踪模块及其特征的映射概念。

要查看该数组的外观示例,您可以打开 StackBlitz 应用程序,其链接可在本节开头(或此处)找到,然后运行 ​​npm run build 脚本。然后,如果您打开 dist/main.js 文件,您已经可以注意到我们之前谈到的映射:

var map = {
	"./cat.js": [
		2,
		0
	],
	"./dog.js": [
		3,
		1
	],
	"./fish.js": [
		4,
		2
	],
	"./lion.js": [
		5,
		3
	]
};

再一次,这个对象遵循这个模式:{ filename: [moduleId, chunkId] }。具体来说,如果用户键入 cat 然后按下按钮,id 为 2 的块将被加载,一旦块准备好,它将使用 id 为 0 的模块。

还值得探索一个数组指定了模块的导出类型的情况。在这种情况下,cat.js 文件是 CommonJS 模块,其余的是 ES 模块:

// cat.js
module.exports = () => console.log('CAT');

如果你运行 npm run build 并检查 dist/main.js 文件,map看起来会有点不同:

var map = {
	"./cat.js": [
		2,
		7,
		0
	],
	"./dog.js": [
		3,
		9,
		1
	],
	"./fish.js": [
		4,
		9,
		2
	],
	"./lion.js": [
		5,
		9,
		3
	]
};

在这里,模式是这样的:{ filename: [moduleId, moduleExportsMode, chunkId] }。基于模块的导出类型,webpack 知道在块加载后如何加载模块。基本上,9 表示一个简单的 ES 模块,在这种情况下,将需要具有 moduleId的模块。 7表示一个 CommonJS 模块,在这种情况下 webpack 需要从它创建一个fake ES 模块。

要在实践中查看它,您可以打开最后提供的示例并启动服务器。如果我想使用 cat 模块,在单击按钮后,我应该会看到一个对包含相关模块的块的新请求:

可能已经注意到,控制台告诉我们块已经加载,以及它包含的模块,即 cat 模块。例如,如果我们想使用 fish 模块,也会采取相同的步骤:

导入匹配的每个文件,都会发生同样的情况。

 

eager模式

让我们首先看看我们将在本节中使用的示例:

let fileName;

// Here the animal name is written by the user.
document.querySelector('input').addEventListener('input', ev => {
  fileName = ev.target.value;
});

// Here the chunk that depends on `fileName` is loaded.
document.getElementById('demo').addEventListener('click', () => {
  import(/* webpackChunkName: 'animal', webpackMode: 'eager' */ `./animals/${fileName}.js`)
    .then(m => {
      console.warn('FILE LOADED!', m);
      m.default();
    })
    .catch(console.warn);
});

如您所见,可以使用 webpackMode: 'eager' 注释指定模式。

使用 Eager 模式时,不会创建任何额外的块。与导入模式匹配的所有模块都将成为同一主块的一部分。更具体地说,考虑到相同的文件结构,

├── animals
│   ├── cat.js
│   ├── dog.js
│   ├── fish.js
│   └── lion.js
└── index.js

就好像当前模块将直接需要动物目录中的模块,但实际上没有任何模块将被执行。它们只会被放置到模块的对象/数组中,当它单击按钮时,它将在现场执行并检索该模块,而无需额外的网络请求或任何其他异步操作。

运行 npm run build 并打开 dist/main.js 文件后,您应该会看到一个像这样的map对象:

var map = {
	"./cat.js": 2,
	"./dog.js": 3,
	"./fish.js": 4,
	"./lion.js": 5
};

每个值都表示模块的 ID,如果您向下滚动一点,您会找到这些模块:

/* 2 */ // -> The `cat.js` file
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {},
/* 3 */ // -> The `dog.js` file
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {}

因此,这种方法的优点是模块在需要时会立即被检索,而不是为每个模块发出额外的 HTTP 请求,这是使用惰性模式时发生的情况。

这可以在我们的示例中得到验证:启动服务器后,尝试 require 存在于 animals 目录中的任何模块。预期的行为是“网络”面板中不应出现任何请求,并且应正确执行每个现有模块

最后,这里有一张图表来总结这个模式的行为:

lazy-once模式

在上一节中,我们已经看到了如何手动指定模式,因此告诉 webpack 我们要使用惰性一次模式的方式应该不足为奇:

/*
The same file structure is assumed:

├── animals
│   ├── cat.js
│   ├── dog.js
│   ├── fish.js
│   └── lion.js
└── index.js
*/
let fileName;

// Here the user chooses the name of the module.
document.querySelector('input').addEventListener('input', ev => {
  fileName = ev.target.value;
});

// When clicked, the chunk will be loaded and the module that matches with the `fileName`
// variable will be executed and retrieved.
document.getElementById('demo').addEventListener('click', () => {
  import(/* webpackChunkName: 'animal', webpackMode: 'lazy-once' */ `./animals/${fileName}.js`)
    .then(m => {
      console.warn('FILE LOADED!', m);
      m.default();
    })
    .catch(console.warn);
});

这种情况下的行为在某种程度上类似于我们在上一节中遇到的情况,除了所有与导入表达式匹配的模块都将添加到子块而不是主块中。这样,就惰性块而言,它也接近惰性模式。

运行 npm run build 后,dist 目录应该有 2 个文件:main.js,它是主块,以及 animal.js,它是所有与目录中的文件对应的模块所驻留的块。这种加载模块的方式的好处是你不会用所有可以匹配导入表达式的可能模块来重载主块,而是将它们放在另一个可以延迟加载的块中。当用户按下按钮加载模块时,将通过网络请求整个块,当它准备好时,将执行并检索用户请求的模块。

此外,这个新加载的块包含的所有模块都将被 webpack 注册。有趣的是,如果现在用户需要一个不同的模块,该模块也属于刚刚加载的块,那么网络上不会有任何额外的请求。这是因为块将从 webpack 内部维护的缓存中提供服务,并且所需的模块将从 webpack 记录它们的模块的数组/对象中检索。

让我们在我们的示例中也尝试一下。我将首先输入 cat 然后按下按钮。在 Network 选项卡中,应该有一个对 animal 块的请求,如前所述,它包含所有必要的模块:

现在,如果我们想使用 lion 模块,我应该不会看到新请求,而只会看到 lion 模块已执行的确认:

这是一个图表来补充到目前为止所积累的内容:

weak模式

由于它的特殊性,我们将这部分保存到最后。通过使用弱导入,我们实质上是在告诉 webpack 我们想要使用的资源应该已经准备好进行检索。意味着现在应该从其他地方加载(即需要和使用)有问题的资源,因此,当使用弱导入时,此操作不会触发任何获取机制(例如,发出网络请求以加载块),而仅使用 webpack 用于跟踪模块的数据结构中的模块.

我们将从一个简单的示例开始,该示例最初会引发错误,然后我们将对其进行扩展,以便更好地了解此弱模式的含义:

let fileName;

// Here the user types the name of the module
document.querySelector('input').addEventListener('input', ev => {
  fileName = ev.target.value;
});

// Here that module is retrieved directly if possible, otherwise
// an error will be thrown.
document.getElementById('demo').addEventListener('click', () => {
  import(/* webpackChunkName: 'animal', webpackMode: 'weak' */ `./animals/${fileName}.js`)
    .then(m => {
      console.warn('FILE LOADED!', m);
      m.default();
    })
    .catch(console.warn);
});

到目前为止没有详细说明,这只是我们在其他部分一直在做的,即指定我们希望导入函数运行的模式,在这种情况下是弱的。

如果您在输入中键入 cat 然后按下按钮,您会在控制台中注意到一个错误:

这应该是有道理的,因为正如前面提到的,弱导入期望资源应该已经准备好使用,而不是让 webpack 采取行动以使其可用。我们目前做事的方式, cat 模块不是从其他任何地方加载的,所以这就是我们面临错误的原因。

为了快速缓解这个问题,我们可以添加一个 import * as c from './animals/cat';文件开头的声明:

// index.js

import * as c from './animals/cat';

let fileName;
/* ... */

如果我们再次运行 npm run build 和 npm run start 并执行相同的步骤,我们应该会看到 cat 模块已成功执行。但是,如果您尝试使用 cat 以外的任何其他模块,则会出现相同的错误:

此功能可用于强制预先加载模块,以便您确保在某个点可访问模块。否则会抛出错误。

与其他模式相反,模块不会被添加到当前块中,也不会添加到子块中,也不会添加到自己的块中。在这种情况下,webpack 所做的是跟踪与导入表达式匹配的模块是否存在,并在需要时跟踪模块的导出类型(例如,如果它们都是 ES 模块,则不需要它) .例如:

var map = {
	"./cat.js": 1,
	"./dog.js": null,
	"./fish.js": null,
	"./lion.js": null
};

在上面的地图中(可以在 dist/main.js 文件中找到 - 唯一生成的文件),可以确定 cat 模块在整个应用程序中使用。但是,它并不一定保证 cat 模块可用。因此,上面的地图对象的作用就是跟踪项目中完全有目的的模块(即它们是否被使用)。换句话说,它跟踪模块的存在。其他值为 null 的模块称为孤立模块。

可能存在模块存在但不可用的情况。考虑以下示例:

let fileName;

// Here the user chooses the name of the file.
document.querySelector('input').addEventListener('input', ev => {
  fileName = ev.target.value;
});

// Requesting the module that should already be available.
document.getElementById('demo').addEventListener('click', () => {
  import(/* webpackChunkName: 'animal', webpackMode: 'weak' */ `./animals/${fileName}.js`)
    .then(m => {
      console.warn('FILE LOADED!', m);
      m.default();
    })
    .catch(console.warn);
});

// Dynamically loading the `cat.js` module.
document.getElementById('load-cat').addEventListener('click', () => {
  import('./animals/cat.js').then(m => {
    console.warn('CAT CHUNK LOADED');
  });
});

import('./animals/cat.js') 语句中,我们可以看出该模块存在于应用程序中,但为了使其可用,必须先单击#load-cat 按钮。通过单击它,将获取块并且 cat 模块将变得可访问,这是因为当加载块时,它的所有模块都将可用于整个应用程序。

我们可以尝试直接 require cat 模块(无需先按 Load cat 块),但最终会报错,说模块不可用:

但是,如果我们先加载 cat 块然后再 require 模块,那么一切都应该正常工作:

本节的要点是,当使用弱模式时,预计资源已经在手边。因此,模块必须克服 3 个过滤器:它必须与导入表达式匹配,它必须在整个应用程序中使用(例如,它直接导入或通过块导入)并且它必须可用(即已经从其他地方加载)。

总结:

在本文中,我们了解到导入函数可以做的不仅仅是创建一个块。希望在这一点上,当涉及到使用带有动态参数的导入时,事情会变得更有意义。

After Throwing advice in Spring AOP – @AfterThrowing

After Throwing advice in Spring AOP – @AfterThrowing

简介

匹配的方法通过抛出异常完成/中止其执行后调用抛出通知之后。您已经知道,这种匹配方法的名称是 Joinpoint。您可以使用 @AfterThrowing 注释声明 After throwing advice。

您已经了解了上一篇文章中列出的 5 种建议类型,如下所示。

After throwing advice execution

您可以声明一个 After throwing 通知,如下所示。或者,您可以获取方法执行引发的异常和 Joinpoint 详细信息。

package com.jbd.saop.after.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Service;
@Service
@Aspect
public class AllAfterAspects {
  @AfterThrowing(
      pointcut = "execution(* c.jbd.saop.at.dao.*.*(..))",
      throwing = "exception")
  public void logDaoExceptions(JoinPoint joinPoint, RuntimeException exception) {
    System.out.println("Exception at: " + joinPoint.getSignature().toShortString());
    System.out.println("Exception details: " + exception);
  }
}

抛出子句还将匹配限制为仅抛出指定类型异常的那些方法执行。在这种情况下,它是 RuntimeException。

 

After throwing advice示例 – @AfterThrowing

问题陈述——假设我们有一个用户管理应用程序。我们想要记录方法抛出的所有异常。我们将使用上一篇文章中的相同示例,并将空值传递给 add() 和 delete() 方法。

步骤 1:添加依赖项

<dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>5.2.2.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.2.RELEASE</version>
    </dependency>
    <!-- Step-1 Add the dependencies -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>5.2.2.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.5</version>
    </dependency>
<dependencies>

步骤 2:创建用户存储库

我们将切面应用到 DAO 类。所以我们创建了示例代码——UserRepositoryJava 配置来注册这个类,如下所示。正如我们之前讨论过的,注解@EnableAspectJAutoProxy 用于启用Spring-AOP。

UserRepository.java

package c.jbd.saop.at.dao;
import org.springframework.stereotype.Repository;
//A very stupid demo repository
@Repository
public class UserRepository {
  //Add a user
  public UserRepository add(String username){
    if(username == null) {
      throw new RuntimeException("username is null", new NullPointerException());
    }
    System.out.println("New user added: " + username);
    return this;
  }
  //Update an user
  public UserRepository update(String username, String email) {
    System.out.println("Update email: " + email);
    return this;
  }
  //Delete an user
  public boolean delete(String username){
    if (username == null) {
      throw new RuntimeException("username is null", new NullPointerException());
    }
    System.out.println("User deleted: " + username);
    return true;
  }
}

ApplicationConfig.java

package c.jbd.saop.at;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("c.jbd.saop.at")
public class ApplicationConfig {
}

步骤 3:创建切入点表达式和通知

切入点表达式与所有 Dao 类方法匹配。如果 Dao 类方法中的任何一个抛出 RuntimeException,它将与这个切面进行匹配,并执行 logDaoExceptions() 中的代码。

package c.jbd.saop.at.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.context.annotation.Configuration;
@Configuration
@Aspect
public class ExceptionAspect {
  /**
   * Match with all dao methods.
   */
  @AfterThrowing(
      pointcut = "execution(* c.jbd.saop.at.dao.*.*(..))",
      throwing = "exception")
  public void logDaoExceptions(JoinPoint joinPoint, RuntimeException exception) {
    System.out.println("Exception at: " + joinPoint.getSignature().toShortString());
    System.out.println("Exception details: " + exception);
  }
}

第 4 步:测试示例

最后,我们将测试该示例以查看其输出。在运行测试用例之前,在 pom.xml 中添加测试依赖项。

<dependencies>
  ... 
   <!-- Test Dependencies-->
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-api</artifactId>
      <version>5.5.2</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-engine</artifactId>
      <version>5.5.2</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.2.2.RELEASE</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
...
<!-- Important for Jupiter-engine -->
<build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>3.0.0-M3</version>
      </plugin>
    </plugins>
  </build>
</project>

永远记得在使用 Junit-jupiter-* 时将 maven-surefire-plugin 升级到 3.0.0-M3 或更高版本。

添加测试依赖项后,在 src\test 中创建 Test 类。

package c.jbd.saop.at;
import c.jbd.saop.at.dao.UserRepository;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
@SpringJUnitConfig(ApplicationConfig.class)
public class TestAfterThrowing {
  @Autowired
  private UserRepository userRepository;
  @Test
  public void notNull() {
    Assertions.assertNotNull(userRepository);
  }
  @Test
  public void testThrows() {
    //Assert throws RuntimeException
    Assertions.assertThrows(RuntimeException.class, () -> {
      userRepository.add(null);
      userRepository.delete(null);
    });
    //Assert does not throw exception
    Assertions.assertDoesNotThrow(() -> {
      userRepository.delete("alexa");
    });
  }
}

测试用例应通过控制台中的以下输出。

我希望已经让您对@AfterThrowing – Spring AOP 中的投掷后建议有一个很好的理解。在实际用例中,您可能希望使用它对异常执行日志或分析。

After method - Exception at: UserRepository.add(..)
Exception details: java.lang.RuntimeException: username is null
User deleted: alexa

在 Spring AOP 中返回通知后 – @AfterReturning

在 Spring AOP 中返回通知后 – @AfterReturning

简介

在匹配的方法完成执行并返回一个值后,返回通知被调用。您已经知道,这种匹配方法的名称是 Joinpoint。您可以使用 @AfterReturning 注释声明 After 返回通知。

您已经了解了上一篇文章中列出的 5 种建议类型,如下所示。

  • Before advice – @Before
  • After returning – @AfterReturning
  • After throwing – @AfterThrowing
  • After (finally) advice – @After
  • Around advice – @Around

@AfterReturning Advice 的执行

@AfterReturning 建议在 Joinpoint 方法正常返回后执行。我们只是指方法执行正常返回,匹配的方法可以有任何返回类型。

@Configuration
@Aspect
public class UppercaseAspect {
  @AfterReturning("c.jbd.saop.ar.aspect.DaoExpressions.findMethod()")
  public void uppercaseUsername(JoinPoint joinPoint, User result){
  //advice body
  }
}

访问返回值

您可能需要访问从 Joinpoint 返回的实际值。您可以使用绑定返回值的@AfterReturning 来获得该访问权限。下面的示例显示了访问返回值和 Joinpoint。

@Configuration
@Aspect
public class UppercaseAspect {
  @AfterReturning(
      pointcut = "c.jbd.saop.ar.aspect.DaoExpressions.findMethod()",
      returning = "result")
  public void uppercaseUsername(JoinPoint joinPoint, User result){
   //Advice body goes here
  }
}

返回属性中使用的名称必须与通知方法中的参数名称相对应。

当方法执行返回时,返回值被传递给通知方法。

 

返回通知后的示例 – @AfterReturning

问题陈述——假设我们有一个用户管理应用程序。我们需要确保大写的用户名总是从 userDao 返回。将使用 @AfterReturning 来操作来自 UserRepository:find() 方法的实际返回值。

步骤 1:添加依赖项

创建一个maven项目,在pom.xml中添加如下依赖

<dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>5.2.2.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.2.RELEASE</version>
    </dependency>
    <!-- Step-1 Add the dependencies -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>5.2.2.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.5</version>
    </dependency>
<dependencies>

步骤 2:创建用户存储库

我们将方面应用到 DAO 类。所以我们创建了示例代码——UserRepository、User 和 Java 配置来注册这个类,如下所示。正如我们之前讨论过的,注解@EnableAspectJAutoProxy 用于启用Spring-Aop。

UserRepository.java

package c.jbd.saop.ar.dao;
import c.jbd.saop.ar.pojo.User;
import org.springframework.stereotype.Repository;
//A very stupid demo repository
@Repository
public class UserRepository {
  //find an User
  public User find(String username) {
    if (username == null) {
      throw new RuntimeException("username is null", new NullPointerException());
    }
    return new User(username, "hello@world.com");
  }
  //Update an user
  public UserRepository update(String username, String email) {
    System.out.println("Update email: " + email);
    return this;
  }
  //Delete an user
  public boolean delete(String username) {
    if (username == null) {
      throw new RuntimeException("username is null", new NullPointerException());
    }
    System.out.println("User deleted: " + username);
    return true;
  }
}

User.java

package c.jbd.saop.ar.pojo;
import java.util.StringJoiner;
//The User pojo
public class User {
  private String username;
  private String email;
  public User(String username, String email){
    this.email = email;
    this.username = username;
  }
  public String getUsername() {
    return username;
  }
  public User setUsername(String username) {
    this.username = username;
    return this;
  }
  public String getEmail() {
    return email;
  }
  public User setEmail(String email) {
    this.email = email;
    return this;
  }
  @Override
  public String toString() {
    return new StringJoiner(", ", User.class.getSimpleName() + "[", "]")
        .add("username='" + username + "'")
        .add("email='" + email + "'")
        .toString();
  }
}

ApplicationConfig.java

package c.jbd.saop.ar;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("c.jbd.saop.ar")
public class ApplicationConfig {
}

步骤 3:创建切入点表达式和advice

我会将 Pointcut 表达式和 Advice 方法保存在单独的类中。我只是试图向您展示表达式的组合和共享。整个表达式可以简化为“execution(* c.jbd.saop.ar.dao.UserRepository.find(..))”。

DaoExpressions.java

package c.jbd.saop.ar.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class DaoExpressions {
  //Match with only UserRepository
  @Pointcut("within(c.jbd.saop.ar.dao.UserRepository)")
  public void userDao() {}
  //Match with any find() method
  @Pointcut("execution(* find(..))")
  public void findMethod() {}
  @Pointcut("userDao() && findMethod()")
  public void userDaoFind(){}
}

UppercaseAspect.java

package c.jbd.saop.ar.aspect;
import c.jbd.saop.ar.pojo.User;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.context.annotation.Configuration;
@Configuration
@Aspect
public class UppercaseAspect {
  @AfterReturning(
      pointcut = "c.jbd.saop.ar.aspect.DaoExpressions.findMethod()",
      returning = "result")
  public void uppercaseUsername(JoinPoint joinPoint, User result){
    System.out.println("After method - " +
        joinPoint.getSignature().toShortString());
    System.out.println("original result:" + result);
    if (result.getUsername() != null){
      result.setUsername(result.getUsername().toUpperCase());
    }
    System.out.println("final result: " + result);
  }
}

第 4 步:测试示例

最后,我们将测试该示例以查看其输出。在运行测试用例之前,在 pom.xml 中添加测试依赖项。

<dependencies>
  ... 
   <!-- Test Dependencies-->
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-api</artifactId>
      <version>5.5.2</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-engine</artifactId>
      <version>5.5.2</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.2.2.RELEASE</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
...
<!-- Important for Jupiter-engine -->
<build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>3.0.0-M3</version>
      </plugin>
    </plugins>
  </build>
</project>

永远记得在使用 Junit-jupiter-* 时将 maven-surefire-plugin 升级到 3.0.0-M3 或更高版本。

添加测试依赖项后,在 src\test 中创建 Test 类。

package c.jbd.saop.ar;
import c.jbd.saop.ar.dao.UserRepository;
import c.jbd.saop.ar.pojo.User;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
@SpringJUnitConfig(ApplicationConfig.class)
public class TestAfterReturning {
  @Autowired
  private UserRepository userRepository;
  @Test
  public void checkNull(){
    Assertions.assertNotNull(userRepository);
  }
  @Test
  public void testFindUser(){
    String username = "JsTobigdata";
    User user = userRepository.find(username);
    Assertions.assertEquals(user.getUsername(), username.toUpperCase());
    Assertions.assertNotEquals(user.getUsername(), username);
  }
}

测试应通过控制台中的以下输出。

After method - UserRepository.find(..)
original result:User[username='JsTobigdata', email='hello@world.com']
final result: User[username='JSTOBIGDATA', email='hello@world.com']

BabelJS - Babel CLI

发布于 2022.09.13 13分钟阅读 0 评论 5 推荐

    作者:

BabelJS - Babel CLI

BabelJS 带有一个内置的命令行界面,其中 JavaScript 代码可以使用的命令轻松编译为相应的 ECMA 脚本。我们将在本文中讨论这些命令的使用。

首先,我们将为我们的项目安装 babel-cli。我们将使用 babeljs 来编译代码。

为您的项目创建一个文件夹以使用 babel-cli。

npm init

为项目创建的 Package.json

让我们运行命令来安装 babel-cli。

Package for babel 6

npm install --save-dev babel-cli

Package for babel 7

npm install --save-dev @babel/cli

我们已经安装了 babel-cli,这里是更新的 package.json

除此之外,我们还需要安装 babel-preset 和 babel-core。现在让我们看看安装命令。

Packages for babel 6

npm install --save-dev babel-preset-env
npm install --save-dev babel-core

Packages for babel 7

npm install --save-dev @babel/core
npm install --save-dev @babel/preset-env

这是上述命令的更新 package.json

由于我们需要编译为我们将要编写的 JavaScript 代码以具有向后兼容性,因此我们将其编译为 ECMA Script 5。为此,我们需要指示 babel 查找预设,即将在其中完成编译的 es 版本。我们需要在我们创建的项目的根文件夹中创建一个 .babelrc> 文件,如下所示。

它包含一个带有以下预设详细信息的 json 对象

{ "presets": ["env"] }

对于 babel 7,.babelrc 如下

{
   "presets":["@babel/env"]
}

我们已经在项目本地安装了 babel。为了在我们的项目中使用 babel,我们需要在 package.json 中指定相同的内容,如下所示

编译 JS 文件

现在我们准备编译我们的 JavaScript 文件。在你的项目中创建一个文件夹 src;在这个文件夹中,创建一个名为 main.js 的文件并编写一个 es6 javascript 代码,如下所示

npx babel src/main.js

在上述情况下,来自 main.js 的代码在 es5 版本的终端中显示。如上图所示,es6 中的箭头函数转换为 es5。我们不会在终端中显示编译后的代码,而是将其存储在不同的文件中,如下所示。

我们在项目中创建了一个名为 out 的文件夹,我们希望存储编译后的文件。以下是将编译输出并将其存储在我们想要的位置的命令。

npx babel src/main.js --out-file out/main_out.js

命令中的选项 --out-file 帮助我们将输出存储在我们选择的文件位置。

如果我们希望每次更改主文件时都更新文件,请在命令中添加 --watch 或 -w 选项,如下所示。

npx babel src/main.js --watch --out-file out/main_out.js

您可以对主文件进行更改;此更改将反映在编译文件中。

在上述情况下,我们更改了日志消息,并且 --watch 选项不断检查任何更改,并且相同的更改被添加到编译文件中。

编译后文件

在我们之前的部分中,我们学习了如何编译单个文件。现在,我们将编译一个目录并将编译后的文件存储在另一个目录中。

在 src 文件夹中,我们将再创建一个名为 main1.js 的 js 文件。目前src文件夹有2个javascript文件main.jsmain1.js

以下是文件中的代码

main.js

var arrowfunction = () => {
   console.log("Added changes to the log message");
}

main1.js

var handler = () => {
   console.log("Added one more file");
}

以下命令将从 src 文件夹编译代码并将其存储在 out/ 文件夹中。我们已经从 out/ 文件夹中删除了所有文件并将其保留为空。我们将运行命令并检查 out/ 文件夹中的输出。

npx babel src --out-dir out

我们在 out 文件夹中有 2 个文件 - main.jsmain1.js

main.js

"use strict";

var arrowfunction = function arrowfunction() {
   console.log("Added changes to the log message");
};

main1.js

"use strict";

var handler = function handler() {
   console.log("Added one more file");
};

接下来,我们将执行下面给出的命令,使用 babeljs 将这两个文件编译成一个文件。

npx babel src --out-file out/all.js

all.js

"use strict";

var arrowfunction = function arrowfunction() {
   console.log("Added changes to the log message");
};
"use strict";

var handler = function handler() {
console.log("Added one more file");
};

如果我们想忽略一些正在编译的文件,我们可以使用 --ignore 选项,如下所示。

npx babel src --out-file out/all.js --ignore src/main1.js

all.js

"use strict";

var arrowfunction = function arrowfunction() {
   console.log("Added changes to the log message");
};

我们可以在文件编译期间使用插件选项。要使用插件,我们需要安装它,如下所示。

npm install --save-dev babel-plugin-transform-exponentiation-operator

expo.js

let sqr = 9 ** 2;
console.log(sqr);
npx babel expo.js --out-file expo_compiled.js --plugins=babel-plugin-transform-exponentiation-operator

输出

"use strict";

var sqr = Math.pow(9, 2);
console.log(sqr);

我们也可以在命令中使用presets,如下图

npx babel src/main.js --out-file main_es5.js --presets=es2015

为了测试上述情况,我们从 .babelrc 中删除了presets选项。

main.js

var arrowfunction = () => {
   console.log("Added changes to the log message");
}

main_es5.js

"use strict";

var arrowfunction = function arrowfunction() {
   console.log("Added changes to the log message");
};

我们也可以在命令行中忽略 .babelrc,如下所示

npx babel --no-babelrc src/main.js --out-file main_es5.js --presets=es2015

为了测试上述情况,我们已经将presets添加回 .babelrc 并且由于我们在命令中添加了 --no-babelrc,同样会被忽略。 main_es5.js文件详情如下

main_es5.js

"use strict";

var arrowfunction = function arrowfunction() {
   console.log("Added changes to the log message");
};

BabelJS - Babel Polyfill

发布于 2022.09.09 8分钟阅读 0 评论 5 推荐

    作者:

BabelJS - Babel Polyfill

Babel Polyfill 为 Web 浏览器添加了对功能的支持,这些功能不可用。 Babel 将代码从最近的 ecma 版本编译为我们想要的版本。它根据预设更改语法,但不能对使用的对象或方法做任何事情。为了向后兼容,我们必须为这些功能使用 polyfill。

可以polyfilled的功能

以下是在旧浏览器中使用时需要 polyfill 支持的功能列表:

  • Promises
  • Map
  • Set
  • Symbol
  • Weakmap
  • Weakset
  • Array.from, Array.includes, Array.of, Array#find, Array.buffer, Array#findIndex
  • Object.assign, Object.entries, Object.values

我们将创建项目设置并查看 babel polyfill 的工作。

npm init

我们现在将安装 babel 所需的包。

Packages for babel 6

npm install babel-cli babel-core babel-preset-es2015 --save-dev

Packages for babel 7

npm install @babel/cli @babel/core @babel/preset-env --save-dev

这是最终的 package.json

我们还将 es2015 添加到预设中,因为我们要将代码编译为 es5。

.babelrc 用于 babel 6

{
    "presets": [
        "es2015"
    ]
}

.babelrc for babel 7

{
   "presets":["@babel/env"]
}

我们将安装一个 lite-serve 以便我们可以在浏览器中测试我们的代码

npm install --save-dev lite-server

让我们在 package.json 中添加 babel 命令来编译我们的代码

我们还添加了调用 lite-server 的构建命令。

Babel-polyfill 与 babel-core 包一起安装。 babel-polyfill 将在节点模块中可用,如下所示

我们将进一步处理 Promise 并与它一起使用 babel-polyfill。

ES6 - Promises

let timingpromise = new Promise((resolve, reject) => {
   setTimeout(function() {
      resolve("Promise is resolved!");
   }, 1000);
});

timingpromise.then((msg) => {
   console.log("%c"+msg, "font-size:25px;color:red;");
});
npx babel promise.js --out-file promise_es5.js

BabelJS - ES5

"use strict";

var timingpromise = new Promise(function (resolve, reject) {
   setTimeout(function () {
      resolve("Promise is resolved!");
   }, 1000);
});

timingpromise.then(function (msg) {
   console.log("%c"+msg, "font-size:25px;color:red;");
});

编译不需要改变任何东西。 Promise 的代码已按原样编译。但是不支持 Promise 的浏览器会抛出错误,即使我们已经将代码编译为 es5。

为了解决这个问题,我们需要在最终的 es5 编译代码中添加 polyfill。要在浏览器中运行代码,我们将从节点模块中获取 babel-polyfill 文件并将其添加到我们要使用 Promise 的 .html 文件中,如下所示

index.html

<html>
   <head>
   </head>
   <body>
      <h1>Babel Polyfill Testing</h1>
      <script type="text/javascript" src="node_modules/babel-polyfill/dist/polyfill.min.js"></script>
      <script type="text/javascript" src="promise_es5.js"></script>
   </body>
</html>

输出

在 index.html 文件中,我们使用了 node_modules 中的 polyfill.min.js 文件,然后是 promise_es5.js

<script type="text/javascript" src="node_modules/babel-polyfill/dist/polyfill.min.js"></script>

<script type="text/javascript" src="promise_es5.js"></script>

注意 - 在主 javascript 调用之前,必须在开始时使用 Polyfill 文件。

 

String Padding

字符串填充根据指定的长度从左侧添加另一个字符串。字符串填充的语法如下所示:

str.padStart(length, string);
str.padEnd(length, string);
const str = 'abc';

console.log(str.padStart(8, '_'));
console.log(str.padEnd(8, '_'));

输出:

_____abc
abc_____
npx babel strpad.js --out-file strpad_es5.js

结果:

'use strict';

var str = 'abc';

console.log(str.padStart(8, '_'));
console.log(str.padEnd(8, '_'));

js 必须与 babel-polyfill 一起使用,如下所示

<!DOCTYPE html>
<html>
   <head>
      <title>BabelJs Testing </title>
   </head>
   <body>
      <script src="node_modules/babel-polyfill/dist/polyfill.min.js" type="text/javascript"></script>
      <script type="text/javascript" src="strpad_es5.js"></script>
   </body>
</html>

Map, Set, WeakSet, WeakMap

本节我们将学习Map、Set、WeakSet、WeakMap。

Map 是一个具有键/值对的对象。 

Set 也是一个对象,但具有唯一的值。 

WeakMap 和 WeakSet 也是具有键/值对的对象。

Map、Set、WeakMap 和 WeakSet 是 ES6 新增的特性。要将其转换为在旧浏览器中使用,我们需要使用 polyfill。我们将研究一个示例并使用 polyfill 来编译代码。

let m = new Map(); //map example
m.set("0","A");
m.set("1","B");
console.log(m);

let set = new Set(); //set example
set.add('A');
set.add('B');
set.add('A');
set.add('B');
console.log(set);

let ws = new WeakSet(); //weakset example
let x = {};
let y = {};
ws.add(x);
console.log(ws.has(x));
console.log(ws.has(y));

let wm = new WeakMap(); //weakmap example
let a = {};
wm.set(a, "hello");
console.log(wm.get(a));

输出

Map(2) {"0" => "A", "1" => "B"}
Set(2) {"A", "B"}
true
false
hello
npx babel set.js --out-file set_es5.js
"use strict";

var m = new Map(); //map example
m.set("0", "A");
m.set("1", "B");
console.log(m);

var set = new Set(); //set example
set.add('A');
set.add('B');
set.add('A');
set.add('B');
console.log(set);

var ws = new WeakSet(); //weakset example
var x = {};
var y = {};
ws.add(x);
console.log(ws.has(x));
console.log(ws.has(y));

var wm = new WeakMap(); //weakmap example
var a = {};
wm.set(a, "hello");
console.log(wm.get(a));

js 必须与 babel-polyfill 一起使用,如下所示

<!DOCTYPE html>
<html>
   <head>
      <title>BabelJs Testing</title>
   </head>
   <body>
      <script src="node_modules/babel-polyfill/dist/polyfill.min.js" type="text/javascript"></script>
      <script type="text/javascript" src="set_es5.js"></script>
   </body>
</html>

Array Methods

许多属性和方法可以用于数组;例如,array.from、array.includes 等。 让我们考虑处理以下示例以更好地理解这一点。

arraymethods.js

var arrNum = [1, 2, 3];

console.log(arrNum.includes(2));
console.log(Array.from([3, 4, 5], x => x + x));
true
[6, 8, 10]
npx babel arraymethods.js --out-file arraymethods_es5.js
"use strict";

var arrNum = [1, 2, 3];

console.log(arrNum.includes(2));
console.log(Array.from([3, 4, 5], function (x) {
return x + x;
}));

数组上使用的方法按原样打印。为了使它们在旧浏览器上工作,我们需要在开始时添加 polyfill 文件,如下所示

<html>
   <head></head>
   <body>
      <h1>Babel Polyfill Testing</h1>
      <script type="text/javascript" src="node_modules/babel-polyfill/dist/polyfill.min.js"></script>
      <script type="text/javascript" src="arraymethods_es5.js"></script>
   </body>
</html>

在 Spring AOP 中组合和重用切入点表达式

在 Spring AOP 中组合和重用切入点表达式

在本文中,您将学习在Spring AOP 中组合和重用多个 Pointcut 表达式。我们在上一篇文章中只讨论了它的一些知识。组合意味着使用两个或多个由逻辑运算符分隔的表达式 - &&、||和 !

我已经包含了高级示例以及一个完整的示例,以使您对这个主题有很好的理解。

1、组合切入点表达式

您可以使用 AND – &&, OR – || 或者 NOT - !组合多个切入点表达式。您还可以按名称引用切入点表达式。以下示例显示相同:

@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {} 
@Pointcut("within(com.jsbd.trading..*)")
private void inTrading() {} 
@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {} 

记住:

1、使用@Pointcut 声明切入点表达式只需要方法签名,不允许方法体。

2、方法名称成为切入点表达式名称。

3、如果将表达式保存在单独的类中,则需要完全限定名称才能在另一个切入点表达式中访问它们。

 

2、重用(共享)切入点表达式

您还可以将切入点表达式保存在一个单独的类 (Pointcut_class) 中,并使用 @Aspect 进行注释,并在具有完整限定名称的 Aspect 类中重用它们(例如 Pointcut_class.expression_name())。这样,您可以在多个方面方法中重用相同的表达式。我们将在下面显示的简单代码的帮助下看到这一点。

package com.jbd.someapp.aspect;
@Aspect //Only aspect annotation
public class SystemArchitecture {
    @Pointcut("within(com.jbd.someapp.web..*)")
    public void inWebLayer() {}
    @Pointcut("within(com.jbd.someapp.service..*)")
    public void inServiceLayer() {}
}
@Aspect
@Configuration
public class ExampleAspect {
  @Before("com.jbd.someapp.aspect.SystemArchitecture.inWebLayer()")
  public void logWebLayer(){
    System.out.print("Log in web layer");
  }
}

3一个完整的示例

这是一个完整详细的逐步示例,用于学习组合和重用连接点。到目前为止,我们只探索了@Before 建议,因此我们将在下面的示例中使用@Pointcut 和@Before。

问题陈述——下面的示例通过报告分析 API 来跟踪所有 DAO 操作。但是,所有 update() 方法都必须调用特殊的分析 api,这与其他操作调用的 api 不同。

步骤 1:添加依赖项

<dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>5.2.2.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.2.RELEASE</version>
    </dependency>
    <!-- Step-1 Add the dependencies -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>5.2.2.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
     <version>1.9.5</version>
    </dependency>
<dependencies>

步骤 2:示例 UserRepository

我们将方面应用于 DAO 类。所以我们创建了一个示例 User Dao 类和 Java 配置来注册这个类,如下所示。

UserRepository.java

package c.jbd.saop.cjp.dao;
import org.springframework.stereotype.Repository;
//A very stupid demo repository
@Repository
public class UserRepository {
  //Add a user
  public UserRepository add(String username){
    if(username == null) {
      throw new RuntimeException("username is null", new NullPointerException());
    }
    System.out.println("New user added: " + username);
    return this;
  }
  //Update an user
  public UserRepository update(String username, String email) {
    System.out.println("Update email: " + email);
    return this;
  }
  //Delete an user
  public boolean delete(String username){
    if (username == null) {
      throw new RuntimeException("username is null", new NullPointerException());
    }
    System.out.println("User deleted: " + username);
    return true;
  }
}

ApplicationConfig.java

package c.jbd.saop.cjp;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy //Enable AspectJ
@ComponentScan("c.jbd.saop.cjp")
public class ApplicationConfig {
}

提醒一下,@EnableAspectJAutoProxy 用于在应用程序中启用 AspectJ 功能。

步骤 3:创建切入点表达式和方面

我将使用单个类 AnalyticsAdvice,而不是创建两个单独的类。在实际用例中,您应该尝试将表达式和方面保持在单独的类中。

package c.jbd.saop.cjp.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.annotation.Configuration;
@Configuration
@Aspect
public class AnalyticsAdvice {
  //All Dao classes
  @Pointcut("within(c.jbd.saop.cjp.dao.*)")
  private void allDao() {
  }
  // All update() methods
  @Pointcut("execution(* update(..))")
  private void updateMethod() {
  }
  //Special analytics for Update methods
  @Before("allDao() && updateMethod()")
  public void specialAnalytics(JoinPoint joinPoint) {
    System.out.println("Call special analytics for: "
        + joinPoint.getSignature());
  }
  //general analytics for other methods
  @Before("allDao() && !updateMethod()")
  public void generalAnalytics(JoinPoint joinPoint) {
    System.out.println("Call general analytics for:"
        + joinPoint.getSignature());
  }
}

如您所见,顾名思义,有 2 个切入点表达式:

allDao() – 用于匹配所有 DAO 类,使用 within 指示符 updateMethod() – 用于匹配具有任意数量参数的任何类的 update() 方法。

@Before("allDao() && updateMethod()") 方面与 dao 包内的所有 update() 方法匹配。这就是调用特殊分析 api 所需要的。

同样,切面@Before("allDao() && !updateMethod()") 匹配除更新方法之外的所有Dao 类方法。这用于调用通用分析 API。

 

第 4 步:测试示例

最后,我们将测试该示例以查看其输出。在运行测试用例之前,在 pom.xml中添加测试依赖项。

<dependencies>
  ... 
   <!-- Test Dependencies-->
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-api</artifactId>
      <version>5.5.2</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-engine</artifactId>
      <version>5.5.2</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.2.2.RELEASE</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
...
<!-- Important for Jupiter-engine -->
<build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>3.0.0-M3</version>
      </plugin>
    </plugins>
  </build>
</project>

永远记得在使用 Junit-jupiter-* 时将 maven-surefire-plugin 升级到 3.0.0-M3 或更高版本。

添加测试依赖项后,在 src\test 中创建 Test 类。

package c.jbd.saop.cjp;
import c.jbd.saop.cjp.dao.UserRepository;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
@SpringJUnitConfig(ApplicationConfig.class)
public class TestAnalytics {
  @Autowired
  UserRepository userRepository;
  @Test
  public void notNull(){
    Assertions.assertNotNull(userRepository);
  }
  @Test
  public void testAddUser(){
    userRepository.add("Alexa");
  }
  @Test
  public void testUpdateUser(){
    userRepository.update("Alexa", "alexa@aws.com");
  }
}

输出

Call general analytics for:UserRepository c.jbd.saop.cjp.dao.UserRepository.add(String)
New user added: Alexa

Call special analytics for: UserRepository c.jbd.saop.cjp.dao.UserRepository.update(String,String)
Update email: alexa@aws.com

正如您在上面的示例中看到的,在调用更新方法之前调用了特殊的分析 API,对于调用通用分析的所有其他 DAO 方法。

总结

本文介绍了如何在 Spring AOP 中组合和重用切入点表达式。

BabelJS - Babel 插件

发布于 2022.09.08 34分钟阅读 0 评论 5 推荐

    作者:

BabelJS - Babel 插件

BabelJS 是一个 javascript 编译器,它可以根据可用的预设和插件更改代码的语法。 babel编译的流程涉及到以下3个部分:

  • parsing
  • transforming
  • printing

给 babel 的代码原样返回,只是语法改变了。我们已经看到将预设添加到 .babelrc 文件中以将代码从 es6 编译到 es5,反之亦然。预设只不过是一组插件。如果在编译期间没有提供预设或插件详细信息,Babel 将不会更改任何内容。

现在让我们讨论以下插件

  • transform-class-properties
  • Transform-exponentiation-operator
  • For-of
  • object rest and spread
  • async/await

现在,我们将创建一个项目设置并处理几个插件,这将清楚地了解 babel 中插件的要求。

npm init

我们必须安装 babel 所需的包——babel cli、babel core、babel-preset 等。

Packages for babel 6

npm install babel-cli babel-core babel-preset-es2015 --save-dev

Packages for babel 7

npm install @babel/cli @babel/core @babel/preset-env --save-dev

在你的项目中创建一个js文件并编写你的js代码。

 

Classes - Transform-class-properties

为此请看下面给出的代码

main.js

class Person {
   constructor(fname, lname, age, address) {
      this.fname = fname;
      this.lname = lname;
      this.age = age;
      this.address = address;
   }

   get fullname() {
      return this.fname + "-" + this.lname;
   }
}
var a = new Person("Siya", "Kapoor", "15", "Mumbai");
var persondet = a.fullname;

目前,我们还没有向 babel 提供任何预设或插件详细信息。如果我们碰巧使用命令转译代码

npx babel main.js --out-file main_out.js

main_out.js

class Person {
   constructor(fname, lname, age, address) {
      this.fname = fname;
      this.lname = lname;
      this.age = age;
      this.address = address;
   }

   get fullname() {
      return this.fname + "-" + this.lname;
   }
}
var a = new Person("Siya", "Kapoor", "15", "Mumbai");
var persondet = a.fullname;

我们将按原样获取代码。现在让我们将预设添加到 .babelrc 文件中。

注意 - 在项目的根文件夹中创建 .babelrc 文件。

 .babelrc 用于 babel 6

{
    "presets": [
        "es2015"
    ]
}

.babelrc for babel 7

{
   "presets":["@babel/env"]
}

我们已经安装了预设;现在让我们再次运行命令

npx babel main.js --out-file main_out.js

main_out.js

"use strict";

var _createClass = function () {
   function defineProperties(target, props) {
      for (var i = 0; i < props.length; i++) {
         var descriptor = props[i];
         descriptor.enumerable = descriptor.enumerable || false; 
         descriptor.configurable = true; 
         if ("value" in descriptor) descriptor.writable = true; 
         Object.defineProperty(target, descriptor.key, descriptor); 
      }
   }
   return function (Constructor, protoProps, staticProps) { 
      if (protoProps) defineProperties(Constructor.prototype, protoProps); 
      if (staticProps) defineProperties(Constructor, staticProps); 
      return Constructor; 
   }; 
}();

function _classCallCheck(instance, Constructor) { 
   if (!(instance instanceof Constructor)) {
      throw new TypeError("Cannot call a class as a function"); 
   } 
}

var Person = function () {
   function Person(fname, lname, age, address) {
      _classCallCheck(this, Person);

      this.fname = fname;
      this.lname = lname;
      this.age = age;
      this.address = address;
   }

   _createClass(Person, [{
      key: "fullname",
      get: function get() {
         return this.fname + "-" + this.lname;
      }
   }]);
   return Person;
}();

var a = new Person("Siya", "Kapoor", "15", "Mumbai");
var persondet = a.fullname;

在 ES6 中,类语法如下

class Person {
   constructor(fname, lname, age, address) {
      this.fname = fname;
      this.lname = lname;
      this.age = age;
      this.address = address;
   }

   get fullname() {
      return this.fname + "-" + this.lname;
   }
}

有构造函数,类的所有属性都在其中定义。万一,我们需要在我们不能这样做的类之外定义类属性。

class Person {
   name = "Siya Kapoor";

   fullname = () => {
      return this.name;
   }
}
var a = new Person();
var persondet = a.fullname();
console.log("%c"+persondet, "font-size:25px;color:red;");

如果我们碰巧编译了上面的代码,它会在 babel 中抛出错误。这会导致代码无法编译。

为了使这项工作按我们想要的方式工作,我们可以使用名为 babel-plugin-transform-class-properties 的 babel 插件。为了让它工作,我们需要先安装它,如下所示:

Packages for babel 6

npm install --save-dev babel-plugin-transform-class-properties

Package for babel 7

npm install --save-dev @babel/plugin-proposal-class-properties

Add the plugin to .babelrc file for babel 6 

.babelrc for babel 7

{
   "plugins": ["@babel/plugin-proposal-class-properties"]
}

现在,我们将再次运行该命令。

npx babel main.js --out-file main_out.js

main.js

class Person {
   name = "Siya Kapoor";

   fullname = () => {
      return this.name;
   }
}
var a = new Person();
var persondet = a.fullname();
console.log("%c"+persondet, "font-size:25px;color:red;");

Compiled to main_out.js

class Person {
   constructor() {
      this.name = "Siya Kapoor";

      this.fullname = () => {
         return this.name;
      };
   }
}
var a = new Person();
var persondet = a.fullname();
console.log("%c"+persondet, "font-size:25px;color:red;");

以下是我们在浏览器中使用时得到的输出

 

Exponentiation Operator - transform-exponentiation-operator

** 是 ES7 中用于求幂的运算符。以下示例显示了 ES7 中相同的工作方式。它还展示了如何使用 babeljs 转译代码。

let sqr = 9 ** 2;
console.log("%c"+sqr, "font-size:25px;color:red;");

要转换幂运算符,我们需要安装一个插件,如下所示:

Packages for babel 6

npm install --save-dev babel-plugin-transform-exponentiation-operator

Packages for babel 7

npm install --save-dev @babel/plugin-transform-exponentiation-operator

将插件详细信息添加到 .babelrc 文件中,如下所示为 babel 6

{
   "plugins": ["transform-exponentiation-operator"]
}

.babelrc for babel 7

{
   "plugins": ["@babel/plugin-transform-exponentiation-operator"]
}
npx babel exponeniation.js --out-file exponeniation_out.js

exponeniation_out.js

let sqr = Math.pow(9, 2);
console.log("%c" + sqr, "font-size:25px;color:red;");

输出

For-of

babel6和7中的插件需要的包如下:

Babel6

npm install --save-dev babel-plugin-transform-es2015-for-of

Babel 7

npm install --save-dev @babel/plugin-transform-for-of

.babelrc for babel6

{
   "plugins": ["transform-es2015-for-of"]
}

.babelrc for babel7

{
   "plugins": ["@babel/plugin-transform-for-of"]
}

forof.js

let foo = ["PHP", "C++", "Mysql", "JAVA"];
for (var i of foo) {
   console.log(i);
}
npx babel forof.js --out-file forof_es5.js

Forof_es5.js

let foo = ["PHP", "C++", "Mysql", "JAVA"];
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;

try {
   for (var _iterator = foo[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
      var i = _step.value;

      console.log(i);
   }
} catch (err) {
   _didIteratorError = true;
   _iteratorError = err;
} finally {
   try {
      if (!_iteratorNormalCompletion && _iterator.return) {
         _iterator.return();
      }
   } finally {
      if (_didIteratorError) {
         throw _iteratorError;
      }
   }
}

输出

object rest spread

babel6和7中的插件需要的包如下

Babel 6

npm install --save-dev babel-plugin-transform-object-rest-spread

Babel 7

npm install --save-dev @babel/plugin-proposal-object-rest-spread

.babelrc for babel6

{
   "plugins": ["transform-object-rest-spread"]
}

.babelrc for babel7

{
   "plugins": ["@babel/plugin-proposal-object-rest-spread"]
}

o.js

let { x1, y1, ...z1 } = { x1: 11, y1: 12, a: 23, b: 24 };
console.log(x1);
console.log(y1);
console.log(z1);

let n = { x1, y1, ...z1};
console.log(n);
npx babel o.js --out-file o_es5.js

o_es5.js

var _extends = Object.assign || function (target) {
   for (var i = 1; i < arguments.length; i++) {
      var source = arguments[i]; for (var key in source) {
         if (Object.prototype.hasOwnProperty.call(source, key)) {
            target[key] = source[key]; 
         } 
      } 
   } 
   return target; 
};

function _objectWithoutProperties(obj, keys) {
   var target = {};
   for (var i in obj) {
      if (keys.indexOf(i) >= 0) continue;
      if (!Object.prototype.hasOwnProperty.call(obj, i)) continue;
      target[i] = obj[i];
   }
   return target;
}

let _x1$y1$a$b = { x1: 11, y1: 12, a: 23, b: 24 },
   { x1, y1 } = _x1$y1$a$b,
   z1 = _objectWithoutProperties(_x1$y1$a$b, ["x1", "y1"]);
console.log(x1);
console.log(y1);
console.log(z1);

let n = _extends({ x1, y1 }, z1);
console.log(n);

输出

async/await

我们需要为 babel 6 安装以下包

npm install --save-dev babel-plugin-transform-async-to-generator

Packages for babel 7

npm install --save-dev @babel/plugin-transform-async-to-generator

.babelrc for babel 6

{
   "plugins": ["transform-async-to-generator"]
}

.babelrc for babel 7

{
   "plugins": ["@babel/plugin-transform-async-to-generator"]
}

async.js

let timer = () => {
   return new Promise(resolve => {
      setTimeout(() => {
         resolve("Promise resolved after 5 seconds");
      }, 5000);
   });
};
let out = async () => {
   let msg = await timer();
   console.log(msg);
   console.log("hello after await");
};

out();
npx babel async.js --out-file async_es5.js

async_es5.js

function _asyncToGenerator(fn) {
   return function () {
      var gen = fn.apply(this, arguments);
      return new Promise(function (resolve, reject) {
         function step(key, arg) {
            try {
               var info = gen[key](arg);
               var value = info.value; 
            } catch (error) {
               reject(error);
               return; 
            } if (info.done) {
               resolve(value); 
            } else {
               return Promise.resolve(value).then(function (value) {
                  step("next", value);
               },
               function (err) {
                  step("throw", err); }); 
            }
         } return step("next"); 
      });
   };
}

let timer = () => {
   return new Promise(resolve => {
      setTimeout(() => {
         resolve("Promise resolved after 5 seconds");
      }, 5000);
   });
};
let out = (() => {
   var _ref = _asyncToGenerator(function* () {
      let msg = yield timer();
      console.log(msg);
      console.log("hello after await");
   });

   return function out() {
      return _ref.apply(this, arguments);
   };
})();
out();

我们必须使用 polyfill,因为它在不支持 Promise 的浏览器中不起作用。

输出

Webpack 或 Browserify & Gulp:哪个更好?

Webpack 或 Browserify & Gulp:哪个更好?

随着 Web 应用程序变得越来越复杂,使您的 Web 应用程序可扩展变得至关重要。过去编写ad-hot时候, JavaScript 和 jQuery 就足够了,而如今构建 Web 应用程序需要更高程度的纪律原则和正式的软件开发实践,例如:

1、单元测试以确保对代码的修改不会破坏现有功能。

2、Linting 以确保一致的编码风格没有错误。

3、不同于开发版本的生产版本。

Web 还提供了一些自己独特的开发挑战。例如,由于网页发出大量异步请求,您的 Web 应用程序的性能可能会因必须请求数百个 JS 和 CSS 文件而显着降低,每个文件都有自己的微小开销(标题、握手等)。这个特定问题通常可以通过将文件捆绑在一起来解决,因此您只需要一个捆绑的 JS 和 CSS 文件,而不是数百个单独的文件。

您应该使用哪个捆绑工具:Webpack 或 Browserify + Gulp?这是选择指南。

使用编译为原生 JS 和 CSS 的语言预处理器(如 SASS 和 JSX)以及 JS 转译器(如 Babel)在保持 ES5 兼容性的同时从 ES6 代码中受益也是很常见的。

这相当于编写大量与Web 应用程序本身的逻辑无关的任务。这就是任务运行器的用武之地。任务运行器的目的是自动化所有这些任务,以便您可以从增强的开发环境中受益,同时专注于编写您的应用程序。配置任务运行程序后,您需要做的就是在终端中调用单个命令。

我将使用 Gulp 作为任务运行器,因为它对开发人员非常友好、易于学习且易于理解。

Gulp 简介

Gulp 的 API 包含四个函数:

  • gulp.src
  • gulp.dest
  • gulp.task
  • gulp.watch

例如,这里是一个使用这四个函数中的三个的示例任务:

gulp.task('my-first-task', function() {
  gulp.src('/public/js/**/*.js')
  .pipe(concat())
  .pipe(minify())
  .pipe(gulp.dest('build'))
});

当执行 my-first-task 时,所有匹配 glob 模式 /public/js/**/*.js 的文件都会被缩小,然后转移到构建文件夹。

其美妙之处在于 .pipe() 链接。您获取一组输入文件,通过一系列转换对它们进行管道传输,然后返回输出文件。为了更方便,实际的管道转换,例如 minify(),通常由 NPM 库完成。因此,在实践中,除了重命名管道中的文件之外,您很少需要编写自己的转换。

理解 Gulp 的下一步是理解任务依赖的数组。

gulp.task('my-second-task', ['lint', 'bundle'], function() {
  ...
});

这里,my-second-task 只在 lint bundle 任务完成后运行回调函数。这允许关注点分离:您创建一系列具有单一职责的小任务,例如将 LESS 转换为 CSS,并创建一种主任务,它通过任务依赖关系数组简单地调用所有其他任务。

最后,我们有 gulp.watch,它监视 glob 文件模式的变化,当检测到变化时,运行一系列任务。

gulp.task('my-third-task', function() {
  gulp.watch('/public/js/**/*.js', ['lint', 'reload'])
})

在上面的示例中,对匹配 /public/js/**/*.js 的文件的任何更改都会触发 lint 并重新加载任务。gulp.watch 的一个常见用途是在浏览器中触发实时重新加载,该功能对开发非常有用,一旦您体验过它,您将无法离开它。

就这样,你了解了所有你真正需要知道的关于 gulp 的知识。

 

Webpack 适合哪里?

使用 CommonJS 模式时,捆绑 JavaScript 文件并不像连接它们那么简单。相反,您有一个入口点(通常称为 index.js 或 app.js),文件顶部有一系列 require import 语句:

ES5

var Component1 = require('./components/Component1');
var Component2 = require('./components/Component2');

ES6

import Component1 from './components/Component1';
import Component2 from './components/Component2';

赖项必须在 app.js 中的剩余代码之前解决,并且这些依赖项本身可能还有进一步的依赖项需要解决。 此外,您可能在应用程序的多个位置需要相同的依赖项,但您只想解决该依赖项一次。 可以想象,一旦你有一个很深的依赖树,捆绑你的 JavaScript 的过程就会变得相当复杂。 这就是 Browserify 和 Webpack 等打包工具的用武之地。

为什么开发人员使用 Webpack 而不是 Gulp?

Webpack 是一个打包器,而 Gulp 是一个任务运行器,因此您希望看到这两个工具经常一起使用。相反,越来越多的趋势,尤其是在 React 社区中,使用 Webpack 代替 Gulp。为什么是这样?

简而言之,Webpack 是一个非常强大的工具,它已经可以执行您通过任务运行器完成的绝大多数任务。例如,Webpack 已经为你的包提供了压缩和source的选项。此外,Webpack 可以通过名为 webpack-dev-server 的自定义服务器作为中间件运行,该服务器支持实时重载和热重载(我们稍后会讨论这些功能)。通过使用加载器,您还可以将 ES6 添加到 ES5 转换,以及 CSS 预处理器和后处理器。这真的让单元测试和 linting 成为 Webpack 无法独立处理的主要任务。鉴于我们已经将至少六个潜在的 gulp 任务减少到两个,许多开发人员选择直接使用 NPM 脚本,因为这避免了将 Gulp 添加到项目中的开销(我们稍后也会讨论) .

使用 Webpack 的主要缺点是配置相当困难,如果您试图快速启动并运行项目,这将没有吸引力。

今天暂时简单介绍到这里,感兴趣的可以查看这篇文章

BabelJS - 将 ES8 功能转换为 ES5

发布于 2022.09.07 3分钟阅读 0 评论 5 推荐

    作者:

BabelJS - 将 ES8 功能转换为 ES5

字符串填充是添加到 javascript 中的新 ES8 功能。我们将研究一个简单的示例,它将使用 babel 将字符串填充转换为 ES5。

String Padding

字符串填充根据指定的长度从左侧添加另一个字符串。字符串填充的语法如下所示:

str.padStart(length, string);
str.padEnd(length, string);
const str = 'abc';

console.log(str.padStart(8, '_'));
console.log(str.padEnd(8, '_'));

输出:

_____abc
abc_____
npx babel strpad.js --out-file strpad_es5.js

编译后:

'use strict';

var str = 'abc';

console.log(str.padStart(8, '_'));
console.log(str.padEnd(8, '_'));

js 必须与 babel-polyfill 一起使用,如下所示:

<!DOCTYPE html>
<html>
   <head>
      <title>BabelJs Testing</title>
   </head>
   <body>
      <script src="node_modules\babel-polyfill\dist\polyfill.min.js" type="text/javascript"></script>
      <script type="text/javascript" src="strpad_es5.js"></script>
   </body>
</html>

输出如下:

BabelJS - 将 ES7 功能转换为 ES5

发布于 2022.09.06 12分钟阅读 0 评论 5 推荐

    作者:

BabelJS - 将 ES7 功能转换为 ES5

在本文中,我们将学习如何将 ES7 功能转换为 ES5。

ECMA Script 7 添加了以下新功能

  • Async-Await
  • Exponentiation Operator
  • Array.prototype.includes()

我们将使用 babeljs 将它们编译为 ES5。根据您的项目要求,也可以在任何 ecma 版本中编译代码,即 ES7 到 ES6 或 ES7 到 ES5。由于 ES5 版本是最稳定的,并且在所有现代和旧浏览器上都可以正常工作,因此我们将代码编译为 ES5。

 

Async-Await

Async 是一个异步函数,它返回一个隐式的承诺。承诺要么被解决,要么被拒绝。异步函数与普通的标准函数相同。该函数可以有 await 表达式,它会暂停执行,直到它返回一个 Promise,一旦它得到它,执行就会继续。只有当函数是异步的时,Await 才会起作用。

这是一个关于 async 和 await 的工作示例。

let timer = () => {
   return new Promise(resolve => {
      setTimeout(() => {
         resolve("Promise resolved after 5 seconds");
      }, 5000);
   });
};
let out = async () => {
   let msg = await timer();
   console.log(msg);
   console.log("hello after await");
};
out();

输出:

Promise resolved after 5 seconds
hello after await

await 表达式是在调用计时器函数之前添加的。计时器函数将在 5 秒后返回承诺。因此 await 将暂停执行,直到计时器功能上的承诺被解决或拒绝,然后再继续。

现在让我们使用 babel 将上述代码转换为 ES5。

npx babel asyncawait.js --out-file asyncawait_es5.js
"use strict";

var timer = function timer() {
   return new Promise(function (resolve) {
      setTimeout(function () {
         resolve("Promise resolved after 5 seconds");
      }, 5000);
   });
};
var out = async function out() {
   var msg = await timer();
   console.log(msg);
   console.log("hello after await");
};

out();

Babeljs 不编译对象或方法;所以这里使用的 Promise 不会被转译,而是按原样显示。为了在旧浏览器上支持 Promise,我们需要添加支持 Promise 的代码。现在,让我们按如下方式安装 babel-polyfill 

npm install --save babel-polyfill

它应该保存为依赖而不是开发依赖。

要在浏览器中运行代码,我们将使用 node_modules\babel-polyfill\dist\polyfill.min.js 中的 polyfill 文件,并使用 script 标签调用它,如下所示

<!DOCTYPE html>
<html>
   <head>
      <title>BabelJs Testing</title>
   </head>
   <body>
      <script src="node_modules\babel-polyfill\dist\polyfill.min.js" type="text/javascript"></script>
      <script type="text/javascript" src="aynscawait_es5.js"></script>
   </body>
</html>

 

Exponentiation Operator

** 是 ES7 中用于求幂的运算符。以下示例显示了 ES7 中相同的工作,代码使用 babeljs 进行了转译。

let sqr = 9 ** 2;
console.log(sqr);

输出:

81

要转换幂运算符,我们需要安装一个插件,如下所示

npm install --save-dev babel-plugin-transform-exponentiation-operator

将插件详细信息添加到 .babelrc 文件中,如下所示

{
   "presets":[
      "es2015"
   ],
   "plugins": ["transform-exponentiation-operator"]
}
npx babel exponeniation.js --out-file exponeniation_es5.js
"use strict";

var sqr = Math.pow(9, 2);
console.log(sqr);

Array.prototype.includes()

如果传递给它的元素存在于数组中,则此功能为 true,否则为 false。

let arr1 = [10, 6, 3, 9, 17];
console.log(arr1.includes(9));
let names = ['Siya', 'Tom', 'Jerry', 'Bean', 'Ben'];
console.log(names.includes('Tom'));
console.log(names.includes('Be'));
true
true
false

我们必须在这里再次使用 babel-polyfill,因为 include 是数组上的一个方法,它不会被转译。我们需要额外的步骤来包含 polyfill 以使其在旧浏览器中工作。

npx babel array_include.js --out-file array_include_es5.js
'use strict';

var arr1 = [10, 6, 3, 9, 17];
console.log(arr1.includes(9));
var names = ['Siya', 'Tom', 'Jerry', 'Bean', 'Ben'];
console.log(names.includes('Tom'));
console.log(names.includes('Be'));

要在旧版浏览器中测试它,我们需要使用 polyfill,如下所示

<!DOCTYPE html>
<html>
   <head>
      <title>BabelJs Testing</title>
   </head>
   <body>
      <script src="node_modules\babel-polyfill\dist\polyfill.min.js" type="text/javascript"></script>
      <script type="text/javascript" src="array_include_es5.js"></script>
   </body>
</html>

 

BabelJS - 将 ES6 模块转换为 ES5

更新于 2022.09.05 2分钟阅读 0 评论 5 推荐

    作者:

BabelJS - 将 ES6 模块转换为 ES5

在本文中,我们将看到如何使用 Babel 将 ES6 模块转换为 ES5。

Modules

考虑需要重用部分 JavaScript 代码的场景。 ES6 用模块的概念来拯救你。

一个模块只不过是写在一个文件中的一段 JavaScript 代码。模块中的函数或变量不可用,除非模块文件导出它们。

简单来说,这些模块帮助您在模块中编写代码,并且只公开应该由代码的其他部分访问的那些代码部分。

让我们考虑一个示例来了解如何使用模块以及如何导出它以在代码中使用它。

举个例子

add.js

var add = (x,y) => {
   return x+y;
}

module.exports=add;

multiply.js

var multiply = (x,y) => {
   return x*y;
};

module.exports = multiply;

main.js

import add from './add';
import multiply from './multiply'

let a = add(10,20);
let b = multiply(40,10);

console.log("%c"+a,"font-size:30px;color:green;");
console.log("%c"+b,"font-size:30px;color:green;");

我有三个文件 add.js 添加 2 个给定数字,multiply.js 将两个给定数字相乘,main.js 调用 add 和 multiply,并控制输出。

给main.js中的add.js和multiply.js,我们要先导出,如下

module.exports = add;
module.exports = multiply;

要在 main.js 中使用它们,我们需要导入它们,如下所示

import add from './add';
import multiply from './multiply'

我们需要模块捆绑器来构建文件,以便我们可以在浏览器中执行它们。

我们可以做到 -

使用 Webpack 

使用 Gulp

 

ES6 模块和 Webpack

在本节中,我们将了解 ES6 模块是什么。我们还将学习如何使用 webpack。 

在开始之前,我们需要安装以下包

npm install --save-dev webpack
npm install --save-dev webpack-dev-server
npm install --save-dev babel-core
npm install --save-dev babel-loader
npm install --save-dev babel-preset-env

package.json

{
  "name": "babelwebpack",
  "version": "1.0.0",
  "description": "babel webpack ingegraty",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "pack": "webpack",
    "publish": "webpack-dev-server --output-public-path=/dev/"
  },
  "keywords": [
    "babel",
    "webpack"
  ],
  "author": "hk",
  "license": "ISC",
  "devDependencies": {
    "@babel/cli": "^7.18.10",
    "@babel/core": "^7.18.13",
    "@babel/preset-env": "^7.18.10",
    "webpack": "^5.74.0",
    "webpack-cli": "^4.10.0",
    "webpack-dev-server": "^4.10.1"
  },
  "dependencies": {
    "babel-loader": "^8.2.5"
  }
}

我们已将打包和发布任务添加到脚本以使用 npm 运行它们。这是构建最终文件的 webpack.config.js 文件。

webpack.config.js

var path = require('path');

module.exports = {
   entry: {
      app: './src/main.js'
   },
   output: {
      path: path.resolve(__dirname, 'dev'),
      filename: 'main_bundle.js'
   },
   mode:'development',
   module: {
      rules: [
         {
            test: /\.js$/,
            include: path.resolve(__dirname, 'src'),
            loader: 'babel-loader',
            query: {
               presets: ['env']
            }
         }
      ]
   }
};

运行命令 npm run pack 来构建文件。最终文件将存储在 dev/ 文件夹中。

dev/main_bundle.js 通用文件被创建。该文件结合了 add.js、multiply.js 和 main.js 并将其存储在 dev/main_bundle.js 中。

/******/ (function(modules) { // webpackBootstrap
/******/    // The module cache
/******/    var installedModules = {};
/******/
/******/    // The require function
/******/    function __webpack_require__(moduleId) {
/******/
/******/       // Check if module is in cache
/******/       if(installedModules[moduleId]) {
/******/          return installedModules[moduleId].exports;
/******/       }
/******/       // Create a new module (and put it into the cache)
/******/       var module = installedModules[moduleId] = {
/******/          i: moduleId,
/******/          l: false,
/******/          exports: {}
/******/       };
/******/
/******/       // Execute the module function
/******/       modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/       // Flag the module as loaded
/******/       module.l = true;
/******/
/******/       // Return the exports of the module
/******/       return module.exports;
/******/    }
/******/
/******/
/******/    // expose the modules object (__webpack_modules__)
/******/    __webpack_require__.m = modules;
/******/
/******/    // expose the module cache
/******/    __webpack_require__.c = installedModules;
/******/
/******/    // define getter function for harmony exports
/******/    __webpack_require__.d = function(exports, name, getter) {
/******/       if(!__webpack_require__.o(exports, name)) {
/******/          Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/       }
/******/    };
/******/
/******/    // define __esModule on exports
/******/    __webpack_require__.r = function(exports) {
/******/      if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/         Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/      }
/******/      Object.defineProperty(exports, '__esModule', { value: true });
/******/    };
/******/
/******/    // create a fake namespace object
/******/    // mode & 1: value is a module id, require it
/******/    // mode & 2: merge all properties of value into the ns
/******/    // mode & 4: return value when already ns object
/******/    // mode & 8|1: behave like require
/******/    __webpack_require__.t = function(value, mode) {
/******/       if(mode & 1) value = __webpack_require__(value);
/******/       if(mode & 8) return value;
/******/       if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/       var ns = Object.create(null);
/******/       __webpack_require__.r(ns);
/******/       Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/       if(mode & 2 && typeof value != 'string')
               for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/       return ns;
/******/    };
/******/
/******/    // getDefaultExport function for compatibility with non-harmony modules
/******/    __webpack_require__.n = function(module) {
/******/       var getter = module && module.__esModule ?
/******/       function getDefault() { return module['default']; } :
/******/       function getModuleExports() { return module; };
/******/       __webpack_require__.d(getter, 'a', getter);
/******/       return getter;
/******/    };
/******/
/******/    // Object.prototype.hasOwnProperty.call
/******/     __webpack_require__.o = function(object, property) {
               return Object.prototype.hasOwnProperty.call(object, property); 
            };
/******/
/******/    // __webpack_public_path__
/******/    __webpack_require__.p = "";
/******/
/******/
/******/    // Load entry module and return exports
/******/    return __webpack_require__(__webpack_require__.s = "./src/main.js");
/******/ })
/************************************************************************/
/******/ ({
/***/       "./src/add.js":
/*!********************!*\
!*** ./src/add.js ***!
\********************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
   "use strict";

   eval(
      "\n\nvar add = function add(x, y) {\n return x + y;\n};
      \n\nmodule.exports = add;
      \n\n//# sourceURL = webpack:///./src/add.js?"
   );
   /***/ }),
/***/ "./src/main.js":
/*!*********************!*\
!*** ./src/main.js ***!
\*********************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

      "use strict";
      eval(
         "\n\nvar _add = __webpack_require__(/*! ./add */ \"./src/add.js\");
         \n\nvar _add2 = _interopRequireDefault(_add);
         \n\nvar _multiply = __webpack_require__(/*! ./multiply */ \"./src/multiply.js\");
         \n\nvar _multiply2 = _interopRequireDefault(_multiply);
         \n\nfunction _interopRequireDefault(obj) {
            return obj &gt;&gt; obj.__esModule ? obj : { default: obj };
         }
         \n\nvar a = (0, _add2.default)(10, 20);
         \nvar b = (0, _multiply2.default)(40, 10);
         \n\nconsole.log(\"%c\" + a, \"font-size:30px;color:green;\");
         \nconsole.log(\"%c\" + b, \"font-size:30px;color:green;\");
         \n\n//# sourceURL = webpack:///./src/main.js?"
      );

/***/ }),

/***/ "./src/multiply.js":
/*!*************************!*\
   !*** ./src/multiply.js ***!
   \*************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

"use strict";
eval(
   "\n\nvar multiply = function multiply(x, y) {\n return x * y;\n};
   \n\nmodule.exports = multiply;
   \n\n//# sourceURL = webpack:///./src/multiply.js?"
);

/***/ })

/******/ });

以下是在浏览器中测试输出的命令

npm run publish

在您的项目中添加 index.html。这会调用 dev/main_bundle.js。

<html>
   <head></head>
   <body>
      <div>public path</div>
      <script type="text/javascript" src="dev/main_bundle.js"></script>
   </body>
</html>

输出如下:

 

BabelJS - 将 ES6 功能转换为 ES5

更新于 2022.09.05 0分钟阅读 0 评论 5 推荐

    作者:

BabelJS - 将 ES6 功能转换为 ES5

在本文以及接下来的文章中,我们将看到 ES6 中添加的特性。我们还将学习如何使用 BabelJS 将功能编译到 ES5。

以下是我们将在本章中讨论的各种 ES6 特性

  • Let + Const
  • Arrow Functions
  • Classes
  • Promises
  • Generators
  • Destructuring
  • Iterators
  • Template Literalst
  • Enhanced Object
  • Default, Rest & Spread Properties

Let + Const

Let 在 JavaScript 中声明一个块作用域的局部变量。请考虑以下示例以了解 let 的用法。

let a = 1;
if (a == 1) {
   let a = 2;
   console.log(a);
}
console.log(a);

输出

2
1

第一个控制台打印 2 的原因是因为 a 再次使用 let 声明并且仅在 if 块中可用。使用 let 声明的任何变量都只能在声明的块中使用。我们已经使用 let 声明了变量 a 两次,但它并没有覆盖 a 的值。

这就是 var 和 let 关键字的区别。当您使用 var 声明变量时,该变量将在函数范围内可用,或者如果声明将充当全局变量。

如果使用 let 声明变量,则该变量在块范围内可用。如果在 if 语句中声明,它将仅在 if 块中可用。这同样适用于 switch、for 循环等。

我们现在将看到在 ES5 中使用 babeljs 进行代码转换。 让我们运行以下命令来转换代码

npx babel let.js --out-file let_es5.js

let 关键字从 es6 到 es5 的输出如下

"use strict";

var a = 1;
if (a == 1) {
   var _a = 2;
   console.log(_a);
}
console.log(a);

如果您看到 ES5 代码,let 关键字将替换为 var 关键字。 if 块中的变量也被重命名为 _a 以具有与使用 let 关键字声明时相同的效果。

Const

在本节中,我们将了解 const 关键字在 ES6 和 ES5 中的工作原理。 const关键字在范围内也可用;如果在外面,它会抛出一个错误。const 声明变量的值一旦分配就不能更改。让我们考虑以下示例以了解如何使用 const 关键字。

let a =1;
if (a == 1) {
   const age = 10;
}
console.log(age);

输出

Uncaught ReferenceError: age is not defined at :5:13

上面的输出会抛出一个错误,因为 const age 是在 if 块内定义的,并且在 if 块内可用。 我们将了解如何使用 BabelJS 转换为 ES5。

npx babel const.js --out-file const_es5.js

输出如下:

"use strict";

var a = 1;
if (a == 1) {
   var _age = 10;
}
console.log(age);

在 ES5 中,const 关键字被替换为 var 关键字,如上所示。

Arrow Functions

与变量表达式相比,箭头函数的语法更短。它也被称为胖箭头函数或 lambda 函数。该函数没有自己的 this 属性。在这个函数中,关键字function被省略了。

var add = (x,y) => {
   return x+y;
}

var k = add(3,6);
console.log(k);

输出:

9

使用 BabelJS,我们将上面的代码转换为 ES5。

npx babel arrowfunction.js --out-file arrowfunction_es5.js

使用 Babel 将箭头函数转换为变量表达式函数,如下所示。

"use strict";

var add = function add(x, y) {
   return x + y;
};

var k = add(3, 6);
console.log(k);

Classes

ES6 带有新的类特性。类类似于 ES5 中可用的基于原型的继承。class 关键字用于定义类。类就像特殊的函数,也有类似函数表达式的相似之处。它有一个构造函数,在类内部调用。

class Person {
   constructor(fname, lname, age, address) {
      this.fname = fname;
      this.lname = lname;
      this.age = age;
      this.address = address;
   }

   get fullname() {
      return this.fname +"-"+this.lname;
   }
}
var a = new Person("Siya", "Kapoor", "15", "Mumbai");
var persondet = a.fullname;

输出

Siya-Kapoor
npx babel class.js --out-file class_es5.js

使用 babeljs 添加了额外的代码,以使功能与 ES5 中的类相同。BabelJs 确保功能与 ES6 中的功能相同。

"use strict";

var _createClass = function () {
   function defineProperties(target, props) {
      for (var i = 0; i < props.length; i++) {
         var descriptor = props[i];
         descriptor.enumerable = descriptor.enumerable || false;
         descriptor.configurable = true;
         if ("value" in descriptor) descriptor.writable = true;
         Object.defineProperty(target, descriptor.key, descriptor);
      }
   }
   return function (Constructor, protoProps, staticProps) {
      if (protoProps) defineProperties(Constructor.prototype, protoProps);
      if (staticProps) defineProperties(Constructor, staticProps);
      return Constructor;
   };
}();

function _classCallCheck(instance, Constructor) {
   if (!(instance instanceof Constructor)) {
      throw new TypeError("Cannot call a class as a function");
   }
}

var Person = function () {
   function Person(fname, lname, age, address) {
      _classCallCheck(this, Person);

      this.fname = fname;
      this.lname = lname;
      this.age = age;
      this.address = address;
   }

   _createClass(Person, [{
      key: "fullname",
      get: function get() {
         return this.fname + "-" + this.lname;
      }
   }]);

   return Person;
}();

var a = new Person("Siya", "Kapoor", "15", "Mumbai");
var persondet = a.fullname;

Promises

JavaScript Promise 用于管理代码中的异步请求。

当您管理来自具有依赖性的异步请求的多个回调时,它使生活更轻松并保持代码干净。Promise 提供了一种更好的使用回调函数的方法。 Promise 是 ES6 的一部分。默认情况下,当您创建一个 Promise 时,该 Promise 的状态是挂起的。

Promise来自三种状态:

  • pending (initial state)
  • resolved (completed successfully)
  • rejected(failed)

new Promise() 用于构造一个 Promise。 Promise 构造函数有一个参数,它是一个回调函数。回调函数有两个参数——resolve和reject;

这两个都是内部函数。您编写的异步代码,即 Ajax 调用、图像加载、计时函数将进入回调函数。

如果回调函数中执行的任务成功,则调用resolve函数;否则,将使用错误详细信息调用拒绝函数。

以下代码行显示了一个 Promise 结构调用

var _promise = new Promise (function(resolve, reject) {
   var success = true;
   if (success) {
      resolve("success");
   } else {
      reject("failure");
   }
});
_promise.then(function(value) {
   //once function resolve gets called it comes over here with the value passed in resolve
   console.log(value); //success
}).catch(function(value) {
   //once function reject gets called it comes over here with the value passed in reject
   console.log(value); // failure.
});

ES6 Promise 示例

let timingpromise = new Promise((resolve, reject) => {
   setTimeout(function() {
      resolve("Promise is resolved!");
   }, 1000);
});

timingpromise.then((msg) => {
   console.log(msg);
});

输出

Promise is resolved!
npx babel promise.js --out-file promise_es5.js
"use strict";

var timingpromise = new Promise(function (resolve, reject) {
   setTimeout(function () {
      resolve("Promise is resolved!");
   }, 1000);
});

timingpromise.then(function (msg) {
   console.log(msg);
});

对于 Promise,代码在转译时不会改变。我们需要使用 babel-polyfill 才能在旧版浏览器上工作。关于 babel-polyfills 的详细信息在 babel - poyfill 章节中进行了解释。

 

Generators

生成器函数就像普通函数一样。该函数具有特殊的语法 function* 和 * 到函数和要在函数内部使用的 yield 关键字。这意味着在需要时暂停或启动该功能。正常函数一旦执行开始,就不能在两者之间停止。当它遇到返回语句时,它将执行完整的函数或停止。生成器在这里执行不同的操作,您可以使用 yield 关键字暂停函数,并在需要时再次调用生成器来启动它。

function* generatorfunction(a) {
   yield a;
   yield a +1 ;
}

let g = generatorfunction(8);
console.log(g.next());
console.log(g.next());

输出:

{value: 8, done: false}
{value: 9, done: false}

执行命令:

npx babel generator.js --out-file generator_es5.js

输出结果如下:

"use strict";

var _marked = /*#__PURE__*/regeneratorRuntime.mark(generatorfunction);

function generatorfunction(a) {
   return regeneratorRuntime.wrap(function generatorfunction$(_context) {
      while (1) {
         switch (_context.prev = _context.next) {
            case 0:
               _context.next = 2;
               return a;

            case 2:
               _context.next = 4;
               return a + 1;
               
            case 4:
            case "end":
               return _context.stop();
         }
      }
   }, _marked, this);
}

var g = generatorfunction(8);
console.log(g.next());
console.log(g.next());

 

Iterators

JavaScript 中的迭代器返回一个 JavaScript 对象,该对象具有值。该对象还有一个名为 done 的标志,它具有真/假值。如果它不是迭代器的结尾,则返回 false。让我们考虑一个例子,看看迭代器在数组上的工作。

let numbers = [4, 7, 3, 10];
let a = numbers[Symbol.iterator]();
console.log(a.next());
console.log(a.next());
console.log(a.next());
console.log(a.next());
console.log(a.next());

在上面的示例中,我们使用了一个数字数组,并使用 Symbol.iterator 作为索引在数组上调用了一个函数。

我们在数组上使用 next() 得到的输出如下:

{value: 4, done: false}
{value: 7, done: false}
{value: 3, done: false}
{value: 10, done: false}
{value: undefined, done: true}

输出给出一个具有值的对象,并作为属性完成。每个 next() 方法调用都会给出数组中的下一个值,并以 false 完成。只有当数组中的元素完成时,done 的值才会为真。我们可以使用它来迭代数组。还有更多可用的选项,例如 for-of 循​​环,使用如下:

let numbers = [4, 7, 3, 10];
for (let n of numbers) {
   console.log(n);
}

输出:

4
7
3
10

当 for-of 循​​环使用键时,它会给出数组值的详细信息,如上所示。我们将检查这两种组合,看看 babeljs 如何将它们转换为 es5。

执行如下命令:

npx babel iterator.js --out-file iterator_es5.js

结果如下:

"use strict";

var numbers = [4, 7, 3, 10];
var a = numbers[Symbol.iterator]();
console.log(a.next());
console.log(a.next());
console.log(a.next());
console.log(a.next());
console.log(a.next());

var _array = [4, 7, 3, 10];
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;

try {
   for (var _iterator = _array[Symbol.iterator](),
      _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done);
      _iteratorNormalCompletion = true) {
      var n = _step.value;

      console.log(n);
   }
} catch (err) {
   _didIteratorError = true;
   _iteratorError = err;
} finally {
   try {
      if (!_iteratorNormalCompletion && _iterator.return) {
         _iterator.return();
      }
   } finally {
      if (_didIteratorError) {
         throw _iteratorError;
      }
   }
}

在 es5 中添加了 for-of 循​​环的更改。但是 iterator.next 保持原样。我们需要使用 babel-polyfill 让它在旧浏览器中工作。 Babel-polyfill 与 babel 一起安装,并且可以从 node_modules 中使用,如下所示

<html>
   <head>
      <script type="text/javascript" src="node_modules/babel-polyfill/dist/polyfill.min.js"></script>
      <script type="text/javascript" src="iterator_es5.js"></script>
   </head>
   <body>
      <h1>Iterators</h1>
   </body>
</html>

Destructuring

解构属性的行为就像一个 JavaScript 表达式,它从数组、对象中解包值。 下面的例子将解释解构语法的工作。

let x, y, rem;
[x, y] = [10, 20];

console.log(x);
console.log(y);
[x, y, ...rem] = [10, 20, 30, 40, 50];
console.log(rem);

let z = 0;
({ x, y } = (z) ? { x: 10, y: 20 } : { x: 1, y: 2 });
console.log(x);
console.log(y);
10
20
[30, 40, 50]
1
2

上面的代码行显示了如何将值从数组的右侧分配给左侧的变量。带有 ...rem 的变量从数组中获取所有剩余的值。

我们还可以使用条件运算符从左侧的对象中分配值,如下所示

({ x, y } = (z) ? { x: 10, y: 20 } : { x: 1, y: 2 });
console.log(x); // 1
console.log(y); // 2

让我们使用 babeljs 将其转换为 ES5

npx babel destructm.js --out-file destruct_es5.js
"use strict";

var x = void 0,
   y = void 0,
   rem = void 0;
x = 10;
y = 20;

console.log(x);
console.log(y);
x = 10;
y = 20;
rem = [30, 40, 50];

console.log(rem);

var z = 0;

var _ref = z ? { x: 10, y: 20 } : { x: 1, y: 2 };

x = _ref.x;
y = _ref.y;

console.log(x);
console.log(y);

Template Literals

Template literal是一个字符串文字,它允许在其中使用表达式。它使用反引号(``)而不是单引号或双引号。当我们在字符串中说表达式时,这意味着我们可以在字符串中使用变量、调用函数等。

let a = 5;
let b = 10;
console.log(`Using Template literal : Value is ${a + b}.`);
console.log("Using normal way : Value is " + (a + b));
Using Template literal : Value is 15.
Using normal way : Value is 15
npx babel templateliteral.js --out-file templateliteral_es5.js
"use strict";

var a = 5;
var b = 10;
console.log("Using Template literal : Value is " + (a + b) + ".");

console.log("Using normal way : Value is " + (a + b));

Enhanced Object Literals

在 es6 中,添加到对象字面量的新特性非常好用。我们将通过几个 ES5 和 ES6 中的对象字面量示例 

ES5
var red = 1, green = 2, blue = 3;
var rgbes5 = {
   red: red,
   green: green,
   blue: blue
};
console.log(rgbes5); // {red: 1, green: 2, blue: 3}

ES6
let rgbes6 = {
   red,
   green,
   blue
};
console.log(rgbes6); // {red: 1, green: 2, blue: 3}

如果你看到上面的代码,ES5 和 ES6 中的对象是不同的。在 ES6 中,如果变量名称与键相同,我们不必指定键值。

让我们看看使用 babel 编译到 ES5。

const red = 1, green = 2, blue = 3;
let rgbes5 = {
   red: red,
   green: green,
   blue: blue
};
console.log(rgbes5);

let rgbes6 = {
   red,
   green,
   blue
};
console.log(rgbes6);

let brand = "carbrand";
const cars = {
   [brand]: "BMW"
}
console.log(cars.carbrand);  //"BMW"
npx babel enhancedobjliteral.js --out-file enhancedobjliteral_es5.js
"use strict";

function _defineProperty(obj, key, value) {
   if (key in obj) {
      Object.defineProperty(obj, key, {
         value: value, enumerable: true, configurable: true, writable: true
      });
   } else { obj[key] = value; } return obj;
}

var red = 1,
   green = 2,
   blue = 3;
var rgbes5 = {
   red: red,
   green: green,
   blue: blue
};
console.log(rgbes5);

var rgbes6 = {
   red: red,
   green: green,
   blue: blue
};
console.log(rgbes6);

var brand = "carbrand";
var cars = _defineProperty({}, brand, "BMW");

console.log(cars.carbrand); //"BMW"

Default, Rest & Spread Properties

在本节中,我们将讨论 default、rest 和 spread 属性。

使用 ES6,我们可以对函数 params 使用默认参数,如下所示

let add = (a, b = 3) => {
   return a + b;
}

console.log(add(10, 20));  // 30
console.log(add(10));      // 13

让我们使用 babel 将上述代码转换为 ES5。

npx babel default.js --out-file default_es5.js
"use strict";

var add = function add(a) {
   var b = arguments.length > 1 >> arguments[1] !== undefined ? arguments[1] : 3;
   return a + b;
};

console.log(add(10, 20));
console.log(add(10));

Rest 参数以三个点 (...) 开头,如下例所示

let add = (...args) => {
   let sum = 0;
   args.forEach(function (n) {
      sum += n;
   });
   return sum;
};

console.log(add(1, 2));    // 3
console.log(add(1, 2, 5, 6, 6, 7));   //27

在上面的函数中,我们将 n 个参数传递给函数 add。如果在 ES5 中添加所有这些参数,我们必须依赖 arguments 对象来获取参数的详细信息。在 ES6 中,rest 有助于用三个点定义参数,如上所示,我们可以遍历它并获得数字的总和。

注意 - 使用三个点时,我们不能使用额外的参数,即rest。

let add = (...args, value) => {    //syntax error
   let sum = 0;
   args.forEach(function (n) {
      sum += n;
   });
   return sum;
};

上面的代码会给出语法错误。 es5的编译如下

npx babel rest.js --out-file rest_es5.js
"use strict";

var add = function add() {
   for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
      args[_key] = arguments[_key];
   }

   var sum = 0;
   args.forEach(function (n) {
      sum += n;
   });
   return sum;
};

console.log(add(1, 2));
console.log(add(1, 2, 5, 6, 6, 7));

Spread 属性也有像 rest 一样的三个点。以下是一个工作示例,展示了如何使用 spread 属性。

let add = (a, b, c) => {
   return a + b + c;
}
let arr = [11, 23, 3];
console.log(add(...arr));   //37

现在让我们看看上面的代码是如何使用 babel 转译的

npx babel spread.js --out-file spread_es5.js
"use strict";

var add = function add(a, b, c) {
   return a + b + c;
};
var arr = [11, 23, 3];
console.log(add.apply(undefined, arr));

Proxies

代理是一个对象,您可以在其中为属性查找、赋值、枚举、函数、调用等操作定义自定义行为。

var a = new Proxy(target, handler);

目标和处理程序都是对象。target 是一个对象,也可以是另一个代理元素。 handler 将是一个对象,其属性作为函数,在调用时将给出行为。

让我们试着通过一个例子来理解这些特性

let handler = {
   get: function (target, name) {
      return name in target ? target[name] : "invalid key";
   }
};

let o = {
   name: 'Siya Kapoor',
   addr: 'Mumbai'
}

let a = new Proxy(o, handler);
console.log(a.name);
console.log(a.addr);
console.log(a.age);

我们在上面的示例中定义了目标和处理程序,并将其与代理一起使用。代理返回带有键值的对象。

Siya Kapoor
Mumbai
invalid key

现在让我们看看如何使用 babel 将上述代码转换为 ES5

npx babel proxy.js --out-file proxy_es5.js
'use strict';

var handler = {
   get: function get(target, name) {
      return name in target ? target[name] : "invalid key";
   }
};

var o = {
   name: 'Siya Kapoor',
   addr: 'Mumbai'
};

var a = new Proxy(o, handler);
console.log(a.name);
console.log(a.addr);
console.log(a.age);

Spring AOP 中的切入点表达式