ES6那些事儿

200 阅读11分钟

let const

1.不能重复声明变量

//不能在函数内重新声明参数
function f3 (a1){
    let a1; 
}
// ** 不报错 **
function f4 (a2){
    {
        let a2
    }
}

2.const 声明时,必须赋值;否则报错

变量的解构赋值

1.在ES6中,直接从数组和对象中取值,按照对应位置,赋值给变量的操作。

如果解构不成功,变量的值就等于 undefined 。

let [a, b, ..c] = [1];
console.log(a, b, c); // 1, undefined, [] 使用剩余符...为空数组

let [a] = [];     // a => undefined
let [a, b] = [1]; // a => 1 , b => undefined

**两边模式不同,报错。**如下

let [a] = 1;
let [a] = false;
let [a] = NaN;
let [a] = undefined;
let [a] = null;
let [a] = {};

指定解构的默认值: 右边模式对应的值,必须严格等于 undefined ,默认值才能生效,而 null 不严格等于 undefined 。

let [a = 1] = [undefined]; // a => 1
let [a = 1] = [null];      // a => null

let {a=1} = {a:undefined};  // a => 1
let {a=1} = {a:null};   // a => null

2.对象解构赋值,若 变量名 和 属性名 不一致,则需要修改名称。

let {a:b} = {a:1, c:2}; 
// error: a is not defined
// b => 1

let {a:b=3} = {a:4};   // b = >4

对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。
上面代码中, a 是匹配的模式, b 才是变量。真正被赋值的是变量 b ,而不是模式 a 。

3.解构赋值的规则是, 只要等号右边的值不是对象或数组,就先将其转为对象 。由于 undefined 和 null 无法转为对象 ,所以对它们进行解构赋值,都会报错。

// 数值和布尔值的包装对象都有toString属性
let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true

let { prop: x } = undefined; // TypeError
let { prop: y } = null;      // TypeError

4.函数参数的解构赋值

function fun ({a=0, b=0} = {}){
    return [a, b];
}
fun ({a:1, b:2}); // [1, 2]
fun ({a:1});      // [1, 0]
fun ({});         // [0, 0]
fun ();           // [0, 0]

function fun ({a, b} = {a:0, b:0}){
    return [a, b];
}
fun ({a:1, b:2}); // [1, 2]
fun ({a:1});      // [1, undefined]
fun ({});         // [undefined, undefined]
fun ();           // [0, 0]

字符串的拓展

1.includes(),startsWith(),endsWith()

includes('xxx',n) :返回 布尔值 ,表示 是否找到参数字符串 。

startsWith('xxx',n) :返回 布尔值 ,表示参数字符串是否在原字符串的 头部 。

endsWith('xxx',n) :返回 布尔值 ,表示参数字符串是否在原字符串的 尾部 。

endsWith 是针对前 n 个字符,而其他两个是针对从第 n 个位置直到结束。

2.repeat():repeat 方法返回一个新字符串,表示将原字符串重复 n 次。

  • 参数为 小数 ,则取整
  • 参数为 负数 或 Infinity ,则报错
  • 参数为 0到-1的小数 或 NaN ,则取0 例如:'ab'.repeat(-0.5); //''
  • 参数为 字符串 ,则转成 数字

数值的拓展

1.Number.isFinite(), Number.isNaN()

Number.isFinite() 用于检查一个数值是否是有限的,即不是 Infinity ,若参数不是 Number 类型,则一律返回 false 。

Number.isNaN() 用于检查是否是 NaN ,若参数不是 NaN ,则一律返回 false 。

与传统全局的 isFinite() 和 isNaN() 方法的区别,传统的这两个方法,是先将参数转换成 数值 ,再判断。 而ES6新增的这两个方法则只对 数值 有效, Number.isFinite() 对于 非数值 一律返回 false , Number.isNaN() 只有对于 NaN 才返回 true ,其他一律返回 false 。

isFinite(25);          // true
isFinite("25");        // true
Number.isFinite(25);   // true
Number.isFinite("25"); // false

isNaN(NaN);            // true
isNaN("NaN");          // true
Number.isNaN(NaN);     // true
Number.isNaN("NaN");   // false

2.Number.parseInt(), Number.parseFloat() 这两个方法与全局方法 parseInt() 和 parseFloat() 一致,目的是逐步 减少全局性的方法 ,让 语言更模块化 。

3.Number.isInteger():用来判断一个数值是否是整数,若参数不是数值,则返回 false

