篇幅稍微有点长,因为记录了近期居家办公期间学习到的前端新特性,查ES6之后版本的资料得知后面的新特性是ES6的补充,不存在ES7、8、9、10等(Emm...),先mark下来再说。下面是笔记源码,包含18个点(新特性例子or用法)

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>My ES6 study page</title>
        <style>
            .highlight {
                padding: 2px 5px;
                background: #00abd5;
                color: white;
            }
        </style>
        <script src="https://cdn.bootcss.com/dompurify/1.0.3/purify.js"></script>
        <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    </head>

    <body>
        <ul>
            <li>Assassin's Creed</li>
            <li>Prince of Persia</li>
            <li>Devil May Cry</li>
            <li>Prototype</li>
            <li>Darksiders</li>
        </ul>

        <div style="margin: 10px 0;">
            <button>ok</button>
            <button>cancel</button>
            <button>reset</button>
            <button>submit</button>
            <button>redirect</button>
        </div>

        <div class="content">
        </div>

        <script type="text/javascript">
            // 1. 模板标签的使用
            //--------------------------------------------------------------------------------------------------------

            function highlight(string, ...values) {
                // debugger;
                // 变量高亮
                const highlighted = values.map(value => `<span class="highlight">${value}</span>`);
                const display = string.reduce((prev, curr, i) => `${prev}${curr}${highlighted[i] || ''}`, '');

                // 使用第三方库防止XSS跨站脚本攻击
                return DOMPurify.sanitize(display);
            }

            const user = 'Mary';
            const topic = 'Learn to use markdown';
            const sentence = highlight`${user} has commented on your topic ${topic}`;

            document.querySelector('.content').innerHTML = sentence;

            // 2. ES6 新增的字符串函数
            //--------------------------------------------------------------------------------------------------------

            console.log('the topic first string is \'Learn\': ', topic.startsWith('Learn'));
            console.log('the topic sixth string is \'to\': ', topic.startsWith('to', 6));
            console.log('the topic end string is \'markdown\': ', topic.endsWith('markdown'));
            console.log('the topic twelfth end string is \'use\': ', topic.endsWith('use', 12));
            console.log('the topic include string \'use\': ', topic.includes('use'));
            console.log('repeat string \'哈\': ', '哈'.repeat(10));

            // 3. 对象解构常用方式
            //--------------------------------------------------------------------------------------------------------

            // 对象解构,灵活定义默认参数
            function appendChildDiv(option = {}) {
                const {
                    parent                  = 'body',
                    width                   = '100px',
                    height                  = '80px',
                    backgroundColor         = 'pink',
                    marginTop               = '10px'
                } = option;
                const div = document.createElement('div');

                div.style.width             = width;
                div.style.height            = height;
                div.style.backgroundColor   = backgroundColor;
                div.style.marginTop         = marginTop;

                document.querySelector(parent).appendChild(div);
            }

            appendChildDiv({
                width: '200px',
                height: '150px',
                backgroundColor: 'blue'
            });

            // 4. 巧用数组解构交换变量值
            //--------------------------------------------------------------------------------------------------------

            let a = 10;
            let b = 20;

            [a, b] = [b, a];

            console.log('a: ', a);
            console.log('b: ', b);

            // 5. 数组转化及使用for..of..来迭代操作
            //--------------------------------------------------------------------------------------------------------

            // 数组转化,并处理每个元素的值
            const games_list = document.querySelectorAll('li');
            const games = Array.from(games_list, li => li.textContent);

            // 使用数组的迭代对象去便捷操作键值
            for (let [index, game] of games.entries()) {
                console.log('li dom text: ', `${index}. ${game}`);
            }

            // 6. 自构建Promise对象来实现异步链式操作
            //--------------------------------------------------------------------------------------------------------

            const humans = [
                {id: 1, name: 'Jay Chou', age: 43, job: 'Singer & Actor', nationality: 'China'},
                {id: 2, name: 'JJ Lin', age: 40, job: 'Singer', nationality: 'Singapore'},
                {id: 3, name: 'Eddie Peng', age: 39, job: 'Actor', nationality: 'Canada'}
            ];

            const resumes = [
                {human: 'Jay Chou', works: 'Fantasy', date: '2001.9.14', style: 'pop'},
                {human: 'JJ Lin', works: 'Sixology', date: '2008.10.18', style: 'pop'},
                {human: 'Eddie Peng', works: 'The Rescue', date: '2020.12.18', style: 'action'}
            ];

            function getHumanById(id) {
                return new Promise((resolve, reject) => {
                    // 使用setTimeout来模拟异步请求耗时
                    setTimeout(() => {
                        const human = humans.find(human => human.id === id);

                        if (human) {
                            resolve(human);
                        } else {
                            reject(Error('Man not found'));
                        }
                    }, 5000);
                });
            }

            function comboundResume(human) {
                return new Promise((resolve, reject) => {
                    const resume = resumes.find(resume => resume.human === human.name);

                    if (resume) {
                        human.resumes = Array.of(resume);
                        resolve(human);
                    } else {
                        reject(Error('Resume not found'));
                    }
                });
            }

            // 这里只是异步链式操作的形式,如果需要同步监听批量请求,
            // Promise.all() 及 Promise.race() 可实现批量请求,
            // 区别是前者为所有Promise返回resolve才会执行then(参数按顺序返回所有的结果)否则执行catch,
            // 后者为只要其中一个首先返回resolve就会执行到then(参数只返回resolve的结果),全部返回reject才会执行catch
            getHumanById(1).then(human => comboundResume(human)).then(human => {
                console.log('The human object: ', human);
            }).catch(error => {
                console.error(error);
            });

            // 7. ES6新增的数据类型:Symbol
            //--------------------------------------------------------------------------------------------------------

            // Symbol可以用来生成唯一标识,同时不可遍历
            // 使用Symbol来设置半隐藏属性(私有属性),利用闭包完全隐藏
            const myObj = (() => {
                const symbol = Symbol('sex');
                class myObj {
                    constructor(name, age, sex) {
                        this[symbol] = sex;
                        this.name = name;
                        this.age = age;
                    }
                    getSex() {
                        return this[symbol];
                    }
                    static info() {
                        return `这是一个包含隐藏属性的类,想获取我的性别,请调用getSex方法`;
                    }
                }
                return myObj;
            })();

            const obj = new myObj("neo", 31, "男");
            console.log('The obj object: ', Object.keys(obj)); // 获取不到sex
            console.log('obj\'s sex: ', obj.getSex());

            // 8. ES6的类定义语法糖
            //--------------------------------------------------------------------------------------------------------

            // 首先看一下ES5中的继承方式
            function Animal(name) {
                this.name = name;
            }

            function Dog(name, age) {
                // 调用父类构造方法
                Animal.call(this, name);
                this.age = age;
            }

            // 指定原型对象为父类,并且将构造方法指定为函数本身
            Dog.prototype = new Animal();
            Dog.prototype.constructor = Dog;

            // ES6中继承就很简单了
            // 类跟函数的不同在于函数会作用域提升,而类不会
            class Cat extends Animal {
                constructor(name, age) {
                    // 同样是调用父类构造方法,ES6中只需要一个super方法就搞定了
                    super(name);
                    this.age = age;
                }

                speak() {
                    console.log('meow~');
                }

                static info() {
                    console.log(`Hi, I'm a cat, my name is ${this.name}`);
                }

                set color(color) {
                    this.color = color;
                }

                get color() {
                    return this.color;
                }
            }

            const fu = new Dog('fu', 1);
            const lucky = new Cat('lucky', 2);
            console.log('The fu object: ', fu);
            console.log('The lucky object: ', lucky);

            // 9. ES6生成器Generator
            //--------------------------------------------------------------------------------------------------------

            class GamesGenerator {
                // 不使用生成器迭代,需要自定义next迭代方法返回value数据及done状态,代码量略多
                /*constructor(list) {
                    this.list = list;
                }
                [Symbol.iterator]() {
                    let current = 0;
                    let that = this;
                    return {
                        next() {
                            return current < that.list.length ? {value : that.list[current++], done : false} : {done: true};
                        }
                    };
                }*/
                constructor(list) {
                    this.list = list;
                    // 定义生成器方法,需要在function后加上*,然后yield每次的返回值,并不需要关注迭代本身细节
                    this[Symbol.iterator] = function*() {
                        let current = 0;
                        while (current < this.list.length) {
                            yield this.list[current++];
                        }
                    }
                }
            }

            const game_generator = new GamesGenerator(games);
            for (let game of game_generator) {
                console.log('GamesGenerator iterator value: ', game);
            }

            // 10. ES6生成器的应用
            //--------------------------------------------------------------------------------------------------------

            // 可以使用生成器去控制Ajax工作流
            // 首先使用axios去ajax请求链接中的数据,然后将返回值带入到生成器中的next方法中
            function ajax(url) {
                axios.get(url).then(res => userGen.next(res.data));
            }

            // 然后在生成器方法中依次yield需要调用的接口,每次请求完的返回值会通过next方法传回来,通过定义变量存储后再利用
            function* steps() {
                console.log('Generator steps: fetching users');
                const users = yield ajax('https://api.github.com/users');
                console.log('Generator steps: ', users);

                console.log('Generator steps: fetching firstUser');
                const firstUser = yield ajax(`https://api.github.com/users/${users[0].login}`);
                console.log('Generator steps: ', firstUser);

                console.log('Generator steps: fetching followers');
                const followers = yield ajax(firstUser.followers_url);
                console.log('Generator steps: ', followers);
            }

            // 最后让生成器开始执行
            const userGen = steps();
            userGen.next();

            // 11. ES6代理Proxy
            //--------------------------------------------------------------------------------------------------------

            const person = {name: 'fantasticbin', age: 32};
            const personProxy = new Proxy(person, {
                // 改变获取方式
                get(target, key) {
                    if (key === 'mobile') {
                        return target[key].replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3');
                    }

                    return target[key][0].toUpperCase() + target[key].substr(1);
                },

                // 改变设置方式
                set(target, key, value) {
                    if (typeof value === 'string') {
                        target[key] = value.trim();
                    }

                    if (key === 'mobile') {
                        target[key] = value.match(/[0-9]/g).join('');
                    }
                }
            });

            personProxy.name = '       fantasticbin    ';
            personProxy.mobile = '138 383 89438';
            console.log('personProxy.name: ', personProxy.name);
            console.log('personProxy.mobile: ', personProxy.mobile);

            // 12. ES6新增的数据类型:Set
            //--------------------------------------------------------------------------------------------------------

            // Set的特点:和数组类似,值唯一,数字不会被转换成字符串,没有下标,获取长度不是用length而是size
            // 可使用add、delete、has、clear等方法来进行操作,也可用for循环进行迭代
            // 这里用Set来实现数组去重
            const numbers = [1, 2, 3, 4, 5, 5, 5, 2];
            const numbersSet = new Set(numbers);
            const uniqueNumbers = [...numbersSet];

            console.log('uniqueNumbers: ', uniqueNumbers);

            // 13. ES6新增的数据类型:Map
            //--------------------------------------------------------------------------------------------------------

            // Map的特点:存储键值对,键可以是任意类型,获取长度不是用length而是size
            // Map的操作方法跟Set差不多,一样有add、delete、has、clear等方法,也可用for循环进行迭代
            // 这里用Map进行记录button的点击数
            const clickCounts = new Map();
            document.querySelectorAll('button').forEach(button => {
                clickCounts.set(button, 0);
                button.addEventListener('click', function () {
                    const count = clickCounts.get(this);
                    clickCounts.set(this, count + 1);
                    console.log('clickCounts: ', clickCounts);
                });
            });

            // ES6新增的数据类型:WeakSet、WeakMap,跟Set、Map其实差不多,区别在于前者会根据成员是否存在引用而动态清除,但不能手动去改变成员
            // WeakSet、WeakMap适用于内存监控、变量监控等场景,因使用的情况不会很多所以不做举例

            // 14. ES7(7?)新增的Array.prototype.includes()
            //--------------------------------------------------------------------------------------------------------

            // ES7之前查找是否存在数组成员的方式

            /**
             * 方式1:indexOf()
             * 不够直观,且无法对NaN进行正确判断
             * [NaN].indexOf(NaN) === -1
             */
            console.log('games.indexOf Darksiders: ', games.indexOf('Darksiders') !== -1);

            /**
             * 方式2:find() 和 findIndex()
             * 依然不够直观,但相比indexOf好在可以对NaN进行正确判断
             */
            console.log('games.find Darksiders: ', games.find(game => game === 'Darksiders') !== undefined);
            console.log('games.findIndex Darksiders: ', games.findIndex(game => game === 'Darksiders') !== -1);

            // ES7后直接使用includes方法就好
            console.log('games.includes Darksiders: ', games.includes('Darksiders'));

            // 15. ES7(7?)新增的求幂运算符
            //--------------------------------------------------------------------------------------------------------

            // ES7之前求幂方式
            console.log('10th power of 2: ', Math.pow(2, 10))
            // ES7可以使用**
            console.log('10th power of 2: ', 2 ** 10);

            // 16. ES8(8?)新增的async/await新特性
            //--------------------------------------------------------------------------------------------------------

            // 上面的例子中有Promise异步链式操作请求的示例,可以用来解决回调地狱的情况
            // 但如果请求过多及每个请求依赖的上一个请求,代码还是不够清晰,比如:

            /**
             * 传入参数 n,表示这个函数执行的时间(毫秒)
             * 执行的结果是 n + 200,这个值将用于下一步骤
             */
            function takeLongTime(n) {
                return new Promise(resolve => {
                    setTimeout(() => resolve(n + 200), n);
                });
            }

            function step1(n) {
                console.log(`step1 with ${n}`);
                return takeLongTime(n);
            }

            function step2(m, n) {
                console.log(`step2 with ${m} and ${n}`);
                return takeLongTime(m + n);
            }

            function step3(k, m, n) {
                console.log(`step3 with ${k}, ${m} and ${n}`);
                return takeLongTime(k + m + n);
            }

            // 首先看直接用Promise方式实现依次请求step1、step2、step3
            function doItPromise() {
                console.time('doItPromise');
                let p1 = 300, p2, p3;
                step1(p1)
                    .then((param) => step2(p1, p2 = param))
                    .then((param) => step3(p1, p2, p3 = param))
                    .then((param) => {
                        console.log('result is', param);
                        console.timeEnd('doItPromise');
                    });
            }

            // 再看用async/await实现
            async function doIt() {
                console.time('doIt');
                const time1 = 300;
                const time2 = await step1(time1);
                const time3 = await step2(time1, time2);
                const result = await step3(time1, time2, time3);
                console.log('result is', result);
                console.timeEnd('doIt');
            }

            // 其实async/await就是上面例子10-Generator函数的语法糖,区别就是用async替换了函数的*号,await替换了函数体中的yield,而且并不需要关注异步执行细节
            // 阮一峰老师说过async函数对Generator函数的改进,并且列举了中途请求失败进入reject方法的处理方式,具体可参考:http://www.ruanyifeng.com/blog/2015/05/async.html
            async function ajaxAsync(url) {
                return axios.get(url).then(res => {
                    return new Promise(resolve => {
                        resolve(res.data);
                    })
                });
            }

            async function stepsAsync() {
                const users = await ajaxAsync('https://api.github.com/users').catch(err => {console.error(err)});
                const firstUser = await ajaxAsync(`https://api.github.com/users/${users[0].login}`).catch(err => {console.error(err)});
                const followers = await ajaxAsync(firstUser.followers_url).catch(err => {console.error(err)});
                console.log('async followers: ', followers);
            }

            stepsAsync();

            // 17. ES8(8?)新增的Object.values() 和 Object.entries()
            //--------------------------------------------------------------------------------------------------------

            console.log('The humans[0] object of values: ', Object.values(humans[0]));
            for (let [attr, value] of Object.entries(humans[0])) {
                console.log(`human[${attr}] : ${value}`);
            }

            // 18. ES8(8?)新增的String.prototype.padStart() 和 String.prototype.padEnd()
            //--------------------------------------------------------------------------------------------------------

            console.log('普京'.padStart(16, '弗拉基米尔·弗拉基米罗维奇·'));
            console.log('基努'.padEnd(6, '·里维斯'));
            console.log(personProxy.mobile.padStart(personProxy.mobile.length + 4, '+86 '));
        </script>
    </body>
</html>