ES6语法~解构赋值、箭头函数、class类继承及属性方法、map、set、symbol、rest、new.target、 Object.entries...

2015年6月17日 ECMAScript 6发布正式版本

前面介绍基本语法,  后面为class用法及属性方法、set、symbol、rest等语法.

一、基本语法: 

1、         定义变量:let

使用var 定义的变量没有{ }限制,在条件中定义的i,全局中都可以使用,造成变量污染,有变量提升预解析作用,只提升变量名,不提升值!降低js代码的可阅读性

相同作用域内,let不允许重复声明变量!!否则报错!!但可以更改变量值

使用let定义的变量:不会有变量提升,必须先定义后使用,否则先使用会报错: ReferenceError

在for循环条件中用let定义i,只能在{ }内使用,不会造成全局污染

2、         定义常量:const

const d =10; 不允许再为d重新赋值

const定义的常量必须在定义时给一个初始化的值,否则undefind无意义

利用const定义的常量,在相同作用域中,无法被重新赋值

const定义的常量有块级作用域{ } ,for循环中每一次循环都重新定义了一个作用域

3、         解构赋值:

定义:所谓的解构赋值,就是把某个对象中的属性,当作变量,给解放出来,今后就能够当作变量直接使用了;

语法:

let user = {  name: “zs”, age : 20, genter: ‘男’ }

let { name } = user  --à 把name解构出来做变量使用

let { name: username , age: userage } = user  -à这时name就不存在了,取而代之的是username, 且username/userage无法再被重新赋值!

使用时:直接用username、userage---àconsole.log(username)  //zs

4、         箭头函数--(只针对改造匿名函数)

(形参体列表)=> { 函数体代码 }

<1> 特点:

箭头函数,本质上就是一个匿名函数

箭头函数的特性: 箭头函数内部的 this, 永远和箭头函数外部的 this 保持一致;

btn.onclick = function() {

setTimeout(() => {      //原本定时器内部的this指向window

console.log(this)  //<button id="btn">点击<button>

this.style.backgroundColor = "red"

}, 1000)

}

<2> 箭头函数的三个变体:

正规:去掉function、函数名:

var 函数名 = (参数1,…) => {    }

函数名(参数1,…)----调用

如:var add = (x, y) => { return x+y }

add(1, 2)

①   变体1:如果箭头函数,左侧的形参列表中,只有一个参数,则,左侧小括号可以省略;

var  add  =  x  =>   { return x + 10}

console.log( add(1) )  //11

②    变体2:如果右侧函数体中,只有一行代码,则右侧的 { } 和return可以省略;

var  add  =  (x , y)  =>  x + y

console.log(add(1, 2))  //3

③    变体3:如果箭头函数左侧 只有一个形参,而且右侧只有一行代码,则两边的 () 和 {} 都可以省略

var  add  =  x  =>  x + 10

console.log(add(1))  //11

二、其他语法

1、Class 的基本语法

  constructor(x, y) {
  this.x = x;
  this.y = y;
}

toAdd() {
  return this.x + this.y
}
}

上面代码定义了一个“类”,可以看到里面有一个`constructor`方法,这就是构造方法,而`this`关键字则代表实例对象。也就是说,ES5 的构造函数`Point`,对应 ES6 的`Point`类的构造方法。

方法之间不需要逗号分隔,加了会报错。

使用时:

```javascript
let b = new Point(1, 2)
console.log(b.toAdd()) //3

typeof Point // "function"
Point === Point.prototype.constructor // true

构造函数的prototype属性,在 ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype属性上面。

第一段代码相当于:
Point.prototype = {
 constructor() {},
 toAdd() {},
};

在类的实例上面调用方法,其实就是调用原型上的方法。

constructor 方法

constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor`方法会被默认添加。

class Point {
}

// 等同于
class Point {
 constructor() {}
}

与 ES5 一样,类的所有实例共享一个原型对象。

