1 ES6 新特性
1.1 let 关键字
let 关键字用来声明变量,使用 let 声明的变量有几个特点:
- 不允许重复声明;
- 块级作用域(局部变量);
- 不存在变量提升;
- 不影响作用域链;
let创建变量代码示例:
// let关键字使用示例:
let a; // 单个声明
let b,c,d; // 批量声明
let e = 100; // 单个声明并赋值
let f = 521, g = 'iloveyou', h = []; // 批量声明并赋值
块级作用域
不仅仅指花括号{},还有if else while for 等语句后面的循环也是
{
let cat = "猫";
}
console.log(cat);
// 报错:Uncaught ReferenceError: cat is not defined
不存在变量提升
变量提升: 就是在变量创建之前使用,let不存在,var存在;
console.log(people1); // 可输出默认值
console.log(people2); // 报错
var people1 = "大哥"; // 存在变量提升
let people2 = "二哥"; // 不存在变量提升
不影响作用域链
作用域链:就是代码块内有代码块,跟常规编程语言一样,上级代码块中的局部变量下级可用
{
let p = "大哥";
function fn(){
console.log(p); //这里是可以使用的
}
fn();
}
let案例
使用 var 下面的声明会将上面的覆盖掉,所以点击事件每次找到的都是3,越界报错
由于 let 声明的是局部变量,每一个保持着原来的值,调用的时候拿到的是对应的i
<body>
<div class="container">
<h2 class="page-header">let案例:点击div更改颜色</h2>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
<script>
// 获取div元素对象
let items = document.getElementsByClassName('item');
// 遍历并绑定事件
for (let i = 0; i < items.length; i++) {
items[i].onclick = function() {
// 修改当前元素的背景颜色
// this.style.background = 'pink'; // 写法一:常规写法一般无异常
items[i].style.background = 'pink'; // 写法二
// 写法二:需要注意的是for循环内的i必须使用let声明
// 如果使用var就会报错,因为var是全局变量,
// 经过循环之后i的值会变成3,items[i]就会下标越界
// let是局部变量
// 我们要明白的是当我们点击的时候,这个i是哪个值
// 使用var相当于是:
// { var i = 0; }
// { var i = 1; }
// { var i = 2; }
// { var i = 3; }
// 下面的声明会将上面的覆盖掉,所以点击事件每次找到的都是3
// 而使用let相当于是:
// { let i = 0; }
// { let i = 1; }
// { let i = 2; }
// { let i = 3; }
// 由于let声明的是局部变量,每一个保持着原来的值
// 点击事件调用的时候拿到的是对应的i
}
}
</script>
</body>
1.2 const关键字
const 关键字用来声明常量,const 声明有以下特点:
- 声明必须赋初始值;
- 标识符一般为大写(习惯);
- 不允许重复声明;
- 值不允许修改;
- 块儿级作用域(局部变量);
声明必须赋初始值
const CAT; //报错
const CAT = "喵喵"; //正确
值不允许修改
对数组元素的修改和对对象内部的修改是可以的(数组和对象存的是引用地址);
应用场景:声明对象类型使用 const,非对象类型声明选择 let
对于数组:
const TEAM = ['UZI','MXLG','Ming','Letme'];
TEAM.push('Meiko'); //正确
TEAM = 100; //直接赋值,报错
console.log(TEAM);
对于对象:
const obj = {
uname: 'rick',
age: 30
}
obj.age = 40;
// 只要不改变地址,就不报错
1.3 变量的解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构赋值;
应用场景:频繁使用对象方法、数组元素,就可以使用解构赋值形式
数组的解构赋值
const arr = ['red', 'green', 'blue'];
let [a, b, c] = arr;
对象的解构赋值:同名赋值
const F3 = {
name : "大哥",
age : 22,
xiaopin : function(){ // 常用
console.log("我会演小品!");
}
}
//let {name,age,xiaopin} = F3; //注意解构对象这里用的是{}
//console.log(name + age + xiaopin); // 大哥22
//xiaopin(); // 此方法可以正常调用
let {xiaopin} = F3;
xiaopin(); // 这样就简化了F3.xiaopin();
1.4 模板字符串
模板字符串(template string)是增强版的字符串,用反引号(`)标识,
特点: 字符串中可以出现换行符; 可以使用 ${xxx} 形式引用变量;
应用场景: 当遇到字符串与变量拼接的情况使用模板字符串
在拼接中, ${内容} 是固定格式
//1. 声明
// let str = `我也是一个字符串哦!`;
// console.log(str, typeof str);
//2. 内容中可以直接出现换行符
let str = `<ul>
<li>沈腾</li>
<li>玛丽</li>
<li>魏翔</li>
<li>艾伦</li>
</ul>`;
变量拼接
// 变量拼接
let lovest = '魏翔';
let out = `${lovest}是我心目中最搞笑的演员!!`;
console.log(out);
let x = 1;
let y = 2;
`${x} + ${y} = ${x + y}`// "1 + 2 = 3"
`${x} + ${y * 2} = ${x + y * 2}`// "1 + 4 = 5"
let obj = {x: 1, y: 2};
`${obj.x + obj.y}`// "3"
1.5 简化对象和函数写法
ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法,更加简洁+
对象里面的方法也可简写,比如improve: function(){} 写成 improve(){}
let name = '尚硅谷';
let change = function(){
console.log('我们可以改变你!!');
}
const school = {
name, //直接写入变量和函数
change,
//improve: function(){} //之前的写法
improve(){
console.log("我们可以提高你的技能");
}
}
console.log(school);
1.6 箭头函数
ES6允许使用箭头(=>)定义函数,箭头函数提供了一种更加简洁的函数书写方式,箭头函数
多用于匿名函数的定义;
箭头函数适合与 this 无关的回调. 定时器, 数组的方法回调
箭头函数不适合与 this 有关的回调. 事件回调, 对象的方法
1.6.1 基本声明调用
// 传统方法
let fn = function(){ }
// 箭头函数
let fn = (a,b) => {
return a + b;
}
//调用函数
let result = fn(1, 2);
console.log(result);
1.6.2 箭头函数的简写
- 省略小括号, 当形参有且只有一个的时候**
let add = n => { // let add = (n) =>
return n + n;
}
- 省略花括号, 当代码体只有一条语句的时候, 此时 return 必须省略**
而且语句的执行结果就是函数的返回值
let pow = n => n * n;
console.log(pow(8)); //64
1.6.3 this为静态
this 是静态的. this 始终指向函数声明时所在作用域下的 this 的值(外层的作用域)
let getName2 = () => {
console.log(this.name);
}
//设置 window 对象的 name 属性
window.name = '尚硅谷';
const school = {
name: "ATGUIGU"
}
//直接调用
getName2();
//call 方法调用
getName2.call(school); //输出为指向window的 尚硅谷
1.6.4 不能作为构造实例化对象
// 会报错
let Persion = (name,age) => {
this.name = name;
this.age = age;
}
let me = new Persion("訾博",24);
console.log(me);
1.6.5 不能使用 arguments 变量
// 会报错
let fn = () =>{
console.log(arguments);
}
fn(1,2,3);
1.6.6 箭头函数的应用前景
需求-1 点击 div 2s 后颜色变成『粉色』
this.style.background = ‘pink’;的 this 用普通函数指向window,
使用箭头函数,指向addEventListener点击的对象
<div id="ad"></div>
<script>
let ad = document.getElementById("ad");
ad.addEventListener("click",function(){
// let _this = this;
setTimeout(()=>{
// 之前的做法
// _this.style.background = 'pink';
this.style.background = 'pink';
},2000);
});
</script>
需求-2 从数组中返回偶数的元素
const arr = [1,6,9,10,100,25];
// 传统写法
// const result = arr.filter(function(item){
// if(item % 2 === 0){
// return true;
// }else{
// return false;
// }
// });
// 箭头函数写法
const result = arr.filter(item => item % 2 === 0);
// 输出
console.log(result);
1.7 设置函数参数的默认值
ES6 允许给函数参数设置默认值,当调用函数时不给实参,则使用参数默认值。
具有默认值的形参
一般要靠后(潜规则)
let add = (x, y, z=3) => x + y + z;
console.log(add(1, 2)); // 6
与解构赋值结合
function connect({host="127.0.0.1", username,password, port}){
console.log(host)
console.log(username)
console.log(password)
console.log(port)
}
connect({
host: 'atguigu.com',
username: 'root',
password: 'root',
port: 3306
})
1.8 rest参数
ES6 引入 rest 参数 …args ,用于获取函数的实参,用来代替 arguments;
用在函数形参中,语法格式:
fn(a, b, ...args)
,写在参数列表最后面将接收的参数序列转换为一个数组对象
// ES5 获取实参的方式
function date(){
console.log(arguments);
}
date('白芷','阿娇','思慧');
// rest 参数
function date(...args){
console.log(args);
}
date('阿娇','柏芝','思慧');
// rest 参数必须要放到参数最后
function fn(a,b,...args){
console.log(a); //1
console.log(b); //2
console.log(args); //[3,4,5,6]
}
fn(1,2,3,4,5,6);
1.9 扩展运算符
… 扩展运算符 能将数组转换为逗号分隔的参数序列;
它的作用
扩展运算符(spread)也是三个点(…),它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列,对数组进行解包;
const tfboys = ['易烊千玺', '王源', '王俊凯'];
// 声明一个函数
function chunwan() {
console.log(arguments);
}
// 使用扩展运算符
chunwan(...tfboys); // 相当于chunwan('易烊千玺','王源','王俊凯')
它的应用
//1. 数组的合并
const kuaizi = ['王太利','肖央'];
const fenghuang = ['曾毅','玲花'];
// 传统的合并方式
// const zuixuanxiaopingguo = kuaizi.concat(fenghuang);
// 使用扩展运算符
const zuixuanxiaopingguo = [...kuaizi, ...fenghuang];
console.log(zuixuanxiaopingguo);
//2. 数组的克隆
const sanzhihua = ['E','G','M'];
const sanyecao = [...sanzhihua]; //['E','G','M']
console.log(sanyecao);
//3. 将伪数组转为真正的数组
const divs = document.querySelectorAll('div');
console.log(divs); // 伪数组 Object
const divArr = [...divs];
console.log(divArr); // 真正的数组 Object
1.10 Symbol
ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值。它是JavaScript 语言的第
七种数据类型,是一种类似于字符串的数据类型;
JavaScript 的七种基本数据类型:
- 值类型(基本类型):string、number、boolean、undefined、null、symbol
- 引用数据类型:object(包括了array、function)
Symbol 特点
- Symbol 的值是唯一的,用来解决命名冲突的问题;
- Symbol 值不能与其他数据进行运算;
- Symbol 定义的对象属性不能使用for…in 循环遍历 ,但是可以使用Reflect.ownKeys 来获取对象的所有键名;
Symbol 的创建
使用 Symbol()
方法创建
let s1 = Symbol();
console.log(s1, typeof s1); // Symbol() 'symbol'
添加具有标识的 Symbol()
let s2 = Symbol('尚硅谷');
let s2_1 = Symbol('尚硅谷');
console.log(s2 === s2_1); // false Symbol 都是独一无二的
使用 Symbol.for()
方法创建,名字相同的 Symbol
实际上是同一个值
let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');
console.log(s1 === s2); // true
// s1和s2都是 Symbol 值,但是它们都是由同样参数的Symbol.for方法生成的,所以实际上是同一个值
Symbol.for()
与Symbol()
这两种写法,都会生成新的 Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。Symbol.for()
不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key
是否已经存在,如果不存在才会新建一个值。比如,如果你调用Symbol.for("cat")
30 次,每次都会返回同一个 Symbol 值,但是调用Symbol("cat")
30 次,会返回 30 个不同的 Symbol 值。
输出 Symbol
变量的描述,使用 description
属性
let s4 = Symbol('测试');
console.log(s4.description); // 测试
对象添加Symbol类型的属性
let mySymbol = Symbol();
// 第一种写法
let a = {};
a[mySymbol] = 'Hello!';
// 第二种写法
let a = {
[mySymbol]: 'Hello!'
};
案例:安全的向对象中添加属性和方法。
分析:如果直接向对象中添加属性或方法,则原来对象中可能已经存在了同名属性或方法,会覆盖掉原来的。所以使用 Symbol
生成唯一的属性或方法名,可以更加安全的添加。
注意,Symbol 值作为对象属性名时,不能用点运算符
在对象的内部,使用 Symbol 值定义属性时,Symbol 值必须放在方括号之中。
// 这是一个 game 对象,假设我们不知道里面有什么属性和方法
const game = {
uname: '俄罗斯方块',
up: function () { },
down: function () { }
}
// 通过 Symbol 生成唯一的属性名,然后给 game 添加方法
let [up, down] = [Symbol('up'), Symbol('down')];
game[up] = function () {
console.log('up');
}
game[down] = function () {
console.log('down');
}
// 调用刚刚创建的方法
game[up]();
game[down]();
此外,还可以在对象中添加
let youxi = {
name:"狼人杀",
[Symbol('say')]: function(){
console.log("我可以发言")
},
[Symbol('zibao')]: function(){
console.log('我可以自爆');
}
}
Symbol内置值
除了定义自己使用的 Symbol 值以外,ES6 还提供了 11 个内置的 Symbol 值,指向语言内部使用的方法。可以称这些方法为魔术方法,因为它们会在特定的场景下自动执行;
方法 | 描述 |
---|---|
Symbol.hasInstance | 当其他对象使用 instanceof 运算符,判断是否为该对象的实例时,会调用这个方法 |
Symbol.isConcatSpreadable | 对象的这个属性等于的是一个布尔值,表示该对象用于Array.prototype.concat() 数组合并时,是否可以展开 |
Symbol.species | 创建衍生对象时,会使用该属性 |
Symbol.match | 当执行 str.match(myObject) 时,如果该属性存在,会调用它,返回该方法的返回值。 |
Symbol.replace | 当该对象被 str.replace(myObject) 方法调用时,会返回该方法的返回值。 |
Symbol.search | 当该对象被 str.search(myObject) 方法调用时,会返回该方法的返回值。 |
Symbol.split | 当该对象被 str.split(myObject) 方法调用时,会返回该方法的返回值。 |
Symbol.iterator | 对象进行for...of 循环时,会调用 Symbol.iterator 方法,返回该对象的默认遍历器 |
Symbol.toPrimitive | 该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。 |
Symbol. toStringTag | 在该对象上面调用 toString() 方法时,返回该方法的返回值 |
Symbol. unscopables | 该对象指定了使用 with 关键字时,哪些属性会被 with 环境排除。 |
案例1:Symbol.hasInstance
方法判断是否属于这个对象时被调用
class A {
static [Symbol.hasInstance](param) { //para为传递的 obj
console.log(param);
console.log('判断是否属于这个对象时被调用');
return false; //内部返回什么,结果就是什么
}
}
let obj = {};
console.log(obj instanceof A) // false 内部返回什么,结果就是什么
案例2:数组使用 concat
方法时,设置是否可以展开
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
arr2[Symbol.isConcatSpreadable] = false;
console.log(arr1.concat(arr2));
//结果:[ 1, 2, 3, Array(3) ] //最后一个元素为结合体
1.11 迭代器 Iterator
迭代器(Iterator)就是一种接口,为各种不同的数据结构提供统一的访问机制。任何
数据结构只要部署 Iterator 接口(对象的一个属性),就可以完成遍历操作;
ES6 创造了一种新的遍历命令 for…of 循环,Iterator 接口主要供 for…of 消费;
原生具备 iterator 接口的数据(可用 for of 遍历):
Array; Arguments; Set; Map; String; TypedArray; NodeList;
for…of 循环
for … of 中保存的是值,而for … in 中保存的是index
//声明一个数组
const xiyou = ['唐僧','孙悟空','猪八戒','沙僧'];
//使用 for...of 遍历数组
for(let v of xiyou){
console.log(v);
}
遍历 iterator 接口的数据
const xiyou = ['唐僧','孙悟空','猪八戒','沙僧'];
let iterator = xiyou[Symbol.iterator]();
//调用对象的next方法
console.log(iterator.next()); //{ value: '唐僧', done: false }
console.log(iterator.next()); //{ value: '孙悟空', done: false }
console.log(iterator.next()); //{ value: '猪八戒', done: false }
console.log(iterator.next()); //{ value: '沙僧', done: false }
工作原理
- 创建一个指针对象,指向当前数据结构的起始位置
- 第一次调用对象的
next
方法,指针自动指向数据结构的第一个成员 - 接下来不断调用
next
方法,指针一直往后移动,直到指向最后一个成员 - 每调用
next
方法返回一个包含value
和done
属性的对象
应用场景:需要自定义遍历数据的时候,要想到迭代器。
自定义遍历数据
我们可以通过给数据结构添加自定义 [Symbol.iterator]()
方法来使该数据结构能够直接被遍历,从而使 for...of
能够直接遍历指定数据,达到为 for...of
服务的功能
遍历对象中的数组示例:
<script>
const banji = {
name: "一班",
stus: ['xiaoming','xiaoning','xiaotian','knight'],
[Symbol.iterator]() {
let index = 0; //索引变量
let _this = this; // 保存this
return {
next: function(){
if(index < _this.stus.length){
const result = { value: _this.stus[index], done: false };
index++; //下标自增
//返回结果
return result;
}else{
return {value: undefined, done: true};
}
}
}
}
}
// 遍历这个对象
for (let v of banji){
console.log(v);
}
</script>
1.12 生成器 Generator
生成器函数是 ES6 提供的一种 异步编程解决方案,语法行为与传统函数完全不同
生成器其实就是一个特殊的函数
异步编程 纯回调函数 node fs ajax mongodb
声明和调用
-
*
的位置没有限制 -
使用 * 和
yield
可以声明一个生成器函数。生成器函数返回的结果是迭代器对象,调用迭代器对象的next
方法可以得到yield
语句后的值。 -
每一个
yield
相当于函数的暂停标记,也可以认为是一个分隔符,每调用一次next()
,生成器函数就往下执行一段。 -
next
方法可以传递实参,作为上一个yield
语句的返回值Generator 函数是分段执行的,
yield
表达式是暂停执行的标记,而next
方法可以恢复执行。
// 以下生成器函数中,3 个 `yield` 语句将函数内部分成了 4 段
function * gen(){
console.log(111);
yield '一只没有耳朵';
console.log(222);
yield '一只没有尾部';
console.log(333);
yield '真奇怪';
console.log(444);
}
let iterator = gen();
console.log(iterator.next()); //迭代器对象的next方法
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
输出结果:
函数参数
next
方法里的参数表示上一个yield
表达式的返回值
function* generator(arg) {
console.log(arg); // 生成器第 1 段
let one = yield 111;
console.log(one); // 生成器第 2 段
let two = yield 222;
console.log(two); // 生成器第 3 段
let three = yield 333;
console.log(three); // 生成器第 4 段
}
let iter = generator('AAA'); // 传给生成器第 1 段
console.log(iter.next());
console.log(iter.next('BBB')); // 传给生成器第 2 段,作为这一段yield语句返回值
console.log(iter.next('CCC')); // 传给生成器第 3 段,作为这一段yield语句返回值
console.log(iter.next('DDD')); // 传给生成器第 4 段,作为这一段yield语句返回值
输出结果:
生成器函数案例1
案例1:1s后输出111,2s后输出222,3s后输出333
传统方式:嵌套太多,代码复杂,产生 回调地狱
setTimeout(() => {
console.log(111);
setTimeout(() => {
console.log(222);
setTimeout(() => {
console.log(333);
}, 3000);
}, 2000);
}, 1000);
生成器实现:结构简洁明了
function one() {
setTimeout(() => {
console.log(111);
iterator.next(); //执行第二个yield语句
}, 1000);
}
function two() {
setTimeout(() => {
console.log(222);
iterator.next(); //执行第三个yield语句
}, 2000);
}
function three() {
setTimeout(() => {
console.log(333);
}, 3000);
}
// 生成器函数
function* gen() {
yield one();
yield two();
yield three();
}
let iterator = gen();
iterator.next(); //执行第一个yield语句
// 或者也可以在最下面多次调用iterator.next();
生成器函数案例2
案例2:生成器函数模拟每隔1s获取商品数据
function getUsers(){
setTimeout(()=>{
let data = '用户数据';
//调用 next 方法, 并且将数据传入
iterator.next(data);
}, 1000);
}
function getOrders(){
setTimeout(()=>{
let data = '订单数据';
iterator.next(data);
}, 1000)
}
function getGoods(){
setTimeout(()=>{
let data = '商品数据';
iterator.next(data);
}, 1000)
}
function * gen(){
let users = yield getUsers();
// console.log(users); //用户数据
let orders = yield getOrders();
// console.log(orders); //订单数据
let goods = yield getGoods();
// console.log(goods); //商品数据
}
//调用生成器函数
let iterator = gen();
iterator.next();
1.13 Promise
Promise
是 ES6 引入的异步编程的新解决方案。语法上 Promise 是一个构造函数,用来封装异步操作并可以获取其成功或失败的结果。
一个 Promise
必然处于以下几种状态之一:
- 待定(
pending
):初始状态,既没有被兑现,也没有被拒绝。 - 已兑现(
fulfilled/resolved
):意味着操作成功完成。 - 已拒绝(
rejected
):意味着操作失败。
Promise 的定义与使用
- Promise 构造函数:
new Promise((resolve, reject)=>{})
Promise.prototype.then
方法Promise.prototype.catch
方法
let p = new Promise(function (resolve, reject) {
// 使用 setTimeout 模拟请求数据库数据操作
setTimeout(function () {
// 这个异步请求数据库数据操作是否正确返回数据
let isRight = true;
if (isRight) {
let data = '数据库中的数据';
// 设置 Promise 对象的状态为操作成功
resolve(data); //可以传值
} else {
let err = '数据读取失败!'
// 设置 Promise 对象的状态为操作失败
reject(err); //可以传值
}
}, 1000);
});
//调用 promise 对象的 then 方法
// 不一定非要用value和reason,但是这样更直观
p.then(function (value) { //成功
console.log(value);
}, function (reason) { //失败
console.error(reason);
})
Promise 封装来读取文件
可以解决多个异步任务,过于嵌套的问题
// 使用 nodejs 的 fs 读取文件模块
const fs = require('fs');
const p = new Promise(function (resolve, reject) {
fs.readFile('./resources/为学.txt', (err, data) => {
// err 是一个异常对象
if (err) reject(err); //状态为操作失败
resolve(data); //状态为操作成功
})
})
p.then(function (value) { //成功
// 转为字符串输出
console.log(value.toString());
}, function (reason) { //失败
console.log('读取失败!!');
})
Promise 封装Ajax请求
const p = new Promise((resolve, reject) => {
// 创建对象
const xhr = new XMLHttpRequest();
// 初始化
xhr.open('get', 'https://api.apiopen.top/getJoke');
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
// 成功
resolve(xhr.response);
} else {
// 失败
reject(xhr.status);
}
}
}
});
// 指定成功和失败的回调
p.then(function (value) {
console.log(value);
}, function (reason) {
console.error(reason);
})
Promise的then 方法
Promise.prototype.then
方法返回的结果依然是 Promise
对象,对象状态由回调函数的执行结果决定。
(1) 若 then
方法没有返回值,则 then
方法返回的对象的状态值为成功 ,返回结果值为 undefined
。
const p = new Promise((resolve, reject) => {
setTimeout(() => {
// resolve('用户数据')
reject('出错了');
}, 1000);
})
// 未设定返回值
const res = p.then((value) => {
console.log(value);
}, (reason) => {
console.warn(reason);
})
// 打印 then 方法的返回值
console.log(res);
(2) 回调函数中返回的结果是非 Promise
类型的属性,则 then
方法返回的对象,其状态为成功,返回结果值取决于 then
方法所执行的是那个函数(resolve
或 reject
)。
const p = new Promise((resolve, reject) => {
setTimeout(() => {
// resolve('用户数据')
reject('出错了');
}, 1000);
})
// 返回的非 Promise 对象
const res = p.then((value) => {
console.log(value);
return '成功了!!';
}, (reason) => {
console.warn(reason);
return '出错啦!!'
})
// 打印 then 方法的返回值
console.log(res);
(3) 回调函数中返回的结果是 Promise
类型(return new Promise()
),则 then
方法返回的 Promise
对象状态与该返回结果的状态相同,返回值也相同
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('用户数据')
// reject('出错了');
}, 1000);
})
const res = p.then((value) => {
console.log(value);
// 返回 Promise 对象
return new Promise((resolve, reject) => {
resolve('(1)成功了!!!');
// reject('(1)出错了!!!')
})
}, (reason) => {
console.warn(reason);
return new Promise((resolve, reject) => {
// resolve('(2)成功了!!!');
reject('(2)出错了!!!')
})
})
// 打印 then 方法的返回值
console.log(res);
(4) 回调函数中返回的结果是 throw
语句抛出异常,则 then
方法的对象的状态值为 rejected
,返回结果值为 throw
抛出的字面量或者 Error
对象。
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('用户数据');
}, 1000);
});
const res = p.then((value) => {
console.log(value);
return new Promise((resolve, reject) => {
throw new Error('错误了!!');
})
});
// 打印结果
console.log(res);
链式调用
Promise.prototype.then
方法返回的结果还是 Promise
对象,这意味着我们可以继续在该结果上使用 then
方法,也就是链式调用。
const p = new Promise(resolve=>{}, reject=>{});
p.then(value=>{}, reason=>{})
.then(value=>{}, reason=>{})
.then(value=>{}, reason=>{})
...
链式调用练习-多个文件读取
传统方式,存在回调地狱
fs.readFile('./resources/为学.md', (err, data1)=>{
fs.readFile('./resources/插秧诗.md', (err, data2)=>{
fs.readFile('./resources/观书有感.md', (err, data3)=>{
let result = data1 + '\r\n' +data2 +'\r\n'+ data3;
console.log(result);
});
});
});
Promise的链式调用
const fs = require('fs');
let p = new Promise((resolve, reject) => {
fs.readFile('./resources/users.md', (err, data) => {
// 传给下一轮文件读取操作
resolve(data);
})
});
p.then(value => {
return new Promise((resolve, reject) => {
// value 为第一次读取的文件数据,data 为第二次(当前)读取的数据
fs.readFile('./resources/orders.md', (err, data) => {
// 将上轮读取结果和本轮合并传到下一轮轮读取操作
resolve([value, data]);
});
});
}).then(value => {
return new Promise((resolve, reject) => {
fs.readFile('./resources/goods.md', (err, data) => {
// value 为上一轮传递过来的文件数据数组
// data 为第三次(当前)读取的数据
value.push(data); //压入
// 传给下一轮操作
resolve(value);
});
});
}).then(value => {
// 合并数组元素,输出
console.log(value.join('\r\n'));
});
Promise的catch方法
catch()
方法返回一个 Promise
,并且处理拒绝的情况。它的行为与调用 Promise.prototype.then(undefined, onRejected)
相同
const p = new Promise((resolve, reject)=>{
setTimeout(()=>{
//设置 p 对象的状态为失败, 并设置失败的值
reject("出错啦!");
}, 1000)
});
// p.then(function(value){}, function(reason){
// console.error(reason);
// });
p.catch(function(reason){
console.warn(reason);
});
1.14 集合 Set
ES6 提供了新的数据结构 Set
(集合),它类似于数组,但 成员的值都是唯一的,集合实现了 iterator
接口,所以可以使用『扩展运算符…』和『for...of
』进行遍历。
定义和使用
let st1 = new Set(); //类型为Object
// 可以传入可迭代数据
// 可以自动去重
let s2 = new Set(['大事儿','小事儿','好事儿','坏事儿','小事儿']);
集合(这里假设有一个集合
st
)的属性和方法:
st.size
:返回集合个数st.add(item)
:往集合中添加一个新元素item
,返回当前集合st.delete(item)
:删除集合中的元素,返回boolean
值st.has(item)
:检测集合中是否包含某个元素,返回boolean
值st.clear()
:清空集合- 集合转为数组:
[...st]
- 合并两个集合:
[...st1, ...st2]
集合案例
案例1: 数组去重
let arr1 = [1, 2, 2, 3, 3, 3, 4, 1, 2];
let res1 = [...new Set(arr1)];
console.log(res1); // [ 1, 2, 3, 4 ]
案例2:数组求交集
let arr2_1 = [1, 2, 2, 3, 4, 5];
let arr2_2 = [3, 6, 6, 7, 1, 4];
let res2 = arr2_1.filter(v => new Set(arr2_2).has(v))
console.log(res2); // [ 1, 3, 4 ]
案例3:数组求并集
let arr3_1 = [1, 2, 2, 3, 4, 5];
let arr3_2 = [3, 6, 6, 7, 1, 4];
let res3 = [...new Set([...arr3_1, ...arr3_2])];
console.log(res3); // 数组[ 1, 2, 3, 4, 5, 6, 7 ]
案例4:数组求差集
let arr4_1 = [1, 2, 2, 3, 4, 5];
let arr4_2 = [3, 6, 6, 7, 1, 4];
let res4 = [...new Set(arr4_1)].filter(v => !(new Set(arr4_2).has(v)))
console.log(res4); // [ 2, 5 ]
1.15 Map
ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合。但是 “键” 的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map 也实现了 iterator 接口,所以可以使用『扩展运算符…』和『for…of』进行遍历
Map的定义
let mp1 = new Map();
mp1.set('aaa', 111); //键和值
mp1.set('bbb', 222);
mp1.set('ccc', 333);
let key = { //对象
school : 'ATGUIGU'
};
mp1.set(key, ['北京','上海','深圳']); //添加键为对象 和值为数组
let mp2 = new Map([
['aaa', 111],
['bbb', 222],
['ccc', 333]
]);
console.log(mp1['aaa']); // 111
console.log(mp2.get('bbb')); // 222
console.log(mp1.get(key)); // ['北京','上海','深圳']
属性和方法
Map 的属性和方法:(
k
为键,v
为值)
size
:返回 Map 的元素(键值对)个数set(k, v)
:增加一个键值对,返回当前 Mapget(k)
:返回键值对的键值has()
:检测 Map 中是否包含某个元素clear()
:清空集合,返回undefined
//for...of 遍历
for(let v of m){
console.log(v);
}
1.16 Class 类
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class 关键字,可以定义类。基本上,ES6 的 class
可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的 class
写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已
Class 的定义
ES5对象的写法
//手机 构造函数
function Phone(brand, price){
this.brand = brand;
this.price = price;
}
//添加方法
Phone.prototype.call = function(){
console.log("我可以打电话!!");
}
//实例化对象
let Huawei = new Phone('华为', 5999);
Huawei.call(); //调用原型里的方法
console.log(Huawei);
ES6的class的写法
class Shouji{
//constructor 构造方法 名字不能修改
constructor(brand, price){
this.brand = brand;
this.price = price;
}
// 类中添加方法
//方法不需要添加 function 关键字
call(){
console.log("我可以打电话!!");
}
}
//实例化对象
let onePlus = new Shouji("1+", 1999);
onePlus. call();
console.log(onePlus);
Class 静态成员
属于类的,不属于实例对象的,叫做静态成员
在object中,实例对象和构造函数对象的属性和方法是不通的
function Phone(){ //构造函数对象
name = "手机";
change = function(){
console.log("我可以改变世界!");
}
}
let nokia = new Phone(); //实例对象
console.log(nokia.name); //undefined
nokia.change(); //报错
class给成员属性或成员方法添加 static
,该成员就成为静态成员,只能由该类调用
class Phone{
//静态属性
static name = '手机';
static change(){
console.log("我可以改变世界");
}
}
let nokia = new Phone();
console.log(nokia.name); //undefined
console.log(Phone.name); //手机
ES5构造函数继承
// 父级 手机
function Phone(brand, price){
this.brand = brand;
this.price = price;
}
Phone.prototype.callPhone = function(){
console.log("我可以打电话");
}
// 子级 智能手机
function SmartPhone(brand, price, color, size){
Phone.call(this, brand, price);
this.color = color;
this.size = size;
}
//设置子级 构造函数的原型
SmartPhone.prototype = new Phone;
// 做一个校正
SmartPhone.prototype.constructor = SmartPhone;
//声明 子类的方法
SmartPhone.prototype.photo = function(){
console.log("我可以拍照")
}
SmartPhone.prototype.playGame = function(){
console.log("我可以玩游戏");
}
// 实例化
const chuizi = new SmartPhone('锤子',2499,'黑色','5.5inch');
console.log(chuizi);
输出chuizi对象的结果:
ES6的类继承
ES6 中直接使用
extends
语法糖(更简洁高级的实现方式)来实现继承,同时可以重写父类的方法,直接在子类中重新写一次要重写的方法即可覆盖父类方法
- 使用
extends
关键字 - 使用
super
关键字
super
关键字,用于访问和调用对象父类上的函数。可以调用父类的构造函数,也可以调用父类的普通函数。子类在构造函数中使用super
, 必须放到this
前面(必须先调用父类的构造方法,再使用子类构造方法)
继承中的属性或者方法查找原则:就近原则
继承中,如果实例化子类输出一个方法,先看子类有没有这个方法,如果有就先执行子类的。继承中,如果子类里面没有,就去查找父类有没有这个方法,如果有,就执行父类的这个方法(就近原则)
// 声明父类
class Phone{
// 构造方法
constructor(brand, price){
this.brand = brand;
this.price = price;
}
// 父类的方法
callPhone(){
console.log("我可以打电话!!");
}
}
// 声明子类
class SmartPhone extends Phone {
//构造方法
constructor(brand, price, color, size){
super(brand, price); //类似 Phone.call(this, brand, price)
this.color = color;
this.size = size;
}
// 子类的方法
photo(){
console.log("拍照");
}
playGame(){
console.log("玩游戏");
}
// 方法的重写
callPhone(){
console.log('我可以进行视频通话');
}
}
const xiaomi = new SmartPhone('小米',799,'黑色','4.7inch');
console.log(xiaomi);
// xiaomi.callPhone(); //我可以进行视频通话
// xiaomi.photo(); //拍照
// xiaomi.playGame(); //玩游戏
输出xiaomi对象的结果:
getter和setter
当属性拥有 get
/set
特性时,属性就是访问器属性。代表着在访问属性或者写入属性值时,对返回值做附加的操作。而这个操作就是 getter
/setter
函数。
使用场景: getter
是一种语法,这种 get
将对象属性绑定到 查询该属性时将被调用的函数。适用于某个需要动态计算的成员属性值的获取。setter
则是在修改某一属性时所给出的相关提示。
class Test {
constructor(log) {
this.log = log;
}
get latest() {
console.log('latest 被调用了');
return this.log;
}
set latest(e) {
console.log('latest 被修改了');
this.log.push(e);
}
}
let test = new Test(['a', 'b', 'c']);
// 每次 log 被修改都会给出提示
test.latest = 'd';
// 每次获取 log 的最后一个元素 latest,都能得到最新数据。
console.log(test.latest);
以上输出:
latest 被修改了
latest 被调用了
[ 'a', 'b', 'c', 'd' ]
1.17 数值扩展
(1)Number.EPSILON
是 JavaScript 表示的最小精度,一般用来处理浮点数运算。例如可以用于两个浮点数的比较。
function equal(a, b){
if(Math.abs(a-b) < Number.EPSILON){
return true;
}else{
return false;
}
}
// 也可以写成箭头函数
let equal = (x, y) => Math.abs(x - y) < Number.EPSILON;
console.log(0.1 + 0.2 === 0.3); // false
console.log(equal(0.1 + 0.2, 0.3)); // true
(2)二进制和八进制:二进制以 0b
开头,八进制以 0o
开头,十六进制以0x
开头
let b = 0b1010;
let o = 0o777;
let d = 100;
let x = 0xff;
console.log(x);
(3)Number.isFinite
检测一个数值是否为有限数。
console.log(Number.isFinite(100)); // true
console.log(Number.isFinite(100 / 0)); // false
console.log(Number.isFinite(Infinity)); // false
(4)Number.isNaN
检测一个数值是否为 NaN
console.log(Number.isNaN(123)); //false
(5)Number.parseInt
和 Number.parseFloat
ES6 给 Number
添加了 parseInt
方法,Number.parseInt
完全等同于 parseInt
。将字符串转为整数,或者进行进制转换。Number.parseFloat
则等同于 parseFloat()
Number.parseInt === parseInt; // true
Number.parseFloat === parseFloat; // true
// s:待转换的字符串
// base: 进位制的基数
// Number.parseInt(s, base);
console.log(Number.parseInt('5211314love')); //5211314
console.log(Number.parseInt('5211314love',16)); //86053652
console.log(Number.parseFloat('3.1415926神奇')); // 3.1415926
(6)Number.isInteger()
判断一个数是否为整数。
console.log(Number.isInteger(5)); // true
console.log(Number.isInteger(2.5)); // false
(7)Math.trunc()
将数字的小数部分抹掉。
console.log(Math.trunc(3.5)); // 3
(8)Math.sign
判断一个数到底为正数 负数 还是零
console.log(Math.sign(100)); // 1
console.log(Math.sign(0)); // 0
console.log(Math.sign(-20000)); // -1
1.18 对象方法扩展
ES6 新增了一些 Object
对象的方法。
Object.is
比较两个值是否严格相等,与『===』行为 基本一致Object.assign
对象的合并,将源对象的所有可枚举属性,复制到目标对象__proto__
、setPrototypeOf
、setPrototypeOf
可以直接设置对象的原型
Object.is 两个值完全相等
Object.is()
方法判断两个值是否完全相同。Object.is
比较两个值是否严格相等,与 ===
行为 基本一致。返回一个 Boolean
类型。
Object.is()
方法判断两个值是否为同一个值。如果满足以下条件则两个值相等:
- 都是
undefined
- 都是
null
- 都是
true
或false
- 都是相同长度的字符串且相同字符按相同顺序排列
- 都是相同对象(意味着每个对象有同一个引用)
- 都是数字且
- 都是
+0
- 都是
-0
- 都是
NaN
- 或都是非零而且非
NaN
且为同一个值
与 ==
运算不同。 ==
运算符在判断相等前对两边的变量(如果它们不是同一类型)进行强制转换 (这种行为的结果会将 "" == false
判断为 true
),而 Object.is
不会强制转换两边的值。
与 ===
算也不相同。 === 运算符 (也包括 ==
运算符) 将数字 -0
和 +0
视为相等,而将 Number.NaN
与 NaN
视为不相等。
console.log(Object.is(NaN, NaN)); // true
console.log(NaN === NaN);// false
console.log(Object.is(+0, -0)); // false
console.log(+0 === -0); // true
Object.assign 对象的合并
Object.assign
对象的合并,相当于浅拷贝,同样的 后面的会把前面的覆盖
const config1 = {
host: 'localhost',
port: 3306,
name: 'root',
pass: 'root',
test: 'test'
};
const config2 = {
host: 'http://atguigu.com',
port: 33060,
name: 'atguigu.com',
pass: 'iloveyou',
test2: 'test2'
}
console.log(Object.assign(config1, config2));
结果如下:
设置原型对象
Object.setPrototypeOf
用于设置对象的原型对象,有多个参数
Object.getPrototypeof
用于获取对象的原型对象,相当于 __proto__
const school = {
name: '尚硅谷'
}
const cities = {
xiaoqu: ['北京','上海','深圳']
}
// 把 cities 设置为 school 的原型对象
Object.setPrototypeOf(school, cities);
console.log(school);
// 获取 school 对象的原型对象
console.log(Object.getPrototypeOf(school));
1.19 ES6 模块化
模块化是指将一个大的程序文件,拆分成许多小的文件,然后将小文件组合起来
模块化的好处:防止命名冲突,代码复用,高维护性
模块化规范产品,ES6 之前的模块化规范有:
- CommonJS => NodeJS、Browserify
- AMD => requireJS
- CMD => seaJS
从ES6 开始,加入了模块化
ES6 模块化语法
模块功能主要由两个命令构成:export
和 import
。
export
命令用于规定模块的对外接口, 暴露import
命令用于输入其他模块提供的功能, 导入
ES6 的模块暴露语法
(1)单个(分别)暴露
// 单个导出
export let uname = 'Rick';
export let sayHello = function () {
console.log('Hi, bro!');
}
(2)合并(统一)暴露
let uname = 'Rick';
let sayHello = function () {
console.log('Hi, bro!');
}
// 合并导出
export { uname, sayHello };
(3)默认暴露
// 默认导出
// export default{} 把要暴露的数据 括起来,里面可以是任意类型
export default {
uname: 'Rick',
sayHello: function () {
console.log('Hi, bro!');
}
}
ES6 的模块导入语法
html 的 script 标签必须要加入 type="module"
(1)通用导入方式
<script type="module">
// 注意:路径最前面的 ./ 不要省略,会报错
import * as m1 from './js/m1.js';
import * as m2 from './js/m2.js';
import * as m3 from './js/m3.js';
</script>
(2)解构赋值导入方式
<script type="module">
import { uname, sayHello } from './js/m1.js';
// 有重复名可以设置别名
import { uname as uname2, sayHello as sayHello2 } from './js/m2.js';
// 配合默认暴露
import {default as m3} from "./src/js/m3.js";
</script>
(3)简便方式导入,只针对默认暴露
<script type="module">
import m3 from "./src/js/m3.js";
</script>
ES6模块化方式二
之前都是在script中写导入的模块,这样不方便,可以将文件导入都写进一个 app.js 文件中,然后在里面写入要导入的模块,想当于模块的一个入口
比如app.js 中的内容如下:
import * as m1 from './js/m1.js';
import * as m2 from './js/m2.js';
import * as m3 from './js/m3.js';
在 html 中引入 app.js 文件内容:
注意路径,还有必须加上 type="module"
<script src="./app.js" type="module"></script>
对模块化代码转换和打包
有的浏览器不支持 ES6 语法,这时候就需要使用 Babel 来将其转换成 ES5 等价语法,然后使用 browserify 进行打包
(1)安装工具,这里选择安装 browserify 来打包,以后会用到webpack
npm init --yes //初始化
npm i babel-cli babel-preset-env browserify -D //安装
(2)编译
对源文件 src/js 里面的代码进行转换,存在 dist/js 下面,–presets=babel-preset-env为参数
npx babel src/js -d dist/js --presets=babel-preset-env
(3)打包
dist/js/app.js为入口文件,dist/bundle.js为输出文件
npx browserify dist/js/app.js -o dist/bundle.js
ES6 模块化引入 npm 包
安装 jquery 包
npm i jquery
npm install jquery //或者
再通过 import
导入
import $ from 'jquery'; //const $ = require("jquery");
// 修改背景颜色为粉色
$('body').css('background','pink');