整理了ES6常用的一些语法,跟大家分享(promise、generator什么的还没有梳理清楚,后续再更新。。。)
1⃣️ 变量声明-let 与 const
(首先关于测试结果:这里有个小问题,如果用let/const声明的变量,在控制台调试的话,第一次输出结果后,第二次如果你还想用,要么把代码放入编辑器中,再打开浏览器看结果,要么就把变量名更改重新输入结果,否则是会报错的)
let与const的作用与var 类似,都是用来声明变量但是在实际应用中,它俩有各自的特殊用途
(注意:ES6里let和const没有变量的提升,一定要声明后再使用,但代码编译成ES5之后,变量提升依旧存在)
先举个栗子
var name = 'aaron'; if(true) { var name = 'peter'; console.log(name); // peter } console.log(name); //peter
我们可以看到,使用var声明的变量,两次输出结果都是peter,那是因为ES5里只有全局作用域和函数作用域,没有块级作用域,那么我们怎么才能让它两次打印的结果分别是aaron 和 peter呢? 现在let就可以派上用场了
改造一下上面的栗子
let name = 'aaron'; if(true) { let name = 'peter'; console.log(name); // peter } console.log(name); //aaron
现在可以看到,两次的结果已经不相同了,let实际上为
JavaScript新增了块级作用域。用它所声明的变量,只在let
命令所在的代码块内有效。
下面再来看一发关于关于for循环的栗子,问题就是用来计数的循环变量泄露为全局变量,会对我们的一些操作带来很大的影响,话不多说,来看栗子
var a = []; for (var i = 0; i < 10; i++) { a[i] = function() { console.log(i) }; } a[6](); //10
原本我们的需求是 a[6](),结果可以输出6,但无论我们写的是a[i](),最终输出的结果都是10,因为for循环结束之后,i的值已经变成了10,而i又是全局变量,当函数之行的时候,首先在函数体内部没有i这么一个变量,所以它会去上一级作用域去寻找,本栗中它的上一级作用域就是全局作用域,所以也就找到了已经变为10的全局变量i,所以a[i]();无论你[]内写0~9哪个数字,最终输出结果都是10;
在没有ES6之前,如果我们想让它的输出就过就是6,就要使用到闭包(闭包这东西,个人的理解,用大白话说就是把你想要实现功能的方法,外面再给它包一个函数。内部return你要实现功能的方法,由于函数的作用域,这样可以避免变量泄露变成全局变量,从而带来一些我们不想看到的结果)
var a = []; function fn(i){ function inner() { console.log(i); } return inner; } for (var i = 0; i < 10; i++) { a[i] = fn (i); } a[6](); //6
讲真,这样很麻烦有没有?现在有了ES6 的let ,完全可以不用这么写了
var a = []; for (let i = 0; i < 10; i++) { a[i] = function() { console.log(i) }; } a[6](); //6
只是改了几个字母,var改成了let,已经实现了我们的需求,很方便有没有?!
const
也用来声明变量,但是声明的是常量。一旦声明,常量的值就不能改变。改变的话,浏览器会报错
栗子
const A = 1; A = 2; // Uncaught TypeError: Assignment to constant variable.
针对const的这个特性,我们可以在引用第三方库的时,把需要声明的变量,用const来声明,这样可以避免未来不小心重命名而导致出现bug
const xxx = require('xxxxx');
注意:当值为基础数据类型时,那么这里的值,就是指值本身。
而当值对应的为引用数据类型时,那么这里的值,则表示指向该对象的引用。这里需要注意,正因为该值为一个引用,只需要保证引用不变就可以,我们仍然可以改变该引用所指向的对象。
栗子
const obj = { a: 20, b: 30 } obj.a = 30; obj.c = 40; console.log(obj); // Object {a: 30, b: 30,c:40}
这种情况下只要不是直接覆盖obj的值,只改变属性什么的还是还可以
2⃣️模版字符串
使用 反引号``(键盘上esc下面那个键) 将整个字符串包裹起来,而在其中使用 ${} 来包裹一个变量或者一个表达式
// es5 var a = 20; var b = 30; var string = a + "+" + b + "=" + (a + b); // es6 const a = 20; const b = 30; const string = `${a}+${b}=${a+b}`;
3⃣️解构(destructuring)赋值
数组以序列号一一对应,这是一个有序的对应关系。
对象根据属性名一一对应,这是一个无序的对应关系。
为了更好的理解,直接上栗子吧
数组的解构赋值
// es5 var arr = [1, 2, 3]; var a = arr[0]; var b = arr[1]; var c = arr[2]; // es6 const arr = [1, 2, 3]; const [a, b, c] = arr; console.log(a,b,c) // 1,2,3
对象的解构赋值
const MYOBJ = { className: 'trigger-button', loading: false, clicked: true, disabled: 'disabled' }
现在我们想要取得其中的2个值:loading与clicked:
// es5 var loading = MYOBJ.loading; var clicked = MYOBJ.clicked; // es6 const { loading, clicked } = MYOBJ; console.log(loading);// false // 还可以给一个默认值,当props对象中找不到loading时,loading就等于该默认值 const { loadings = false, clicked } = MYOBJ; console.log(loadings);// false
4⃣️展开运算符(spread operater)
在ES6中用...
来表示展开运算符,它可以将数组方法或者对象进行展开。上栗子
1.函数调用中使用展开运算符
函数调用里,将一个数组展开成多个参数,我们会用到apply:
function test(a, b, c) { } var args = [0, 1, 2]; test.apply(null, args); 在ES6里可以这样写 function test(a,b,c) { } var args = [0,1,2]; test(...args);
2.数组字面量中使用展开运算符
有了ES6,我们可以直接加一个数组直接合并到另外一个数组当中
const arr1 = [1, 2, 3]; const arr2 = [...arr1, 10, 20, 30]; // 这样,arr2 就变成了[1, 2, 3, 10, 20, 30];
展开运算符也可以用在push函数中,可以不用再用apply()函数来合并两个数组:
var arr1=['a','b','c']; var arr2=['d','e']; arr1.push(...arr2); //['a','b','c','d','e']
3.用于解构赋值
let [arg1,arg2,...arg3] = [1, 2, 3, 4]; arg1 //1 arg2 //2 arg3 //['3','4']
注意:解构赋值中展开运算符只能用在最后,否则会报错
4.展开运算符可以将伪数组变成真正的数组
var list=document.querySelectorAll('div'); var arr=[..list];
Object.prototype.toString.apply(list) // "[object NodeList]"
Object.prototype.toString.apply(arr) // "[object Array]"
关于对象展开
好像目前ES6还不支持这样,现在这样写浏览器会报错,ES7草案里貌似有提到,所以对象展开这里先了解一下就好了
const obj1 = { a: 1, b: 2, c: 3 } const obj2 = { ...obj1, d: 4, e: 5, f: 6 } // 结果类似于 const obj2 = Object.assign({}, obj1, {d: 4,e:5,f:6})
扩展:Object.assign()
方法用于将所有可枚举的属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。 语法:Object.assign(target, 一个或多个sources)
5⃣️ arrow function箭头函数
函数的快捷写法,不需要通过 function
关键字创建函数,并且还可以省略 return
关键字。(注意:箭头函数本身没有this,如果在箭头函数内使用this,这个this一定是它上级的this,再有就是箭头函数可以代替函数表达式,但代替不了函数声明,它还是需要声明才能使用的)。
(parma)=>{expression},箭头函数根据parma个数的不同,写法上还可以做如下改变 () => { expression } // 零个参数用 () 表示 x => { expression } // 一个参数可以省略 () (x, y) => { expression } // 多参数不能省略 ()
注意: 在ES6中,会默认采用严格模式,因此this也不会自动指向window对象了,而箭头函数本身并没有this,因此this就只能是undefined,这一点,在使用的时候,一定要慎重慎重再慎重,不然踩了坑你都不知道自己错在哪!这种情况,如果你还想用this,就不要用使用箭头函数的写法。
栗子
var person = { name: 'tom', getName: function() { return this.name; } } // 用ES6的写法来重构上面的对象 const person = { name: 'tom', getName: () => this.name } // 但是编译结果却是 var person = { name: 'tom', getName: function getName() { return undefined.name; } };
对上面的代码稍作改动
const person = { name: 'tom', getName: function() { return setTimeout(() => this.name, 1000); } } // 编译之后变成 var person = { name: 'tom', getName: function getName() { var _this = this; // 使用了我们在es5时常用的方式保存this引用 return setTimeout(function () { return _this.name; }, 1000); } };
6⃣️函数参数的默认值
之前我们想要保证传入函数的参数有一个默认值,通常需要这么写
function add(x) { var x = x || 20; return x; } console.log(add()); // 20
但这种方法是有缺陷的,比如说我们如果传入一个false
function add(x) { var x = x || 20; return x; } console.log(add(false)); // 20
打印结果是20 而不是fasle,显然合格结果不是我们想要的,如果我们想要打印出false,就还要再做什么if判断等一些列操作,很麻烦,现在有了es6,我们可以很容易解决这个问题,下面我们来看一下es6的写法
function add(x = 20) { return x ; } console.log(add());// 20 console.log(add(false)); // false
可以看到,es6很容易就解决了这个问题。
7⃣️对象字面量({})扩展
ES6针对对象字面量做了许多简化语法的处理
1)精简属性:
const name = 'Jane'; const age = 20 // es6写法 const person = { name, age } // es5写法 var person = { name: name, age: age };
2)精简方法:
// es6写法 const person = { name, age, getName() { // 只要不使用箭头函数,this就还是我们熟悉的this return this.name } } // es5写法 var person = { name: name, age: age, getName: function getName() { return this.name; } };
3)属性名表达式:(这里有点儿恶心,经过几次代码测试,最终确定下面这样解释的话,可能会容易理解一些)
在对象字面量中可以使用中括号作为属性,表示属性也能是一个变量了,而且这个变量的值还可以改变
const name = 'Jane'; const person = { [name]: true, ['a'+'ge']: true } 注意:上面的对象{}内写了两个[]属性 ,切记[]里面如果是一个变量的话,那么这个变量一定要是一个已经声明过的,否则结果就是undefined,[]如果是表达式,那么访问的时候,要向下面这样写 console.log(person['a'+'ge'])/console.log(person['age'])/console.log(oerson.age) // true console.log(person[name]);// true 注意:对象内用[变量]当作属性时,访问该属性只能用[]语法,对象内给这个属性赋什么值,它就会变成什么值,而且这个值也可以通过[]里面传入访问变量最初设置的值,也可以访问到,写就是说像下面这样写 和上面结果是一样的 console.log(person['Jane']); // true
对象的方法也可以这样写
let obj = { ['h'+'ello']() { return 'hi'; } }; console.log(obj.hello()); // hi
8⃣️class、extend、super
class、extend、super这三个特性涉及了ES5中最令人头疼的的几个部分:构造函数、继承,原型...
ES6提供了更接近传统语言的写法,引入了Class(类)这个概念。新的class写法让对象原型的写法更加清晰、更像面向对象编程的语法,也更加通俗易懂。
新旧语法对比
class
// ES5 // 构造函数 function Person(name, age) { this.name = name; this.age = age; } // 原型方法 Person.prototype.getName = function() { return this.name } // ES6 class Person { constructor(name, age) { // 构造函数 this.name = name; this.age = age; } getName() { // 原型方法 return this.name } }
上面代码首先用class
定义了一个“类”,可以看到里面有一个constructor
方法,这就是构造方法,而this
关键字则代表实例对象。
简单地说,constructor
内定义的方法和属性是实例对象自己的,而constructor
外定义的方法和属性则是所有实例对象可以共享的。这个和ES5里的构造函数是差不多的意思,相当于把方法定义在构造函数里是私有的,而把方法定义到原型中,所有实例共享
extend继承
class Person { constructor(name, age) { this.name = name; this.age = age; } getName() { return this.name } } // Student类继承Person类 class Student extends Person { constructor(name, age, gender, classes) { super(name, age); this.gender = gender; this.classes = classes; } getGender() { return this.gender; } }
Class之间可以通过extends
关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。上面定义了一个Cat类,该类通过extends
关键字,继承了Animal类的所有属性和方法。
super
关键字,它指代父类的实例(即父类的this对象)。子类必须在constructor
方法中调用super
方法,否则新建实例时会报错。这是因为子类没有自己的this
对象,而是继承父类的this
对象,然后可以对其进行加工。如果不调用super
方法,子类就得不到this
对象。
关于 super,像上面的栗子
// 构造函数中 // es5写法 Person.call(this); // es6写法 super(name, age);
class、extend/super三者的综合实栗
class Animal { constructor(){ this.type = 'animal' } says(say){ console.log(this.type + ' says ' + say) } } let animal = new Animal() animal.says('hello') //animal says hello class Cat extends Animal { constructor(){ super() this.type = 'cat' } } let cat = new Cat() cat.says('hello') //cat says hello
9⃣️模块的 Import 和 Export
import
用于引入模块,export
用于导出模块。
// 引入整个文件 import dva from 'dva'; // 引入函数(可以是一个或多个) import { connect } from 'dva'; import { Link, Route } from 'dva/router'; // 引入全部并作为 github 对象 import * as github from './services/github'; // 导出默认 export default App; // 部分导出,复合写法是 export { App } from './file'; 等价于import { App } from './file;export{App}
ECMAScript6(ECMAScript 2015 ,ES5,ES2016)技术已经在前端圈子很流行了,他给前端开发人员带来了很多惊喜,提供的语法糖使复杂的操作变得简单。
本文没有详细描述这些新特性,因为网上都已经有很多相关的介绍了。主要针对ES6 新特性收集了相关范例代码,他可以让你快速了解这个新的javascript规范。
箭头函数
function() 函数的简写表示法,但它不绑定 this。
ES6 代码:
var odds = evens.map(v => v + 1); // no parentes and no brackets var nums = evens.map((v, i) => v + i); var pairs = evens.map(v => ({even: v, odd: v + 1})); // Statement bodies nums.forEach(v => { if (v % 5 === 0) fives.push(v); });
this 是如何工作的?
ES6 代码:
var object = { name: "Name", arrowGetName: () => this.name, regularGetName: function() { return this.name }, arrowGetThis: () => this, regularGetThis: function() { return this } } console.log(object); console.log(object.name) console.log(object.arrowGetName()); console.log(object.arrowGetThis()); console.log(this); console.log(object.regularGetName()); console.log(object.regularGetThis());
结果:
console.log(object); => Object { name: "Name", arrowGetName: function, regularGetName: function, arrowGetThis: function, regularGetThis: function } console.log(object.name) => Name console.log(object.arrowGetName()); => undefined console.log(object.arrowGetThis()); => [object Window] Window { stop: function, open: function, alert: function, confirm: function, prompt: function, ... } console.log(this); => 同上 console.log(object.regularGetName()); => Name console.log(object.regularGetThis()); => Object { name: "Name", arrowGetName: function, regularGetName: function, arrowGetThis: function, regularGetThis: function }
我们知道“真正”语言中的类(Classes)。在 ES6 中类(Classes)其实是原型继承的语法糖。
ES6 代码:
class SkinnedMesh extends THREE.Mesh { constructor(geometry, materials) { super(geometry, materials); this.idMatrix = SkinnedMesh.defaultMatrix(); this.bones = []; this.boneMatrices = []; //... } update(camera) { //... super.update(); } get boneCount() { return this.bones.length; } set matrixType(matrixType) { this.idMatrix = SkinnedMesh[matrixType](); } static defaultMatrix() { return new THREE.Matrix4(); } }
增强的对象字面量
ES6 代码:
var theProtoObj = { toString: function() { return "The ProtoOBject To string" } } var handler = () => "handler" var obj = { // __proto__ __proto__: theProtoObj, // Shorthand for ‘handler: handler’ handler, // Methods toString() { // Super calls return "d " + super.toString(); }, // Computed (dynamic) property names [ "prop_" + (() => 42)() ]: 42 }; console.log(obj.handler) console.log(obj.handler()) console.log(obj.toString()) console.log(obj.prop_42)
结果:
obj.handler -> () => "handler" obj.handler() -> handler obj.toString() -> d The ProtoOBject To string obj.prop_42 -> (() => 42)() 是一个立即执行函数 (() => 42)() 相当于 (function() { return 42; })() 42
字符串插值
var name = "Bob", time = "today"; var multiLine = `This Line Spans Multiple Lines`; console.log(`Hello ${name},how are you ${time}?`) console.log(multiLine)
结果:
`Hello ${name},how are you ${time}?` -> Hello Bob,how are you today?
multiLine -> This Line Spans Multiple Lines
解构 Destructuring
// list "matching" var [a, , b] = [1,2,3]; console.log(a); console.log(b);
结果:
a -> 1
b -> 3
对象也能很好的解构
nodes = () => { return {op: "a", lhs: "b", rhs: "c"}} var { op: a, lhs: b , rhs: c } = nodes() console.log(a) console.log(b) console.log(c)
结果:
a -> a b -> b c -> c
使用速记表示法。
nodes = () => { return {lhs: "a", op: "b", rhs: "c"}} // binds `op`, `lhs` and `rhs` in scope var {op, lhs, rhs} = nodes() console.log(op) console.log(lhs) console.log(rhs)
结果:
op -> b lhs -> a rhs -> c
可在参数位置使用
function g({name: x}) { return x } function m({name}) { return name } console.log(g({name: 5})) console.log(m({name: 5}))
结果:
g({name: 5}) -> 5
m({name: 5}) -> 5
故障弱化解构
var [a] = [] var [b = 1] = [] var c = []; console.log(a) console.log(b); console.log(c);
结果:
a -> undefined b -> 1 c -> []
参数默认值(Default)
function f(x, y=12) { return x + y; } console.log(f(3)) console.log(f(3,2))
结果:
f(3) -> 15
f(3,2) -> 5
扩展(Spread)
在函数中: function f(x, y, z) { return x + y + z; } // 传递数组的每个元素作为参数 console.log(f(...[1,2,3]))
结果:
f(...[1,2,3]) -> 6
在数组中:
var parts = ["shoulders", "knees"]; var lyrics = ["head", ...parts, "and", "toes"]; console.log(lyrics)
结果:
lyrics -> ["head","shoulders","knees","and","toes"]
扩展 + 对象字面量
我们可以使用这个创造很酷的对象。
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 }; console.log(x); // 1 console.log(y); // 2 console.log(z); // { a: 3, b: 4 } // Spread properties let n = { x, y, ...z }; console.log(n); // { x: 1, y: 2, a: 3, b: 4 } console.log(obj) 可惜的是它还不支持: npm install --save-dev babel-plugin-transform-object-rest-spread
Rest
我们可以使用 rest 操作符来允许无限参数。
function demo(part1, ...part2) { return {part1, part2} } console.log(demo(1,2,3,4,5,6))
结果:
demo(1,2,3,4,5,6) -> {"part1":1,"part2":[2,3,4,5,6]}
Let
let是新的var。 因为它有块级作用域。
{
var globalVar = "from demo1"
}
{
let globalLet = "from demo2"; } console.log(globalVar) console.log(globalLet)
结果:
globalVar -> from demo1
globalLet -> ReferenceError: globalLet is not defined
但是,它不会向window分配任何内容:
let me = "go"; // 全局作用域
var i = "able"; // 全局作用域 console.log(window.me); console.log(window.i);
结果:
window.me -> undefined
window.i -> able
不能使用let重新声明一个变量:
let me = "foo";
let me = "bar"; console.log(me);
结果:
SyntaxError: Identifier 'me' has already been declared
var me = "foo";
var me = "bar"; console.log(me)
结果:
me -> bar
Const
const 是只读变量。
const a = "b"
a = "a"
结果:
TypeError: Assignment to constant variable.
应该注意,const 对象仍然可以被改变的。
const a = { a: "a" } a.a = "b" console.log(a)
结果:
a -> {"a":"b"}
For..of
迭代器的新类型,可以替代for..in。 它返回的是值而不是keys。
let list = [4, 5, 6]; console.log(list) for (let i in list) { console.log(i); }
结果:
list -> [4,5,6] i -> 0 i -> 1 i -> 2 let list = [4, 5, 6]; console.log(list) for (let i of list) { console.log(i); }
结果:
list -> [4,5,6] i -> 4 i -> 5 i -> 6
迭代器(Iterators)
迭代器是一个比数组更动态的类型。
let infinite = {
[Symbol.iterator]() {
let c = 0; return { next() { c++; return { done: false, value: c } } } } } console.log("start"); for (var n of infinite) { // truncate the sequence at 1000 if (n > 10) break; console.log(n); }
结果:
"start" -> start
n -> 1
n -> 2
n -> 3
n -> 4
n -> 5
n -> 6
n -> 7
n -> 8
n -> 9
n -> 10
使用Typescript,我们可以看到它接口的样子:
Typescript 代码:
interface IteratorResult {
done: boolean;
value: any;
}
interface Iterator {
next(): IteratorResult;
}
interface Iterable {
[Symbol.iterator](): Iterator
}
生成器(Generators)
生成器创建迭代器,并且比迭代器更具动态性。他们不必以相同的方式跟踪状态 并不支持 done 的概念。
var infinity = {
[Symbol.iterator]: function*() { var c = 1; for (;;) { yield c++; } } } console.log("start") for (var n of infinity) { // truncate the sequence at 1000 if (n > 10) break; console.log(n); }
结果:
"start" -> start
n -> 1
n -> 2
n -> 3
n -> 4
n -> 5
n -> 6
n -> 7
n -> 8
n -> 9
n -> 10
使用Typescript 再次显示接口:
Typescript 代码:
interface Generator extends Iterator { next(value?: any): IteratorResult; throw(exception: any); }
function* Iterators and generator
一个产量的例子*
function* anotherGenerator(i) { yield i + 1; yield i + 2; yield i + 3; } function* generator(i) { yield i; yield* anotherGenerator(i); yield i + 10; } var gen = generator(10); console.log(gen.next().value); console.log(gen.next().value); console.log(gen.next().value); console.log(gen.next().value); console.log(gen.next().value);
结果:
gen.next().value -> 10 gen.next().value -> 11 gen.next().value -> 12 gen.next().value -> 13 gen.next().value -> 20
Unicode
ES6 为Unicode 提供了更好的支持。
var regex = new RegExp('\u{61}', 'u'); console.log(regex.unicode) console.log("\uD842\uDFD7") console.log("\uD842\uDFD7".codePointAt())
结果:
regex.unicode -> true
"" ->
"".codePointAt() -> 134103
模块和模块加载器
原生支持模块。
import defaultMember from "module-name";
import * as name from "module-name"; import { member } from "module-name"; import { member as alias } from "module-name"; import { member1 , member2 } from "module-name"; import { member1 , member2 as alias2 , [...] } from "module-name"; import defaultMember, { member [ , [...] ] } from "module-name"; import defaultMember, * as name from "module-name"; import "module-name";
export { name1, name2, …, nameN };
export { variable1 as name1, variable2 as name2, …, nameN }; export let name1, name2, …, nameN; // also var export let name1 = …, name2 = …, …, nameN; // also var, const export expression; export default expression; export default function (…) { … } // also class, function* export default function name1(…) { … } // also class, function* export { name1 as default, … }; export * from …; export { name1, name2, …, nameN } from …; export { import1 as name1, import2 as name2, …, nameN } from …; Import Export
Set
Set 为数学对应,其中所有项目都是唯一的。对于知道SQL的人来说,这相当于distinct。
var set = new Set();
set.add("Potato").add("Tomato").add("Tomato"); console.log(set.size) console.log(set.has("Tomato")) for(var item of set) { console.log(item) }
结果:
set.size -> 2
set.has("Tomato") -> true item -> Potato item -> Tomato
WeakSet
WeakSet对象允许您在集合中存储弱持有的对象。没有引用的对象将被垃圾回收。
var item = { a:"Potato"}
var set = new WeakSet();
set.add({ a:"Potato"}).add(item).add({ a:"Tomato"}).add({ a:"Tomato"}); console.log(set.size) console.log(set.has({a:"Tomato"})) console.log(set.has(item)) for(let item of set) { console.log(item) }
结果:
set.size -> undefined
set.has({a:"Tomato"}) -> false set.has(item) -> true TypeError: set[Symbol.iterator] is not a function
Map
Map 也称为词典。
var map = new Map();
map.set("Potato", 12); map.set("Tomato", 34); console.log(map.get("Potato")) for(let item of map) { console.log(item) } for(let item in map) { console.log(item) }
结果:
map.get("Potato") -> 12
item -> ["Potato",12] item -> ["Tomato",34]
可以使用除字符串之外的其他类型。
var map = new Map();
var key = {a: "a"}
map.set(key, 12); console.log(map.get(key)) console.log(map.get({a: "a"}))
结果:
map.get(key) -> 12 map.get({a: "a"}) -> undefined
WeakMap
使用键的对象,并且只保留对键的弱引用。
var wm = new WeakMap();
var o1 = {} var o2 = {} var o3 = {} wm.set(o1, 1); wm.set(o2, 2); wm.set(o3, {a: "a"}); wm.set({}, 4); console.log(wm.get(o2)); console.log(wm.has({})) delete o2; console.log(wm.get(o3)); for(let item in wm) { console.log(item) } for(let item of wm) { console.log(item) }
结果:
wm.get(o2) -> 2
wm.has({}) -> false
wm.get(o3) -> {"a":"a"} TypeError: wm[Symbol.iterator] is not a function
代理(Proxy)
代理可以用来改变对象的行为。 它们允许我们定义 trap 。
var obj = function ProfanityGenerator() { return { words: "Horrible words" } }() var handler = function CensoringHandler() { return { get: function (target, key) { return target[key].replace("Horrible", "Nice"); }, } }() var proxy = new Proxy(obj, handler); console.log(proxy.words);
结果:
proxy.words -> Nice words
提供以下 trap :
var handler =
{
get:...,
set:...,
has:...,
deleteProperty:..., apply:..., construct:..., getOwnPropertyDescriptor:..., defineProperty:..., getPrototypeOf:..., setPrototypeOf:..., enumerate:..., ownKeys:..., preventExtensions:..., isExtensible:... }
Symbols 是一个新类型。 可用于创建匿名属性。
var typeSymbol = Symbol("type");
class Pet { constructor(type) { this[typeSymbol] = type; } getType() { return this[typeSymbol]; } } var a = new Pet("dog"); console.log(a.getType()); console.log(Object.getOwnPropertyNames(a)) console.log(Symbol("a") === Symbol("a"))
结果:
a.getType() -> dog
Object.getOwnPropertyNames(a) -> []
Symbol("a") === Symbol("a") -> false
可继承内置函数
我们现在可以继承原生类。
class CustomArray extends Array { } var a = new CustomArray(); a[0] = 2 console.log(a[0])
结果:
a[0] -> 2
不能使用数组的代理(Proxy)来覆盖getter函数。
新类库
各种新的方法和常量
console.log(Number.EPSILON)
console.log(Number.isInteger(Infinity)) console.log(Number.isNaN("NaN")) console.log(Math.acosh(3)) console.log(Math.hypot(3, 4)) console.log(Math.imul(Math.pow(2, 32) - 1, Math.pow(2, 32) - 2)) console.log("abcde".includes("cd") ) console.log("abc".repeat(3) ) console.log(Array.of(1, 2, 3) ) console.log([0, 0, 0].fill(7, 1) ) console.log([1, 2, 3].find(x => x == 3) ) console.log([1, 2, 3].findIndex(x => x == 2)) console.log([1, 2, 3, 4, 5].copyWithin(3, 0)) console.log(["a", "b", "c"].entries() ) console.log(["a", "b", "c"].keys() ) console.log(["a", "b", "c"].values() ) console.log(Object.assign({}, { origin: new Point(0,0) }))
结果:
Number.EPSILON -> 2.220446049250313e-16
Number.isInteger(Infinity) -> false Number.isNaN("NaN") -> false Math.acosh(3) -> 1.7627471740390859 Math.hypot(3, 4) -> 5 Math.imul(Math.pow(2, 32) - 1, Math.pow(2, 32) - 2) -> 2 "abcde".includes("cd") -> true "abc".repeat(3) -> abcabcabc Array.of(1, 2, 3) -> [1,2,3] [0, 0, 0].fill(7, 1) -> [0,7,7] [1, 2, 3].find(x => x == 3) -> 3 [1, 2, 3].findIndex(x => x == 2) -> 1 [1, 2, 3, 4, 5].copyWithin(3, 0) -> [1,2,3,1,2] ["a", "b", "c"].entries() -> {} ["a", "b", "c"].keys() -> {} ["a", "b", "c"].values() -> TypeError: ["a","b","c"].values is not a function Object.assign({}, { origin: new Point(0,0) }) -> ReferenceError: Point is not defined 文档: Number, Math, Array.from, Array.of, Array.prototype.copyWithin, Object.assign
二进制和八进制二进制和八进制数字的字面量。
console.log(0b11111)
console.log(0o2342) console.log(0xff); // also in es5
结果:
ES6 代码:
0b11111 -> 31
0o2342 -> 1250
0xff -> 255
Promises
异步编程。
var p1 = new Promise((resolve, reject) => {
setTimeout(() => resolve("1"), 101) }) var p2 = new Promise((resolve, reject) => { setTimeout(() => resolve("2"), 100) }) Promise.race([p1, p2]).then((res) => { console.log(res) }) Promise.all([p1, p2]).then((res) => { console.log(res) })
结果:
res -> 2
res -> ["1","2"]
快速的 Promise
var p1 = Promise.resolve("1")
var p2 = Promise.reject("2") Promise.race([p1, p2]).then((res) => { console.log(res) })
结果:
res -> 1
快速失败
如果一个 promise 失败,all和race也将 reject(拒绝)。
var p1 = new Promise((resolve, reject) => {
setTimeout(() => resolve("1"), 1001) }) var p2 = new Promise((resolve, reject) => { setTimeout(() => reject("2"), 1) }) Promise.race([p1, p2]).then((res) => { console.log("success" + res) }, res => { console.log("error " + res) }) Promise.all([p1, p2]).then((res) => { console.log("success" + res) }, res => { console.log("error " + res) })
结果:
"error " + res -> error 2
"error " + res -> error 2
反射(Reflect)
新类型的元编程与新的API现有的还有一些新的方法。
var z = {w: "Super Hello"}
var y = {x: "hello", __proto__: z}; console.log(Reflect.getOwnPropertyDescriptor(y, "x")); console.log(Reflect.has(y, "w")); console.log(Reflect.ownKeys(y, "w")); console.log(Reflect.has(y, "x")); console.log(Reflect.deleteProperty(y,"x")) console.log(Reflect.has(y, "x"));
结果:
Reflect.getOwnPropertyDescriptor(y, "x") -> {"value":"hello","writable":true,"enumerable":true,"configurable":true} Reflect.has(y, "w") -> true Reflect.ownKeys(y, "w") -> ["x"] Reflect.has(y, "x") -> true Reflect.deleteProperty(y,"x") -> true Reflect.has(y, "x") -> false
尾调用(Tail Call)优化
尾调用的概念非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数。
ES6可以确保尾调用不会造成堆栈溢出。 (不是所有的实现工作)。
function factorial(n, acc = 1) { if (n <= 1) return acc; return factorial(n - 1, n * acc); } console.log(factorial(10)) console.log(factorial(100)) console.log(factorial(1000)) console.log(factorial(10000)) console.log(factorial(100000)) console.log(factorial(1000000))
结果:
factorial(10) -> 3628800
factorial(100) -> 9.332621544394418e+157 factorial(1000) -> Infinity factorial(10000) -> Infinity factorial(100000) -> RangeError: Maximum call stack size exceeded factorial(1000000) -> RangeError: Maximum call stack size exceeded
原文:ES6 Features
ES6的各种新特性的兼容性查询http://kangax.github.io/compat-table/es6/
尽管我们的浏览器还不一定完全支持ES6代码,我们可以使用Babel转码器,在这里我们使用命令行转码babel-cli,命令行$ npm install --global babel-cli
安装babel-cli
let和const命令let命令
ES6新增了 let 命令,用来声明变量。它的用法类似于 var ,但是所声明的变量,只在 let 命令所在的代码块内有效。let不像var那样会发生“变量提升”现象。所以,变量一定要在声明后使用,否则报错。
{
let a = 10; var b = 1; } a // ReferenceError: a is not defined. b // 1
ES6明确规定,如果区块中存在 let 和 const 命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称TDZ)。
let不允许在相同作用域内,重复声明同一个变量。let 实际上为JavaScript新增了块级作用域。 ES6引入了块级作用域,明确允许在块级作用域之中声明函数。
const命令
const 声明一个只读的常量。一旦声明,常量的值就不能改变。
const 命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。
if (true) { console.log(MAX); // ReferenceError const MAX = 5; }
ES6规定var 命令和 function 命令声明的全局变量,依旧是全局对象的属性;let 命令、 const 命令、 class 命令声明的全局变量,不属于全局对象的属性。
变量的解构赋值数组的解构赋值
let [foo, [[bar], baz]] = [1, [[2], 3]]; foo // 1 bar // 2 baz // 3
解构赋值允许指定默认值。
[x, y = 'b'] = ['a']; // x='a', y='b' [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
注意,ES6内部使用严格相等运算符( === ),判断一个位置是否有值。所以,如果一个数组成员不严格等于 undefined ,默认值是不会生效的。
对象的解构赋值
var { foo, bar } = { foo: "aaa", bar: "bbb" }; foo // "aaa" bar // "bbb" var { foo: baz } = { foo: "aaa", bar: "bbb" }; baz // "aaa" foo // error: foo is not defined
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
函数参数的解构赋值
[[1, 2], [3, 4]].map(function([a,b]){ return a + b; }) //[3,7]
变量解构赋值用途
- 交换变量的值
[x, y] = [y, x];
-
提取JSON数据
var jsonData = { id: 42, status: "OK", data: [867, 5309] }; let { id, status, data: number } = jsonData; console.log(id, status, number);// 42, "OK", [867, 5309]
-
函数参数的默认值
jQuery.ajax = function (url, { async = true, beforeSend = function () {}, cache = true, complete = function () {}, crossDomain = false, global = true }) { // ... do stuff };
字符串的扩展includes(), startsWith(), endsWith()
includes():返回布尔值,表示是否找到了参数字符串。
startsWith():返回布尔值,表示参数字符串是否在源字符串的头部。
endsWith():返回布尔值,表示参数字符串是否在源字符串的尾部。
var s = 'Hello world!'; s.startsWith('world', 6); // true s.endsWith('Hello', 5); // true s.includes('Hello', 6); // false
使用第二个参数 n 时, endsWith 的行为与其他两个方法有所不同。它针对前 n 个字符,而其他两个方法针对从第 n 个位置直到字符串结束。
repeat()
返回一个新字符串,表示将原字符串重复 n 次。
'hello'.repeat(2) // "hellohello"
padStart(),padEnd()
padStart 用于头部补全, padEnd 用于尾部补全。
'x'.padStart(5, 'ab') // 'ababx' 'x'.padStart(4, 'ab') // 'abax' 'x'.padEnd(5, 'ab') // 'xabab' 'x'.padEnd(4, 'ab') // 'xaba'
padStart 和 padEnd 一共接受两个参数,第一个参数用来指定字符串的最小长度,第二个参数是用来补全的字符串。
模板字符串
模板字符串(template string)是增强版的字符串,用反引号标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。
var name = "Bob", time = "today"; `Hello ${name}, how are you ${time}?`
数值的扩展
从ES5开始,在严格模式之中,八进制就不再允许使用前缀 0 表示,ES6进一步明确,要使用前缀 0o 表示。
Number.isFinite()
Number.isFinite() 用来检查一个数值是否非无穷(infinity)。
Number.isFinite(0.8); // true Number.isFinite(NaN); // false Number.isFinite(Infinity); // false
Number.isNaN()
Number.isNaN() 用来检查一个值是否为 NaN 。
Number.isNaN(NaN) // true Number.isNaN(15) // false
它们与传统的全局方法 isFinite() 和 isNaN() 的区别在于,传统方法先调用 Number() 将非数值的值转为数值,再进行判断,而这两个新方法只对数值有效,非数值一律返回 false 。
Number.parseInt(), Number.parseFloat()
ES6将全局方法 parseInt() 和 parseFloat() ,移植到Number对象上面,行为完全保持不变。
Number.isInteger()
Number.isInteger() 用来判断一个值是否为整数。需要注意的是,在JavaScript内部,整数和浮点数是同样的储存方法,所以3和3.0被视为同一个值。
安全整数和Number.isSafeInteger()
JavaScript能够准确表示的整数范围在 -2^53 到 2^53 之间(不含两个端点),超过这个范围,无法精确表示这个值。
ES6引入了 Number.MAX_SAFE_INTEGER 和 Number.MIN_SAFE_INTEGER 这两个
常量,用来表示这个范围的上下限。
Number.isSafeInteger() 则是用来判断一个整数是否落在这个范围之内。
Math对象的扩展
Math.trunc 方法用于去除一个数的小数部分,返回整数部分。
Math.sign 方法用来判断一个数到底是正数、负数、还是零。
Math.cbrt 方法用于计算一个数的立方根。
Math.fround方法返回一个数的单精度浮点数形式。
Math.hypot 方法返回所有参数的平方和的平方根。
数组的扩展Array.from()
Array.from 方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括ES6新增的数据结构Set和Map)。
实际应用中,常见的类似数组的对象是DOM操作返回的NodeList集合,以及函数内部的 arguments 对象。 Array.from 都可以将它们转为真正的数组。
// NodeList对象
let ps = document.querySelectorAll('p'); Array.from(ps).forEach(function (p) { console.log(p); }); // arguments对象 function foo() { var args = Array.from(arguments); // ... }
Array.of()
Array.of 方法用于将一组值,转换为数组。
Array.of(3, 11, 8) // [3,11,8]
数组实例的copyWithin()
Array.prototype.copyWithin(target, start = 0, end = this.length)
- target(必需):从该位置开始替换数据。
- start(可选):从该位置开始读取数据,默认为0。如果为负值,表示倒数。
- end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示倒数。
[1, 2, 3, 4, 5].copyWithin(0, 3, 4) // [4, 2, 3, 4, 5]
数组实例的find()和findIndex()
数组实例的 find 方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为 true 的成员,然后返回该成员。如果没有符合条件的成员,则返回 undefined 。
[1, 4, -5, 10].find((n) => n < 0) // -5
数组实例的 findIndex 方法的用法与 find 方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回 -1 。
数组实例的fill()
fill 方法使用给定值,填充一个数组。
['a', 'b', 'c'].fill(7)// [7, 7, 7]
数组实例的entries(),keys()和values()
ES6提供三个新的方法—— entries() , keys() 和 values() ——用于遍历数组。唯一的区别是 keys() 是对键名的遍历、 values() 是对键值的遍历, entries() 是对键值对的遍历。
函数的扩展函数参数的默认值
ES6允许为函数的参数设置默认值,即直接写在参数定义的后面。
function log(x, y = 'World') { console.log(x, y); }
函数的length属性
指定了默认值以后,函数的 length 属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后, length 属性将失真。
(function (a) {}).length // 1 (function (a = 5) {}).length // 0 (function (a, b, c = 5) {}).length // 2
rest参数
ES6引入rest参数(形式为“...变量名”),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest参数搭配的变量是一个数组,该变量将多余的参数放入
数组中。
function add(...values) { let sum = 0; for (var val of values) { sum += val; } return sum; } add(2, 5, 3) // 10
注意,rest参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
扩展运算符
扩展运算符(spread)是三个点( ... )。它好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。
console.log(1, ...[2, 3, 4], 5)// 1 2 3 4 5 // ES5的写法 Math.max.apply(null, [14, 3, 77]) // ES6的写法 Math.max(...[14, 3, 77])
扩展运算符的应用
-
合并数组
var arr1 = ['a', 'b']; var arr2 = ['c']; var arr3 = ['d', 'e']; // ES5的合并数组 arr1.concat(arr2, arr3);// [ 'a', 'b', 'c', 'd', 'e' ] // ES6的合并数组 [...arr1, ...arr2, ...arr3]// [ 'a', 'b', 'c', 'd', 'e' ]
-
扩展运算符还可以将字符串转为真正的数组。
[...'hello']// [ "h", "e", "l", "l", "o" ]
-
实现了Iterator接口的对象
var nodeList = document.querySelectorAll('div'); var array = [...nodeList];
箭头函数
ES6允许使用“箭头”( => )定义函数。
var f = v => v; //等同于 var f = function(v) { return v; };
箭头函数有几个使用注意点
- 函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。
- 不可以当作构造函数,也就是说,不可以使用 new 命令,否则会抛出一个错误。
- 不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用Rest参数代替。
- 不可以使用 yield 命令,因此箭头函数不能用作Generator函数。
对象的扩展属性的简洁表示法
ES6允许在对象之中,只写属性名,不写属性值。这时,属性值等
于属性名所代表的变量。
var Person = { name: '张三', //等同于birth: birth birth, // 等同于hello: function ()... hello() { console.log('我的名字是', this.name); } };
Object.assign()
Object.assign 方法用于对象的合并,将源对象(source)的所有可枚举属性,
复制到目标对象(target)。
var target = { a: 1 }; var source1 = { b: 2 }; var source2 = { c: 3 }; Object.assign(target, source1, source2); target // {a:1, b:2, c:3}
如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性
Object.assign 方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
Object.assign 方法有很多用处。
-
为对象添加属性
class Point { constructor(x, y) { Object.assign(this, {x, y}); } }
-
为对象添加方法
Object.assign(SomeClass.prototype, { someMethod(arg1, arg2) { ··· }, anotherMethod() { ··· } });
-
克隆对象
function clone(origin) { return Object.assign({}, origin); }
-
合并多个对象
const merge = (target, ...sources) => Object.assign(target, ...sources);
-
为属性指定默认值
const DEFAULTS = { logLevel: 0, outputFormat: 'html' }; function processContent(options) { let options = Object.assign({}, DEFAULTS, options); }
ES6属性的遍历5种方法
- for...in 循环遍历对象自身的和继承的可枚举属性(不含Symbol属性)。
- Object.keys(obj)返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含Symbol属性)。
- Object.getOwnPropertyNames(obj)返回一个数组,包含对象自身的所有属性(不含Symbol属性,但是包括不可枚举属性)。
- Object.getOwnPropertySymbols(obj)返回一个数组,包含对象自身的所有Symbol属性。
- Reflect.ownKeys(obj)返回一个数组,包含对象自身的所有属性,不管是属性名是Symbol或字符串,也不管是否可枚举。
Symbol
ES5的对象属性名都是字符串,这容易造成属性名的冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是ES6引入Symbol的原因。
注意,Symbol值作为对象属性名时,不能用点运算符。
var mySymbol = Symbol(); var a = {}; a.mySymbol = 'Hello!'; a[mySymbol] // undefined a['mySymbol'] // "Hello!"
因为点运算符后面总是字符串,所以不会读取 mySymbol 作为标识名所指代的那个值,导致 a 的属性名实际上是一个字符串,而不是一个Symbol值。
Symbol作为属性名,该属性不会出现在 for...in 、 for...of 循环中,也不会被 Object.keys() 、 Object.getOwnPropertyNames() 返回。但是,它也不是私有属性,有一个Object.getOwnPropertySymbols方法,可以获取指定对象的所有Symbol属性名。
Reflect.ownKeys 方法可以返回所有类型的键名,包括常规键名和Symbol键名。
let obj = { [Symbol('my_key')]: 1, enum: 2, nonEnum: 3 }; Reflect.ownKeys(obj)// [Symbol(my_key), 'enum', 'nonEnum']
Symbol.for(),Symbol.keyFor()
有时,我们希望重新使用同一个Symbol值, Symbol.for 方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的Symbol值。如果有,就返回这个Symbol值,否则就新建并返回一个以该字符串为名称的Symbol值。
Symbol.for("bar") === Symbol.for("bar")// true Symbol("bar") === Symbol("bar")// false var s1 = Symbol.for("foo"); Symbol.keyFor(s1) // "foo" var s2 = Symbol("foo"); Symbol.keyFor(s2) // undefined
Set和Map数据结构
ES6提供了新的数据结构Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。Set本身是一个构造函数,用来生成Set数据结构。
var s = new Set(); [2, 3, 5, 4, 5, 2, 2].map(x => s.add(x)); for (let i of s) { console.log(i); } // 2 3 5 4
Set实例的属性和方法
- Set.prototype.constructor :构造函数,默认就是 Set 函数。
- Set.prototype.size :返回 Set 实例的成员总数。
- add(value) :添加某个值,返回Set结构本身。
- delete(value) :删除某个值,返回一个布尔值,表示删除是否成功。
- has(value) :返回一个布尔值,表示该值是否为 Set 的成员。
- clear() :清除所有成员,没有返回值。
Map结构的目的和基本用法
JavaScript的对象(Object),本质上是键值对的集合(Hash结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。为了解决这个问题,ES6提供了Map数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键
Map原生提供三个遍历器生成函数和一个遍历方法。
- keys() :返回键名的遍历器。
- values() :返回键值的遍历器。
- entries() :返回所有成员的遍历器。
- forEach() :遍历Map的所有成员。
Generator 函数
Generator函数有多种理解角度。从语法上,首先可以把它理解成,Generator函数是一个状态机,封装了多个内部状态。
执行Generator函数会返回一个遍历器对象,也就是说,Generator函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历Generator函数内部的每一个状态。
形式上,Generator函数是一个普通函数,但是有两个特征。一是, function关键字与函数名之间有一个星号;二是,函数体内部使用 yield语句,定义不同的内部状态(yield语句在英语里的意思就是“产出”)。
function* helloWorldGenerator() { yield 'hello'; yield 'world'; return 'ending'; } var hw = helloWorldGenerator(); hw.next()// { value: 'hello', done: false } hw.next()// { value: 'world', done: false } hw.next()// { value: 'ending', done: true } hw.next()// { value: undefined, done: true }
Promise对象基本用法
var promise = new Promise(function(resolve, reject) { // ... some code if (/* 异步操作成功 */){ resolve(value); } else { reject(error); } });
Promise实例生成以后,可以用 then 方法分别指定 Resolved 状态和 Reject 状态的回调函数。
var getJSON = function(url) { var promise = new Promise(function(resolve, reject){ var client = new XMLHttpRequest(); client.open("GET", url); client.onreadystatechange = handler; client.responseType = "json"; client.setRequestHeader("Accept", "application/json"); client.send(); function handler() { if (this.readyState !== 4) { return; } if (this.status === 200) { resolve(this.response); } else { reject(new Error(this.statusText)); } }; }); return promise; }; getJSON("/posts.json").then(function(json) { console.log('Contents: ' + json); }, function(error) { console.error('出错了', error); });
Promise.prototype.then()
then 方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为Resolved时调用,第二个回调函数是Promise对象的状态变为Reject时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。
then 方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即 then 方法后面再调用另一个 then 方法。
Promise.prototype.catch()
Promise.prototype.catch 方法是 .then(null, rejection) 的别名,用于指定发生错误时的回调函数。
一般来说,不要在 then 方法里面定义Reject状态的回调函数(即 then 的第二个参数),总是使用 catch 方法。
Class
class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return '(' + this.x + ', ' + this.y + ')'; } }
上面代码定义了一个“类”,可以看到里面有一个 constructor 方法,这就是构造方法,而 this 关键字则代表实例对象。也就是说,ES5的构造函数 Point ,对应ES6的 Point 类的构造方法。
由于类的方法都定义在 prototype 对象上面,所以类的新方法可以添加在 prototype 对象上面。 Object.assign 方法可以很方便地一次向类添加多个方法。
class Point { constructor(){ // ... } } Object.assign(Point.prototype, { toString(){}, toValue(){} });
另外,类的内部所有定义的方法,都是不可枚举的(non-enumerable)。
constructor方法
constructor 方法是类的默认方法,通过 new 命令生成对象实例时,自动调用该方法。一个类必须有 constructor 方法,如果没有显式定义,一个空的 constructor 方法会被默认添加。constructor 方法默认返回实例对象(即 this ),完全可以指定返回另外一个
对象。
Class的继承
Class之间可以通过 extends 关键字实现继承
class ColorPoint extends Point {}
另一个需要注意的地方是,在子类的构造函数中,只有调用 super 之后,才可以
使用 this 关键字,否则会报错。
Class不存在变量提升(hoist),这一点与ES5完全不同。
new Foo(); // ReferenceError class Foo {}
Object.getPrototypeOf()
Object.getPrototypeOf 方法可以用来从子类上获取父类。
因此,可以使用这个方法判断,一个类是否继承了另一个类。
super关键字
super 这个关键字,有两种用法,含义不同。
- 作为函数调用时(即 super(...args) ), super 代表父类的构造函数。
- 作为对象调用时(即 super.prop 或 super.method() ), super 代表父
类。注意,此时 super 即可以引用父类实例的属性和方法,也可以引用父类的静
态方法。
Class的静态方法
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上 static 关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
编程风格
- let取代var
- 静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号。
- 函数的参数如果是对象的成员,优先使用解构赋值。
-
单行定义的对象,最后一个成员不以逗号结尾。多行定义的对象,最后一个成员以
逗号结尾。const a = { k1: v1, k2: v2 }; const b = { k1: v1, k2: v2, };
-
数组
使用扩展运算符(...)拷贝数组。const itemsCopy = [...items];
使用Array.from方法,将类似数组的对象转为数组。const foo = document.querySelectorAll('.foo'); const nodes = Array.from(foo);
-
函数
立即执行函数可以写成箭头函数的形式。(() => { console.log('Welcome to the Internet.'); })();
使用默认值语法设置函数参数的默认值。 -
总是用Class,取代需要prototype的操作。因为Class的写法更简洁,更易于理解。
这些笔记只是看第一遍消化的,还有更多的内容需要对研究几遍才行。