ECMAScript 六学习笔记
- <!-- 加载Traceur编译器 -->
- <scriptsrc="http://google.github.io/traceur-compiler/bin/traceur.js" type="text/javascript"></script>
- <!-- 将Traceur编译器用于网页 -->
- <scriptsrc="http://google.github.io/traceur-compiler/src/bootstrap.js" type="text/javascript"></script>
- <!-- 打开实验选项,否则有些特性可能编译不成功 -->
- <script>
- traceur.options.experimental =true;
- </script>
- <scripttype="module">
- classCalc{
- constructor(){
- console.log('Calc constructor');
- }
- add(a, b){
- return a + b;
- }
- }
- var c =newCalc();
- console.log(c.add(4,5));
- </script>
注意,script标签的type属性的值是module(或者traceur),而不是text/javascript。这是Traceur编译器识别ES6代码的标识,编译器会自动将所有type=module的代码编译为ES5,然后再交给浏览器执行。
2.新增关键字
(1)let是ES6中新增关键字。它的作用类似于var,用来声明变量,但是所声明的变量,只在let命令所在的代码块内有效。
(2)const 声明的是常量,一旦声明,值将是不可变的。const 的特点:
- 具有块级作用域
- 不能变量提升(必须先声明后使用)
- 不可重复声明
- const 指令指向变量所在的地址,所以对该变量进行属性设置是可行的(未改变变量地址),如果想完全不可变化(包括属性),那么可以使用冻结Object.freeze
3.新增方法
(1)是否包含字符串
- includes():返回布尔值,表示是否找到了参数字符串。
- startsWith():返回布尔值,表示参数字符串是否在源字符串的头部。
- endsWith():返回布尔值,表示参数字符串是否在源字符串的尾部。
(2)重复字符串repeat()
repeat()返回一个新字符串,表示将原字符串重复n次。
(3)模版字符串
模板字符中,支持字符串插值:
- let first ='world';
- let last='中国';
- document.write(`Hello ${first} ${last}!`);
- // Hello world 中国!
(4)String.row()
若使用String.raw 作为模板字符串的前缀,则模板字符串可以是原始(raw)的。反斜线也不再是特殊字符,\n 也不会被解释成换行符:
- let raw =String.raw`Not a newline: \n`;
- document.write(raw ==='Not a newline: \\n');// true
(5)isFinite(),isNaN(),isInteger()
在Number对象上,新提供了Number.isFinite()和Number.isNaN()两个方法,用来检查Infinite和NaN这两个特殊值
Number.isInteger()用来判断一个值是否为整数。需要注意的是,在JavaScript内部,整数和浮点数是同样的储存方法,所以3和3.0被视为同一个值。
(6)Math对象新增方法
- Math.trunc():去除一个数的小数部分,返回整数部分
- Math.sign():判断一个数到底是正数、负数、还是零。它返回五种值:参数为正数,返回+1;参数为负数,返回-1;参数为0,返回0;参数为-0,返回-0;其他值,返回NaN
- Math.cbrt:计算一个数的立方根
- Math.fround:返回一个数的单精度浮点数形式
- Math.hypot:返回所有参数的平方和的平方根。
- Math.expm1(x):返回ex
- 1。
- Math.log1p(x):返回1
+ x的自然对数。如果x小于-1,返回NaN。
- Math.log10(x):返回以10为底的x的对数。如果x小于0,则返回NaN。
- Math.log2(x):返回以2为底的x的对数。如果x小于0,则返回NaN
- let array =Array.from({0:"a",1:"b",2:"c", length:3});
- document.write(array);// [ "a", "b" , "c" ]
- Array(3)// [undefined, undefined, undefined]
- Array.of(3)// [3]
- Array.of(3).length // 1
- let array =[1,5,10,15].find(function(value, index, arr){
- return value >9;
- })
- document.write(array);// 10
- entries()
- keys()
- values()
- for(let index of ['a','b'].keys()){
- document.write(index);//0,1
- }
- for(let elem of ['a','b'].values()){
- document.write(elem);//'a','b'
- }
- for(let [index, elem] of ['a','b'].entries()){
- document.write(index, elem);//0a,1b
- }
- var target={name:'张三'}
- var source={age:'19',sex:'男'}
- Object.assign(target, source1, source2); {"name":"张三","age":"19","sex":"男"}
注意,Symbol函数前不能使用new命令,否则会报错。这是因为生成的Symbol是一个原始类型的值,不是对象。
Symbol类型的值不能与其他类型的值进行运算,会报错。但是,Symbol类型的值可以转为字符串。
(4).Proxy 内置的一个代理工具,使用他可以在对象处理上加一层屏障,new Proxy()表示生成一个Proxy实例,它的target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。- var proxy =newProxy(target, handler)
- let log =::console.log;
- // 等同于var log = console.log.bind(console);
- foo::bar;
- // 等同于bar.call(foo);
- foo::bar(...arguments);
- // 等同于bar.apply(foo, arguments);
var array = [1, 2, 3];//传统写法 array.forEach(function(v, i, a) {console.log(v);}); //ES6 array.forEach(v = > console.log(v));
- 函数体内的this对象,绑定定义时所在的对象,而不是使用时所在的对象。
- 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
- 不可以使用arguments对象,该对象在函数体内不存在。
上面三点中,第一点尤其值得注意。this对象的指向是可变的,但是在箭头函数中,它是固定的。
(1).Set结构实例的属性:
- Set.prototype.constructor:构造函数,默认就是Set函数。
- Set.prototype.size:返回Set实例的成员总数。
(2).操作方法:
- add(value):添加某个值,返回Set结构本身。
- delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
- has(value):返回一个布尔值,表示该值是否为Set的成员。
- clear():清除所有成员,没有返回值。
(3).遍历方法:
- keys():返回一个键名的遍历器
- values():返回一个键值的遍历器
- entries():返回一个键值对的遍历器
- forEach():使用回调函数遍历每个成员
- let set=newSet(['red','green','blue']);
- for( let item of set.keys()){
- document.write(item);
- }
- for( let item of set.values()){
- document.write(item);
- }
- for( let item of set.entries()){
- document.write(item);
- }//["red", "red"]["green", "green"]["blue", "blue"]
- set.forEach(function(item){
- document.write(item);
- })
8.Map
Map 是一个“超对象”,其 key 除了可以是 String 类型之外,还可以为其他类型(如:对象),他的方法和 Set 差不多:
- size:返回成员总数。
- set(key, value):设置key所对应的键值,然后返回整个Map结构。如果key已经有值,则键值会被更新,否则就新生成该键。
- get(key):读取key对应的键值,如果找不到key,返回undefined。。
- has(key):返回一个布尔值,表示某个键是否在Map数据结构中。
- delete(key):删除某个键,返回true。如果删除失败,返回false。
- clear():清除所有成员。
- keys():返回键名的遍历器。
- values():返回键值的遍历器。
- entries():返回所有成员的遍历器。
9.Iterator遍历器
遍历器(Iterator)就是统一的接口机制,来处理所有不同的数据结构。
Iterator的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是ES6创造了一种新的遍历命令for...of循环,Iterator接口主要供for...of消费。
Iterator的遍历过程:
- 创建一个指针,指向当前数据结构的起始位置。也就是说,遍历器的返回值是一个指针对象。
- 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
- 第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
- 调用指针对象的next方法,直到它指向数据结构的结束位置。
每一次调用next方法,都会返回当前成员的信息,具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。
遍历器返回的指针对象除了具有next方法,还可以具有return方法和throw方法。其中,next方法是必须部署的,return方法和throw方法是否部署是可选的。
return方法的使用场合是,如果for...of循环提前退出(通常是因为出错,或者有break语句或continue语句),就会调用return方法。如果一个对象在完成遍历前,需要清理或释放资源,就可以部署return方法。
throw方法主要是配合Generator函数使用,一般的遍历器用不到这个方法。
Generator函数是一个函数的内部状态的遍历器(也就是说,Generator函数是一个状态机)。形式上,Generator函数是一个普通函数,但是有两个特征。
- 一是,function命令与函数名之间有一个星号*;
- 二是,函数体内部使用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 }
总结一下,调用Generator函数,返回一个部署了Iterator接口的遍历器对象,用来操作内部指针。以后,每次调用遍历器对象的next方法,就会返回一个有着value和done两个属性的对象。value属性表示当前的内部状态的值,是yield语句后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束。
yield语句本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield语句的返回值。
for...of循环可以自动遍历Generator函数,且此时不再需要调用next方法。注意:一旦next方法的返回对象的done属性为true,for...of循环就会中止,且不包含该返回对象,所以return语句返回的对象,不包括在for...of循环之中。
Generator 函数内部还可以部署错误处理代码,捕获函数体外抛出的错误。使用指针对象的 throw 方法抛出的错误,可以被函数体内的 try
... catch 代码块捕获。这意味着,出错的代码与处理错误的代码,实现了时间和空间上的分离,这对于异步编程无疑是很重要的。
11.Promise
所谓Promise,就是一个对象,用来传递异步操作的消息。
Promise对象有以下两个特点:
- 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称Fulfilled)和Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。
Promise也有一些缺点:
- 首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。
- 其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
- 第三,当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
Promise对象是一个构造函数,用来生成Promise实例
- //创建promise
- var promise =newPromise(function(resolve, reject){
- // 进行一些异步或耗时操作
- if(/*如果成功 */){
- resolve("Stuff worked!");
- }else{
- reject(Error("It broke"));
- }
- });
- //绑定处理程序
- promise.then(function(result){
- //promise成功的话会执行这里
- document.write(result);// "Stuff worked!"
- },function(err){
- //promise失败会执行这里
- document.write(err);// Error: "It broke"
- });
- resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从Pending变为Resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
- reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从Pending变为Rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise实例具有then方法,也就是说,then方法是定义在原型对象,作用是为Promise实例添加状态改变时的回调函数。
then方法两个参数:
- Resolved状态的回调函数;
- Rejected状态的回调函数(可选)。
then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
- getJSON("/posts.json").then(function(json){
- return json.post;
- }).then(function(post){
- // ...
- });
上面的代码使用then方法,依次指定了两个回调函数。第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。
Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。
- getJSON("/posts.json").then(function(posts){
- // ...
- }).catch(function(error){
- // 处理前一个回调函数运行时发生的错误
- document.write('发生错误!', error);
- });
getJSON方法返回一个Promise对象,如果该对象状态变为Resolved,则会调用then方法指定的回调函数;如果异步操作抛出错误,状态就会变为Rejected,就会调用catch方法指定的回调函数,处理这个错误。
- var promise =newPromise(function(resolve, reject){
- thrownewError('test')
- });
- promise.catch(function(error){ document.write(error)});
- // Error: test
上面代码中,Promise抛出一个错误,就被catch方法指定的回调函数捕获。
Promise对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。
- getJSON("/post/1.json").then(function(post){
- return getJSON(post.commentURL);
- }).then(function(comments){
- // some code
- }).catch(function(error){
- // 处理前面三个Promise产生的错误
- });
上面代码中,一共有三个Promise对象:一个由getJSON产生,两个由then产生。它们之中任何一个抛出的错误,都会被最后一个catch捕获。
Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例。
- var p =Promise.all([p1,p2,p3]);
上面代码中,Promise.all方法接受一个数组作为参数,p1、p2、p3都是Promise对象的实例。(Promise.all方法的参数不一定是数组,但是必须具有iterator接口,且返回的每个成员都是Promise实例。)
p的状态由p1、p2、p3决定,分成两种情况。
- 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
- 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
Promise.race方法同样是将多个Promise实例,包装成一个新的Promise实例。
- var p =Promise.race([p1,p2,p3]);
上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的Promise实例的返回值,就传递给p的回调函数。
如果Promise.all方法和Promise.race方法的参数,不是Promise实例,就会先调用Promise.resolve方法,将现有对象转为Promise对象,如果Promise.resolve方法的参数,不是具有then方法的对象(又称thenable对象),则返回一个新的Promise对象,且它的状态为Resolved。
- var p =Promise.resolve('Hello');
- p.then(function(s){
- document.write(s)
- });
- // Hello
由于字符串Hello不属于异步操作(判断方法是它不是具有then方法的对象),返回Promise实例的状态从一生成就是Resolved,所以回调函数会立即执行。
Promise.reject(reason)方法也会返回一个新的Promise实例,该实例的状态为rejected。Promise.reject方法的参数reason,会被传递给实例的回调函数。
- var p =Promise.reject('出错了');
- p.then(null,function(s){
- document.write(s)
- });// 出错了
上面代码生成一个Promise对象的实例p,状态为rejected,回调函数会立即执行。
12.Generator函数与Promise结合
使用Generator函数管理流程,遇到异步操作的时候,通常返回一个Promise对象。
- function getFoo (){
- returnnewPromise(function(resolve, reject){
- resolve('foo');
- });
- }
- var g =function*(){
- try{
- var foo =yield getFoo();
- document.write(foo);
- }catch(e){
- document.write(e);
- }
- };
- function run (generator){
- var it = generator();
- function go(result){
- if(result.done)return result.value;
- return result.value.then(function(value){
- return go(it.next(value));
- },function(error){
- return go(it.throw(value));
- });
- }
- go(it.next());
- }
- run(g);
上面代码的Generator函数g之中,有一个异步操作getFoo,它返回的就是一个Promise对象。函数run用来处理这个Promise对象,并调用下一个next方法。
13.Class
ES6提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。
- //定义类
- classPoint{
- constructor(x, y){
- this.x = x;
- this.y = y;
- }
- toString(){
- return'('+this.x+', '+this.y+')';
- }
- }
上面代码定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法,而this关键字则代表实例对象。
constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。
(1).class的继承
Class之间可以通过extends关键字,实现继承。子类会继承父类的属性和方法。
- classColorPointextendsPoint{
- constructor(x, y, color){
- this.color = color;// ReferenceError
- super(x, y);
- this.color = color;// 正确
- }
- }
注意:ColorPoint继承了父类Point,但是它的构造函数必须调用super方法,super方法必须放在第一行。
在Class内部可以使用get和set关键字,对某个属性设置存值函数和取值函数。
(2).class的静态方法
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。父类的静态方法,可以被子类继承。
- classFoo{
- static classMethod(){
- return'hello';
- }
- }
- Foo.classMethod()// 'hello'
- var foo =newFoo();
- foo.classMethod()
- // TypeError: undefined is not a function
(3).new.target属性
new是从构造函数生成实例的命令。ES6为new命令引入了一个new.target属性,(在构造函数中)返回new命令作用于的那个构造函数。如果构造函数不是通过new命令调用的,new.target会返回undefined,因此这个属性可以用来确定构造函数是怎么调用的。
- Class内部调用new.target,返回当前Class。
- 子类继承父类时,new.target会返回子类。
(4).修饰器
修饰器(Decorator)是一个表达式,用来修改类的行为。这是ES7的一个提案,目前Babel转码器已经支持。修饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,修饰器能在编译阶段运行代码。
- function testable(target){
- target.isTestable =true;
- }
- @testable
- classMyTestableClass{}
- console.log(MyTestableClass.isTestable)// true
上面代码中,@testable就是一个修饰器。它修改了MyTestableClass这个类的行为,为它加上了静态属性isTestable。
基本上,修饰器的行为就是下面这样。
- @decorator
- class A {}
- // 等同于
- class A {}
- A = decorator(A)|| A;
修饰器函数可以接受三个参数,依次是目标函数、属性名和该属性的描述对象。后两个参数可省略。testable函数的参数target,就是所要修饰的对象。如果希望修饰器的行为,能够根据目标对象的不同而不同,就要在外面再封装一层函数。
- function testable(isTestable){
- returnfunction(target){
- target.isTestable = isTestable;
- }
- }
- @testable(true)classMyTestableClass(){}
- document.write(MyTestableClass.isTestable)// true
- @testable(false)classMyClass(){}
- document.write(MyClass.isTestable)// false
如果想要为类的实例添加方法,可以在修饰器函数中,为目标类的prototype属性添加方法。
- function testable(target){
- target.prototype.isTestable =true;
- }
- @testable
- classMyTestableClass(){}
- let obj =newMyClass();
- document.write(obj.isTestable)// true
14.模块(module)
模块功能主要由两个命令构成:export和import。
- export命令用于用户自定义模块,规定对外接口;
- import命令用于输入其他模块提供的功能,同时创造命名空间(namespace),防止函数名冲突。
ES6允许将独立的JS文件作为模块,允许一个JavaScript脚本文件调用另一个脚本文件。ES6支持多重加载,即所加载的模块中又加载其他模块。
- // profile.js
- var firstName ='Michael';
- var lastName ='Jackson';
- var year =1958;
- export{firstName, lastName, year};
- // main.js
- import{firstName, lastName, year}from'./profile';
- function sfirsetHeader(element){
- element.textContent = firstName +' '+ lastName;
- }
import命令接受一个对象(用大括号表示),里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块(profile.js)对外接口的名称相同。
module命令可以取代import语句,达到整体输入模块的作用。module命令后面跟一个变量,表示输入的模块定义在该变量上。
- module profile from './profile';
为加载模块指定默认输出,使用export default命令。其他模块加载该模块时,import命令可以为该匿名函数指定任意名字。
- // export-default.js
- exportdefaultfunction(){
- document.write('foo');
- }
- // import-default.js
- import customName from'./export-default';
- customName();// 'foo'
上面代码的import命令,可以用任意名称指向export-default.js输出的方法。需要注意的是,这时import命令后面,不使用大括号。