Number.isInteger(10);   // true
Number.isInteger(10.0); // true
Number.isInteger(10.1); // false

Math对象的拓展

1.Math.trunc:用来去除小数的小数部分, 返回整数部分 。

  • 若参数为 非数值 ,则 先转为数值 。
  • 若参数为 空值 或 无法截取整数的值 ,则返回 NaN

2.Math.sign():判断一个数是 正数 、 负数 还 是零 ,对于非数值,会先转成 数值 。

  • 参数为正数, 返回 +1
  • 参数为负数, 返回 -1
  • 参数为0, 返回 0
  • 参数为-0, 返回 -0
  • 参数为其他值, 返回 NaN

函数的拓展

1.使用参数默认值时,参数名不能相同:

function f (a, a, b){ ... };     // 不报错
function f (a, a, b = 1){ ... }; // 报错

2.尾参数定义默认值:

function f (a=1,b){
    return [a, b];
}
f();    // [1, undefined]
f(2);   // [2, undefined]
f(,2);  // 报错

f(undefined, 2);  // [1, 2]

function f (a, b=1, c){
    return [a, b, c];
}
f();        // [undefined, 1, undefined]
f(1);       // [1,1,undefined]
f(1, ,2);   // 报错
f(1,undefined,2); // [1,1,2]

在给参数传递默认值时,传入 undefined 会触发默认值,传入 null 不会触发。

3.rest 参数

函数的 length 属性不包含 rest 参数。

函数的length属性:length 属性将返回,没有指定默认值的参数数量,并且rest参数不计入 length 属性。

function f1 (a){...};
function f2 (a=1){...};
function f3 (a, b=2){...};
function f4 (...a){...};
function f5 (a,b,...c){...};

f1.length; // 1
f2.length; // 0
f3.length; // 1
f4.length; // 0
f5.length; // 2

rest 参数只能放在最后一个,否则报错:

拓展运算符的运用

1.复制数组(深拷贝)

let a1 = [1, 2];
let a2 = [...a1];
// let [...a2] = a1; // 作用相同
a2[0] = 3;
console.log(a1,a2); // [1,2] [3,2]

2.合并数组(浅拷贝)

3.与解构赋值结合

与解构赋值结合生成数组,但是使用拓展运算符需要放到参数最后一个,否则报错。

数组的扩展

1.flat(),flatMap()

flat() 用于将数组一维化,返回一个新数组,不影响原数组。

默认一次只一维化一层数组,若需多层,则传入一个整数参数指定层数。

若要一维化所有层的数组,则传入 Infinity 作为参数。

[1, 2, [2,3]].flat();        // [1,2,2,3]
[1,2,[3,[4,[5,6]]]].flat(3); // [1,2,3,4,5,6]
[1,2,[3,[4,[5,6]]]].flat('Infinity'); // [1,2,3,4,5,6]

flatMap() 是将原数组每个对象先执行一个函数,在对返回值组成的数组执行 flat() 方法,返回一个新数组,不改变原数组。

flatMap() 只能展开一层。

[2, 3, 4].flatMap((x) => [x, x * 2]); 
// [2, 4, 3, 6, 4, 8] 

对象的扩展

1.属性名表达式????搞不懂是啥意思???有人给解释下吗???

let a = 'hi leo';
let b = {
    [a]: true,
    ['a'+'bc']: 123,
    ['my' + 'fun'] (){
        return 'hi';
    }
};
// b.a => undefined ; b.abc => 123 ; b.myfun() => 'hi'
// b[a] => 'hi leo' ; b['abc'] => 123 ; b['myfun'] => 'hi'

2.Object.is() 用于比较两个值是否严格相等,在ES5时候只要使用 相等运算符 ( == )和 严格相等运算符 ( === )就可以做比较,但是它们都有缺点,前者会 自动转换数据类型 ,后者的 NaN 不等于自身,以及 +0 等于 -0

Object.is('a','a');   // true
Object.is({}, {});    // false

// ES5
+0 === -0 ;           // true
NaN === NaN;          // false

// ES6
Object.is(+0,-0);     // false
Object.is(NaN,NaN);   // true

3.Object.assign() 实现的是浅拷贝。 由于 undefined 或 NaN 无法转成对象,所以做为参数会报错。

Object.assign(undefined) // 报错
Object.assign(NaN);      // 报错

Set

1.数组去重:

// 方法1
[...new Set([1,2,3,4,4,4])]; // [1,2,3,4]
// 方法2
Array.from(new Set([1,2,3,4,4,4]));    // [1,2,3,4]

