ECMAScript6标准入门(一)新增变量与数据结构

一、ECMAScript6 简介

(1) 与JavaScript的关系

ES是JS的规格,JS是ES的一种实现(另外的ECMAScript方言还有Jscript和ActionScript);

(2) Babel转码器

命令行环境

安装babel-cli, babel-preset-2015;

  • 直接运行ES6代码:babel-node命令;

  • 将ES6转换成ES5:babel命令;

浏览器环境

加入brower.js,ES6代码用type="text/babel"

Node.js环境

安装 babel-core,babel-preset-2015,根目录建立.babelrc文件:

{
    "preset":["es2015"]
}

(1) babel加载为require的一个钩子:设置完.babelrc文件后,在应用入口加入require("babel-core/register")

(2) 若有使用Iterator,Generator等全局对象及上方法:安装babel-polyfill模块,并在应用头部require("babel-polyfill")

二、变量

(1) let命令

基本用法

与var类似,但变量只在let的代码块内有效;

var a = [];
for (let i = 0; i < 10;i++) {
    a[i] =function () {
        console.log(i);
    };
}
a[6](); 
// 6,变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,最后输出的是6;若用var i = 0,则将为10
注意:

(1) 无“变量提升”,用let声明的对象之前需要先声明再使用;

(2) 暂时性死区(Temporal Dead Zone),只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量;同时,也意味着:typeof操作不是绝对安全的!

// 2.暂时性死区 例子
var tmp = 123;
if (true) {
    tmp = 'abc'; //ReferenceError
    let tmp;
}

typeof x; // ReferenceError
let x;

function bar(x = y, y = 2) {
    return [x, y];
}
bar(); // 报错

(3) 不允许重复声明,相同作用域中不能用var,let再次声明let变量(若不是相同作用域则无妨,即再嵌套一个{},花括号里外就是不同作用域)

(2) 块级作用域

可以在块级作用域中声明函数,且该行为类似于let:

function f() { console.log('I am outside!'); }
(function () {
    if(false) {
        //重复声明一次函数f
        function f() { console.log('I am inside!'); }
    }
    f();
}());

以上代码在不同的版本下会有差别:

  • ES5:运行结果“I am inside!”,相当于声明提前;

  • ES6:运行结果“I am outside!”,相当于里面的是块作用域,f() 用的是闭包里的;

  • ES6的浏览器:报错“f is not a function”,根据规则:函数声明会提前到全局、函数作用域的头部(类似var),也会提前到块级作用域的头部,相当于在只执行函数头部var f = undefined

(3) const命令

  • 与let相同,块级作用域,但是常量需要初始化;

  • const声明对象时,其地址不可变,但其中内容可变;

(4) 全局对象的属性 与 全局变量

  • 用ES5的命令var, function声明的变量依然是全局对象的属性,而用ES6命令声明的变量则不是

  • 以上规则在Node的REPL中适用,模块环境下,则只能用global.a来调用全局变量;

三、变量的解构赋值

(1) 数组的解构

例子:

var [a, b, c] = [1, 2,3]; // 则abc分别为123

特殊情况

  • 跳过:let [x, , y] = [1, 2, 3]; // y为3

  • 数组:let [head, ...tail] = [1, 2, 3, 4]; // tail为[2,3,4]

  • 左边解构不成功(左边多),则变量为undefined

  • 右边不完全解构(左边少),则忽略右边;

  • 右边为非可遍历,则报错;

默认值 undefined,[]

例子:(右边必须为=== undefined)

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

注意:

  • 惰性求值:若默认为函数,只有在解构用到默认值时才调用;

  • 可以用已经声明的变量作为默认值,未声明的则报错

(2) 对象的解构

同名属性赋值,左边含义为{模式:变量},完整格式如:

var { foo: foo, bar: bar } = { foo:"aaa", bar: "bbb" };

若模式与变量同名则可以省略模式,否则必须完整

var { foo: baz } = { foo: "aaa", bar:"bbb" };// 变量baz为"aaa"

注意:

  • “变量”部分的重新声明(代码见后);

  • 嵌套赋值(代码见后);

  • 将已声明的变量用于解构赋值,需要加括号(代码见后);

// --------- 1. 变量部分重新声明 ---------
let foo;
let {foo} = {foo: 1}; // SyntaxError: Duplicate declaration "foo"
({foo} = {foo: 1}); // 成功

let baz;
let {bar: baz} = {bar: 1}; // SyntaxError: Duplicate declaration "baz"
({bar: baz} = {bar: 1});  // 成功

// --------- 2. 嵌套赋值 ---------
let obj = {};
let arr = [];

({ foo: obj.prop, bar: arr[0] } = { foo: 123,bar: true });
obj // {prop:123}
arr // [true]

