文章目录
一、初识ES6
ES与JavaScript的关系
JavaScript(浏览器端) = ECMAScript(语法 + API) + DOM + BOM
对于不支持的浏览器,可以使用Bable转码
let和const
let代替var声明变量
const用来声明常量
注意:
- const声明的常量,允许在不重新赋值的情况下修改它的值
const person = {username : 'aaa'};
person = {}; // 报错
person.username = 'niubi'; // 可以成功修改
console.log(person);
- 在一个具体的业务场景中,如果不清楚用const还是let,就先写成const
和var的区别
-
重复声明:var一个变量可以重复声明,let和const则不能
-
变量提升:let和const不存在变量提升
-
暂时性死区
只要作用域内存在let、const,他们所声明的变量或常量就会自动 “绑定” 这个区域,不再受外部作用域的影响
let a = 2; function func () { console.log(a); let a = 1; } func(); // 报错 let b = 1; function func() { console.log(b); } func(); // 成功打印
-
window对象的属性和方法
全局作用域中,var声明的变量、通过function声明的函数,会自动变为window对象的属性或方法,但let、const不会
-
块级作用域
var没有块级作用域,let、const有块级作用域
作用域:块级作用域,函数作用域,全局作用域
二、模板字符串与箭头函数
模板字符串
const person = {
username: 'name',
age: 20,
sex: 'male'
};
const info1 = '我叫' + person.username + ',性别: ' + person.sex + ',今年' + person.age + '岁了';
console.log(info1);
const info2 = `我叫${person.username},性别: ${person.sex},今年${person.age}岁了`;
console.log(info);
注意:
- 模板字符串中,所有空格、换行或缩进都会被保留在输出中
- 只要最终可以得出一个值的就可以通过
${}
注入到模板字符串中
箭头函数
省略了function关键字
(x, y) => {
return x + y;
}; // 此时是一个匿名函数
const add = (x, y) => {
return x + y;
}
注意:
- 单个参数:可以将参数的括号省略掉
const add = x => {
return x + 1;
};
- 单行函数体:可以同时省略
{}
和return
const add = (x, y) => {
return x + y;
};
const add = (x, y) => x + y;
- 单行对象:如果箭头函数返回单行对象,可以在{}外面加上(),让浏览器不再认为那是函数体的花括号
const add = (x, y) => {
return {
value: x + y
};
};
const add = (x, y) => ({
value: x + y
});
this指向
非箭头函数的this指向
- 全局作用域中的this指向
console.log(this); // window
- 一般函数中的this指向
this指向和函数在哪调用无关,只和谁在调用有关
// 1.
function add() {
console.log(this);
}
add();
// 严格模式下,指向undefined
// 非严格模式下,undefined -> window
// 2.
document.onclick = function () {
console.log(this);
};
document.onclick(); // 指向document
// 3.
function Person(username) {
this.username = username;
console.log(this);
}
const p = new Person('lisi');
箭头函数的this指向
箭头函数没有自己的this(通过作用域链查找)
不适合箭头函数的场景
- 作为构造函数
- 需要this指向调用对象的时候
- 需要使用arguments的时候
三、解构赋值
什么是解构赋值
解析某一数据的结构,将我们想要的东西提取出来,赋值给变量或常量
const arr = [1, 2, 3];
const a = arr[0];
const b = arr[1];
const c = arr[2];
console.log(a, b, c);
// 通过下标来访问元素比较麻烦
// 可通过如下方式简化
const [a, b, c] = [1, 2, 3];
console.log(a, b, c);
数组的解构赋值
- 模式(解构)匹配
- 索引值相同的完成赋值
const [a, [, , b], c] = [1, [2, 4, 5], 3];
console.log(a, b, c); // 1, 5, 3
数组解构赋值的默认值
const [a, b] = []; // 等价于 const [a, b] = [undefined, undefined];
console.log(a, b); // undefined undefined
// 给取到undefined元素一个默认值
const [a = 1, b = 2] = [];
console.log(a, b); // 1 2
默认值的生效条件
只有当一个数组成员严格等于 ===
undefined
时,对应的默认值才会生效
默认值表达式
如果默认值是表达式,默认值表达式是惰性求值的
// 如果用不到表达式,那么表达式就不会执行
const func = () => {
console.log('执行了表达式');
};
const [a = func()] = [1];
console.log(a); // 1
类数组的解构赋值
function func() {
const [a, b] = arguments;
console.log(a, b); // 1 2
}
func(1, 2);
<p>123</p>
<p>456</p>
<script>
const [p1, p2] = document.querySelectAll('p');
console.log(p1, p2);
</script>
函数参数的解构赋值
const array = [1, 1];
const add = arr => arr[0] + arr[1];
// 用解构赋值之后
const add = ([a, b]) => a + b;
console.log(add(array)); // 2
交换变量的值
[x, y] = [y, x];
对象的解构赋值
- 模式匹配
- 属性名相同的完成赋值(和书写顺序无关)
const {age, username} = {username: 'lisi', age: 18};
// 完整写法
const {age: age, username: username} = {username: 'lisi', age: 18};
// 起别名
const {age: nage, username: nname} = {username: 'lisi', age: 18};
console.log(nage, nname); // 18 'lisi'
console.log(age, username); // 报错
对象解构赋值的默认值
对象的属性值严格等于undefined
时,对应的默认值才会生效
const {username = 'niubi', age = 0} = {username: 'lisi'};
- 默认值表达式
如果默认值是表达式,那么默认值表达式是惰性求值的
- 将一个已经声明的变量用于解构赋值
let x = 2;
({x} = {x: 1}); // 加上圆括号,防止{}被浏览器认为是代码块
- 可以取到继承的属性
const {a = 1} = {};
console.log(a); // 1
const {toString} = {};
console.log(toString); // 不是undefined
// Object.prototype 可以取到继承的属性
console.log(Object.prototype);
其他数据类型的解构赋值
字符串的解构赋值
- 数组形式的解构赋值
const [a, b, , , c] = 'hello';
console.log(a,b,c);
- 对象形式的解构赋值
const {0: a, 1: b, length} = 'hello'; // 属性名就是索引值
数值和布尔值的解构赋值
将等号右边的值转为对象,只能获取到继承的东西
const {aa, toString} = 123;
console.log(aa, toString);
undefined 和 null 的解构赋值
由于undefined和null无法转为对象,所以对它们进行解构赋值都会报错
四、对象字面量的增强与函数参数的默认值
对象字面量的增强
对象字面量
// 对象字面量
const person = {
age: 18,
speak: function () {};
};
属性简洁表示
当键名和变量或常量名一样的时候,可以只写一个
const age = 18;
const person2 = {
age: age
};
const person3 = {
age
};
方法的简洁表示
const person = {
// speak: function () {}
speak(){};
};
方括号语法
给对象添加属性
const prop = 'age';
const person = {};
person[prop] = 18;
// 此时person对象如下
{age: 18}
// 方括号语法可以写在对象字面量中
const person = {
[prop]: 18
};
方括号中可以放什么?
[值或通过计算可以得到值的(表达式)]
方括号语法和点语法的区别
点语法是方括号语法的特殊形式,当属性名或方法名是合法标识符时,可以使用点语法,其他情况使用方括号语法
函数参数的默认值
和解构赋值时设置默认值的方式类似
const multiply = (x, y) => {
if (typeof y === 'undefined') {
y = 1;
}
return x * y;
};
// 设置参数默认值
const multiply2 = (x, y = 1) => {
return x * y;
};
注意:
- 默认值生效条件:不传参,或明确传undefined为参数的情况下,默认值才会生效
- 默认值表达式:惰性求值
- 函数参数的默认值最好从参数列表的右边开始设置
经典用法
const loguser = ({username = 'zhangsan', age = 0, sex = 'male'} = {}) => console.log(username, age, sex);
logUser();
五、剩余参数与展开运算符
剩余参数
当不确定要传几个参数的时候就用剩余参数
[1, 2, 3] ---> 1, 2, 3
const add = (x, y, z, ...args) => {
console.log(x, y, z, args);
};
实参与形参中的固定参数进行对应,剩余的就归剩余参数管(放入数组当中)
剩余参数永远是个数组,若没有取到值,那就是空数组
注意:
-
箭头函数的剩余参数
箭头函数的参数部分当只有一个剩余参数时,不能省略圆括号
const add = (...args) => { }
-
使用剩余参数代替arguments获取实际参数
由于箭头函数中没有arguments,所以要想获取参数数组,就可以用剩余参数
const add = (...args) => { console.log(args); }; add(1, 2);
-
剩余参数的位置
剩余参数只能是最后一个参数,之后不能再有其他参数,否则会报错
数组的展开运算符
将数组的形式展开为参数列表的形式
[1, 2, 3] ---> 1, 2, 3
eg. 求数组中的最小值
let min = Math.min(...[2, 1, 3]);
// 相当于 Math.min(2, 1, 3);
展开运算符应用
-
复制数组
let arr = [1, 2, 3, 4]; let arr2 = [...arr];
-
合并数组
let arr1 = [1, 2]; let arr2 = [3, 4]; let arr3 = [5]; let arr = [...arr1, ...arr2, ...arr3];
-
字符串转为数组
[...'hello'];
-
类数组对象转换为数组
[...arguments];
[...document.querySelectorAll('p')];
对象的展开运算符
对象不能直接展开,必须要在{}
中
const student = {
name: 'xiaohong',
sex: 'female'
};
const student1 = {...student};
也可以将对象的属性进行合并
注意:
-
空对象的展开
如果展开一个空对象,则没有任何效果
-
非对象的展开
如果展开的不是对象,则会自动将其转为对象,再将其属性罗列出来
如果展开字符串,它会自动转成一个类似数组的对象,因此返回的不是空对象
console.log({...'hello'}); // {0: 'h', 1: 'e', 2: 'l', 3: 'l', 4: 'o'}
-
不会展开对象中的对象属性
应用:
-
复制对象
-
用户参数和默认参数
const logUser = userParam => { const defaultParam = { username: 'ZhangSan', age: 0, sex: 'male' }; const param = {...defaultParam, ...userParam}; // 将用户参数和默认参数进行合并 console.log(param.username); };
六、Set 和 Map 数据结构
Set
Set 是一系列无序、没有重复值的数据集合
const s = new Set();
s.add(1);
s.add(2);
注意:
- Set中不能有重复成员
- Set 没有下标去标示每一个值,所以 Set 是无序的,也不能像数组那样通过下标访问
方法
-
add 方法
s.add(1).add(2); // 支持连续调用
-
has 方法
判断Set中是否含有某个成员
console.log(s.has(1)); // true
-
delete 方法
删除成员(删除不存在的成员不会报错)
-
clear
清空 Set
-
forEach
遍历set中的元素(按照成员添加进集合的顺序遍历)
s.forEach(function (value, key, set) { // set是当前set的引用 console.log(value, key, set === s) }, document); // 第二个参数指定函数中的this指向 // set 中value == key
-
size
Set 构造函数的参数
Set 构造函数可以传入数组、字符串、arguments、NodeList、Set等
注意
-
Set判断重复的方式
Set 对重复值的判断基本遵循严格相等
===
,但是对于NaN的判断与===
不同,Set 中NaN 等于 NaN -
什么时候使用Set
- 数组或字符串去重
- 不需要通过下标访问,只需要遍历时
- 为了使用Set提供的方法和属性时
Map
映射
Map 和对象都是键值对的集合
const m = new Map();
m.set('age', 18); // 添加元素
Map 和对象的区别
对象一般用字符串当作键,而 Map 的键可以是基本数据类型,引用数据类型
Map实例的方法和属性
-
set
const m = new Map(); m.set('age', 18).set(true, 'true').set('age', 20); console.log(m); // 使用set添加新成员,键如果已经存在,后添加的键值会覆盖已有的
-
get
通过键来获取值
console.log(m.get('age'));
-
has
-
delete
-
clear
-
forEach
-
size
和Set的方法用法类似
构造函数的参数
-
数组
只能传二维数组,并且必须体现出键和值
console.log(new Map([['name', 'lisi'], ['age', 18]]));
-
Set、Map
Set中也必须体现出键和值
const s = new Set([['name', 'lisi'], ['age', 18]]); console.log(new Map(s));
const m1 = new Map([['name', 'lisi'], ['age', 18]]); const m2 = new Map(m1);
注意
-
判断键名的方式
基本遵循严格相等
===
,是对于NaN的判断与===
不同,Map 中NaN 等于 NaN -
什么时候使用Map
如果只是需要 key -> value 的结构,或者需要字符串以外的值做键,使用Map更合适
七、遍历器与for…of循环
Iterator
Symbol.iterator(可遍历对象的生成方法) --> it (可遍历对象) --> it.next() --> it.next --> 直到done为 true
const it = [1, 2][Symbol.iterator]();
console.log(it.next()); // {value: 1, done: false}
console.log(it.next()); // {value: 2, done: false}
console.log(it.next()); // {value: undefined, done: true};
console.log(it.next()); // {value: undefined, done: true};
Iterator遍历器是一个统一的遍历方式,但使用过程较繁琐,一般情况下不会直接使用Iterator遍历
ES6提供了for…of使遍历更方便
for…of
Iterator自动化
const arr = [1, 2, 3];
const it = arr[Symbol.iterator]();
let next = it.next();
console.log(next);
while (!next.done) {
console.log(next.value);
next = it.next();
console.log(next);
}
使用for…of
const arr = [1, 2, 3];
for(const item of arr) {
console.log(item);
} // for…of循环只会遍历出那些done为false时,对应的value值
在for…of中得到数组的索引
const arr = [1, 2, 3];
for (const keys of arr.keys()) {
console.log(keys);
}
entries()得到的是索引 + 值组成的数组的可遍历对象
for (const entries of arr.entries()) {
console.log(entries);
}
原生可遍历与非原生可遍历
只要有 Symbol.iterator 方法,并且这个方法可以生成可遍历对象,就是可遍历的
只要可遍历,就可以使用for…of来统一遍历
原生可遍历
可以直接使用for…of遍历
- 数组
- 字符串
- Set
- Map
- arguments
- NodeList
非原生可遍历
默认情况下不能直接使用for…of来遍历
- 一般对象
const person = {sex: 'male', age: 18};
person[Symbol.iterator] = () => {
let index = 0;
return {
next(){
index++;
if (index === 1) {
return {
value: person.sex,
done: false
};
} else if (index === 2) {
return {
value: person.age,
done: false
};
} else {
return {
done: true
};
}
}
}
}
for (const item of person) {
console.log(item);
}
使用了iterator的地方
-
数组的展开运算符(只要可遍历,就可以使用数组的展开运算符)
-
数组的解构赋值(只要可遍历,就可按数组方式进行解构赋值)
const [a, b] = [1, 2]; const [a, b] = [...[1, 2]]; const [a, b] = 'hi'; const [a, b] = [...'hi']; const [a, b] = new Set([3, 4]); const [a, b] = [...new Set([3, 4])];
-
Set和Map的构造函数
构造函数的参数必须是可遍历的
八、ES6新增方法
1. 字符串新增方法
includes()
判断字符串中是否含有某些字符
console.log('abc'.includes('a')); // true
console.log('abc'.includes('ab')); // true
console.log('abc'.includes('ac')); // false
穿第二个参数表示开始搜索的位置(默认是0)
console.log('abc'.includes('a', 0)); // true
console.log('abc'.includes('a', 1)); // false
padStart() 和 padEnd()
补全字符串长度
'm'.padStart(6,'ab'); // 'ababam'
'm'.padEnd(6, 'ab'); // 'mababa'
注意:
-
原字符串的长度等于或大于最大长度,不会消减原字符串,字符串不全不生效,返回原字符串
-
用来补全的字符串与原字符串长度之和超过了最大长度,截去超出位数的补全字符串,原字符串不动
console.log('abc',padStart(10, '0123456789'));
-
如果省略第二个参数,默认使用空格补全
trimStart() 和 trimEnd()
清除字符串的首或尾空格
对应和trimLeft() 和 trimRight() 一样
trim()首尾空格全部清除
replace() 和replace()
replace()用来替换第一个匹配的内容
replaceAll() 用来替换所有匹配的内容
'aabbcc'.replace('b', 'x'); // aaxbcc
'aabbcc'.replaceAll('b', 'x'); // aaxxcc
2. 数组新增方法
includes()
基本遵循严格相等,但认为NaN 等于 NaN
Array.from()
将其他数据类型转换为数组
可以转化为数组的有:
-
数组、字符串、Set、Map、nodeList、arguments均可转换为数组
-
拥有length属性的任意对象
第二个参数类似于map方法,对每个元素处理之后放入数组中
let arr = [1, 2, 3, 4];
const arr2 = arr.map((value) => {
return value * 2;
});
console.log(arr2); // [2, 4, 6, 8] map方法会返回一个新数组
const arr = [1, 2, 3, 4];
const arr2 = Array.from(arr, value => value * 2);
console.log(arr);
console.log(arr2);
第三个参数用来修改内部的第二个参数内部的this指向
find() 和 findIndex()
find() 找到满足条件的一个立即返回
findIndex() 找到满足条件的一个,立即返回其索引
const arr = [1, 2, 3, 4, 9, 3, 4];
const num = arr.find((value) => {
return value > 5;
});
console.log(num); // 9
第二个参数修改第一个参数中的this指向
3. 对象新增方法
Object.assign()
用来合并对象
const apple = {
color: '红色',
shape: '圆形',
taste: '甜'
};
const pen = {
color: '黑色',
shape: '圆柱形',
use: '写'
};
// 合并对象
console.log(Object.assign(apple, pen)); // {color: '黑色', shape: '圆柱形', taste: '甜', use: '写'}
console.log(apple); // {color: '黑色', shape: '圆柱形', taste: '甜', use: '写'}
console.log(pen); // {color: '黑色', shape: '圆柱形', use: '写'}
// 该方法直接合并到了第一个参数中,返回的就是合并后的对象
如果要返回一个新对象,可以用下面的方法
Object.assign({}, apple, pen);
注意:
基本数据类型作为源对象
与对象的展开类似,先转换成对象再合并
console.log(Object.assign({}, undefined));
Object.keys()、Object.values()、Object.entries()
const person = {
name: 'lisi',
age: 18
};
console.log(Object.keys(person)); // ['name', 'age']
console.log(Object.values(person)); // ['lisi', 18]
console,log(Object.entries(person)); // 二维数组
返回的都是数组
使用for…of遍历对象
const person = {
name: 'lisi',
age: 18
};
for (const person1 of Object.entries(person)) {
console.log(person1);
} // 不能保证顺序
九、Promise
Promise 是异步操作的一种解决方案
一般用来解决层层嵌套的回调函数的问题
解决的不是回调函数,而是回调地狱
const p = new Promise(() => {}); // pending 未完成
1. Promise 的状态
const p = new Promise((resolve, reject) => {
resolve(); // pending -> fulfilled 已成功
// reject(); // pending -> rejected 已失败
});
Promise 的状态一旦变化,就不会改变了
2. then 方法
p.then(() => {
console.log('success');
}, () => {
console.log('fail');
}); // 已成功 执行第一个函数,已失败,执行第二个函数
执行后的返回值
then 方法执行后返回一个新的Promise 对象
const p = new Promise((resolve, reject) => {
resolve('resolve');
});
p.then(data => {
console.log('succ1', data); // succ1 resolve
}, data2 => {
console.log('err1', data2);
// return undefined; // 默认返回undefined
// 相当于
return new Promise(resolve => {
resolve(undefined);
});
}).then(data3 => {
console.log('succ2', data3); // succ2 undefined
}, (data4) => {
console.log('err2', data4);
});
3. catch() 方法
catch() 本质上是then() 的特例,专门用来处理rejected 状态
const p = new Promise((resolve, reject) => {
reject('fail');
});
p.then(null, data => {
console.log(data);
});
个人理解:then() 和 chtch() 都是用来捕获Promise的执行状态的,then() 既可以捕获resolve,也可以捕获reject,而catch() 只能捕获reject,两者都是只要捕获成功就返回一个fulfilled状态的Promise,否则catch() 没反应,then() 会报错
catch() 可以捕获它前面的错误
一般建议:Promise 对象后面要跟catch 方法,这样可以处理Promise内部的错误
4. finally() 方法
也是then() 方法的特里,返回传过来的值(不分状态)
6. 构造函数方法
Promise.resolve()
是成功状态Promise的一种简写形式
new Promise(resolve => resolve('succ'));
// 相当于
Promise.resolve('succ');
- 可以传的参数
一般参数
Promise对象
当参数为Promise对象时,直接返回这个Promise对象,什么都不做
const p = new Promise(resolve => resolve('succ')); Promise.resolve(p).then(data => { console.log(data); });
当resolve函数接收的时Promise对象时,后面的then会根据传递的Promise对象的状态变化决定执行哪一个回调
const p = new Promise(resolve => resolve('succ')); const p2 = new Promise(resolve => resolve(p)).then(data => { console.log(data); }); // 个人理解,这其实相当于是Promise对象的复制
具有then方法的对象
const obj = { then() { console.log('obj--then'); } }; Promise.resolve(obj); // 传入之后会执行obj中的then方法,并返回一个Promise对象,状态为pending // obj的then方法可以有resolve和reject两个参数,来决定状态
// 参数是其他值时,相当于时通过resolve传参
Promise.reject()
是失败状态Primise的一种简写形式
const p = new Promise((resolve, reject) => {
reject('fail');
});
Promise.reject('fail');
- 参数
不管什么参数,都会原封不动的向后传递,作为后续方法的参数
Promise.all()
关注多个Promise对象的状态变化
传入多个Promise实例,包装成一个新的Promise实例返回
Promise.all() 的状态变化与所传入的Promise实例对象状态有关
所有状态都变成resolved,最终状态才会变成resolved
只要有一个变成rejected,最终的状态就会变成rejected
Promise.race()
它的状态取决于第一个完成的 Promise实例对象,如果第一个完成的成功了,那最终的就成功;如果第一个完成的失败了,那最终的就失败
Promise.allSettled()
Promise.allSettled() 的状态与传入的Promise状态无关,永远都是成功的
它只会忠实的记录下各个Promise的表现,分别以对象的形式表现
7. 注意
resolve或reject函数执行后的代码
后面的代码还可以继续执行
建议在调用这两个函数的时候加上return,不再执行它们后面的代码
const p = new Promise((resolve, reject) => {
return resolve('resolve');
});
Promise.all / race / allSettled的参数问题
-
参数如果不是Promise数组,会将不是Promise的数组元素转变为Promise对象(成功状态)
-
任何可遍历的都可以作为参数
-
Promise.all / race / allSettled的错误处理
八、Class类
1. 初始Class
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHello() {
console.log(this.name, this.age);
}
}
const p = new Person('姓名', 18);
console.log(p);
p.sayHello();
// class 和 构造函数其实底层是一样的,只是在语法层面是class,但作用效果和构造函数一样
两种定义形式
// 声明形式
class Person {}
// 表达式形式
const Person2 = class {}
// 立即执行的类
new (class {
constructor() {
console.log('constructor');
}
})();
2. 属性与方法
实例属性和实例方法
class Person {
age = 18; // 实例属性
sex = 'male';
static version = '1.0'; // 静态属性(目前只是提案,有兼容性问题),可以考虑将它写成方法的形式
static getVersion() {
return '1.0';
}
getAge = function () { // 实例方法
return this.age;
}
constructor(age, sex) { // 构造方法
this.age = age;
this.sex = sex;
}
static getAge() { // 静态方法
console.log('666');
}
}
私有属性和方法
一般情况下,类的属性和方法都是公开的,公有属性和方法可以被外界修改,造成意向不到的后果
- 模拟私有属性和方法
-
_
开头表示私有 -
将私有属性和方法移除类
(function () {
let name = '';
class Person {
constructor(username) {
name = username;
}
getName() {
return name;
}
}
window.Person = Person;
})();
(function () {
const p = new Person('lisi');
console.log(p.name); // undefined
console.log(p.getName()); // lisi
})();
3. 继承
entends
super
-
在构造方法中使用
super 代表父类的原型对象Person.prototype
所以定义在父类实例上的方法或属性,无法通过super调用
通过super调用父类的方法时,方法内部的this指向当前子类的实例
-
在静态方法中使用
super 指向父类,而不是父类的原型对象
通过super调用父类的方法时,方法内部的this指向当前的子类,而不是子类的实例
注意:使用super的时候,必须显式指定是作为函数还是作为对象使用
九、Module模块
模块:一个一个的局部作用域的代码块
模块内部默认是严格模式
模块系统需要解决的问题:
- 模块化的问题
- 消除全局变量
- 管理加载顺序
1. Module的两种导出和导入方式
export default和import
导出方式
const age = 18;
export default age;
// export default 18;
// 也可导出对象等
一个模块没有导出,也可以将其导入
import './index.js';
被导入的代码都会执行一遍,也仅会执行一遍
一个模块只能有一个export default
导入
import age from './index.js';
// import './index.js';
export 和 import
导出方式
// export 声明或语句
export const age = 555;
// const age = 5;
//export {age};
导入
import {age} from 'index.js';
多个导入导出,起别名
export {fn as func, className, age};
import {func, className as person, age} from './index.js';
整体导入
会导入所有导出,包括 export default 导出的
import * as obj from './index.js';
同时导入
// 一定是export default的在前
import age, {func, age, className} from './index.js';
2. 注意
模块顶层的this
模块顶层的this指向
undefined
可以利用这个做提示信息
// 在要作为模块的的js文件中,如果未被按照模块的方式加载,进行提示
if (typeof this !== "undefined") {
throw new Error('请使用模块化加载');
}
import 和 import()
import 命令具有提升效果,会提升到整个模块的头部,率先执行
import 和 export 命令只能在模块的顶层,不能在代码块中执行
import() 可以按条件导入
if (PC) {
import('pc.js').then().catch();
} else if (Mobile) {
import('mobile.js').then().catch();
}
导入导出复合写法
export {age} from './module.js'; // 复合写法export的东西无法在当前模块中使用
// 等价于
import {age} from './module.js';
export {age} from './module.js';
相当于中转站(不推荐写)
十、Babel与Webpack
1. Babel
Babel是js的一个编译器,将新版本的代码转换成浏览器都兼容支持的ES5或ES3版本的代码,兼容性问题交给Babel来处理
Babel本身可以编译ES6的大部分语法,如let,const,箭头函数,类
但是对于ES6新增的API,如Set,Map,Promise等全局对象,以及一些定义在全局对象上的定义,如Object.assign / Array.from 都不能直接编译,需要借助其他模块
Babel一般需要配合Webpack来编译模块语法
准备
node.js:是一个平台或者工具,对应浏览器
npm:node包管理工具
npm init
package.json:记录安装的包
-
安装node.js
-
初始化项目
npm init -> package.json
-
安装Babel需要的包
// 切换安装源,把源从国外切换到国内 npm config set registry https://registry.npm.taobao.org
npm install --save-dev @babel/core @babel/cli
使用Babel编译ES6代码
- 执行编译命令,在
package.json
文件中添加执行babel的命令
"scripts": {
"build": "babel src -d dist" // 使用babel命令将src目录中的文件编译并输出到dist目录中
// babel src --out-dir dist
},
- babel的配置文件
npm install @babel/preset-env --save-dev
- 创建
.babelrc
文件
{
"presets": ["@babel/preset-env"]
}
npm run build
2. Webpack
静态模块打包器,当webpack处理应用程序时,会将所有这些模块打包成一个或多个文件
模块:webpack可以处理js / css / 图片 / 图标字体等单位
静态:开发过程中存在于本地的 js/css 图片/ 图标字体等文件,就是静态的(从远程服务器获取到的就是动态的,webpack无法处理)
使用
-
初始化项目
npm init
-
安装Webpack需要的包
npm install --save-dev webpack-cli webpack
-
配置webpack
创建
webpack.config.js
文件const path = require('path'); // path是node自己提供的一个模块(导入) module.exports = { // 导出 mode: 'development', entry: './src/index.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'bundle.js' } };
"scripts": { "webpack": "webpack --config webpack.config.js" },
-
npm run webpack
核心概念
-
entry
单入口
entry: './src/index.js',
多入口
entry: { main: './src/index.js', search: './src/search.js' },
-
output
对应单入口
output: { path: path.resolve(__dirname, 'dist'), filename: 'bundle.js' }
对应多入口
output: { path: path.resolve(__dirname, 'dist'), filename: '[name].js' }
-
loader
loader让webpack能够去处理那些非js文件的模块(有的loader也可以处理js文件)
帮助webpack处理各种模块
babel-loader将babel和webpack连接了起来,先完成代码编译再进行模块打包
使用
npm install --save-dev babel-loader@8.1.0 @babel/core @babel/preset-env
.babelrc配置文件
{ "presets": ["@babel/preset-env"] }
webpack.config.js文件中添加
module: { rules: [ { test: /\.js$/, // 想在哪些文件上面使用babel-loader exclude: /node_modules/, loader: 'babel-loader' // 使用 babel-loader } // 不同的loader放在不同的对象中 ] }
npm run webpack
这样的操作相当于把之前在命令行中的操作放到了webpack中,并不会编译新增API
加入@babel/polyfill
npm install --save-dev core-js@3.6.5
引入
core-js
,在代码开头加import 'core-js/stable';
npm run webpack
3. plugins
插件用于执行范围更广的任务
html-webpack-plugin
-
安装插件
npm install --save-dev html-webpack-plugin
-
在配置文件
webpack.config.js
中将其引入// 导入插件 const HtmlWebpackPlugin = require('html-webpack-plugin'); // 在module.exports中加入 plugins: [ new HtmlWebpackPlugin({ // 几个入口就创建几个实例 template: './3.html', // 指定一个html作为模板,最终打包完成后的js文件放在模板中 filename: 'index.html', chunks: ['index'], // 指明要引入哪一个js文件(entry那里的名字) minify: { // html-webpack-plugin的其他功能 // 删除index.html 中的注释 removeComments: true, // 删除index.html 中的空格 collapseWhitespace: true, // 删除各种html标签属性值的双引号 removeAttributeQuotes: true } }) ]
-
运行webpack打包
npm run webpack
处理css文件
-
需要安装
css-loader
// 帮助webpack去识别css文件 npm install --save-dev css-loader // 识别css npm install --save-dev style-loader // 处理css 将样式添加到了style标签中
module: { rules: [ { test: /\.css$/, use: ['style-loader' ,'css-loader'] // 从右向左执行 } ], },
要想使css通过link
来引入,就用mini-css-extract-plugin
new MiniCssExtractPlugin({
filename: 'css/[name].css'
})
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader']
}
]
}
使用file-loader
处理css中的本地图片
{
test: /\.(png|jpg|gif)/,
use: {
loader: "file-loader",
options: {
name: 'img/[name].[ext]' // 图片复制到dist的img目录下并保持原名
}
}
},
使用html-withimg-loader
来处理html中的图片
解析HTML文件,和file-loader配合使用
使用url-loader
可以对js中的图片进行处理(将图片作为模块引入)
将一下比较小的图片进行base64编码
配合
file-loader
一起使用{ test: /\.(jpg|png|gif)$/, use: { loader: 'url-loader', options: { name: 'img/[name].[ext]', esModule: false, limit: 3000 // 当图片小于3K的时候进行base64编码 } } }
各个包的版本号
"css-loader": "^4.1.1",
"file-loader": "^6.0.0",
"html-webpack-plugin": "^4.3.0",
"mini-css-extract-plugin": "^0.9.0",
"style-loader": "^1.2.1",
"webpack": "^4.44.1",
"webpack-cli": "^3.3.12",
"html-withimg-loader": "^0.1.16",
"url-loader@4.1.0"
4. 使用webpack-dev-serve搭建开发环境
webpack-dev-server@3.11.0