ES6 新特性(2)
- 十、Symbol 数据类型
- 十一、迭代器
- 十二、生成器
- 十三、Promise
- 十四、set
- 十五、Map
- 十六、Class
- 十七、数值扩展
- 十八、对象方法扩展
- 十九、模块化
前接 ES6 新特性(1)
十、Symbol 数据类型
ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值。它是 Javascript 的第七种数据类型,是一种类似于字符串的数据类型。
特点:
- Symbol 的值是唯一的,用来解决命名冲突的问题。
- Symbol 的值不能与其他数据进行运算。
- Symbol 定义的对象属性不能使用
for...in
循环遍历,但是可以使用 Reflect.ownKeys 来获取对象的所有键名进行遍历。
- 创建 Symbol
// 返回 Symbol() symbol
let s = Symbol();
console.log(s, typeof s);
- 使用 Symbol 函数创建的值是独一无二的(相当于名字一样,身份证不同)。
// 返回
// Symbol(andy)
// false
let s2 = Symbol('andy');
let s3 = Symbol('andy');
console.log(s2);
console.log(s2 === s3);
- 使用 Symbol 对象创建的值是相等的。
// 返回
// Symbol(red)
// true
let s4 = Symbol.for('red');
let s5 = Symbol.for('red');
console.log(s4);
console.log(s4 === s5);
- 不能与其他数据进行运算
// 报错,不能进行算术运算,比较,连接。
let s = Symbol();
let result = s + s;
let result = s + 1;
let result = s > 100;
let result = s + '100';
- 给对象添加方法
// 返回 { user: 'admin', pwd: 123456, [Symbol()]: [Function (anonymous)] }
let game = {
user: 'admin',
pwd: 123456
}
let method = {
up: Symbol(),
down: Symbol()
}
game[method.up] = function() {
console.log('up');
}
console.log(game);
可以看到上面返回的是 [Symbol()]: [Function (anonymous)],所以也可以这样给对象添加方法。
// 返回
// {
// name: '狼人杀',
// [Symbol(say)]: [Function: [say]],
// [Symbol(zibao)]: [Function: [zibao]]
// }
let game = {
name: '狼人杀',
[Symbol('say')]() {
console.log('我可以发言');
},
[Symbol('zibao')]() {
console.log('我可以自爆');
}
}
console.log(game);
也可以用同样的方法添加属性。game[Symbol('num')] = 8;
- Symbol 内置值
除了定义自己使用的 Symbol 值以外,ES6 还提供了 11 个内置的 Symbol 值,指向语言内部使用的方法。
可以到 MDN 文档查看。
基本概念是:
- Symbol 内置属性所对应的值都是来控制对象在特定场景下的表现。比如,字符串有 split 方法,当字符串对象调用 split 方法时(
str.split('zifuchuan')
),就会自动触发 Symbol.split 方法。- Symbol 内置属性都是固定写法,属性整体(
Symbol.split
)又作为对象属性去设置,来改变对象在特定场景下的表现,以扩展对象功能。
十一、迭代器
迭代器(Iterator)是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署了 Iterator 接口,就可以完成遍历操作。
ES6 创造了一种新的遍历命令 for...of
循环,迭代器接口主要供 for...of
使用。for...of
循环保存键值, for...in
循环保存键名。
原生具备 Iterator 接口的数据(可用
for...of
遍历):
- Array
- Arguments
- Set
- Map
- String
- TypedArray
- NodeList
Iterator 接口就是对象的一个属性,这个属性叫 Symbol.Iterator
。
工作原理:
- 由
Symbol.Iterator
创建一个指针对象,指向当前数据结构的起始位置。
- 返回的对象有一个 Next 方法,第一次调用 Next 方法,指针自动指向数据结构的第一个成员。每调用 Next 方法都会返回一个包含 value 和 done 属性的对象。
- 接下来不断调用 Next 方法,指针一直往后移动,直到指向最后一个成员。
- 自定义遍历数据
根据迭代器工作原理自定义遍历数据。
const banji = {
name: '一班',
stus: [
'小利',
'小李',
'小莉',
'小粒'
],
// 给对象定义 iterator 接口
[Symbol.iterator]() {
// 索引变量
let index = 0;
return {
next: () => {
let result = {}
if (index < this.stus.length) {
result = {
value: this.stus[index],
done: false
}
index++;
} else {
result = {
value: undefined,
done: true
}
}
return result;
}
}
}
}
for (var x of banji) {
console.log(x);
}
十二、生成器
生成器函数是 ES6 提供的一种异步编程解决方案。
- 声明和调用方法
// 声明生成器函数
function* gen() {
console.log('Hello generator');
}
// 调用
let iterator = gen();
iterator.next();
- yield
yield
是函数代码的分隔符,将函数代码分割成多块。两个分隔符产生三块代码。
function* gen() {
console.log('111');
yield '我是第一个 yield';
console.log('222');
yield '我是第二个 yield';
console.log('333');
}
let iterator = gen();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
// 输出:
// 111
// { value: '我是第一个 yield', done: false }
// 222
// { value: '我是第二个 yield', done: false }
// 333
// { value: undefined, done: true }
next()
将 yield
后边的值和是否迭代完成作为返回。
- 生成器传参
往生成器函数传参
function* gen(arg) {
console.log(arg);
yield '我是第一个 yield';
yield '我是第二个 yield';
}
let iterator = gen('我是参数');
console.log(iterator.next());
// 输出
// 我是参数
// { value: '我是第一个 yield', done: false }
- next() 传参
next() 方法可以传入实参,这个实参就是 yield 语句返回的结果。第 n 个 next 方法的实参将作为第 n-1 个 yield 语句整体返回结果。
function* gen(arg) {
console.log(arg);
let one = yield '我是第一个 yield';
console.log(one);
yield '我是第二个 yield';
}
let iterator = gen('我是函数实参');
console.log(iterator.next());
console.log(iterator.next('我是 next 实参'));
// 输出
// 我是函数实参
// { value: '我是第一个 yield', done: false }
// 我是 next 实参
// { value: '我是第二个 yield', done: false }
可以理解为:let yield '我是第一个 yield' = '我是 next 实参' ;
,console.log(one);
返回的实际上是 next 的实参
- 生成器实例
每隔1s获取data
function getUsers() {
setTimeout(() => {
let data = '用户数据';
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();
十三、Promise
Promise 是 ES6 引入的异步编程的新解决方案。语法上 Promise 是一个构造函数,用来封装异步操作并可以获取成功或失败的结果。
- 基本语法
把异步任务封装在 Promise 对象里面,通过 resolve 和 reject 改变异步的状态(成功或失败),改变完状态后,再调用 then 方法里面的回调,如果成功则调用第一个回调,如果失败则调用第二个回调。
const p = new Promise(function(resolve, reject) {
setTimeout(() => {
// 如果调用 resolve 则表示成功
resolve('success')
// 如果调用 reject 则表示失败
// reject('error');
}, 1000);
});
// 调用 promise 对象的 then 方法
// 如果成功则执行第一个函数,如果失败则执行第二个函数
p.then(function(value) {
console.log(value);
}, function(reason) {
console.log(reason);
})
- then 方法
then 方法返回结果是一个 Promise 对象,对象状态由回调函数的执行结果决定。共有三种情况。
- 情况一:返回非 Promise 对象
如果回调函数中返回的结果是非 Promise 对象的属性,状态都为成功,返回值为对象的成功的值。 - 情况二:返回 Promise 对象
如果回调函数中返回的结果是 Promise 对象,内部 Promise 状态决定外部状态。 - 情况三:抛出错误
只要抛出错误,状态都是失败的状态,抛出来的值就是 then 结果值。
- Promise 实例
- 读取文件
const fs = require('fs');
const p = new Promise(function(resolve, reject) {
fs.readFile('./01_let.js', (err, data) => {
// 判断,改变状态
if (err) reject(err);
resolve(data);
})
})
// 根据不同状态执行不同回调
p.then(value => {
console.log(value.toString());
}, reason => {
console.log('获取失败!!');
})
- 读取多个文件
链式编程解决回调地狱。
const fs = require('fs');
const p = new Promise((resolve, reject) => {
fs.readFile('./01_let.js', (err, data) => {
if (err) reject(err);
resolve(data);
})
}).then(value => {
return new Promise((resolve, reject) => {
fs.readFile('./02_const.js', (err, data) => {
if (err) reject(err);
resolve([value, data]);
})
})
}).then(value => {
return new Promise((resolve, reject) => {
fs.readFile('./03_解构赋值.js', (err, data) => {
if (err) reject(err);
value.push(data);
resolve(value);
})
})
}).then(value => {
console.log(value.join('\n'));
})
- Promise 封装 Ajax
const p = new Promise((resolve, reject) => {
// 封装
const xhr = new XMLHttpRequest();
xhr.open('GET', 'http://127.0.0.1:8000/promise');
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(value => {
console.log(value);
}, reason => {
console.log(reason);
})
成功的返回结果失败的返回结果
- catch 方法
catch 用于指定 Promise 失败的回调。
then 需要指定成功与失败两种情况,catch 只需要指定失败的情况。
const p = new Promise((resolve, reject) => {
setTimeout(() => {
reject('失败啦');
}, 1000);
})
p.catch(reason => {
console.log(reason);
})
十四、set
ES6 提供了新的数据结果 Set(集合)。它类似于数组,但成员的值是唯一的。集合实现了 iterator 接口,所以可以使用扩展运算符和 for...of
遍历。
- Set 的方法
let s = new Set();
let s2 = new Set([1, 2, 3, 1]);
console.log(s2); // 去重,返回 Set(3) { 1, 2, 3 }
// 元素个数
console.log(s2.size); // 返回 3
// 添加新元素
s2.add(4); // 返回 Set(4) { 1, 2, 3, 4 }
// 删除元素
s2.delete(1); //返回 Set(4) { 2, 3, 4 }
// 检测元素
s2.has(3); // 返回 true
// 遍历
for (let x of s2){
console.log(x);
}
// 清空
s2.clear();
- 集合实践
let arr = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let arr2 = [4, 5, 6, 4, 5];
// 数组去重
let repetition = [...new Set(arr)];
console.log(repetition); // 返回 [ 1, 2, 3, 4, 5 ]
// 交集
let intersection = [...new Set(arr)].filter(item => new Set(arr2).has(item));
console.log(intersection); // 返回 [ 4, 5 ]
// 并集
let union = [...new Set([...arr, ...arr2])];
console.log(union); // 返回 [ 1, 2, 3, 4, 5, 6 ]
// 差集
let diff = [...new Set(arr)].filter(item => !(new Set(arr2).has(item)));
console.log(diff); // 返回 [ 1, 2, 3 ]
十五、Map
ES6 提供了 Map 数据解构,它类似于对象,也是键值对的集合。但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map 也实现了 iterator 接口。
- Map 的方法
let m = new Map();
// 添加元素
m.set('name', 'andy');
m.set('fn', function() {
console.log('我是函数');
})
let key = {
age: 18
}
m.set(key, ['red', 'green', 'blue']);
// 元素个数
m.size;
// 获取元素
m.get('fn');
// 删除元素
m.delete('fn');
// 遍历,返回的是数组
for (let x of m) {
console.log(x);
}
// 清空
m.clear();
十六、Class
ES6 提供了更接近传统语言的写法,引入了 Class(类)的概念,作为对象的模板。
- 声明方式
class Phone {
// 构造方法,不能固定写法
constructor(brand, price) {
this.brand = brand;
this.price = price;
}
call() {
console.log('我能打电话');
}
}
let mi = new Phone('小米', 1999);
console.log(mi); // Phone { brand: '小米', price: 1999 }
mi.call(); // 我能打电话
- 静态成员
静态成员只能类自己使用,实例不能使用。定义静态属性和静态方法在前面加 static
class Phone {
static name = '手机';
price = 1999;
static call() {
console.log('我可以打电话');
}
}
let nokia = new Phone();
console.log(nokia.name); // undefined
console.log(nokia.price); // 1999
- 类继承
class Phone {
// 构造方法,不能固定写法
constructor(brand, price) {
this.brand = brand;
this.price = price;
}
call() {
console.log('我能打电话');
}
}
// extends 继承
class SmartPhone extends Phone {
constructor(brand, price, color, size) {
super(brand, price);
this.color = color;
this.size = size;
}
photo() {
console.log('我可以拍照');
}
playGame() {
console.log('我可以玩游戏');
}
}
let mi = new SmartPhone('小米', 1999, 'black', '5.5inch');
console.log(mi.brand); // 小米
mi.call(); // 我能打电话
mi.photo(); // 我可以拍照
- 重写
重写就是可以在子类声明一个和父类同名的方法。重写只能完全重写,不能调用父类方法。
class Phone {
constructor(brand) {
this.brand = brand;
}
call() {
console.log('我能打电话');
}
}
class SmartPhone extends Phone {
constructor(brand, price) {
super(brand);
this.price = price;
}
// 重写
call() {
console.log('我可以视频通话');
}
}
let mi = new SmartPhone('小米', 1999);
mi.call(); // 我可以视频通话
- get 和 set
get 是当属性被读取的时候自动调用的函数。
set 是当属性被修改的时候自动调用的函数。
class Phone {
set price(val) {
console.log('被修改了');
}
get price() {
return '被读取了'
}
}
let mi = new Phone();
mi.price = 1999; // 修改了属性,会自动调用set
console.log(mi.price); // 读取了属性,自动调用 get
十七、数值扩展
- Number.EPSILON
Number.EPSILON 表示最小精度,如果两个数的差值小于 Number.EPSILON 则返回这两个数相等。
console.log(0.1 + 0.2); // 返回 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // 返回 false
function equal(a, b) {
return (Math.abs(a - b) < Number.EPSILON) ? true : false;
}
console.log(equal(0.1 + 0.2, 0.3)); // 返回 true
- 二进制和八进制
let b = 0b1010;
let o = 0o777;
let d = 100;
let x = 0xfff;
console.log(`二进制:${b}, 八进制:${o}, 十进制:${d}, 十六进制:${x}`);
// 返回 二进制:10, 八进制:511, 十进制:100, 十六进制:4095
- Number.isFinite
Number.isFinite 判断一个数值是否为有限数。
console.log(Number.isFinite(100)); // true
console.log(Number.isFinite(100 / 0)); // false
console.log(Number.isFinite(Infinity)); // false
- parseInt 和 parseFloat
将字符串转数值。
console.log(Number.parseInt('521212 ldowad')); // 511212
console.log(Number.parseFloat('5212.12 ldowad')); // 5212.12
- Number.isInteger
Number.isInteger 判断一个数是否为一个整数。
console.log(Number.isInteger(5)); // true
console.log(Number.isInteger(5.5)); // false
- Math.trunc
Math.trunc 将数字的小数部分抹掉。
console.log(Math.trunc(3.1415)); // 3
- Math.sign
Math.sign 判断一个数是正数负数还是零。
console.log(Math.sign(100)); // 1
console.log(Math.sign(0)); // 0
console.log(Math.sign(-200)); // -1
十八、对象方法扩展
- Object.is
Object.is 判断两个值是否完全相等
console.log(Object.is(NaN, NaN)); // true
console.log(NaN === NaN); // false
- Object.assign
Object.assign 用作对象的合并,如果两个对象有相同的键,则第二个对象覆盖第一个对象,如果不相同,则复制。
const config1 = {
host: 'localhost',
port: 3306,
user: 'admin',
password: '123456'
}
const config2 = {
host: '127.0.0.1',
port: 3306,
user: 'admin',
test: 'test'
}
console.log(Object.assign(config1, config2));
// 返回
// {
// host: '127.0.0.1',
// port: 3306,
// user: 'admin',
// password: '123456',
// test: 'test'
// }
- setPrototypeOf 和 getPrototypeOf
Object.setPrototypeOf 用于设置原型对象,Object.getPrototypeOf 用于获取原型对象。
const phone = {
brand: 'huawei'
}
const price = {
price: 1999
}
Object.setPrototypeOf(phone, price); // 设置原型对象
console.log(phone);
console.log(Object.getPrototypeOf(phone)); // 获取原型对象
十九、模块化
模块化是指将一个大的程序文件,拆分成许多小的文件,然后将小文件组合起来。小文件就是模块。
模块化的优点:
- 防止命名冲突
- 代码复用
- 高维护性
- 模块化语法
模块功能主要由两个命令构成:export 和 import。
- expoet:用于规定模块的对外接口。
- import:用于输入其他模块提供的功能。
js 文件下写 export :
export let user = 'admin';
html 文件导入,script 标签类型为 module:
<script type="module">
import * as m1 from './m1.js';
</script>
- export 三种情况
- 分别暴露
在每个要暴露的数据前面加 export
export let user = 'admin';
export function login() {
console.log('登录');
}
- 统一暴露
在某个地方用对象的简化结构暴露数据。
let user = 'admin';
function login() {
console.log('登录');
}
export { user, login }
- 默认暴露
用 export default {}
对象的形式暴露数据。
export default {
user: 'admin',
login: function() {
console.log('登录');
}
}
引入必须加 default
<script type="module">
import * as m1 from './m1.js';
m1.default.login();
</script>
- import 导入方式
- 通用方式
<script type="module">
import * as m1 from './m1.js';
</script>
- 解构赋值形式
import {user, login} from './m1.js';
// 防止命名冲突
import {user as usr, login as log} from './m2.js';
// 引入默认暴露的数据
import {default as m3} from './m3.js';
- 简便形式
简便形式只针对默认暴露。
import m3 from './m3.js';
- 导入 npm 包
// const $ = require("jquery");
import $ from 'jquery';
- 模块化代码转换
因为浏览器兼容问题,在项目开发是不会直接在 HTML 文件中引入模块。ES6 模块化还不能够直接对 npm 安装的一些模块做导入。因此需要用 Babel 做转换。
Babel 是一个 JavaScript 编译器,能够将 ES 新特性语法转化为浏览器能识别的 ES5 语法。
转换完后再对代码打包,形成最终单独的文件,再在页面中引入。
- 安装工具
Babel-cli:是 Babel 的一个命令行工具
Babel-present-env:是一个预设包,能够将新特性语法转换为 ES5。
browserify:是一个打包工具,实际项目中一般使用 webpack 进行打包。
npm init --yes # 初始化
npm i babel-cli babel-present-env browserify -D
- 转换
npx babel src/js -d dist/js --presets=babel-preset-env
- 打包
npx browserify dist/js/app.js -o dist/bundle.js
打包完再引入。