JS新特性

ES

JS包含三个部分:ECMAScript(核心)、扩展浏览器端、扩展服务器端
1. ECMAScript:2009年发布ES5,2015年发布ES6,2016年发布ES7
2. 扩展浏览器端:BOM(浏览器对象模型)、DOM(文档对象模型)
3. 扩展服务器端:Node.js

ES5

1. 严格模式:除了正常运行模型(混杂模型),ES5增加了严格模式(strict mode)
    1. 消除JS语法的一些不合理、不严谨、不安全之处,减少一些怪异行为;
    2. 使用:在全局/函数的首行定义'use strict'; 即使不支持,也没有任何副作用;
2. 严格模式的语法和行为:
    1. 变量必须用var声明,混杂模式下,如果不使用,则升级为全局变量,即添加给window
    2. 禁止自定义函数中的this指向window
    function Person(name) { this.name = name; }
    Person('Mack') --> 直接调用构造函数,则this指向window,严格模式不允许
    new Person('Mack') --> 创建实例对象,则this指向当前的实例对象
    3. 创建eval作用域:var str = 'Mack';
    eval('var str = "Any"; alert(str);'); --> var str = "Any"; alert(str);
    alert(str); --> 非严格模式下,str='Any',那么就可以借助eval()攻击
    4. 对象不能有重名的属性。
3. 为Object扩展一些静态方法,如create()、defineProperties()、defineProperty()
    var obj = { name: 'Mack', age: 30 }
    1. create(prototype, [descriptors]):以指定对象为原型,创建新的对象;
    var obj2 = {};  var obj4 = {};
    obj2 = Object.create(obj); --> obj2仍是空的,但其隐式原型指向obj
    obj4 = Object.create(obj, {
        sex: {  --------------> 扩展属性
            value: 'Male', ---> 属性值
            writale: true, ---> 当前属性值是否可修改,默认为false
            configurable: true, ---> 当前属性是否能被删除,默认为false
            enumerable: true ------> 当前属性能否被for-in遍历,默认为false
        }
    })
    2. defineProperties(object, props):为指定对象定义扩展属性。
    Object.defineProperties(obj, {
        job: { ---> 扩展新的属性
            get: function() {  --> 访问扩展属性时回调,计算当前属性值
                return this.name + ':' + this.age;
            },
            set: function(data) { --> 修改属性时回调,用于监视属性的变化
                var n = data.split(':')
                this.name = n[0];  this.age = n[1]
            },
        }
    })
    console.log(obj.job) --> 回调get():'Mack:30'
    obj.job = 'Any:16' --> 回调set():name='Any',age=16
    2. getter和setter都是惰性执行的,只有在访问/修改属性时才会调用,直接打印obj并不会
    直接显示job的属性值。
    3. 对象本身也有getter和setter,其实defineProperties()中调用的也是它们;
    var obj = { name: 'Mack', age: 30,
        get job() {
            return this.name + ':' + this.age;
        },
        set job(data) {
            var n = data.split(':')
            this.name = n[0];  this.age = n[1]
        }
    }
    4. defineProperty(obj, prop, descriptor):参数3为将被定义或修改的属性描述符
4. 数组Array扩展的方法
    1. indexOf(value)/lastIndexOf(value):返回元素的索引;
    2. forEach(function(item, index){ ... }):遍历数组;
    3. map(callback):计算每一个数组元素,并不会影响原数组,而是返回一个新的数组;
    4. reduce(function(arg1, arg2){ ... }):两两计算,返回最终结果;
    5. filter(callback):过滤数组的元素,返回一个新的数组;
    6. find(callback)、findIndex(callback):返回第一个为true的数组元素、数组角标;
    7. sort(callback)、every(callback)、some(callback);
5. 函数扩展的方法:call(),apply(),bind()
    1. call/apply/bind()都能修改函数中的this指向;
    2. bind(obj):让函数内的this指向obj,但不会执行函数,而是将函数返回;
    3. bind()传递参数的方式与call()相同,通常用于修改回调函数中的this指向。

ES6