2.由于 Set 结构 没有键名只有键值 ,所以 keys() 和 values() 是返回结果相同。

map

1.传入数组作为参数, 指定键值对的数组 。

let a = new Map([
    ['name','leo'],
    ['age',18]
])

2.同样的值 的两个实例,在 Map 结构中被视为两个键。

let a = new Map();
let a1 = ['aaa'];
let a2 = ['aaa'];
a.set(a1,111).set(a2,222);
a.get(a1); // 111
a.get(a2); // 222

promise

1.调用resolve或reject不会结束Promise参数函数的执行,除了return:

new Promise((resolve, reject){
    resolve(1);
    console.log(2);
}).then(r => {
    console.log(r);
})
// 2
// 1

new Promise((resolve, reject){
    return resolve(1);
    console.log(2);
})
// 1

for of

1.普通对象不能直接使用 for...of 会报错,要部署Iterator才能使用。

let a = {a:'aa',b:'bb',c:'cc'};
for (let k in a){console.log(k)}; // a b c
for (let k of a){console>log(k)}; // TypeError

1.ES6的 类 的所有方法都是定义在 prototype 属性上,调用类的实例的方法,其实就是调用原型上的方法。

class P {
    constructor(){ ... }
    toString(){ ... }
    toNumber(){ ... }
}
// 等同于
P.prototyoe = {
    constructor(){ ... },
    toString(){ ... },
    toNumber(){ ... }
}

let a = new P();
a.constructor === P.prototype.constructor; // true

2.与 ES5 一样,实例的属性除非显式定义在其本身(即定义在 this 对象上),否则都是定义在原型上(即定义在 class 上)。

class P {
    constructor(x, y){
        this.x = x;
        this.y = y;
    }
    toString(){
        return '(' + this.x + ', ' + this.y + ')';
    }
}
var point = new Point(2, 3);

point.toString() // (2, 3)

point.hasOwnProperty('x') // true
point.hasOwnProperty('y') // true
point.hasOwnProperty('toString') // false 
point.__proto__.hasOwnProperty('toString') // true
// toString是原型对象的属性(因为定义在Point类上)

3.类内部方法的 this 默认指向类的实例,但单独使用该方法可能报错,因为 this 指向的问题。

class P{
    leoDo(thing = 'any'){
        this.print(`Leo do ${thing}`)
    }
    print(text){
        console.log(text);
    }
}
let a = new P();
let { leoDo } = a;
leoDo(); // TypeError: Cannot read property 'print' of undefined
// 问题出在 单独使用leoDo时,this指向调用的环境,
// 但是leoDo中的this是指向P类的实例,所以报错

解决方法:

1.在类里面绑定 this

class P {
    constructor(){
        this.name = this.name.bind(this);
    }
}

2.使用箭头函数

class P{
    constructor(){
        this.name = (name = 'leo' )=>{
            this.print(`my name is ${name}`)
        }
    }
}

4.静态方法包含 this 关键字,则 this 指向类,而不是实例。

class P {
    static f1 (){
        this.f2();
    }
    static f2 (){
        console.log('aaa');
    }
    f2(){
        console.log('bbb');
    }
}
P.f1();  // 'aaa'

5.静态方法可以被子类继承,或者 super 对象中调用

class P{
    static f1(){ return 'leo' };
}
class Q extends P { ... };
Q.f1();  // 'leo'

class R extends P {
    static f2(){
        return super.f1() + ',too';
    }
}
R.f2();  // 'leo , too'

6.子类必须在 constructor() 调用 super() 否则报错 ,并且只有 super 方法才能调用父类实例,还有就是, 父类的静态方法,子类也可以继承到 。

7.super关键字

  • 1.当函数调用,代表父类的构造函数,但必须执行一次。

    class P {... };
    class R extends P {
        constructor(){
            super();
        }
    }
    
  • 当对象调用,指向原型对象,在静态方法中指向父类。

     class P {
       f (){ return 2 };
      }
      class R extends P {
          constructor (){
              super();
              console.log(super.f()); // 2
          }
      }
      let a = new R()
    

模块

1.**export 暴露的必须是接口,不能是值。 **

// 错误
export 1; // 报错

let a = 1;
export a; // 报错

// 正确
export let a = 1; // 正确

let a = 1;
export {a};       // 正确

let a = 1;
export { a as b}; // 正确

// 错误
function f(){...};
export f;

// 正确
export function f () {...};

function f(){...};
export {f};

