模块化
模块就是在单个文件中声明的JavaScript代码。我们可以用JS代码直接从其他文件中导入函数、变量和类。
在NodeJS之前,由于没有过于复杂的开发场景,前端是不存在模块化的,后端才有模块化。
NodeJS诞生之后,它使用CommonJS的模块化规范。从此,js模块化开始快速发展
CommonJS
多用module.exports定义当前模块对外输出的接口(不推荐直接用exports),用require加载模块。
// name.js
module.exports.name = 'abc'
module.exports.sayName = function() {console.log('name')}
或者
// name.js
var name = 'abc';
function sayName() {console.log('name')}
module.exports = {
name: name,
sayName: sayName
}
引用:[普通模块name.js]
var nameObj = require('./name.js')
console.log(nameObj.name)
nameObj.sayName(); // name
如果引用系统模块[会去node_modules里找]
var http = require('http');
http.createService(...).listen(3000);
特点:用同步的方式加载模块。
缺点:在服务端,模块文件都存放在本地磁盘,读取非常快,所以这样做不会有问题。但是在浏览器端,限于网络原因,更合理的方案是使用异步加载。
AMD
AMD是一种异步模块化标准。
RequireJS是AMD最流行的实现。(CommonJS与requireJS无关,只是用了require语句)
场景:项目中我们会将JS组件放到不同的文件里,并通过script标签引入。当组件间存在依赖关系的时候,被依赖的组件需要放到前面。否则的话会出现XXX is undefined或者XXXX is not a function之类的错误。比如一个jquery的插件显然是依赖jquery核心库的,所以jquery核心库文件必须先引入。当组件间依赖复杂时,使用RequireJs可以从一个根开始检查依赖,根据这些依赖关系自动的帮助我们插入script标签。
原理:所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
简单使用:
引入requireJS。指定工程JS模块入口。
// index.html
<script src="lib/require.js" data-main="js/scripts/main.js"></script>
情景一: 入口文件main.js里用RequireJs映射Jquery并使用
require.config(
{
// baseUrl——用于加载模块的根路径
baseUrl:'./js',
// paths——用于映射不存在根路径下面的模块路径
paths: {
'Jquery': lib/jquery'
}
}
);
require(['Jquery'],function ($) {
// jquery操作
$(document).on('click','#contentBtn',function(){
$('#messagebox').html('You have access Jquery by using require()');
});
});
情景二:app.js需要依赖tools.js模块
// tool.js
define(function(){
return{
decs : 'abcd',
};
})
// app.js
require(['./tool'],function(data){
console.log(data.desc)
})
define函数接收[moduleName][requireModeule][callback]三个参数。
完整写法诸如:
define('./app',['./tool'],function(data){
.....
})
第一个参数是定义模块名,第二个参数是传入定义模块所需要的依赖,第三个函数则是定义模块的主函数,主函数和require的回调函数一样,同样是在依赖加载完以后再调用执行。
不建议传入第一个参数,即自定义模块名,因为如果哪一天我将这个文件转移到其他目录下,那我就得在这这里再修改一次模块名。官方其实也不推荐,用官方的说法是:让优化工具去自动生成这些模块名吧!
情景三:将require.config配置在入口文件,以供全局使用
// main.js
define(function(){
require.config({
baseUrl:'./js',
paths: {
'Jquery': lib/jquery'
}
});
});
// app.js
require(['./main.js'],function(){
require(['Jquery'],function ($) {
$(document).on('click','#btn1',function(){
$('#messagebox').html('anc');
require(['./tool'],function(data){
console.log(data.desc)
});
});
});
});
require.config额外配置:
shims——虽然目前已经有一部分流行的函数库(比如 jQuery)符合 AMD 规范,但还有很多库并不符合。shim 就是为了加载这些非 AMD 规范的 js,并解决其载入顺序的。
require.config({
baseUrl: './js',
paths: {
'Jquery': lib/jquery'
}
shim: {
'backbone': {
deps: ['underscore', 'Jquery'],
exports: 'Backbone'
},
'underscore': {
exports: '_'
}
},
});
我们想通过 RequireJS 来使用 backbone,那么你就需要在配置中把它定义为一个 shim。同时通过 deps 配置其依赖关系,可以保证 underscore、jquery 先被加载。
CMD
CMD是另一种js模块化方案,它与AMD很类似,不同点在于:AMD推崇依赖前置、提前执行,CMD推崇依赖就近、延迟执行。CMD最流行的实现模式便是sea.js
AMD模式:
define(["a", "b", "c", "d", "e", "f"], function(a, b, c, d, e, f) {
// 等于在最前面声明并初始化了要用到的所有模块
a.do()
if (false) {
// 即便没用到某个模块 b,但 b 还是提前加载了。**这就CMD要优化的地方**
b.do()
}
});
CMD模式:
define(function(require, exports, module) {
var a = require('./a'); //在需要时申明
a.doSomething();
if (false) {
var b = require('./b');
b.doSomething();
}
});
完整写法:
// index.html
<script type="text/javascript" src="js/libs/sea.js"></script>
<script type="text/javascript">
seajs.use('./js/main.js')
</script>
// main.js
define(function(require,exports,module){
let module1 = require('./module1.js')
console.log(module1.getName()) // module1
let module4 = require('./module4.js') //module2 module4 module3
module4.getVal()
})
// modoule1
define(function(require,exports,module){
let name = 'module1';
function getName(){
return name;
}
//暴露模块
module.exports = {getName}
})
// module4
define(function(require,exports,module){
let val = 'module4'
function getVal(){
console.log(val)
}
// 引入module2 同步
let module2 = require('./module2.js');
// 执行module2
module2()
// 异步引入module3
require.async('./module3.js',function(module3){
//执行module3
module3.module3.getData()
});
// 暴露模块
module.exports = {getVal}
}
// module2.js
define(function(require,exports,module){
let msg = 'module2';
function getMsg(){
console.log(msg)
}
//暴露模块
module.exports = getMsg;
})
// module3.js
define(function(require,exports,module){
let data = 'module3'
function getData(){
console.log(data)
}
// 暴露模块
exports.module3 = {getData}
})
因为在module4.js中引入了module3.js是异步的,所以先打印出module4,再打印出module3.
UMD
UMD是AMD和CommonJS的一个糅合。AMD是浏览器优先,异步加载;CommonJS是服务器优先,同步加载。
既然要通用,怎么办呢?那就先判断是否支持node.js的模块,存在就使用node.js;再判断是否支持AMD(define是否存在),存在则使用AMD的方式加载。这就是所谓的UMD。
((root, factory) => {
if (typeof define === 'function' && define.amd) {
//AMD
define(['jquery'], factory);
} else if (typeof exports === 'object') {
//CommonJS
var $ = requie('jquery');
module.exports = factory($);
} else {
//都不是,浏览器全局定义
root.testModule = factory(root.jQuery);
}
})(this, ($) => {
//do something... 这里是真正的函数体
});
ES6Module
ES2015在JavaScript标准中引入官方模块功能。
// a.js
const numberFn = r=> r * r;
const nmber = 5;
export {numberFn,number}
或者
// a.js
export const numberFn = r=> r * r;
export const nmber = 5;
使用:
import {number as count, numberFn} from './a.js'
console.log(numberFn(count))
或者将整个模块当做一个变量来导入
import * as nn from './a.js'
console.log(nn.numberFn(nn.number))
假设模块中只有一个成员被导出。可以使用export default关键字
// b.js
export default class Book{
constructor(title) {
this.title = title
}
printTitle() {
console.log(this.title)
}
}
import Book from './b.js'
const aBook = new Book('aaa')
aBook.printTitle(); // aaa
导入export default导出模块。不需要将类名包含在花括号中(import {book} from './b.js' X)。只有模块有多个成员被导出才用花括号。
TypeScrip
TypeScript是一个开源的、渐进式包含类型的JavaScript超集。
作用是让开发者增强js的能力并使应用的规模扩展变得更容易。
主要功能之一是为JavaScript变量提供类型支持,在js中提供类型支持以实现静态检查。
有了TS可以使用一些js中没有的面向对象的概念,比如接口和私有属性(对开发数据结构和排序算法非常有用)
最终ts会变编译成简单的js代码。
安装:
npm i -g typescript
创建ts文件:
// a.ts
const tag = 'abc';
tag = 10;
let tag1 = 'ddd'
tag1 = 20
VScode编译器支持在编写代码时进行ts错误检查。
编译ts文件:
tsc a
终端输出了警告的错误信息(不会阻止编译器生成js代码)
Ts特性-类型推断:
// 常见的类型判断
let age: number = 20;
let isDead: boolean = true;
let name: string = 'abc';
ts允许我们给变量设置一个类型。但是上面的写法太啰嗦了。ts有一个类型推断机制,ts会根据为变量赋的值自动给该变量设置一个类型。上面可以写为:
let age = 20;
let isDead = true;
let name = 'abc';
在上面的代码中,ts知道age就是一个数,isDead就是一个布尔值,name就是字符串,不需要显式设置变量类型。
// 当声明了一个变量但是没有设置初始值,建议为其设置一个类型
let ttt: string;
如果没有设置类型,那么它的类型会被设置为any,即接受任何值。
Ts特性-接口:
在ts中,有两个接口概念
概念一:给变量设置一个类型.它是对应各对象必须包含的属性和方法描述。
interface Person {
name: string;
age: number;
}
function printName(person:Person){console.log(person.name)}
概念二:与面向对象编程相关
interface Comparable {
compareTo(b): number;
}
class myClass implements Comparable {
age: number;
compareTo(b): number {
if (this.age === b.age) retrun 0;
return this.age > b.age ? 1 : -1
}
}
comparable接口告诉myclass类,他需要思想一个叫做compareTo的方法,并且该方法接受一个参数,并返回数字结果。在该方法内部,我们可以实现对比逻辑。
Ts特性-泛型:
我们定义一个变量不确定类型的时候有两种解决方式:
使用any
使用any定义时存在的问题:虽然 以 知道传入值的类型但是无法获取函数返回值的类型;另外也失去了ts类型保护的优势
使用泛型
泛型指的是在定义函数/接口/类型时,不预先指定具体的类型,而是在使用的时候在指定类型限制的一种特性。
在函数中使用泛型:
function test <T> (b:T):T{
console.log(b);
return b;
}
test<number>(111);// 返回值是number类型的 111
test<string | boolean>('hahaha')//返回值是string类型的 hahaha
test<string | boolean>(true);//返回值是布尔类型的 true
使用方式类似于函数传参,传什么数据类型,T就表示什么数据类型, 使用表示,T也可以换成任意字符串。
在接口中使用泛型:
// 注意,这里写法是定义的方法哦
interface Search {
<T,Y>(name:T,age:Y):T
}
let fn:Search = function <T, Y>(name: T, id:Y):T {
console.log(name, id)
return name;
}
fn('li',11);//编译器会自动识别传入的参数,将传入的参数的类型认为是泛型指定的类型
在类中使用泛型:
class Animal<T> {
name:T;
constructor(name: T){
this.name = name;
}
action<T>(say:T) {
console.log(say)
}
}
let cat = new Animal('cat');
cat.action('mimi')
————————————————
版权声明:本文为CSDN博主「xianghong_yang」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_44761091/article/details/124017662
附录-在浏览器中运行ES2015模块
可以看到部分浏览器依然不支持ES6。
想要在浏览器里有更好的兼容性体验,可以使用流行的代码打包工具,如Browserify或Webpack。生成编译成ES5代码的JS文件。
ES6有很好的向后兼容性。ES6是JS语言的超集,所有符合ES5规范的特性都可以在往后的浏览器上使用。
想要开发一套在浏览器和Nodejs环境中可以通用使用的,我们需要将代码转译成UMD通用模块定义。
附录-让TS检查在js文件里运行
①在计算机全局安装TypeScript
npm i -g typescript
②在jis文件的第一行添加一句// @ts-check