1. 声明变量:let、const
    1. let:块级作用域,不能重复声明,不会预处理,不存在变量提升;
    2. const:与let类似,但它定义的是常量,不能修改;
2. 变量的结构解析(解构赋值):从对象/数组中提取数据,并赋值给一个/多个变量;
    1. 对象:let obj = { name: 'Mack', age: 12 }
    let {name, age} = obj; -->变量名必须与对象中的属性名保持一致,位置可以任意排列
    console.log(name, age); --> name='Mack', age=12
    let {name} = obj; --> 需要哪些属性,就获取哪些属性
    2. 数组:let arr = [ 1, 3, 5, false, 'hello' ]
    let [ a, b ] = arr; --> 变量值与位置有关,与数组的下标对应
    let [,,, a, b] = arr; --> a=false, b='hello'
3. 模板字符串:``和${xxx}
    1. `` 是模板字符串的标识,${xxx} 是变化的部分,简化字符串的拼接
    2. let str = `My name is ${obj.name}`  --> 'My name is Mack'
4. 对象属性/方法的简写方式:let name = 'Mack'
    let obj = { name,  ---> 等同于 name: name,同名属性可以省略
        getName() { ... }, --> 等同于 getName: function() { ... }
    }
5. 形参默认值:function test(x=1, y=2, z) { ... }

箭头函数

1. 箭头函数:(arg1, arg2,) => { ... },类似于lambda表达式
    1. 无参数:let fun = () => console.log('Hello JS');
    fun(); --> fun指向箭头函数,fun()调用箭头函数
    2. "=>"后是单条语句时,可省略"{}",且该语句执行的结果会作为函数的返回值;
    3. 一个参数:let fun = a => console.log(a);
    4. 多个参数:let fun = (a, b) => console.log(a, b);
    5. 多条语句时,"{}"不能省略,函数的返回值仍默认为undefined
    let fun = () => {
        return 'Hello World';  --> 手动指定返回值
    }
