一、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会返回子类;