var p1 = new Point(2,3);
var p2 = new Point(3,2);

p1.__proto__ === p2.__proto__
//true

Class 表达式

与函数一样,类也可以使用表达式的形式定义。

const MyClass = class Me {
 getClassName() {
   return Me.name;
}
};

需要注意的是,这个类的名字是Me,但是Me只在 Class 的内部可用,指代当前类。在 Class 外部,这个类只能用MyClass引用。

如果类的内部没用到Me的话,可以省略Me,也就是可以写成下面的形式。

const MyClass = class { /* ... */ };

采用 Class 表达式,可以写出立即执行的 Class。

自执行类:
let person = new class {  //直接new调用自己
 constructor(name) {
   this.name = name;
}

 sayName() {
   console.log(this.name);
}
}('张三');

person.sayName(); //张三

上面代码中,person是一个立即执行的类的实例。

注意点

(1)严格模式

类和模块的内部,默认就是严格模式,所以不需要使用use strict指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。

(2)不存在提升

类不存在变量提升(hoist),这一点与 ES5 完全不同。

new Foo(); // ReferenceError
class Foo {}

这种规定的原因与下文要提到的继承有关,必须保证子类在父类之后定义。

(3)name 属性

由于本质上,ES6 的类只是 ES5 的构造函数的一层包装,所以函数的许多特性都被Class继承,包括name属性。

class Point {}
Point.name // "Point"

name属性总是返回紧跟在class关键字后面的类名。

(4)this 的指向

类的方法内部如果含有this,它默认指向类的实例。但是,必须非常小心,一旦单独使用该方法(单独解放出来使用),很可能报错。

class Logger {

printName(name = 'there') {

this.print(Hello ${name});

}

print(text) {

console.log(text);

}

}

const logger = new Logger();

const { printName } = logger;

printName(); // 单独使用报错:TypeError: Cannot read property 'print' of undefined

logger.printName(). //Hello there. 实例调用不会报错

如果需要单独使用它:

解决办法:

1、箭头函数:

class Person {

printName = (name = 'aaa') => {

this.print(hello ${name})

}

print(text) {

console.log(text)

}

}

let a = new Person()

a.printName('zhang') //hello zhang

2、绑定this

constructor() {

this.printName = this.printName.bind(this) //this就是Person

}

静态方法

类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类直接来调用,这就称为“静态方法”。

class Foo {
 static classMethod() {
   return 'hello';
}
}

Foo.classMethod() // 'hello'

var foo = new Foo(); //这是实例属性
foo.classMethod() // TypeError: foo.classMethod is not a function

注意,如果静态方法包含this关键字,这个this指的是类,而不是实例。

1、静态方法调用与实例方法调用的区别:

只要没被static定义的,实例都可以直接访问到!

class Foo {
 static bar() {
   this.baz();
}
 static baz() {  //静态方法调用的也必须是static声明的方法
   console.log('hello');
}
 baz() {
   console.log('world');
}
}

Foo.bar() // hello 静态调用,this指向类Foo
let a = new Foo()
a.baz()  //world 实例调用
a.bar()  // 报错:a.bar is not a function

2、父类的静态方法,可以被子类继承。

class Foo {
 static classMethod() {
   return 'hello';
}
}

class Bar extends Foo {
}

Bar.classMethod() // 'hello'

3、静态方法也是可以从super对象上调用的。

class Foo {
 static classMethod() {
   return 'hello';
}
}

class Bar extends Foo {
 static classMethod() {
   return super.classMethod() + ', too';
}
}

Bar.classMethod() // "hello, too"

实例属性的新写法

实例属性除了定义在constructor()方法里面的this上面,也可以定义在类的最顶层。

所有实例对象自身的属性都定义在类的头部,看上去比较整齐,一眼就能看出这个类有哪些实例属性。