2.import 不能直接修改输入变量的值,因为输入变量只读只是个接口,但是如果是个对象,可以修改它的属性。

// 错误
import {a} from './f.js';
a = {}; // 报错

// 正确
a.foo = 'leo';  // 不报错

3.当一个模块暴露多个方法和变量,引用时可以用 * 整体加载。但是,不允许运行时改变:

import * as obj from '/a.js';
// 不允许
obj.a = 'leo';   
obj.b = function(){...}; 

4.export defualt 其实是输出一个名字叫 default 的变量,所以后面不能跟变量赋值语句。export default 命令的本质是将后面的值,赋给 default 变量,所以可以直接将一个值写在 export default 之后。

// 正确
export let a= 1;

let a = 1;
export defualt a;

// 错误
export default let a = 1;

// 正确
export detault 1;

##es7 1.Array.prototype.includes()方法

includes 相比 indexOf 更准确, includes 认为两个 NaN 相等,而 indexOf 不会。

let a = [1, NaN, 3];
a.indexOf(NaN);     // -1
a.includes(NaN);    // true

另外在判断 +0 与 -0 时, includes 和 indexOf 的返回相同。

[1, +0, 3, 4].includes(-0);   // true
[1, +0, 3, 4].indexOf(-0);    // 1

2.async await

  • await 命令放在 try...catch 代码块中,防止Promise返回 rejected 。
  • 若多个 await 后面的异步操作不存在继发关系,最好让他们同时执行。
  • await 命令只能用在 async 函数之中,如果用在普通函数,就会报错。

3.Object.values 如果参数不是对象,则返回空数组:

Object.values(10);   // []
Object.values(true); // []

##es9 1.拓展运算符的解构赋值,不能复制继承自原型对象的属性。

let o1 = { a: 1 };
let o2 = { b: 2 };
o2.__proto__ = o1;
let { ...o3 } = o2;
o3;    // { b: 2 }
o3.a;  // undefined

若参数是 null 或 undefined 则忽略且不报错。

let a = { ...null, ...undefined }; // 不报错

若有取值函数 get 则会执行

// 不会打印 因为f属性只是定义 而不没执行
let a = {
    ...a1,
    get f(){console.log(1)}
}

// 会打印 因为f执行了
let a = {
    ...a1,
    ...{
        get f(){console.log(1)}
    }
}

补充

1.ES5/6对数组空位的处理

数组的空位不是 undefined ,而是没有任何值,数组的 undefined 也是有值。

0 in [undefined,undefined,undefined] // true
0 in [,,,] // false

prop in object: 如果指定的属性在指定的对象或其原型链中,则in 运算符返回true。

  • prop:一个字符串类型或者 symbol 类型的属性名或者数组索引(非symbol类型将会强制转为字符串)。

  • objectName:检查它(或其原型链)是否包含具有指定名称的属性的对象

ES5对空位的处理:

  • forEach() , filter() , reduce() , every() 和 some() 都会跳过空位。
  • map() 会跳过空位,但会保留这个值。
  • join() 和 toString() 会将空位视为 undefined ,而 undefined 和 null 会被处理成空字符串
[,'a'].forEach((x,i) => console.log(i)); // 1
['a',,'b'].filter(x => true);      // ['a','b']
[,'a'].every(x => x==='a');        // true
[1,,2].reduce((x,y) => x+y);       // 3
[,'a'].some(x => x !== 'a');       // false
[,'a'].map(x => 1);                // [,1]
[,'a',undefined,null].join('#');   // "#a##"
[,'a',undefined,null].toString();  // ",a,,"

ES6对空位的处理: 将空位视为正常值,转成 undefined 。

Array.from(['a',,'b']);// [ "a", undefined, "b" ]
[...['a',,'b']];       // [ "a", undefined, "b" ]

//copyWithin() 会连空位一起拷贝。  
[,'a','b',,].copyWithin(2,0) // [,"a",,"a"]

//fill()会将空位视为正常的数组位置。
new Array(3).fill('a') // ["a","a","a"]

//for...of循环也会遍历空位。
let arr = [, ,];
for (let i of arr) {
  console.log(1);
}  // 1 1

entries() 、 keys() 、 values() 、 find() 和 findIndex() 会将空位处理成 undefined 。

[...[,'a'].entries()] // [[0,undefined], [1,"a"]]

[...[,'a'].keys()] // [0,1]

[...[,'a'].values()] // [undefined,"a"]

[,'a'].find(x => true) // undefined

[,'a'].findIndex(x => true) // 0