2. 箭头函数的this:
    1. 箭头函数没有自己的this,它的this不是调用时决定的,而是在定义时所处的环境决定的;
    2. 如果箭头函数直接定义在一个对象/函数中,那么箭头函数的this就是该对象/函数的this,
    否则,箭头函数的this指向window;
    3. 也就是说,箭头函数的this取决于箭头函数所定义的外层空间;
    element.onclick = function() { ...// this就是element对象 }
    element.onclick = () => {
        // 箭头函数定义在全局作用域,也即定义在window中,那么this指向window
    }
3. 可变参数:...rest,用于取代arguments
    1. arguments只是类似于数组,但并不具备数组的方法,如forEach()
    2. ...rest只接收多余的实参,而且是一个数组;
    function(a, b, ...rest) { --> 必须放在参数的最后位置
        rest.forEach((item, index) => { ... }) --> 遍历多余参数
    }
    3. 扩展应用-三点运算符:let arr = [2, 3, 4];
    let arr2 = [1, ...arr, 5] --> [1, 2, 3, 4, 5]
    console.log(...arr2) --> 1 2 3 4 5,三点运算符会自动遍历数组元素
    4. 三点运算符还可以浅拷贝复制数组和对象
    let copyArr = [...arr]
    let obj = { name:'Mack' } ---> let copyObj = { ...obj }

Promise

1. Promise对象:表示未来某个将要发生的事件,通常是一个异步操作;
2. Promise可以将异步操作以同步的方式表达出来,避免了层层嵌套的回调(回调地狱);
3. 使用Promise:Promise是ES6的一个构造函数
    1. 创建Promise对象:
    let promise = new Promise((resolve, reject) => {
        //Promise为初始化状态:pending
        ...... //执行异步操作
        if(异步操作成功) { resolve(value); } -->修改Promise为成功状态:fullfilled
        else { reject(errMsg); } --> 修改Promise为失败状态:rejected
    });
    2. 调用Promise的then()
    promise.then(result => { -->执行resolve(),则回调then()的第一个函数
            console.log(result);
        }, errMsg => {  --> 执行reject(),则回调then()的第二个函数
            console.log(errMsg);
        }
    ) --> 回调参数result、errMsg分别是resolve()、reject()传递的value、errMsg
    3. 封装AJAX请求:
    fuction getNews(url) {
        let promise = new Promise((resolve, reject) => {
            let http = new XMLHttpResponse();
            http.onreadystatechange = function() {
                if(http.readyState!==4) return;
                if(http.status == 200) {
                    resolve(http.responseText); -->请求成功,修改状态
                } else {
                    reject('暂时没有数据'); --->请求失败,修改状态
                }
            };
            http.open('GET', url);  http.send(); -->发送请求
        });
        return promise;
    }
    getNews('http://').then(res => { ······
            return getNews(res.url); -->发送第二次AJAX请求,必须返回一个
        }, error => { ······ }   --------> Promise对象,then()可以链式回调;
    ).then(res => {  ----> 回调第二次AJAX的结果
        ······
    }, error => { ······ });

Symbol属性

1. ES5中对象的属性名都是字符串,容易重名,污染环境;
2. Symbol是ES6新增的一种数据类型,它对应的值是唯一的;
3. 不能与其他数据计算,包括字符串拼接,for-in/for-of不会遍历Symbol属性;
    let sym = Symbol();  --> Symbol不是构造函数,不用new创建
    let obj = { name: 'Mack' }
    obj[sym] = 'Hello'; --> 为对象添加Symbol属性,其属性值为'Hello'
4. 创建Symbol时还可以传递参数,作为Symbol的标识;
    let sym = Symbol('Hello')
    const SYM = Symbol('key') ---> 定义为常量
5. ES6还提供了11个内置的Symbol值,指向内部使用方法,如Symbol.iterator

Iterator

1. Iterator:一种接口机制,为各种不同的数据结构提供统一的访问机制;
2. ES6提供了一种新的遍历方式:for-of,Iterator主要供for-of消费;
3. 模拟Iterator的原理:let arr = [1, 4, 27, 'abc']
    function myIterator(arr) {
        let index = 0;  ---> 记录指针的位置
        return {  -----> 遍历器对象
            next: function() {
                return index < arr.length ? {value:arr[index++], done:false}
                 : { value: undefined, done: true }
            }
        }
    }
    let iter = myIterator(arr);
    console.log(iter.next()); --> { value: 1, done: false }
    ......
    console.log(iter.next()); --> { value: 'abc', done: false }
    console.log(iter.next()); --> { value: undefined, done: true }
4. 将Iterator接口部署到指定的数据类型上,则可以用for-of遍历;
    1. JS的原生数据类型中具备Iterator接口的有:String、Array、arguments、Set、Map
    2. for(let i of [1,2,3]) { ...//i就是每次循环的元素 }
5. Symbol.iterator
    1. String、Array、arguments等对象具备Iterator接口,但普通对象并没有Iterator接口,
    所以不能使用for-of遍历;
    2. 为普通对象添加Symbol.iterator属性,指向其默认遍历器方法,for-of就能遍历该对象;
    3. Symbol.iterator属性添加给对象,就等同于为该对象部署了Iterator接口;
    let data = {
        [Symbol.iterator]: function() {
            let index = 0; ---> 记录指针的位置
            return { ... } ---> 遍历器对象
        }
    }
    4. 三点运算符和解构赋值,默认调用的就是Iterator接口。

Generator

1. Generator函数:ES6提供的解决异步编程的方案之一;
    function* test() {
        yield 'test1'
        yield 'test2'
    }
2. 调用Generator函数,返回一个指针对象,由该对象调用next(),遇到yield返回;
    let gen = test();
    console.log(gen.next());  --> {value: "test1", done: false}
    console.log(gen.next());  --> {value: "test2", done: false}
    console.log(gen.next());  --> {value: undefined, done: true}
3. 为普通对象添加Symbol.iterator属性,指向一个Generator函数;
    let obj = { name: 'Mack' }
    obj[Symbol.iterator] = function* gen() { -->为对象部署Iterator接口
        yield 1
        yield 2
    }
    for(let i of obj) {  ---> 具备Iterator接口的对象,for-of都可以遍历
        console.log(i)  --> 1 2
    }
4. let result = yield 'test1'  --> yield的返回值默认是undefined
5. next(arg):参数arg会先赋值给当前指针所在的yield,再向下移动;
    function getNews(url) {
        $.get(url, function(data) { --> jQuery-GET请求成功的回调
            HTTP.next(data.url);  ---> 获取新的URL,发送第二次请求
        });
    }
    function* sendHttp() {
        let url = yield getNews('http://...') --> 第一次next()
        yield getNews(url)  --> 第二次next(),需要传递参数
    }
    let HTTP = sendHttp(); ---> 获取遍历器对象
    HTTP.next();  --> 发送第一次GET请求

async

1. async函数:真正意义上解决回调地狱的问题,同步的方式表达异步操作;
2. async函数的本质是Generator的语法糖,源自ES2016(ES7);
    1. async函数返回的总是Promise对象,用then()进行下一步操作;
    async function test() {  ---> 函数test变成一个异步函数
        return 'hello async';
    }
    test().then(res=>{
        console.log(res); -->hello async
    })
    2. async取代Generator函数的"*",await取代Generator函数的yield;
    async function test() {
        await 异步操作; --------> 该异步操作总是返回一个Promise对象
        await 异步操作;
    }
    3. async函数不需要像Generator函数那样去调用next(),遇到await就会等待,当前的异步操作
    完成后,则继续向下执行。
3. async的使用方式
    1. 基本使用
    async function test() { return 'hello async'; }
    async function exec() {
        let t = await test(); --> 阻塞程序,等待异步方法test() 执行完成
        console.log(t)
    }
    2. async函数返回一个Pormise对象,await总是配合Promise使用,返回值由resolve()决定
    async function test() {
        let res = await new Promise((resolve, reject) => {
                setTimeout(() => { resolve('Mack'); }, 2000);
            })
        console.log(res); --> 2000ms之后打印 Mack
    }
    3. await语句默认返回undefined,resolve()可以为awiat传递参数,reject()表示异步任务
    失败,会终止继续向下执行。
4. 发送AJAX请求:
    function getNews(url) {
        return new Promise((resolve, reject) => {
            $.ajax({ method: 'GET', url,
                success: data => resolve(data), --> GET请求成功
                error: error => resolve(error) --> 请求失败不使用reject()
            }) ------> 那么await会继续向下执行,由此判断成功还是失败
        })
    }
    async function sendHttp() {
        let res = await getNews('http://...')
        if(res) { --> 判断res的状态
            res = await getNews(res.url) --> 继续执行异步任务
        } else { alert('请求失败') --> 请求失败,则提示给用户 }
    }
    sendHttp();

其他新增

1. class:可以定义类、实现类的继承,通过类中的constructor定义构造方法;
    1. new 创建类的实例对象,extends实现类的继承,super调用父类的构造方法;
    class Person {
        constructor(name, age) {  ---> 构造函数
            this.name = name;  this.age = age; --> 定义属性
        }
        showName() {   -------------> 类的方法必须是简写形式
            console.log(this.name);
        }
        static area = 2;   // 静态属性
        static run() {       // 静态方法
            console.log('static method');
        }
    }
    2. 创建实例对象:let p = new Person('Mack', 18)
    3. 静态方法/属性只能通过类名调用:Person.run();   Person.area;
    4. extends:类的继承
    class Stu extends Person {
        constructor(name, age, job) {
            super(name, age);  ------> 必须先初始化父类的构造方法
            this.job = job;
        }
        showName() {  ...... } ---> 重写父类的方法
    }
    5. new.target:保存了通过 new 关键字调用的类或函数,以此判断抽象基类
    class Person {  // 模拟抽象基类
        constructor() {
            if(new.target === Person) {
                 throw new Error("我是抽象基类,不允许new实例");
            }
            if(!this.foo) {
                 throw new Error("子类必须定义方法 foo");
            }
        }
    }
    class Stu extends Person {
        foo() { }  // 如果没有该方法,也会报错
    }  // 派生类
    let p = new Person();  // 报错
    let s = new Stu ();  // 通过
2. 字符串的扩展
    1. includes(str):判断是否包含某个字符串;
    2. startsWidth(str)/endsWidth(str):判断是否以某个字符串开头/结尾;
    3. repeat(count):把当前字符串拼接count次,并返回;
3. number的扩展
    1. 二进制与八进制的数值表示:0b、0o
    2. Number.isFinite(num):判断num是否是一个有限大的是数字;
    3. Number.isNaN(num)/isInteger(num):判断num是否是NaN/整数;
    4. Number.parseInt(str):把字符串转为数值;
    5. Math.trunc(num):取整,去除小数部分。
4. Array的扩展
    1. Array.from(v):将伪数组/可遍历对象转为真实的数组,包括Set、Map、字符串
    低于ES6的版本:Array.prototype.slice.call(v)
    2. Array.of(v1, v2, v3):将一系列的值转为一个数组[v1, v2, v3]
    3. find/findIndex((value, index, arr) => { return true })
    查找第一个满足条件返回true的元素/元素下标:let arr = [2, 3, 5, 7, 1]
    let res = arr.find(function(v, i) { return v > 4 }) --> 5
5. 对象方法的扩展
    1. Object.is(v1, v2):判断2个数据是否完全相等;,Object.is(NaN, NaN) -->true
    Object.is(0, -0) --> false,其实该方法是按照字符串的标准比较的
    2. Object.assign(target, s1,s2,...):将源对象s1、s2...的属性复制给目标对象target
    let s1 = { name:'Mack', job:'Teacher' };  let s2 = { name:'JJJ', age:20 };
    let target1 = {}
    let target2 = Object.assign(target, s1,s2); ---> s2的name属性会覆盖s1的name属性
    --> target1和target2:{ name:'JJJ', age:20, job:'Teacher' }
    3. 直接操作隐式原型__proto__,ES6之前不能访问隐式原型。
6. 深度克隆
    1. 浅拷贝:拷贝的是引用,修改拷贝后的数据,会影响原数据;
    2. 深拷贝对象/数组:JSON.parse(JSON.stringify(data))
    3. 三点运算符、Object.assign(target, s1,s2) 都是浅拷贝

Set、Map、WeakSet、WeakMap

1. Set:无序、不可重复的、多个value的集合体;
    1. let set = new Set(); --> set.size:value的个数
    2. let set = new Set(array); -->会去除array中的重复数据;
    3. 添加、删除、清空:add(value)、delete(value)、clear()
    4. has(value):判断value是否存在。
2. Map:无序的、key不重复的、多个key-value的集合体;
    1. let map = new Map(); --> set.size:key的个数
    2. new Map([[1], [4,5], [7,8,9]]) -->{ 1=>undefined, 4=>5, 7=>8 }
    3. 添加/修改、获取value、删除:set(key, value)、get(key)、delete(key)
    4. has(key):判断key是否存在;  clear():清空。
3. WeakSet和WeakMap设计的目的是不干扰垃圾回收机制,避免内存泄漏。
    1. WeakSet存放的元素、WeakMap的键 都必须是对象,且它们对该对象的引用是弱引用。
    2. 正常情况下,当引用一个对象时,垃圾回收机制的计数器会 +1 ,但弱引用不会!
    let dom = document.getElementById('example')  // Dom对象的引用计数器 +1  --> 1
    let set = new Set()
    set.add(dom)  // Dom对象的引用计数器 +1  --> 2
    let weakset = new WeakSet()
    weakset.add(dom)  // Dom对象的引用计数器不变  --> 2
4. 当访问弱引用指向的对象时,该对象可能已经被回收了。正因如此,WeakSet和WeakMap是不可枚举的(不可遍历)!

ES7

1. 指数运算符:3**3 = 27
2. Array.prototype.includes(value):判断数组中是否包含value

你可能感兴趣的:(JS新特性)