class IncreasingCounter {
 _count = 0; //直接把属性定义在最顶层,不需在constructor中this._count = 0
 get value() {
   console.log('Getting the current value!');
   return this._count;
}
 increment() {
   this._count++;
}
}

静态属性

静态属性指的是 Class 本身的属性,写法是在实例属性法的前面,加上static关键字。

class MyClass {
 static myStaticProp = 42;

 constructor() {
   console.log(MyClass.myStaticProp); // 42
}
}

new.target 属性

ES6 为new命令引入了一个new.target属性,该属性一般用在构造函数之中,返回new命令作用于的那个构造函数。如果构造函数不是通过new命令或Reflect.construct()调用的,new.target会返回undefined,因此这个属性可以用来确定构造函数是怎么调用的。

function Person(name) {
 if (new.target !== undefined) {
   this.name = name;
} else {
   throw new Error('必须使用 new 命令生成实例');
}
}

// 另一种写法
function Person(name) {
 if (new.target === Person) {
   this.name = name;
} else {
   throw new Error('必须使用 new 命令生成实例');
}
}

var person = new Person('张三'); // 正确
var notAPerson = Person.call(person, '张三');  // 报错

需要注意的是,子类继承父类时,new.target会返回子类。

class A {
 constructor() {
   console.log(new.target.name);
}
}
class B extends A {
 constructor() {
   super();
}
}
new A() // A
new B() // B

new.target指向当前正在执行的函数。可以看到,在super()执行时,它指向的是子类B的构造函数,而不是父类A的构造函数。也就是说,super()内部的this指向的是B

利用这个特点,可以写出不能独立使用、必须继承后才能使用的类。

2、Class 的继承extends

Class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。

(1)子类通过extends关键字,继承了父类的所有属性和方法(包括静态方法)

在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,基于父类实例,只有super方法才能调用父类实例。

class Point {
 constructor(x, y) {
   this.x = x;
   this.y = y;
}
}

class ColorPoint extends Point {
 constructor(x, y, color) {
   this.color = color; // ReferenceError
   super(x, y);
   this.color = color; // 正确
}
}

上面代码中,子类的constructor方法没有调用super之前,就使用this关键字,结果报错,而放在super方法之后就是正确的。

(2)实例对象即是子类实例,也是父类实例

let cp = new ColorPoint(25, 8, 'green');

cp instanceof ColorPoint // true
cp instanceof Point // true

上面代码中,实例对象cp同时是ColorPointPoint两个类的实例,这与 ES5 的行为完全一致。

Object.getPrototypeOf()

Object.getPrototypeOf方法可以用来从子类上获取父类。

Object.getPrototypeOf(ColorPoint) === Point   // true

因此,可以使用这个方法判断,一个类是否继承了另一个类。

super 关键字

super这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。

(1)第一种情况,super作为函数调用时,代表调用父类的构造函数。ES6 要求,子类的构造函数必须执行一次super函数,否则报错。

class A {}

class B extends A {
 constructor() {
   super();
}
}

注意,super虽然代表了父类A的构造函数,但是返回的是子类B的实例,即super内部的this指的是B的实例,因此super()在这里相当于A.prototype.constructor.call(this)

(2)super()内部的this指向的是子类实例而不是父类!!。

而ES6 规定,在子类普通方法中通过super调用父类的方法时,方法内部的this也指向当前的子类实例。

由于this指向子类实例,所以如果通过super对某个属性赋值,这时super就是this,赋值的属性会变成子类实例的属性。

class A {
 constructor() {
   this.x = 1;
}
}

class B extends A {
 constructor() {
   super();
   this.x = 2;
   super.x = 3;
   console.log(super.x); // undefined
   console.log(this.x); // 3
}
}

let b = new B();

上面代码中,super.x赋值为3,这时等同于对this.x赋值为3。而当读取super.x的时候,读的是A.prototype.x,所以返回undefined

