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
同时是ColorPoint
和Point
两个类的实例,这与 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
,子类B
的prototype
属性的__proto__
属性指向父类A
的prototype
属性。
3、Symbol—js的第七种数据类型
ES6 引入了一种新的原始数据类型Symbol
,表示独一无二的值。
它是 JavaScript 语言的第七种数据类型,前六种是:undefined
、null
、布尔值(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)"
上面代码中,s1
和s2
是两个 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']
而且,数组的map
和filter
方法也可以间接用于 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
上面代码的set
和get
方法,表面是针对同一个键,但实际上这是两个值,内存地址是不一样的,因此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
上面代码中,变量k1
和k2
的值是一样的,但是它们在 Map 结构中被视为两个键。
如果 Map 的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map 将其视为一个键,比如0
和-0
就是一个键,布尔值true
和字符串true
则是两个不同的键。另外,undefined
和null
也是两个不同的键。虽然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 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。