随着现代 JavaScript 开发 Web 应用变得复杂,命名冲突和依赖关系也变得难以处理,因此需要模块化。而引入模块化,可以避免命名冲突、方便依赖关系管理、提高了代码的复用性和和维护性,因此,在 JavaScript 没有模块功能的前提下,只能通过第三方规范实现模块化:
它们都是基于 JavaScript 的语法和词法特性 “伪造” 出类似模块的行为。而 TC-39 在 ECMAScript 2015 中加入了模块规范,简化了上面介绍的模块加载器,原生意味着可以取代上述的规范,成为浏览器和服务器通用的模块解决方案,比使用库更有效率。而 ES6 的模块化的设计目标:
ECMAScript 在 2015 年开始支持模块标准,此后逐渐发展,现已经得到了所有主流浏览器的支持。ECMAScript 2015 版本也被称为 ECMAScript 6。
ES6 模块借用了 CommonJS 和 AMD 的很多优秀特性,如下所示:
ES6 模块系统也增加了一些新行为。
浏览器运行时在知道应该把某个文件当成模块时,会有条件地按照上述 ES6 模块行为来施加限制。与 <script type="module"> 关联或者通过 import 语句加载的 JavaScript 文件会被认定为模块。
ES6 模块内部的所有变量,外部无法获取,因此提供了 export 关键字从模块中导出实时绑定的函数、对象或原始值,这样其他程序可以通过 import 关键字使用它们。export 支持两种导出方式:命名导出和默认导出。不同的导出方式对应不同的导入方式。
在 ES6 模块中,无论是否声明 "use strict;" 语句,默认情况下模块都是在严格模式下运行。export 语句不能用在嵌入式脚本中。
通过在声明的前面加上 export 关键字,一个模块可以导出多个内容。这些导出的内容通过名字区分,被称为命名导出。
// 导出单个特性(可以导出 var,let,const)
export let name="小明";
export function sayHi(name) {
console.log(`Hello, ${name}!`);
}
export class Sample {
...
}
或者导出事先定义的特性
let name="小明";
const age=18;
function sayHi(name) {
console.log(`Hello, ${name}!`);
}
export {name, age, sayHi}
导出时也可以指定别名,别名必须在 export 子句的大括号语法中指定。因此,声明值、导出值和未导出值提供别名不能在一行完成。
export {name as username, age, sayHi}
但导出语句必须在模块顶级,不能嵌套在某个块中:
// 允许
export ...
// 不允许
if (condition) {
export ...
}
默认导出就好像模块与被导出的值是一回事。默认导出使用 default 关键字将一个值声明为默认导出,每个模块只能有一个默认导出。重复的默认导出会导致 SyntaxError。如下所示:
// 导出事先定义的特性作为默认值
export default {
name: "Xiao Ming",
age: 18,
sex: "boy"
};
export {sayHi as default} // ES 6 模块会识别作为别名提供的 default 关键字。此时,虽然对应的值是使用命名语法导出的,实际上则会称为默认导出 等同于 export default function sayHi() {}
// 导出单个特性作为默认值
export default function () {...}
export default class {...}
ES6 规范对不同形式的 export 语句中可以使用什么不可以使用什么规定了限制。某些形式允许声明和赋值,某些形式只允许表达式,而某些形式则只允许简单标识符。注意,有的形式使用了分号,有的则没有。
下面列出几种会导致错误的 export 形式:
// 会导致错误的不同形式:
// 行内默认导出中不能出现变量声明
export default const name='小刘';
// 只有标识符可以出现在export 子句中
export { 123 as name }
// 别名只能在export 子句中出现
export const name='小红' as uname;
注意:声明、赋值和导出标识符最好分开。这样不容易搞错了,同时也可以让 export 语句集中在一块。而且,没有被 export 关键字导出的变量、函数或类会在模块内保持私有。
模块导入的值还可以再次导出,这样的话,可以在父模块集中多个模块的多个导出。可以使用 export from 语法实现:
export {default as m1, name} from './module1.js'
// 等效于
import {default as m1, name} from "./module1.js"
export {m1, name}
外部模块的默认导出也可以重用为当前模块的默认导出:
export { default } from './module1.js';
也可以在重新导出时,将导入模块修改为默认导出,如下所示:
export { name as default } from './module1.js';
而想要将所有命名导出可以使用如下语法:
export * from './module1.js';
该语法会忽略默认导出。但这种语法也要注意导出名称是否冲突。如下所示:
// module1.js
export const name="module1:name";
// module2.js
export * from './mudule1.js'
export const name="module2:name";
// index.js
import { name } from './module2.js';
console.log(name); // module2:name
最终输出的是 module2.js 中的值,这个 “重写” 是静默发生的。
使用 export 关键字定义了模块的对外接口以后,其它模块就能通过 import 关键字加载这个模块了。但与 export 类似,import 也必须出现在模块的顶级:
// 允许
import ...
// 不允许
if (condition) {
import ...
}
模块标识符可以是相对于当前模块的相对路径,也可以是指向模块文件的绝对路径。它必须是纯字符串,不能是动态计算的结果。例如,不能是拼接的字符串。
当使用 export 命名导出时,可以使用 * 批量获取并赋值给保存导出集合的别名,而无须列出每个标识符:
const name="Xiao Ming", age=18, sex="boy";
export {name, age, sex}
// 上面的命名导出可以使用如下形式导入(上面的代码是在 module1.js 模块中)
import * as Sample from "./module1.js"
console.log(`My name is ${Sample.name}, A ${Sample.sex},${Sample.age} years old.`);
也可以指名导入,只需要把名字放在 {} 中即可:
import {name, sex as s, age} from "./module1.js";
console.log(`My name is ${name}, A ${s},${age} years old.`);
import 引入是采用的 Singleton 模式,多次使用 import 引入同一个模块时,只会引入一次该模块的实例:
import {name, age} from "./module1.js";
import {sex as s} from "./module1.js";
// 等同于,并且只会引入一个 module1.js 实例
import {name, sex as s, age} from "./module1.js";
而使用默认导出的话,可以使用 default 关键字并提供别名来导入,也可以直接使用标识符就是默认导出的别名导入:
import {default as Sample} from "./module1.js"
// 与下面的方式等效
import Sample from "./module1.js"
而模块中同时有命名导出和默认导出,可以在 import 语句中同时导入。下面三种方式都等效。
import Sample, {sayHi} from "./module1.js"
import {default as Sample, sayHi} from "./module1.js"
import Sample, * as M1 from "./module1.js"
当然,也可以将整个模块作为副作用而导入,而不导入模块中的特定内容。这将运行模块中的全局代码,但实际上不导入任何值。
import './module1.js'
import 导入的值与 export 导出的值是绑定关系,绑定是不可变的。因此,import 对所导入的模块是只读的。但是可以通过调用被导入模块的函数来达到目的。
import Sample, * as M1 from "./module1.js"
Sample="Modify Sample"; // 错误
M1.module1="Module 1"; // 错误
Sample.name="小亮"; // 允许
这样做的好处是能够支持循环依赖,并且一个大的模块可以拆成若干个小模块时也可以运行,只要不尝试修改导入的值。
注意:如果要在浏览器中原生加载模块,则文件必须带有 .js 扩展名,不然可能无法解析。而使用构建工具或第三方模块加载器打包或解析 ES6 模块,可能不需要包含扩展名。
标准的 import 关键字导入模块是静态的,会使所有被导入的模块,在加载时就被编译。而最新的 ES11 标准中引入了动态导入函数 import(),不必预先加载所有模块。该函数会将模块的路径作为参数,并返回一个 Promise,在它的 then 回调里使用加载后的模块:
import ('./module1.mjs')
.then((module)=> {
// Do something with the module.
});
这种使用方式也支持 await 关键字。
let module=await import('./module1.js');
import() 的使用场景如下:
ES6 模块既可以通过浏览器原生加载,也可以与第三方加载器和构建工具一起加载。
完全支持 ES6 模块的浏览器可以从顶级模块异步加载整个依赖图。浏览器会解析入口模块,确定依赖,并发送对依赖模块的请求。这些文件通过网络返回后,浏览器会解析它们的内容,确认依赖,如果二级依赖还没有加载,则会发送更多请求。这个异步递归加载过程会持续到整个依赖图都解析完成。解析完依赖,应用就可以正式加载模块了。
模块文件按需加载,且后续模块的请求会因为每个依赖模块的网络延迟而同步延迟。即,module1 依赖 module2,module2 依赖 module3。浏览器在对 module2 的请求完成之前并不知道要请求 module3。这种架子啊方式效率高,也不需要外部工具,但加载大型应用的深度依赖图可能要花费很长时间。
想要在 HTML 页面中使用 ES6 模块,需要将 type="module" 属性放在 <script> 标签中,来声明该 <script> 所包含的代码在浏览器中作为模块执行。它可以嵌入在网页中,也可以作为外部文件引入:
<script type="module">
// 模块代码
</script>
<script type="module" src="./module1.js"></script>
<script type="module">模块加载的顺序与 <script defer> 加载的脚本一样按顺序执行。但执行会延迟到文档解析完成,但执行顺序就是<script type="module">在页面中出现的顺序。
也可以给模块标签添加 async 属性。这样影响是双重的,不仅模块执行顺序不再与 <script> 标签在页面中的顺序绑定,模块也不会等待文档完成解析才执行。不过,入口模块必须等待其依赖加载完成。
Worker 为了支持 ES6 模块,在 Worker 构造函数中可以接收第二个参数,其 type 属性的默认值是 classic,可以将 type 设置为 module 来加载模块文件。如下所示:
// 第二个参数默认为{ type: 'classic' }
const scriptWorker=new Worker('scriptWorker.js');
const moduleWorker=new Worker('moduleWorker.js', { type: 'module' });
在基于模块的工作者内部,self.importScripts() 方法通常用于在基于脚本的工作者中加载外部脚本,调用它会抛出错误。这是因为模块的 import 行为包含了 importScripts()。
如果浏览器原生支持 ES6 模块,可以直接使用,而不支持的浏览器可以使用第三方模块系统(System.js)或在构建时将 ES6 模块进行转译。
脚本模块可以使用 type="module" 属性设定,而对于不支持模块的浏览器,可以使用 nomodule 属性。此属性会通知支持 ES6 模块的浏览器不执行脚本。不支持模块的浏览器无法识别该属性,从而忽略该属性。如下所示:
// 支持模块的浏览器会执行这段脚本
// 不支持模块的浏览器不会执行这段脚本
<script type="module" src="module.js"></script>
// 支持模块的浏览器不会执行这段脚本
// 不支持模块的浏览器会执行这段脚本
<script nomodule src="script.js"></script>
ES6 在语言层面上支持了模块,结束了 CommonJS 和 AMD 这两个模块加载器的长期分裂状况,重新定义了模块功能,集两个规范于一身,并通过简单的语法声明来暴露。
模块的使用不同方式加载 .js 文件,它与脚本有很大的不同:
浏览器对原生模块的支持越来越好,但也提供了稳健的工具以实现从不支持到支持 ES6 模块的过渡。
、为什么用命令行模式
使用GUI方式启动jmeter,运行线程较多的测试时,会造成内存和CPU的大量消耗,导致客户机卡死;
所以一般采用的方式是在GUI模式下调整测试脚本,再用命令行模式执行;
命令行方式支持在多个环境下使用,windosw的dos环境下,也可以在linux环境上执行。
注意:使用命令执行jmeter脚本必须使用jmeter 3.0及以上版本。
2、怎么用
2.1、执行命令
jmeter -n -t <testplan filename> -l <listener filename>
示例: jmeter -n -t testplan.jmx -l test.jtl
示例含义:表示以命令行模式运行testplan.jmx文件,输出的日志文件为test.jtl
2.2、参数介绍
Jmeter官方手册给的介绍如下:
-h, –help -> prints usage information and exit
-n, –nongui -> run JMeter in nongui mode
-t, –testfile <argument> -> the jmeter test(.jmx) file to run
-l, –logfile <argument> -> the file to log samples to
-r, –runremote -> Start remote servers (as defined in remote_hosts)
-H, –proxyHost <argument> -> Set a proxy server for JMeter to use
-P, –proxyPort <argument> -> Set proxy server port for JMeter to use
中文释义:
-h 帮助 -> 打印出有用的信息并退出
-n 非 GUI 模式 -> 在非 GUI 模式下运行 JMeter
-t 测试文件 -> 要运行的 JMeter 测试脚本文件
-l 日志文件 -> 记录结果的文件
-R 远程执行 -> 远程执行机的IP(ip地址)如果有多个ip时,使用-R 192.168.2.170,192.168.2.171(分布式使用)
-r 远程执行 -> 在Jmter.properties文件中指定的所有远程服务器(分布式使用)
-H 代理主机 -> 设置 JMeter 使用的代理主机
-P 代理端口 -> 设置 JMeter 使用的代理主机的端口号
2.3、执行过程
命令:jmeter -n -t C:\Users\yzs\Desktop\Unione_performance.jmx -l report-result.jtl
不在jmeter安卓目录执行脚本的前提是配置了jmeter的环境变量。
上述的命令有测试结果保存到D:\report中,在GUI模式下查看测试报告:
1、在测试计划下,添加对应的测试报告元件,举例增加了:查看结果树、聚合报告
2、在“所有数据写入一个文件”,选择加载对应的结果文件
3、下面就会有对应的表格展示,具体见下图
2.5、命令行传递变量值
设置线程组的线程数和循环次数。
注意格式:
${__P(threadNum)}
${__P(threadCount)}
其中P前面是两个下划线,()内就是变量名
执行时,在命令行中用-J参数给变量赋值即可:
jmeter -n -t C:\Users\yzs\Desktop\Unione_performance.jmx -J threadNum=10 -J threadCount=2 -l report-result.jtl
此次测试相当于:10个线程,循环2次,共计20个请求。
3、生成HTML报告
生成HTML报告有2种方式,一种是直接在命令行加上-o参数,另一种是已有jtl结果文件,运行命令生成报告
3.1、命令行直接生成报告
jmeter -n -t 【Jmx脚本位置】-l 【中间文件result.jtl位置】-e -o 【报告指定文件夹】
-e:测试结束后,生成测试报告
-o:指定测试报告的存放位置
注意:-o后面跟的文件夹一定是不存在的或者是空文件夹
3.2、已有jtl结果文件,运行命令生成报告
jmeter -g【已经存在的.jtl文件的路径】-o 【用于存放html报告的目录】
注意:经实操,windows系统上,以上2种方法都可以生成HTML测试报告,但是在Linux系统上第1种方法,没有生成报告,只有第二种方法才可以(具体原因后面在慢慢找吧)
3.3、HTML报告注解
用浏览器打开index.html
报告详解
Dashboard:(重点查看)
Test and Report informations:指的是测试和报告信息
APDEX(Application Performance Index):应用程序性能满意度的标准
其中,
Requests Summary:请求的通过率(OK)与失败率(KO),百分比显示
Statistics:数据分析,基本将Summary Report和Aggrerate Report的结果合并(平均响应时间、TPS在此查看)
Errors:错误情况,依据不同的错误类型,将所有错误结果展示
关于Apdex的补充:
性能指数,Apdex(Application Performance Index)是一个国际通用标准,Apdex 是用户对应用性能满意度的量化值。它提供了一个统一的测量和报告用户体验的方法,把最终用户的体验和应用性能作为一个完整的指标进行统一度量。下图表示为通用用户满意度区域,0代表没有满意用户,1则代表所有用户都满意。实际业务系统开发过程中,1是团队的追求目标。
若所有请求的Apdex值都接近1,说明用户满意度优秀,也从侧面说明了服务器响应速度快。
通常而言,最低要求超过0.5,当然项目组可设定具体需求。
Charts:(辅助分析)
主要有如下特点:
(1)将测试过程中经常使用的数据,用图表的形式展示,让测试结果更加直观
(2)每个图表数据,有两种展示形式
(3)支持请求样例过滤显示
(4)支持导出PNG图片格式
Over Time Charts:
Throughput Charts:
Response Times Charts:
3.4、HTML报告的自定义配置
JMeter3.0开始在bin目录新增了reportgenerator.properties文件保存了所有关于图形化HTML报告生成模块的默认配置,要变更配置,建议不要直接编辑该文件,而是推荐在user.properties中去配置和覆盖。
3.4.1总体配置
总体配置都是以jmeter.reportgenerator.为前缀,如:jmeter.reportgenerator.overall_granularity=60000
# Change this parameter if you want to change the granularity of over time graphs.
jmeter.reportgenerator.overall_granularity=6000
Apdext=(Satisfied Count + Tolerating Count / 2) / Total Samples
另外,在jmeter.properties中,有关于集合报告中的三个百分位的默认值:
aggregate_rpt_pct1 : Defaults to 50
aggregate_rpt_pct2 : Defaults to 70
aggregate_rpt_pct3 : Defaults to 99
3.5、HTML报告的定制
JMeter的HTML报告生成时是使用了固定的模板,模板文件路径为./bin/report-template。
进入该目录可以看到报告的每个页面都有一个.fmkr模板文件,包括index.html.fmkr和./content/pages路径下的几个文件。通过查看这些模板文件,就可以知道怎样去进行报告的轻度定制,比如将一些文本修改得更易懂,或者修改为中文等
页面的title
默认为"Apache JMeter Dashboard"
可以由reportgenerator.properties中的jmeter.reportgenerator.report_title来统一定义,这种方式就是所有页面的title都使用同一个。
也可以直接修改对应的.fmkr文件中的title标签中双引号内的值,如<title>${reportTitle!"想要设置的title"}</title>,这中方式可以为每个页面单独定义title
图表的名称
当前版本下,各图表的名称是直接在模板文件中定义,要修改也是直接修改模板文件中对应元素的值即可
如要修改Transactions Per Second图表的名称,可以直接在./content/pages/Throughput.html.fmkr文件中修改,效果如下图
odash, Moment, Axios, Async... 这些是有用的JavaScript库,您希望在许多Vue.js应用程序中使用这些库。
但是,随着项目的发展,您将将代码分离为单个文件组件和模块文件。您还可能希望在不同的环境中运行您的应用程序,以允许服务器呈现。
除非您找到一种简单而健壮的方法将这些JavaScript库包含在组件和模块文件中,否则它们将是一个麻烦!
将库添加到项目中的天真方法是通过将库附加到window目的:
entry.js
JavaScript
window._=require('lodash');
MyComponent.vue
JavaScript
export default { created() { console.log(_.isEmpty() ? 'Lodash everywhere!' : 'Uh oh..'); }}
针对窗口变量的情况很长,但是,特别是在本讨论中,它们不适用于服务器呈现。当应用程序在服务器上运行时,window对象将是未定义的,因此试图访问属性将以错误结束。
另一种二流方法是将库导入到每个文件中:
MyComponent.vue
JavaScript
import _ from 'lodash';
export default {
created() {
console.log(_.isEmpty() ? 'Lodash is available here!' : 'Uh oh..');
}
}
这是可行的,但它并不是很干燥,而且基本上是一种痛苦:您必须记住将它导入到每个文件中,如果您停止在该文件中使用它,则再次删除它。如果您没有正确地设置您的构建工具,那么您的构建中可能会有多个相同库的副本。
在Vue项目中使用JavaScript库的最干净和最健壮的方法是将其代理到Vue Prototype对象的属性。让我们这样做,将时间、日期和时间库添加到我们的项目中:
entry.js
JavaScript
import moment from 'moment';
Object.definePrototype(Vue.prototype, '$moment', { value: moment });
由于所有组件都从Vue Prototype对象继承它们的方法,这将使所有组件都可以在没有全局变量或手动导入的任何组件中自动使用。可以简单地在任何实例/组件中访问this.$moment:
MyNewComponent.vue
JavaScript
export default {
created() {
console.log('The time is ' . this.$moment().format("HH:mm"));
}
}
现在让我们花时间了解一下这是如何工作的。
我们通常会设置如下对象属性:
JavaScript
Vue.prototype.$moment=moment;
你可以在这里做,但是Object.defineProperty相反,我们可以使用descriptor。描述符允许我们设置一些低级别的详细信息,例如我们的属性是否可写,以及它是否在枚举期间显示在for循环等等。
在日常的JavaScript中,我们通常不会考虑这个问题,因为99%的时间我们都不需要这样的细节来分配属性。但是在这里,它给了我们一个明显的优势:用描述符创建的属性是只读默认情况下。
这意味着,一些缺少咖啡的开发人员(可能是您)无法在组件中做一些愚蠢的事情,从而破坏一切:
JavaScript
this.$http='Assign some random thing to the instance method';
this.$http.get('/'); // TypeError: this.$http.get is not a function
相反,我们的只读实例方法保护我们的库,如果您试图覆盖它,您将得到“TypeError:NotAssistto只读属性”。
您会注意到,我们将我们的库代理到一个以美元符号“$”为前缀的属性名。您可能还看到了其他属性和方法,如$refs, $on, $mount,等等,它们也有这个前缀。
虽然不是必需的,但前缀被添加到属性中,以提醒缺乏咖啡的开发人员(再次),这是一个欢迎您使用的公共API属性或方法,与实例的其他属性不同,这些属性可能只是用于Vue的内部使用。
作为一种基于原型的语言,JavaScript中没有(真正的)类,因此它没有“私有”和“公共”变量或“静态”方法。这个公约是一个温和的替代,我认为是值得遵循的。
您还会注意到要使用您使用的库this.libraryName这可能并不令人惊讶,因为它现在是一个实例方法。
但是,这样做的一个结果是,与全局变量不同,您必须确保在使用库时处于正确的范围内。在回调方法中,无法访问this你的图书馆住的地方。
FAT箭头回调是确保您保持在正确范围内的一个很好的解决方案:
JavaScript
this.$http.get('/').then(res=> {
if (res.status !==200) {
this.$http.get('/') // etc
// Only works in a fat arrow callback.
}
});
为什么不把它变成插件呢?
如果您计划在多个Vue项目中使用一个库,或者您想要与世界共享它,您可以将其构建到您自己的插件中!
插件抽象了复杂性,并允许您在项目中简单地执行以下操作以添加所选库:
JavaScript
import MyLibraryPlugin from 'my-library-plugin';
Vue.use(MyLibraryPlugin);
使用这两行,我们可以在任何组件中使用库,就像我们可以使用Vue路由器、Vuex和其他利用Vue.use.
首先,为插件创建一个文件。在本例中,我将制作一个插件,将Axios添加到所有Vue实例和组件中,因此我将调用该文件axios.js.
主要要了解的是,插件必须公开install方法,它将Vue构造函数作为第一个参数:
axios.js
JavaScript
export default {
install: function(Vue) {
// Do stuff
}
}
现在,我们可以使用前面的方法将库添加到Prototype对象中:
axios.js
JavaScript
import axios from 'axios';
export default {
install: function(Vue,) {
Object.defineProperty(Vue.prototype, '$http', { value: axios });
}
}
这个use实例方法是将库添加到项目中所需的全部内容。例如,我们现在可以像这样轻松地添加Axios库:
entry.js
JavaScript
import AxiosPlugin from './axios.js';
Vue.use(AxiosPlugin);
new Vue({
created() {
console.log(this.$http ? 'Axios works!' : 'Uh oh..');
}
})
插件安装方法可以采用可选参数。有些开发人员可能不喜欢调用他们的Axios实例方法。$http由于Vue Resource通常被命名为Vue Resource,所以让我们使用一个可选的参数来允许他们将其更改为他们喜欢的任何东西:
axios.js
JavaScript
import axios from 'axios';
export default {
install: function(Vue, name='$http') {
Object.defineProperty(Vue.prototype, name, { value: axios });
}
}
entry.js
JavaScript
import AxiosPlugin from './axios.js';
Vue.use(AxiosPlugin, '$axios');
new Vue({
created() {
console.log(this.$axios ? 'Axios works!' : 'Uh oh..');
}
})
为感谢您对我们的认可,特意准备了一些IT入门和进阶的干货
包括:Java、UI设计、H5前端、Python+人工智能、软件测试和新媒体运营六大学科视频资料。以及IT就业大礼包。
线上视频、音频,随时学习观看
关注我们并私信“资料”即可获取。
*请认真填写需求信息,我们会在24小时内与您取得联系。