(3)super( ) 作为函数时,super()只能用在子类的构造函数之中,用在其他地方就会报错。不能用在方法中!

class A {}

class B extends A {
 m() {
   super(); // 报错
}
 super();   //报错
}

上面代码中,super()用在B类的m方法之中,或直接写,就会造成句法错误。

(4) super作为对象时,用在普通方法中,指向父类的原型对象;在静态方法中,指向父类。

1⃣️ 这里需要注意,由于super指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的。

super在普通方法之中,指向A.prototype,所以super.p()就相当于A.prototype.p()`。

class A {
 constructor() {
   this.p = 2;
}
}

class B extends A {
 get m() {
   return super.p;
}
}

let b = new B();
b.m // undefined

上面代码中,p是父类A实例的属性,super.p就引用不到它。

如果属性定义在父类的原型对象上,super就可以取到。

class A {}
A.prototype.x = 2;

class B extends A {
 constructor() {
   super();
   console.log(super.x) // 2
}
}

let b = new B();

2⃣️如果super作为对象,用在静态方法之中,这时super将指向父类,`在普通方法之中指向父类的原型对象。

class Parent {
 static myMethod(msg) {
   console.log('static', msg);
}

 myMethod(msg) {
   console.log('instance', msg);
}
}

class Child extends Parent {
 static myMethod(msg) {
   super.myMethod(msg);
}

 myMethod(msg) {
   super.myMethod(msg);
}
}

Child.myMethod(1); // static 1

var child = new Child();
child.myMethod(2); // instance 2

3⃣️在子类的静态方法中通过super调用父类的方法时,方法内部的this指向当前的子类,而不是子类的实例。

class A {
 constructor() {
   this.x = 1;
}
 static print() {
   console.log(this.x);
}
}

class B extends A {
 static x = 4;  //子类静态属性
 constructor() {
   super();
   this.x = 2;
}
 static m() {
   super.print();
}
}

B.x = 3;  子类静态属性
B.m() // 3  

4⃣️使用super的时候,必须显式指定是作为函数、还是作为对象使用,否则会报错。

class A {}

class B extends A {
 constructor() {
   super();
   console.log(super.valueOf() instanceof B); // true
}
}

let b = new B();

上面代码中,super.valueOf()表明super是一个对象,因此就不会报错。同时,由于super使得this指向B的实例,所以super.valueOf()返回的是一个B的实例。

instanceof的普通的用法,obj instanceof Object 检测Object.prototype是否存在于参数obj的原型链上。

Person的原型在p的原型链中

function Person(){};
var p =new Person();
console.log(p instanceof Person);//true

类的 prototype 属性和proto属性

大多数浏览器的 ES5 实现之中,每一个对象都有__proto__属性,指向对应的构造函数的prototype属性。Class 作为构造函数的语法糖,同时有prototype属性和__proto__属性,因此同时存在两条继承链。

(1)子类的__proto__属性,表示构造函数的继承,总是指向父类。

(2)子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。

class A {
}

class B extends A {
}

B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true

上面代码中,子类B__proto__属性指向父类A,子类Bprototype属性的__proto__属性指向父类Aprototype属性。

3、Symbol—js的第七种数据类型

ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。

它是 JavaScript 语言的第七种数据类型,前六种是:undefinednull、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。

Symbol 值通过Symbol函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。

let s = Symbol();

typeof s
// "symbol"

注意,Symbol函数前不能使用new命令,否则会报错。这是因为生成的 Symbol 是一个原始类型的值,不是对象。也就是说,由于 Symbol 值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型。

let s1 = Symbol('foo');
let s2 = Symbol('bar');

s1 // Symbol(foo)
s2 // Symbol(bar)

s1.toString() // "Symbol(foo)"
s2.toString() // "Symbol(bar)"

上面代码中,s1s2是两个 Symbol 值。如果不加参数,它们在控制台的输出都是Symbol(),不利于区分。有了参数以后,就等于为它们加上了描述,输出的时候就能够分清,到底是哪一个值。