// 以下报错
var {foo: {bar}} = {baz:'baz'}; // 因为foo为undefined,则foo.bar会报错

// --------- 3. 将已声明的变量用于解构赋值,需要注意 ---------
var x;
{x} = {x: 1};

// 正确的写法
({x} = {x: 1});
// 因为“{”在行首,会被理解为一个代码块,故而后面报错;

(3) 字符串、数值、布尔型解构

等式的右边会转换层包装对象,故而等式左边可以写成 length:s, toString: s;

(4) 函数参数的解构

例子:

function add([x, y]){
    returnx + y;
}
add([1, 2]); // 3

对比 设定参数对象默认值 与 对象解构的默认值 的区别

// 对象解构的默认值
function move({x = 0, y = 0} = {}) {
  return[x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0] 

// 参数对象的默认值
function move({x, y} = { x: 0, y: 0 }) {
  return[x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, undefined]
move({}); // [undefined, undefined]
move(); // [0, 0]

(5) 解构中的圆括号

声明中不可使用圆括号,赋值语句中的非模式部分(变量与整体)可以使用圆括号

四、第七种数据类型——Symbol

(1) 数组的解构

  • 用途:保证对象属性名的唯一性;

  • 生成方法:通过Symbol()函数,可传参数作为描述;

  • 注意

    • 不能在Symbol()函数前面加new,否则报错,因为Symbol不是对象;

    • Symbol()函数不能与其他类型的值进行运算,但可转成字符串或布尔值(true);

    • Symbol(s)中的参数仅仅用于toString()控制台输出作为人工区分,即使没有参数也是不同的变量;

      var s1 = Symbol();
      var s2 = Sy mbol();
      s1 === s2 // false

(2) 作为属性名

(a) 方式

a[mySymbol] = 'xx';
a = {
  [mySymbol] = 'xx' //方括号不能去
};
Object.defineProperty(a, mySymbol, {
  value: 'xx' 
});

(b) 注意

当Symbol作为属性名时,不能用点运算符,a.mySymbol其实为a['mySymbol'];

(c) 遍历

  • 用Object.getOwnPropertySymbols(),返回数组,其成员是所有用作属性名的Symbol值;

  • Reflect.ownKeys(),返回自身所有类型的键名,包括Symbol值;

(3) Symbol的方法

(a) Symbol.for(s)

重新使用同一个Symbol,搜索有没有s为名字的Symbol,有就返回,无则新建Symbol;这是全局的,即使不同iframe也用同一个Symbol;

(b) Symbol.keyFor()

返回一个已用Symbol.for()登记的Symbol类型值的key;

(4) 对象中内置Symbol.xx属性

  • Symbol.hasInstance:指向内部方法,当其他对象使用instanceof运算符时,会调用这个方法;

  • Symbol.isConcatSpreadable:布尔值,表示使用concat()时,是展开成元素还是整个数组

  • Symbol.species:指向一个方法,当该对象作为构函创造实例时,调用该方法;

  • Symbol.match:指向一个方法,执行match()时会调用;

  • Symbol.replace:指向一个方法,执行replace()时会调用;

  • Symbol.search:指向一个方法,执行search()时会调用;

  • Symbol.split:指向一个方法,执行split()时会调用;

  • Symbol.iterator:指向该对象的默认遍历器;

  • Symbol.toPrimitive:指向一个方法,该对象被转为原始类型的值时调用,返回该对象对应的原始类型值;

  • Symbol.toStringTag:指向一个方法,调用toString()方法时,返回成[object xxx]

  • Symbol.unscopables:指向一个对象,指定了使用with关键字时,哪些属性被with排除;

五、Set与WeakSet

(1) Set

集合,类似数组,成员值唯一;采用===,除了NaN

(a) 格式

new Set([array]) 

(b) 属性

  • Set.prototype.constructor:构造函数,默认就是Set函数;

  • Set.prototype.size:返回Set实例的成员总数;

(c) 方法

  • add(value):添加某个值,返回Set结构本身;

  • delete(value):删除某个值,返回一个布尔值,表示删除是否成功;

  • has(value):返回一个布尔值,表示该值是否为Set的成员;

  • clear():清除所有成员,没有返回值;

以下为遍历方法

  • keys():返回键名的遍历器,Set键名与键值相同;

  • values():返回键值的遍历器,Set键名与键值相同;

  • entries():返回键值对的遍历器;

  • forEach(func):使用回调函数遍历每个成员;

(2) WeakSet

(a) 与Set的区别

  • 元素只能是对象

  • 其中的对象都是弱引用,垃圾回收机制不考虑其对象的引用,即若无其他对象引用该对象,则回收;

  • 因无法引用该对象,故无法遍历

(b) 方法

  • WeakSet.prototype.add(value):向WeakSet实例添加一个新成员;

  • WeakSet.prototype.delete(value):清除WeakSet实例的指定成员;

  • WeakSet.prototype.has(value):返回一个布尔值,表示某个值是否在WeakSet实例之中;

六、Map与WeakMap

(1) Map

类似对象,键值对的集合,但其键的范围不限于字符串;参数是数组,其成员是[[key1, value1], [key2, value2]]

(a) 注意

  • 同一个键多次赋值,则会覆盖

  • 读取未知的键,返回undefined;

  • 对同一个对象的引用作为键,Map才视为同一个键,NaN、正负0分别视为同一个对象

    map.set(['a'], 555);
    map.get(['a']);        //undefined 
    
    var k1 = ['a'];
    var k2 = ['a'];
    map
    .set(k1, 111)
    .set(k2, 222);
    map.get(k1) // 111
    map.get(k2) // 222

(b) 属性与操作方法

  • size属性:返回成员总数;

  • set(key, value):设置key所对应的键值,然后返回整个Map结构,若已存在键,则更新值;

  • get(key):读取对应键值,无则返回undefined;

  • has(key):返回布尔值,表示是否在Map中;

  • delete(key):删除某个键,返回true,失败则false;

  • clear():清除所有成员,无返回;

  • 遍历方法:keys(), values(), entries(), forEach();

(2) WeakMap

  • 与Map结构类似,唯一区别在于只接受对象作为键名(null除外)

  • WeakMap的专用场合就是,它的键所对应的对象,可能会在将来消失,WeakMap结构有助于防止内存泄漏;

  • 没有遍历操作,也没有size属性

  • 无法清空

七、类class

(1) 基本语法

class类型看成是函数,类本身就是指向构造函数;

类的方法都定义在prototype对象上面,所以类的新方法可以添加在prototype对象上面;

//定义类
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
 }  // 这里无分号

  toString() {
      return '(' + this.x + ', ' + this.y + ')';
  }
}

