Skip to content

ES6的变化与新特性(上篇)

Part1. 变量声明与作用域的变化

ES6有6种姿势声明变量: var let const function class import
其中let关键字,用来替代var声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效,即{}内有效.let声明的变量不存在变量提升

var与let的对比

js
var a = [];
for(var i = 0;i < 10;i++) {
    a[i] = function(){
        console.log(i);
    }
}
a[0]() //10;
//--------------------
var a = [];
for (let i = 0; i < 10; i++) {
    a[i] = function () {
        console.log(i);
    };
}
a[6](); // 6  
//在for循环声明的let变量只在该循环块内有效,
//并且每次循环会重新分配,
//故在新的循环轮次中i是新的变量

变量声明的暂时性死区

let变量绑定的代码块不受外部影响,在代码块开始处到声明该变量声明处存在暂时性死区,这将导致该部分使用暂未声明的变量报错,即使全局存在同名变量也报错.const同理.

栗子

js
if (true) {
    /*此处如果用var声明,会报重复声明的错误,
      ES6不允许let和const同作用域重复声明*/
    tmp = 'abc';              
    console.log(tmp);         //报错:变量未定义
    typeof tmp;               //报错:同上

    let tmp;                  //死区结束
    console.log(tmp);         // undefined

    tmp = 123;
    console.log(tmp);         // 123
}

const关键字

const用于声明常量,其值或指向的对象引用不能改变;
const作用域规则同let,不存在变量提升,存在暂时性死区;
const声明的同时必须赋值,'const a;a=1;console.log(a)'这在非严格模式下输出undefined,严格模式报错;
const声明的对象引用不可变,但对象属性可变;
跨模块常量需要用export声明 export const xx = xx;

函数作用域

ES5 : 存在函数提升,在代码块内的函数会提升到整个函数作用域
ES5 严格模式 : 函数只能在函数内或顶级作用域声明, 不能再if{} for{}等块内声明
ES6 : 函数不存在提升, if{} for{}等块内声明的函数只在块内有效

Part2. 解构

ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring).

  • 允许不完全解构
  • 解构可运用于var let const import
  • 对于Set结构,也可以使用数组的解构赋值。eg: let [x, y, z] = new Set(["a", "b", "c"])
  • Generator函数也能解构,赋值过程依次调用函数获取值
  • 解构允许附带默认值,生效条件是匹配到严格undefined eg: let [a=1,b=3] = [2]  //a=2,b=3
  • 解构比较过程是严格相等运算 eg: let [a=undefined] = [null] //a=null 因为 undefined!==null
  • 对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,即属性名称只是作为匹配模式
  • 已经声明的对象解构赋值最好加上()以避免解释器当成代码块 eg : var x; {x} = {x: 1}; //如果解构语句在行首则报错
  • 解构声明等同于分别声明, let和const关键字声明的同区块不能出现重复
  • 对象解构时, 若源为基本类型,会转换为对象类型; 数组解构时, 源必须是可遍历的结构或字符串
  • 函数参数也能解构, 形参被解构赋值为实参, eg : function foo([x,y]) {};foo([1,2])
  • 解构模式内部不要有括号()

示例