注意,Symbol函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的Symbol函数的返回值是不相等的。

所以无论有没有参数,无论参数是否相等,Symbol值都是独一无二的。

// 没有参数的情况
let s1 = Symbol();
let s2 = Symbol();

s1 === s2 // false

// 有参数的情况
let s1 = Symbol('foo');
let s2 = Symbol('foo');

s1 === s2 // false

Symbol 值不能与其他类型的值进行运算,会报错。

但是,Symbol 值可以显式转为字符串。

另外,Symbol 值也可以转为布尔值,但是不能转为数值。

let sym = Symbol();
Boolean(sym) // true
!sym  // false

if (sym) {
 // ...
}

Number(sym) // TypeError
sym + 2 // TypeError
作为属性名的 Symbol

由于每一个 Symbol 值都是不相等的,这意味着 Symbol 值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性。这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或覆盖。

let mySymbol = Symbol();

// 第一种写法
let a = {};
a[mySymbol] = 'Hello!';

// 第二种写法
let a = {
[mySymbol]: 'Hello!'
};

// 第三种写法
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });

// 以上写法都得到同样结果
a[mySymbol] // "Hello!"

Symbol 值作为对象属性名时,不能用点运算符。

在对象的内部,使用 Symbol 值定义属性时,Symbol 值必须放在方括号之中。

4、Set

ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。

Set本身是一个构造函数,用来生成 Set 数据结构。

可用于数组去重!

const s = new Set();

[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));

for (let i of s) {
 console.log(i);
}
// 2 3 5 4

上面代码通过add()方法向 Set 结构加入成员,结果表明 Set 结构不会添加重复的值

