一、初识Module
模块:一个一个的局部作用域的代码块
模块系统需要解决的主要问题:
① 模块化的问题
② 消除全局变量
③ 管理加载顺序
Module的基本用法:
import、export:
只要你会用到 import(导入) 或 export(导出),在使用 script 标签加载的时候,就要加上 type="module"
示例:
<div class="slider-layout">
<div class="slider">
<div class="slider-content">
<div class="slider-item">
<a href="javascript:;"><img src="./imgs/1.jpg" alt="1" class="slider-img" /></a>
</div>
<div class="slider-item">
<a href="javascript:;"><img src="./imgs/2.jpg" alt="1" class="slider-img" /></a>
</div>
<div class="slider-item">
<a href="javascript:;"><img src="./imgs/3.jpg" alt="1" class="slider-img" /></a>
</div>
<div class="slider-item">
<a href="javascript:;"><img src="./imgs/4.jpg" alt="1" class="slider-img" /></a>
</div>
</div>
</div>
</div>
<script src="./index.js" type="module"></script>
/* css reset */
* {
padding: 0;
margin: 0;
}
a {
text-decoration: none;
outline: none;
}
img {
vertical-align: top;
}
/* layout */
.slider-layout {
width: 80%;
height: 420px;
margin: 0 auto;
}
/* slider */
.slider,
.slider-content,
.slider-item,
.slider-img {
width: 100%;
height: 100%;
}
.slider {
overflow: hidden;
}
.slider-item {
float: left;
}
.slider-animation {
transition-property: transform;
transition-duration: 0ms;
}
//base.js
// 默认参数
const DEFAULTS = {
// 初始索引
initialIndex: 0,
// 切换时是否有动画
animation: true,
// 切换速度,单位 ms
speed: 300
};
// base
const ELEMENT_NODE = 1;
const SLIDER_ANIMATION_CLASSNAME = 'slider-animation';
// 父类
class BaseSlider {
constructor(el, options) {
if (el.nodeType !== ELEMENT_NODE)
throw new Error('实例化的时候,请传入 DOM 元素!');
// 实际参数
this.options = {
...DEFAULTS,
...options
};
const slider = el;
const sliderContent = slider.querySelector('.slider-content');
const sliderItems = sliderContent.querySelectorAll('.slider-item');
// 添加到 this 上,为了在方法中使用
this.slider = slider;
this.sliderContent = sliderContent;
this.sliderItems = sliderItems;
this.minIndex = 0;
this.maxIndex = sliderItems.length - 1;
this.currIndex = this.getCorrectedIndex(this.options.initialIndex);
// 每个 slider-item 的宽度(每次移动的距离)
this.itemWidth = sliderItems[0].offsetWidth;
this.init();
}
// 获取修正后的索引值
// 随心所欲,不逾矩
getCorrectedIndex(index) {
if (index < this.minIndex) return this.maxIndex;
if (index > this.maxIndex) return this.minIndex;
return index;
}
// 初始化
init() {
// 为每个 slider-item 设置宽度
this.setItemsWidth();
// 为 slider-content 设置宽度
this.setContentWidth();
// 切换到初始索引 initialIndex
this.move(this.getDistance());
// 开启动画
if (this.options.animation) {
this.openAnimation();
}
}
// 为每个 slider-item 设置宽度
setItemsWidth() {
for (const item of this.sliderItems) {
item.style.width = `${this.itemWidth}px`;
}
}
// 为 slider-content 设置宽度
setContentWidth() {
this.sliderContent.style.width = `${
this.itemWidth * this.sliderItems.length
}px`;
}
// 不带动画的移动
move(distance) {
this.sliderContent.style.transform = `translate3d(${distance}px, 0px, 0px)`;
}
// 带动画的移动
moveWithAnimation(distance) {
this.setAnimationSpeed(this.options.speed);
this.move(distance);
}
// 设置切换动画速度
setAnimationSpeed(speed) {
this.sliderContent.style.transitionDuration = `${speed}ms`;
}
// 获取要移动的距离
getDistance(index = this.currIndex) {
return -this.itemWidth * index;
}
// 开启动画
openAnimation() {
this.sliderContent.classList.add(SLIDER_ANIMATION_CLASSNAME);
}
// 关闭动画
closeAnimation() {
this.setAnimationSpeed(0);
}
// 切换到 index 索引对应的幻灯片
to(index) {
index = this.getCorrectedIndex(index);
if (this.currIndex === index) return;
this.currIndex = index;
const distance = this.getDistance();
if (this.options.animation) {
return this.moveWithAnimation(distance);
} else {
return this.move(distance);
}
}
// 切换上一张
prev() {
this.to(this.currIndex - 1);
}
// 切换下一张
next() {
this.to(this.currIndex + 1);
}
// 获取当前索引
getCurrIndex() {
return this.currIndex;
}
}
//导出--便于外面文件的访问
export default BaseSlider;
//slider.js
//导入
import BaseSlider from './base.js';
class Slider extends BaseSlider {
constructor(el, options) {
super(el, options);
this._bindEvent();
}
_bindEvent() {
document.addEventListener(
'keyup',
ev => {
if (ev.keyCode === 37) {
this.prev();
} else if (ev.keyCode === 39) {
this.next();
}
},
false
);
}
}
export default Slider;
//index.js
import Slider from './slider.js';
new Slider(document.querySelector('.slider'));
当你按键盘上的左右按钮进行切换时:
二、 Module的导入导出
1.export default导出和对应的import导入
导出的东西可以被导入(import),并访问到
一个模块没有导出,也可以将其导入
被导入的代码都会执行一遍,也仅会执行一遍
import './module.js';
示例:
<script type="module">
import './module.js';
</script>
const age = 18;
// const sex = 'male';
console.log(age);
一个模块只能有一个 export default
export default 名;//导出
import 名 from '文件路径';//导入
示例:
<script type="module">
import age from './module.js';
console.log(age);
</script>
const sex = 'male';
// 一个模块只能有一个 export default
export default sex;
2.export导出和对应的import导入
a.基本用法:
语法:export 声明或语句
export const age = 18;
<script type="module">
//切记:不能随意命名
import {age} from './module.js';
console.log(age);
</script>
//方式1
export const age = 18;
//方式2:
const age = 18;
// export age; ×
export { age }; // √
b.导出、导入多个
//导入方式1
// import {fn} from './module.js';
// console.log(fn);
// import {className} from './module.js';
// console.log(className);
// import {age} from './module.js';
// console.log(age);
//导入方式2
import { fn, age, className } from './module.js';
console.log(fn, age, className);
function fn() {};
//export function fn() {}; //导出方式1
// export function () {} // 匿名函数不行
// export { fn }; //导出方式2
class className {}
// export { className }; //导出方式2
// export class className {}//导出方式1
// export class {} // 匿名不行
// export const age = 18;//导出方式1
const age = 18;
// export { age };
export { fn, className, age }; //导出方式3
c.导出导入时起别名
<script type="module">
import { func, age, className as person } from './module.js'; console.log(func, age, person);
</script>
function fn() {};
//export function fn() {}; //导出方式1
// export function () {} // 匿名函数不行
// export { fn }; //导出方式2
class className {}
// export { className }; //导出方式2
// export class className {}//导出方式1
// export class {} // 匿名不行
// export const age = 18;//导出方式1
const age = 18;
// export { age };
export { fn as func, className, age }; //导出方式3
d.整体导入
会导入所有输出,包括通过 export default 导出的
<script type="module">
import * as obj from './module.js'; console.log(obj);
</script>
function fn() {};
//export function fn() {}; //导出方式1
// export function () {} // 匿名函数不行
// export { fn }; //导出方式2
class className {}
// export { className }; //导出方式2
// export class className {}//导出方式1
// export class {} // 匿名不行
// export const age = 18;//导出方式1
const age = 18;
// export { age };
export { fn as func, className, age }; //导出方式3
export default 18;
e.同时导入
注意:一定是 export default 的在前
<script type="module">
import age2, { func, age, className } from './module.js'; console.log(age2);
</script>
function fn() {};
//export function fn() {}; //导出方式1
// export function () {} // 匿名函数不行
// export { fn }; //导出方式2
class className {}
// export { className }; //导出方式2
// export class className {}//导出方式1
// export class {} // 匿名不行
// export const age = 18;//导出方式1
const age = 18;
// export { age };
export { fn as func, className, age }; //导出方式3
export default 18;
三、Module的注意事项和应用
1.注意事项
a.模块顶层的 this 指向
模块中,顶层的 this 指向 undefined
import './module.js';
<script type="module">
import './module.js';
</script>或
<script src="./module.js" type="module"></script>
console.log(this);
//这里的顶层指的是不在if、for这样的块级作用域中或者不在function这样的函数作用域中,而是在模块最顶层的作用域中
b.import关键字 和 import()函数
import关键字 命令具有提升效果,会提升到整个模块的头部,率先执行
<script type="module">
console.log('沙发');
console.log('第二');
import './module.js';
</script>
console.log(this);
注意:
- import 执行的时候,代码还没执行
- import 和 export 命令只能在模块的顶层,不能在代码块中执行
//错误
if (PC) {
import 'pc.js';
} else if (Mobile) {
import 'mobile.js';
}
import() 可以按条件导入
if (PC) {//将import关键字改为import()函数
import('pc.js').then().catch();
} else if (Mobile) {
import('mobile.js').then().catch();
}
c.导入导出的复合写法
复合写法导出的,无法在当前模块中使用
<script type="module">
export { age } from './module.js';
console.log(age);
// 等价于
// import { age } from './module.js';
// export { age } from './module.js';
// console.log(age);
</script>
export const age = 18;
2.应用
<div class="slider-layout">
<div class="slider">
<div class="slider-content">
<div class="slider-item">
<a href="javascript:;"><img src="./imgs/1.jpg" alt="1" class="slider-img" /></a>
</div>
<div class="slider-item">
<a href="javascript:;"><img src="./imgs/2.jpg" alt="1" class="slider-img" /></a>
</div>
<div class="slider-item">
<a href="javascript:;"><img src="./imgs/3.jpg" alt="1" class="slider-img" /></a>
</div>
<div class="slider-item">
<a href="javascript:;"><img src="./imgs/4.jpg" alt="1" class="slider-img" /></a>
</div>
</div>
</div>
</div>
<script src="./index.js" type="module"></script>
/* css reset */
* {
padding: 0;
margin: 0;
}
a {
text-decoration: none;
outline: none;
}
img {
vertical-align: top;
}
/* layout */
.slider-layout {
width: 80%;
height: 420px;
margin: 0 auto;
}
/* slider */
.slider,
.slider-content,
.slider-item,
.slider-img {
width: 100%;
height: 100%;
}
.slider {
overflow: hidden;
}
.slider-item {
float: left;
}
.slider-animation {
transition-property: transform;
transition-duration: 0ms;
}
//index.js
import Slider from './slider.js';
new Slider(document.querySelector('.slider'));
//slider.js
import BaseSlider from './base.js';
import Keyboard from './keyboard.js';
// import Mouse from './mouse.js';
class Slider extends BaseSlider {
constructor(el, options) {
super(el, options);
this._bindEvent();
}
_bindEvent() {
Keyboard.bindEvent(this);
// Mouse.bindEvent(this);
}
}
export default Slider;
//base.js
// 默认参数
import DEFAULTS from './defaults.js';
// 常量
import { ELEMENT_NODE, SLIDER_ANIMATION_CLASSNAME } from './constants.js';
class BaseSlider {
constructor(el, options) {
if (el.nodeType !== ELEMENT_NODE)
throw new Error('实例化的时候,请传入 DOM 元素!');
// 实际参数
this.options = {
...DEFAULTS,
...options
};
const slider = el;
const sliderContent = slider.querySelector('.slider-content');
const sliderItems = sliderContent.querySelectorAll('.slider-item');
// 添加到 this 上,为了在方法中使用
this.slider = slider;
this.sliderContent = sliderContent;
this.sliderItems = sliderItems;
this.minIndex = 0;
this.maxIndex = sliderItems.length - 1;
this.currIndex = this.getCorrectedIndex(this.options.initialIndex);
// 每个 slider-item 的宽度(每次移动的距离)
this.itemWidth = sliderItems[0].offsetWidth;
this.init();
}
// 获取修正后的索引值
// 随心所欲,不逾矩
getCorrectedIndex(index) {
if (index < this.minIndex) return this.maxIndex;
if (index > this.maxIndex) return this.minIndex;
return index;
}
// 初始化
init() {
// 为每个 slider-item 设置宽度
this.setItemsWidth();
// 为 slider-content 设置宽度
this.setContentWidth();
// 切换到初始索引 initialIndex
this.move(this.getDistance());
// 开启动画
if (this.options.animation) {
this.openAnimation();
}
}
// 为每个 slider-item 设置宽度
setItemsWidth() {
for (const item of this.sliderItems) {
item.style.width = `${this.itemWidth}px`;
}
}
// 为 slider-content 设置宽度
setContentWidth() {
this.sliderContent.style.width = `${
this.itemWidth * this.sliderItems.length
}px`;
}
// 不带动画的移动
move(distance) {
this.sliderContent.style.transform = `translate3d(${distance}px, 0px, 0px)`;
}
// 带动画的移动
moveWithAnimation(distance) {
this.setAnimationSpeed(this.options.speed);
this.move(distance);
}
// 设置切换动画速度
setAnimationSpeed(speed) {
this.sliderContent.style.transitionDuration = `${speed}ms`;
}
// 获取要移动的距离
getDistance(index = this.currIndex) {
return -this.itemWidth * index;
}
// 开启动画
openAnimation() {
this.sliderContent.classList.add(SLIDER_ANIMATION_CLASSNAME);
}
// 关闭动画
closeAnimation() {
this.setAnimationSpeed(0);
}
// 切换到 index 索引对应的幻灯片
to(index) {
index = this.getCorrectedIndex(index);
if (this.currIndex === index) return;
this.currIndex = index;
const distance = this.getDistance();
if (this.options.animation) {
return this.moveWithAnimation(distance);
} else {
return this.move(distance);
}
}
// 切换上一张
prev() {
this.to(this.currIndex - 1);
}
// 切换下一张
next() {
this.to(this.currIndex + 1);
}
// 获取当前索引
getCurrIndex() {
return this.currIndex;
}
}
export default BaseSlider;
//defaults.js
// 默认参数
// const DEFAULTS = {
// // 初始索引
// initialIndex: 0,
// // 切换时是否有动画
// animation: true,
// // 切换速度,单位 ms
// speed: 300
// };
// export default DEFAULTS;
export default {
// 初始索引
initialIndex: 0,
// 切换时是否有动画
animation: true,
// 切换速度,单位 ms
speed: 300
};
//keyboard.js
import { LEFT_KEYCODE, RIGHT_KEYCODE } from './constants.js';
const keyboard = {
bindEvent(slider) {
document.addEventListener(
'keyup',
ev => {
if (ev.keyCode === LEFT_KEYCODE) {
slider.prev();
} else if (ev.keyCode === RIGHT_KEYCODE) {
slider.next();
}
},
false
);
}
};
export default keyboard;
//constants.js
// base
export const ELEMENT_NODE = 1;
export const SLIDER_ANIMATION_CLASSNAME = 'slider-animation';
// keyboard
export const LEFT_KEYCODE = 37;
export const RIGHT_KEYCODE = 39;
当单击键盘上的左右箭头时: