上篇文章我们介绍了ES6的多行字符串表示方法,map,set,for...of循环,rest参数以及let和const,这次我们接着来看看ES6有哪些过人之处。
ECMAScript 6为数组增添的新方法:map(),reduce(),filter()
①map()
语法:arr.map(函数);
功能:相当于让arr的每个数据执行了一次()中的方法,例:
function add(a){
return b =a*a;
}
var arr = [1,2,3,4];
var newArr = arr.map(add);
console.log( newArr);
②reduce()
语法:arr.reduce(函数);
功能:把一个函数作用在arr的每一个元素上,它必须接收两个参数,reduce()把结果继续和序列的下一个元素做累积计算。例:要把[1,2,3,4,5,6]变换成整数123456,就可以用reduce()做到
function changeNumber(x,y){
return x * 10 + y;
}
var arr = [1,2,3,4,5,6];
var newArr = arr.reduce(changeNumber);
console.log(newArr);
③filter()
语法:arr.filter(函数);
功能:用于把Array的某些元素过滤掉,然后返回剩下的元素,和map()类似,Array的filter()也接收一个函数。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是true还是false决定保留还是丢弃该元素。例:利用filter()删除数组中的偶数项
function deleteOushu(x){
return x % 2 !== 0;
}
var arr = [1,2,3,4,5,6,7,8,9,10];
var newArr = arr.filter(deleteOushu);
console.log(newArr);
filter()接收的回调函数可以有多个参数。第一个参数表示Array的某个元素。另外两个参数,表示元素的位置和数组本身:
var arr = ['A', 'B', 'C'];
var r = arr.filter(function (element, index, self) {
console.log(element);
// 依次打印'A', 'B', 'C'
console.log(index);
// 依次打印0, 1, 2
console.log(self);
// self就是变量arr
return true;
});
我们还可以利用filter()巧妙的去除数组的重复项:
var arr = ["a","b","c","b","a","d"];
var newArr = arr.filter(function(element,index,self){
return self.indexOf(element)===index;
});
箭头函数
ES6标准新增了一种新的函数:Arrow Function(箭头函数)
x => x * x;
这个箭头函数等价于
function (x){
return x * x ;
}
箭头函数相当于匿名函数,并且简化了函数定义。箭头函数有两种格式,一种像上面的,只包含一个表达式,连{ ... }和return都省略掉了。还有一种可以包含多条语句,这时候就不能省略{ ... }和return:
x => {
if(x>0) {
return x * x;
}
else{
return -x * x;
}
};
如果参数不是一个,就需要用括号()括起来:
(x,y) => x * x + y * y;
无参数时就用():
() => 1;
可变参数:
(x, y, ...rest) => {
var i, sum = x + y;
for (i=0; i
如果要返回一个对象就要注意,如果是单表达式,这么写的话会报错:
x => { foo: x } // SyntaxError
因为和函数体的{ ... }有语法冲突,所以要改为:
x => ({ foo: x })
箭头函数看上去是匿名函数的一种简写,但实际上,箭头函数和匿名函数有个明显的区别:箭头函数内部的this是词法作用域,由上下文确定。箭头函数完全修复了this的指向,this总是指向词法作用域,也就是外层调用者obj。由于this在箭头函数中已经按照词法作用域绑定了,所以,用call()或者apply()调用箭头函数时,无法对this进行绑定,this取决于箭头函数在哪定义,this不可变,在函数内部这个执行环境中相当于常量。
箭头函数没有arguments,不能通过arguments对象访问传入参数,只能使用显示命名或者其他特性完成。
箭头函数不能使用new关键字来实例化对象,不然会报错。
箭头函数跟普通函数的区别,什么是匿名函数
(1)不需要function
(2)省略return
(3)继承当前上下文的关键字(定义时所在对象)
( 4)不能用作构造函数
(5)不能用yeild
(6)this固有化
(7)不能使用call,apply,bind去改变
匿名函数:没有名字的函数表达式,可以实现动态编程。
generator(生成器)
一个generator看上去像一个函数,但可以返回多次。函数执行过程中,如果没有遇到return语句(函数末尾如果没有return,就是隐含的return undefined;),控制权无法交回被调用的代码。generator跟函数很像,定义如下:
function* foo(x) {
yield x + 1;
yield x + 2;
return x + 3;
}
和函数不同的是generator由function定义(注意多出的号),并且除了return语句,还可以用yield返回多次。例:编写一个产生斐波那契数列的函数,可以这么写:
function fib(max) {
var t=0;
var a=0;
var b=1;
var arr=[0, 1];
while (arr.length < max) {
t = a + b;
a = b;
b = t;
arr.push(t);
}
return arr;
}
fib(5); // 测试结果[0, 1, 1, 2, 3]
函数只能返回一次,必须返回一个Array。generator就可以一次返回一个数,不断返回多次。用generator改写如下:
function* fib(max) {
var t=0;
var a=0;
var b=1;
var n=1;
while (n < max) {
yield a;
t = a + b;
a = b;
b = t;
n++;
}
return a;
}
fib(5); // fib{[[GeneratorStatus]]: "suspended",[[GeneratorReceiver]]:Window}
fib(5)仅仅是创建了一个generator对象,还没有去执行它
调用generator对象有两个方法,一是不断地调用next()方法:
var f = fib(5);
f.next(); // {value: 0, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 2, done: false}
f.next(); // {value: 3, done: true}
value就是yield的返回值,done表示这个generator是否已经执行结束了。为true时value就是return的返回值,并且generator对象就已经全部执行完毕,不要再继续调用next()了。
第二个方法是直接用for ... of循环迭代generator对象,这种方式不需要我们自己判断done:
for (var x of fib(5)) {
console.log(x); // 依次输出0, 1, 1, 2, 3
}
class继承
继承中我们看到了JavaScript的对象模型是基于原型实现的,特点是简单,缺点是理解起来比传统的类-实例模型要困难,最大的缺点是继承的实现需要编写大量代码,并且需要正确实现原型链。新的关键字class从ES6开始正式被引入到JavaScript中。class的目的就是让定义类更简单。
我们先回顾用函数实现Student的方法:
function Student(name) {
this.name = name;
}
Student.prototype.hello = function () {
alert('Hello, ' + this.name + '!');
}
如果用新的class关键字来编写Student,可以这样写:
class Student {
constructor(name) {
this.name = name;
}
hello() {
alert('Hello, ' + this.name + '!');
}
}
比较一下就可以发现,class的定义包含了构造函数constructor和定义在原型对象上的函数hello()(注意没有function关键字),这样就避免了Student.prototype.hello = function () {...}这样分散的代码。
最后,创建一个Student对象
var xiaoming = new Student('小明');
xiaoming.hello();
用class定义对象的另一个巨大的好处是继承更方便了。想一想我们从Student派生一个PrimaryStudent需要编写的代码量。现在,原型继承的中间对象,原型对象的构造函数等等都不需要考虑了,直接通过extends来实现:
class PrimaryStudent extends Student {
constructor(name, grade) {
super(name);
// 记得用super调用父类的构造方法!
this.grade = grade;
}
myGrade() {
alert('I am at grade ' + this.grade);
}
}
注意PrimaryStudent的定义也是class关键字实现的,而extends则表示原型链对象来自Student。子类的构造函数可能会与父类不太相同,例如,PrimaryStudent需要name和grade两个参数,并且需要通过super(name)来调用父类的构造函数,否则父类的name属性无法正常初始化。
PrimaryStudent已经自动获得了父类Student的hello方法,我们又在子类中定义了新的myGrade方法。
ES6引入的class和原有的JavaScript原型继承有什么区别呢?实际上它们没有任何区别,class的作用就是让JavaScript引擎去实现原来需要我们自己编写的原型链代码。简而言之,用class的好处就是极大地简化了原型链代码。