Set`函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。

// 例一
const set = new Set([1, 2, 3, 4, 4]);  // set返回一个对象Set{1,2,3,4}、size
[...set]   // 扩展运算符把对象展开,放进数组。
// [1, 2, 3, 4]

// 例二
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // 5   // 返回对象中有5个成员

// 例三
const set = new Set(document.querySelectorAll('div'));
set.size // 56

// 类似于
const set = new Set();
document
.querySelectorAll('div')
.forEach(div => set.add(div));
set.size // 56

上面代码也展示了一种去除数组重复成员的方法。

// 去除数组的重复成员
[...new Set(array)]   // 扩展运算符把对象展开放进数组(利用set去重)

上面的方法也可以用于,去除字符串里面的重复字符。

[...new Set('ababbc')].join('')
// "abc"

向 Set 加入值的时候,不会发生类型转换,所以5"5"是两个不同的值。Set内部判断使用精确判断(===)

let set = new Set();
let a = NaN;
let b = NaN;
set.add(a);
set.add(b);
set // Set {NaN}

上面代码向 Set 实例添加了两个NaN,但是只能加入一个。这表明,在 Set 内部,两个NaN是相等。

另外,两个对象总是不相等的。两个空对象不相等,所以它们被视为两个值。

let set = new Set();

set.add({});
set.size // 1

set.add({});
set.size // 2

属性方法:

Set 结构的实例有以下属性。

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

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

Set 实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)。下面先介绍四个操作方法。

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

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

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

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

遍历操作

Set 结构的实例有四个遍历方法,可以用于遍历成员。

  • keys():返回键名的遍历器

  • values():返回键值的遍历器

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

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

由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys方法和values方法的行为完全一致。

上面遍历相当于:

Object.keys(). Object.values()、 Object.entries()

可以省略values方法,直接用for...of循环遍历 Set。

let set = new Set(['red', 'green', 'blue']);

for (let x of set) {
 console.log(x);
}
// red
// green
// blue

扩展运算符(...)内部使用for...of循环,所以也可以用于 Set 结构。

let set = new Set(['red', 'green', 'blue']);
let arr = [...set];
// ['red', 'green', 'blue']

而且,数组的mapfilter方法也可以间接用于 Set 了。

filter()返回新数组,不改变原数组!!

因此使用 Set 可以很容易地实现并集(Union)、交集(Intersect)和差集(Difference)。

let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);

// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}

// 交集  
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}   在a里筛选出b里也有的

// 差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}     在a里筛选出b里没有的

如果想在遍历操作中,同步改变原来的 Set 结构,目前没有直接的方法,但有两种变通方法。一种是利用原 Set 结构映射出一个新的结构,然后赋值给原来的 Set 结构;另一种是利用Array.from方法。

// 方法一
let set = new Set([1, 2, 3]);
set = new Set([...set].map(val => val * 2));
// set的值是2, 4, 6

// 方法二
let set = new Set([1, 2, 3]);
set = new Set(Array.from(set, val => val * 2));
// set的值是2, 4, 6

上面代码提供了两种方法,直接在遍历操作中改变原来的 Set 结构。

5、map

JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。

为了解决这个问题,ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。

Set的属性和方法,Map也适用!

注意,只有对同一个对象的引用,Map 结构才将其视为同一个键。这一点要非常小心。

const map = new Map();

map.set(['a'], 555);
map.get(['a']) // undefined

上面代码的setget方法,表面是针对同一个键,但实际上这是两个值,内存地址是不一样的,因此get方法无法读取该键,返回undefined

同理,同样的值的两个实例,在 Map 结构中被视为两个键。

const map = new Map();

const k1 = ['a'];
const k2 = ['a'];

map
.set(k1, 111)
.set(k2, 222);

map.get(k1) // 111
map.get(k2) // 222

上面代码中,变量k1k2的值是一样的,但是它们在 Map 结构中被视为两个键。

如果 Map 的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map 将其视为一个键,比如0-0就是一个键,布尔值true和字符串true则是两个不同的键。另外,undefinednull也是两个不同的键。虽然NaN不严格相等于自身,但 Map 将其视为同一个键。

6、Number.isInteger() 用来判断一个值是否为整数。
7、 Object.entries()把对象的 每个key和value放进一个数组

ES6,引入了跟Object.keys配套的Object.values和Object.entries。

let {keys, values, entries} = Object;

let obj = { a: 1, b: 2, c: 3 };

for (let key of keys(obj)) {
console.log(key); // 'a', 'b', 'c'
}


for (let value of values(obj)) {
console.log(value); // 1, 2, 3
}



for (let [key, value] of entries(obj)) {
console.log([key, value]); // ['a', 1], ['b', 2], ['c', 3]
}
8、rest

ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,

rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。这样就不需要使用arguments对象了。

function add(...values) {  // 把传入的参数,放进数组中。
 let sum = 0;

 for (var val of values) {
   sum += val;
}

 return sum;
}

add(2, 5, 3) // 10

下面是一个 rest 参数代替arguments变量的例子。

// arguments变量的写法
function sortNumbers() {
 return Array.prototype.slice.call(arguments).sort();
}

// rest参数的写法
const sortNumbers = (...numbers) => numbers.sort();

上面代码的两种写法,比较后可以发现,rest 参数的写法更自然也更简洁。

arguments对象不是数组,而是一个类似数组的对象。所以为了使用数组的方法,必须使用Array.prototype.slice.call先将其转为数组。rest 参数就不存在这个问题,它就是一个真正的数组,数组特有的方法都可以使用。

Array.prototype.slice.call ( obj ) ; 可以将伪数组转换为数组!!

下面是一个利用 rest 参数改写数组push方法的例子:

function push(array, ...items) {
 items.forEach(function(item) {
   array.push(item);
   console.log(item);
});
}

var a = [];
push(a, 1, 2, 3)

注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。

上一篇:for in 与 Object.keys 与 hasOwnProperty区别


下一篇:apache配置虚拟主机后,启动速度慢