js
var { bar, foo } = { foo: "aaa", bar: "bbb" }; //这是简写形式.
var { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" }; //真·解构
var { p: [x, { y : z }] } = { // x='Hello'; p和y 是模式,未声明; z = 'World'   EX·嵌套·解构
    p: [
        "Hello",
        { y: "World", z : "GG" }
    ]
};

let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3

let [ , , third] = ["foo", "bar", "baz"];
third // "baz"

let [x, , y] = [1, 2, 3];
x // 1
y // 3

let [head, ...tail] = [1, 2, 3, 4]; //...运算符不能放在前面,含义为剩下的参数
head // 1
tail // [2, 3, 4]

let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []

用途

  1. 交换变量 [a,b] = [b,a]
  2. 提取数据, 从返回值提取或从Json提取等等
  3. 函数默认参数
  4. 加载模块指定值 eg: import {A,B} from './xx';

Part3. 内置对象扩展与字符串模板

字符串和正则的扩展

  • ES6 unicode: Unicode编码放在大括号内,对于>0xFFFF的字符不会错误解释 \u{xxxxx}
  • codePointAt() JS字符以UTF-16的格式储存,每个字符固定为2个字节。对于那些需要4个字节储存的字符,字符长度为2,此方法避免该问题, 返回, 返回字符码点, 扩展charCodeAt方法
  • fromCodePoint([...code]) 返回Unicode码的对应字符,扩展fromCharCode方法
  • at() 返回指定位置字符, 扩展charAt方法
  • normalize() 统一同一个字符的不同Unicode
  • includes(), startsWith(), endsWith() 终于有这三个方法了,第一个参数是匹配字符,第二个参数是搜索开始位置
  • repeat() 重复字符串,参数是自然数
  • 模板字符串 终于出来了,here doc写法
js
//基本用法, <span style="color: #ff0000;">反引号</span>包含
var str = `
Here

is
doc
`;

/**
 * 模板替换
 * ${}中的变量会被替换成实际值;
 * 非字符串变量会自动调用toString()后替换;
 * 表达式会计算值后替换;
 * 函数会替换为其返回值
 */
var number = 1;
str = `Test ${number} Test`; //Test 1 Test

var bar = {foo : 1 };
var foo = function(){};
`foo ${foo()} ${bar.foo}`; //foo undefined 1

//标签模板(一种函数调用)
var a = 5;
var b = 10;
tag`Hello ${ a + b } world ${ a * b } `;

function tag (){
    console.log(arguments[0]);      //['Hello ', ' world ', ' ']
    console.log(arguments[1]);     //15
    console.log(arguments[2]);    //15
}

//String.raw转义模板字符串
String.raw`Hi\n${2+3}!`  //Hi\\n5

数组扩展

Array.from将任何类数组或实现Iterator的类型转换为真·数组

js
Array.from(arguments);
Array.from({length : 3}); //[undefined,undefined,undefined]
Array.from([1,2],x=&gt;x+1); //[2,3] 接受第二个参数进行map, == Array.from([1,2]).map(x=&gt;x+1);
Array.from({length : 3},()=&gt;this.a,{a:1}); //[1,1,1] 接受第三个参数绑定this

Array.of从参数构造数组,行为等同于new Array()参数>2时,不存在重载,如Array.of(1,2),返回[1,2] Array.copyWithin 没卵用..
Array.find()和findIndex(), 接受一个函数,返回执行至第一个返回true时的value或者index,接受的函数参数:value, index, arr 弥补indexOf方法, indexOf方法无法识别数组的NaN成员,但是findIndex方法可以借助Object.is方法做到,
Array.includes, 返回bool, 相当于可以识别NaN的indexOf === -1
Array.fill(), 填充值,不多说
entries(),keys()和values() 返回三种遍历器

函数扩展

ES6允许函数拥有默认值, 并且可以和解构赋值同时使用

js
function fetch(url, { method = 'GET' } = {}){
    console.log(method);
}
fetch("test1"); //GET
fetch("test2",{method : "PUT"}); //PUT

函数拥有length属性,含义是预期传入参数个数,不包括默认参数和rest参数, name属性也被标准化

扩展运算符(...)

在形参列表作用是将剩余变量加入...variable数组中,避免arguments对象的使用(注:只能在参数列表最后出现); 在实参列表作用是将数组转换为参数序列(相当于apply的第二个参数用法);
另外,扩展运算符也可以合并数组;配合解构生成数组;转换成为数组(相当于Array.from)

js
function foo(a,...b){
    console.log(b);
}
foo(1,2,3);//[2,3]

function bar(a,b,c) {
    console.log(a,b,c);
}
bar(...[1,2,3]); //1,2,3

Lambda表达式

ES6提供了对lambda表达式支持, 又名箭头函数, 格式为 "(/* 参数 /) => {/ 函数体 */}", 以下是其注意点,与匿名函数最大的区别是固化this:
(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象
(2)不可以当作构造函数,也就是说,不可以对lambda表达式使用new命令,否则会抛出一个错误
(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用Rest参数代替
(4)不可以使用yield命令,因此箭头函数不能用作Generator函数
关于尾调用优化和尾递归柯里化等函数式编程概念, 以后详述

对象扩展

对象有简洁写法, 不写属性值时, 属性值为属性名称变量的值
对象声明允许属性名表达式, 代替 obj[expr]的写法

js
let a=1;b=2;
console.log({a,b}); //{a : 1 , b : 2}
let c = {
    [a] : 'a',
    [foo()] : 'b'
}
console.log(c); //{ '1' : 'a' , foo()的返回值 : 'b'}

Object.is方法, 接受两个参数, 判断两个对象是否相同,这是一种Same-value equality, 相当于可以判断NaN===NaN和+0!==-0的严格等于运算符
Object.assign方法, 与_.extent $.extend作用相同,合并对象(注:采用浅拷贝) ES7提出扩展运算符'...'引入对象, 这已经在Babel实现 Object.keys , Object.values, Object.entries等等其他语法糖不详述.

ES6中可用的遍历对象的6种姿势

  • for...in 循环遍历对象自身的和继承的可枚举属性(不含Symbol属性)
  • Object.keys(obj) 返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含Symbol属性)
  • Object.getOwnPropertyNames(obj) 返回一个数组,包含对象自身的所有属性(不含Symbol属性,但是包括不可枚举属性)
  • Object.getOwnPropertySymbols(obj) 返回一个数组,包含对象自身的所有Symbol属性
  • Reflect.ownKeys(obj) 返回一个数组,包含对象自身的所有属性,不管是属性名是Symbol或字符串,也不管是否可枚举
  • Reflect.enumerate(obj) 返回一个Iterator对象,遍历对象自身的和继承的所有可枚举属性(不含Symbol属性),与for...in循环相同。