class表达式

const MyClass = class Me {...}
// 该类的名字是MyClass而不是Me,Me只在Class内部代码可用

(2) constructor方法

  • new命令生成对象实例时,调用该方法;

  • 若无显式的,则默认添加一个空的constructor函数;

  • 注意:无变量提升;

(3) 继承

(a) 基本用法

class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y); // 调用父类的constructor(x, y), 必须要调用super()方法,因为子类需要用到父类的this
   this.color = color;
}

  toString() {
    return this.color + ' ' + super.toString(); // 调用父类的toString()
  }
}

(b) 原型属性

  • 子类的__proto__属性,表示构造函数的继承,总是指向父类;

  • 子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性;

(c) 继承的特殊情况

子类继承Object

A.proto === Object // true
A.prototype.proto === Object.prototype // true

不存在任何继承

A.proto === Function.prototype // true
A.prototype.proto === Object.prototype // true

子类继承null

A.proto === Function.prototype // true
A.prototype.proto === undefined // true

(d) 实例的__proto__属性

子类实例的__proto__属性的__proto__属性,指向父类实例的__proto__属性,即子类的原型的原型,是父类的原型;

(4) 继承原生构造函数

ES5中,先创建子类this,父类的属性添加到子类上,由于父类内部属性无法获取,导致无法继承原生的构造函数;

ES6中,先新建父类this,然后再用子类构造函数修饰this,这使得父类的所有行为都可以继承;

(5) class的存取值函数

class MyClass {
 constructor() {
   // ...
  }
  get prop() {
      return 'getter';
  }
  set prop(value) {
   console.log('setter: '+value);
  }
}

(6) Generator方法

class Foo {
  constructor(...args) {
    this.args = args;
  }
  *Symbol.iterator {
      for(let arg of this.args) {
        yield arg;
    }
  }
}

(7) 静态方法

在方法前加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用;

父类的静态方法,可以被子类继承,也可用super对象调用;

(8)(ES7)静态属性和实例属性

(ES6规定,只有静态方法,无静态属性,ES7草案有静态属性)

实例属性

class MyClass {
  myProp = 42;
  constructor() {
      console.log(this.myProp); // 42
  }
}

静态属性

class MyClass {
  static myStaticProp = 42;
  constructor() {
    console.log(MyClass.myProp); // 42
  }
}

(9) new.target属性

  • 返回new命令作用于的那个构造函数,如果构造函数不是通过new命令调用的,new.target会返回undefined;

  • Class内部调用new.target,返回当前Class

  • 子类继承父类时,new.target会返回子类

你可能感兴趣的:(javascript,es6)