ES6 新特性(2)

  • 十、Symbol 数据类型
  • 十一、迭代器
  • 十二、生成器
  • 十三、Promise
  • 十四、set
  • 十五、Map
  • 十六、Class
  • 十七、数值扩展
  • 十八、对象方法扩展
  • 十九、模块化


前接 ES6 新特性(1)

十、Symbol 数据类型

ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值。它是 Javascript 的第七种数据类型,是一种类似于字符串的数据类型。

特点:

  1. Symbol 的值是唯一的,用来解决命名冲突的问题。
  2. Symbol 的值不能与其他数据进行运算。
  3. Symbol 定义的对象属性不能使用 for...in 循环遍历,但是可以使用 Reflect.ownKeys 来获取对象的所有键名进行遍历。
  • 创建 Symbol
// 返回 Symbol() symbol
let s = Symbol();
console.log(s, typeof s);
  1. 使用 Symbol 函数创建的值是独一无二的(相当于名字一样,身份证不同)。
// 返回
// Symbol(andy)
// false
let s2 = Symbol('andy');
let s3 = Symbol('andy');
console.log(s2);
console.log(s2 === s3);
  1. 使用 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 文档查看。

基本概念是:

  1. Symbol 内置属性所对应的值都是来控制对象在特定场景下的表现。比如,字符串有 split 方法,当字符串对象调用 split 方法时(str.split('zifuchuan')),就会自动触发 Symbol.split 方法。
  2. Symbol 内置属性都是固定写法,属性整体(Symbol.split)又作为对象属性去设置,来改变对象在特定场景下的表现,以扩展对象功能。

十一、迭代器

迭代器(Iterator)是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署了 Iterator 接口,就可以完成遍历操作。
ES6 创造了一种新的遍历命令 for...of 循环,迭代器接口主要供 for...of 使用。
for...of 循环保存键值, for...in 循环保存键名。

原生具备 Iterator 接口的数据(可用 for...of 遍历):

  1. Array
  2. Arguments
  3. Set
  4. Map
  5. String
  6. TypedArray
  7. NodeList

Iterator 接口就是对象的一个属性,这个属性叫 Symbol.Iterator

es6 数据调整位置_ES6

工作原理:

  1. Symbol.Iterator 创建一个指针对象,指向当前数据结构的起始位置。
  1. 返回的对象有一个 Next 方法,第一次调用 Next 方法,指针自动指向数据结构的第一个成员。每调用 Next 方法都会返回一个包含 value 和 done 属性的对象。
  2. 接下来不断调用 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 对象,对象状态由回调函数的执行结果决定。共有三种情况。

  1. 情况一:返回非 Promise 对象
    如果回调函数中返回的结果是非 Promise 对象的属性,状态都为成功,返回值为对象的成功的值。
  2. 情况二:返回 Promise 对象
    如果回调函数中返回的结果是 Promise 对象,内部 Promise 状态决定外部状态。
  3. 情况三:抛出错误
    只要抛出错误,状态都是失败的状态,抛出来的值就是 then 结果值。
  • Promise 实例
  1. 读取文件
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('获取失败!!');
})
  1. 读取多个文件
    链式编程解决回调地狱。
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'));
})
  1. 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);
})
成功的返回结果

es6 数据调整位置_javascript_02


失败的返回结果


es6 数据调整位置_es6_03

  • 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));  // 获取原型对象

十九、模块化

模块化是指将一个大的程序文件,拆分成许多小的文件,然后将小文件组合起来。小文件就是模块。

模块化的优点:

  1. 防止命名冲突
  2. 代码复用
  3. 高维护性
  • 模块化语法

模块功能主要由两个命令构成:export 和 import。

  1. expoet:用于规定模块的对外接口。
  2. import:用于输入其他模块提供的功能。

js 文件下写 export :

export let user = 'admin';

html 文件导入,script 标签类型为 module:

<script type="module">
    import * as m1 from './m1.js';
</script>
  • export 三种情况
  1. 分别暴露

在每个要暴露的数据前面加 export

export let user = 'admin';
export function login() {
    console.log('登录');
}
  1. 统一暴露

在某个地方用对象的简化结构暴露数据。

let user = 'admin';
function login() {
    console.log('登录');
}
export { user, login }
  1. 默认暴露

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 导入方式
  1. 通用方式
<script type="module">
    import * as m1 from './m1.js';
</script>
  1. 解构赋值形式
import {user, login} from './m1.js';
// 防止命名冲突
import {user as usr, login as log} from './m2.js';
// 引入默认暴露的数据
import {default as m3} from './m3.js';
  1. 简便形式

简便形式只针对默认暴露。

import m3 from './m3.js';
  1. 导入 npm 包
// const $ = require("jquery");
import $ from 'jquery';
  • 模块化代码转换

因为浏览器兼容问题,在项目开发是不会直接在 HTML 文件中引入模块。ES6 模块化还不能够直接对 npm 安装的一些模块做导入。因此需要用 Babel 做转换。

Babel 是一个 JavaScript 编译器,能够将 ES 新特性语法转化为浏览器能识别的 ES5 语法。

转换完后再对代码打包,形成最终单独的文件,再在页面中引入。

  1. 安装工具

Babel-cli:是 Babel 的一个命令行工具
Babel-present-env:是一个预设包,能够将新特性语法转换为 ES5。
browserify:是一个打包工具,实际项目中一般使用 webpack 进行打包。

npm init --yes  # 初始化
npm i babel-cli babel-present-env browserify -D
  1. 转换
npx babel src/js -d dist/js --presets=babel-preset-env
  1. 打包
npx browserify dist/js/app.js -o dist/bundle.js

打包完再引入。