ES6中数组、对象和函数的扩展

一、数组的扩展

1.概念

扩展运算符(spread)是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。扩展运算符与正常的函数参数可以结合使用,后面也可以放置表达式,但如果后面是一个空数组,则不产生任何效果。

let arr = [];
arr.push(...[1,2,3,4,5]);
console.log(arr); //[1,2,3,4,5]
console.log(1, ...[2, 3, 4], 5) //1 2 3 4 5
console.log(...(1 > 0 ? ['a'] : [])); //a
console.log([...[], 1]); //[1]
2.替代函数的apply方法

扩展运算符可以展开数组,将数组转换为函数的参数。所以不需要apply()方法。

// ES5 的写法
console.log('ES5方式:' + Math.max.apply(null, [14, 3, 77]))

// ES6 的写法
console.log('ES6方式:' + Math.max(...[14, 3, 77]))

// 等同于
console.log('等价ES6:' + Math.max(14, 3, 77))
3.扩展运算符的应用
  • 复制数组
//数组是复合的数据类型,直接复制的话,只是复制了指向底层数据结构的指针,而不是克隆一个全新的数组

// ES5 的写法
const a1 = [1, 2];
const a2 = a1.concat();

// ES6 的写法
const a1 = [1, 2];
const a2 = [...a1];

//或者
const [...a2] = a1;
  • 合并数组
const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];

// ES5 的写法
console.log(arr1.concat(arr2, arr3));  //[ 'a', 'b', 'c', 'd', 'e' ]

// ES6 的写法
console.log([...arr1, ...arr2, ...arr3])  //[ 'a', 'b', 'c', 'd', 'e' ]
  • 与解构赋值结合
// ES5 的写法
a = list[0], rest = list.slice(1)

// ES6 的写法
[a, ...rest] = list

//eg
const [f1, ...f2] = [1, 2, 3, 4, 5, 6];
console.log(f1); //1
console.log(...f2); //2 3 4 5 6

//将扩展运算符用于数组赋值,只能放在参数的最后一位
const [...f1,f2] = [1, 2, 3]; //报错
const [f1,...f2,f3] = [1, 2, 3]; //报错
  • 转换字符串
//扩展运算符还可以将字符串转为真正的数组,并且能够正确识别四个字节的 Unicode 字符
//所以凡是涉及到操作四个字节的 Unicode 字符的函数,最好都用扩展运算符改写
console.log('x\uD83D\uDE80y'.length);  //4
console.log([...'x\uD83D\uDE80y'].length);  //3

let str = 'x\uD83D\uDE80y';
// ES5 的写法
console.log(str.split('').reverse().join(''));  // 'y\uD83D\uDE80x'

// ES6 的写法
console.log([...str].reverse().join(''));  // 'y\uD83D\uDE80x'
  • 实现Iterator接口的对象

任何 Iterator 接口的对象(参阅 Iterator 一章),都可以用扩展运算符转为真正的数组。对于那些没有部署 Iterator 接口的类似数组的对象,扩展运算符就无法将其转为真正的数组。

//querySelectorAll方法返回的是一个NodeList对象。它不是数组,而是一个类似数组的对象。这时,扩展运算符可以将其转为真正的数组,原因就在于NodeList对象实现了 Iterator 。
let nodeList = document.querySelectorAll('div');
let array = [...nodeList];
  • Map和Set结构
let map = new Map([
	[1, 'one'],
 	[2, 'two'],
    [3, 'three'],
]);
let arrMap = [...map];
console.log(arrMap);	//[ [ 1, 'one' ], [ 2, 'two' ], [ 3, 'three' ] ]
console.log(...map.keys());	//1 2 3		
console.log(...map.values()); //one two three
console.log(...map.entries()); //[ 1, 'one' ] [ 2, 'two' ] [ 3, 'three' ]
  • Generator函数

generate函数运行后,返回一个遍历器的对象,也可使用扩展运算符。

//变量ge是一个 Generator 函数,执行后返回的是一个遍历器对象,对这个遍历器对象执行扩展运算符,就会将内部遍历得到的值,转为一个数组。
const ge = function*() {
	yield 1;
	yield 2;
	yield 3;
};
console.log([...ge()]); //[ 1, 2, 3 ]
4.Array.from()

用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括ES6新增的数据结构Set和Map)。只要部署了Iterator接口的数据结构,Array.from()都能将其转化为真正的数组。

  • 将类似数组的对象转为数组
let arrLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    '3': 'd',
    'length': 4,
};
// ES5 的写法
var arrEs5 = Array.prototype.slice.call(arrLike);
//或者简写成
var arrEs5 = [].slice.call(arrLike);
console.log(arrEs5); //[ 'a', 'b', 'c', 'd' ]

// ES6 的写法
let arrEs6 = Array.from(arrLike);
console.log(arrEs6); //[ 'a', 'b', 'c', 'd' ]
  • 函数的arguments对象
function argsObject() {
    var args = Array.from(arguments);
    console.log(args)
}
argsObject(1, 2, 3); //[ 1, 2, 3 ]
  • 部署了Iterator接口
let str = 'hello,小明!';
console.log(Array.from(str)); //[ 'h', 'e', 'l', 'l', 'o', ',', '小', '明', '!' ]

let nameSet = new Set(['set1', 'set2', 'set3']);
console.log(nameSet); //Set { 'set1', 'set2', 'set3' }
console.log(Array.from(nameSet)); //[ 'set1', 'set2', 'set3' ]
  • Array.from() 接收第二个参数

Array.from() 可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。

let arrMap = Array.from([1, 2, 3], (x) => x * x)
console.log(arrMap)// [1, 4, 9]
  • 返回数据类型
function typesOf() {
    return Array.from(arguments, value => typeof value);
}
console.log(typesOf(null, [], NaN)); //["object", "object", "number"]
  • 第一个参数指定第二个参数的运行次数
let str = 'hello';
console.log(Array.from({ length: 3 }, () => str)); //[ 'hello', 'hello', 'hello' ]
  • 将字符串转为数组,然后返回字符串的长度
function countSymbols(string) {
    return Array.from(string).length;
}
console.log(countSymbols('hello world')); //11
5.Array.of()

用于将一组值,转换为数组。这个方法的主要目的,是弥补数组构造函数Array()的不足。因为参数个数的不同,会导致Array()的行为有差异。

可以用来替换 Array() 或 new Array(),并且不会因参数不同而导致重载。总是返回参数值组成的数组,如果没有参数,就返回一个空数组。

//Array.of()用于将一组值,转换为数组
let a = Array.of(10);
console.log(a);  //[ 10 ]
let b = Array.of(10,11,12);
console.log(b);  //[ 10, 11, 12 ]
console.log(Array.of(undefined)); //[ undefined ]

let c = new Array(10);
console.log(c); //[ <10 empty items> ]
let d = new Array(10,11,12);
console.log(d); //[ 10, 11, 12 ]
6. copyWithin()

数组实例的copyWithin()方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组

Array.prototype.copyWithin(target, start = 0, end = this.length);
 
它接受三个参数(这三个参数都应该是数值,如果不是,会自动转为数值):

  • target(必需):从该位置开始替换数据。如果为负值,表示倒数
  • start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示倒数
  • end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示倒数
//将从3号位直到数组结束的成员(4 和 5),复制到从 0 号位开始的位置,结果覆盖了原来的 1 和 2
let arr1 = [1, 2, 3, 4, 5].copyWithin(0, 3);
console.log(arr1); // [4, 5, 3, 4, 5]

//将3号位复制到0号位
let arr2 = [1, 2, 3, 4, 5].copyWithin(0, 3, 4);
console.log(arr2); // [4, 2, 3, 4, 5]

//-2相当于3号位,-1相当于4号位
let arr3 = [1, 2, 3, 4, 5].copyWithin(0, -2, -1);
console.log(arr3); //[ 4, 2, 3, 4, 5 ]

//将3号位复制到0号位
let arr4 = [].copyWithin.call({ length: 5, 3: 1 }, 0, 3)
console.log(arr4); //{ '0': 1, '3': 1, length: 5 }

//将2号位到数组结束,复制到0号位
let arr5 = new Int32Array([1, 2, 3, 4, 5]);
console.log(arr5.copyWithin(0, 2)); //Int32Array [ 3, 4, 5, 4, 5 ]

//对于没有部署TypedArray的copyWithin方法的平台,需要采用下面的写法
let arr6 = [].copyWithin.call(new Int32Array([1, 2, 3, 4, 5]), 0, 3, 4);
console.log(arr6); //Int32Array [ 3, 4, 5, 4, 5 ]
7.find()

数组实例的find()方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined

find() 方法的回调函数可以接受三个参数,依次为当前的值当前的位置原数组

let arr = [1,2,3,4,5,6,2,1];
let res = arr.find((item)=>{
	return item>3;
});
console.log(res);  //4

//find()方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组
let result = [1, 5, 10, 15].find(function(value, index, arr) {
    return index > 2;
});
console.log(result); //15
8.findIndex()

数组实例的findIndex()方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1

//返回第一个符合条件的元素的索引或-1
let arr = [1,2,3,4,5,6,2,1];
let res = arr.findIndex((item)=>{
	return item>3;
});
console.log(res);  //3

find() 方法和 findIndex() 方法都可以接受第二个参数,用来绑定回调函数的this对象

//find函数接收了第二个参数
function fun(n) {
    return n > this.age;
}
let person = {
    name: 'Tom',
    age: 22
};
console.log([10, 12, 26, 21, 50].find(fun, person)); //26

find() 方法和 findIndex() 方法还可以发现NaN,弥补了数组的indexOf()方法的不足。

//indexOf()
console.log([NaN].indexOf(NaN)); //-1

//findIndex()
console.log([NaN].findIndex(n => Object.is(NaN, n))); //0
9.fill()

fill()方法使用给定值,填充一个数组。

fill()方法用于空数组的初始化非常方便。数组中已有的元素,会被全部抹去。fill()方法还可以接受第二个参数第三个参数,用于指定填充的起始位置结束位置
注意:如果填充对象的类型为对象,那么被赋值的是同一个内存地址的对象,而不是深拷贝对象。

let arr1 = ['a', 'b', 'c'].fill(7);
console.log(arr1); //[ 7, 7, 7 ]

let arr2 = new Array(3).fill(7);
console.log(arr2); //[ 7, 7, 7 ]

//指定起始位置
let arr3 = ['a', 'b', 'c'].fill(7, 1, 2);
console.log(arr3); //[ 'a', 7, 'c' ]

//填充对象
let arr4 = new Array(3).fill({
    name: "Mike"
});
console.log(arr4); //[ { name: 'Mike' }, { name: 'Mike' }, { name: 'Mike' } ]
arr4[0].name = "Ben";
console.log(arr4); //[ { name: 'Ben' }, { name: 'Ben' }, { name: 'Ben' } ]

let arr5 = new Array(3).fill([]);
arr5[0].push(5);
console.log(arr5); //[ [ 5 ], [ 5 ], [ 5 ] ]
10.keys(),values() 和 entries()

ES6 提供三个新的方法 keys()values()entries() 用于遍历数组。它们都返回一个遍历器对象,可以用for…of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。

let arr = [1, 2, 3];

//keys()
let keys = arr.keys();
console.log(keys); //Object [Array Iterator] {}
for (let key of keys) {
    console.log(key);
}
//0
//1
//2

//values()
let values = arr.values();
console.log(values); //Object [Array Iterator] {}
for (let key of values) {
    console.log(key);
}
//1
//2
//3

//entries()
let entries = arr.entries();
console.log(entries); //Object [Array Iterator] {}
for (let [key, value] of entries) {
    console.log(key, value);
}
//0 1
//1 2
//2 3

如果不使用or…of循环,可以手动调用遍历器对象的next方法,进行遍历。

//用next进行遍历
let arr= ['a', 'b', 'c', 'd'];
let entries = arr.entries();
console.log(entries.next()); //{ value: [ 0, 'a' ], done: false }
console.log(entries.next()); //{ value: [ 1, 'b' ], done: false }
console.log(entries.next()); //{ value: [ 2, 'c' ], done: false }
console.log(entries.next()); //{ value: [ 3, 'd' ], done: false }
console.log(entries.next()); //{ value: undefined, done: true }
11.includes()

数组实例的 includes() 返回一个布尔值,表示某个数组是否包含给定的值,当数组含有给定的值是返回true,不包含给定的值时返回false。与字符串的includes方法类似。

该方法的第二个参数表示搜索的起始位置,默认为0。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为-4,但数组长度为3),则会重置为从0开始。

let arr = [1,2,3,4,5];
let arr = [1, 2, 3, 4, 5];
//当只有一个参数是表示从第一个元素开始查找元素'2'
console.log(arr.includes(2)); //true

//表示从索引为2(也就是'3'这个元素)开始查找元素'2'
console.log(arr.includes(2, 2)); //false

//表示从倒数第一个元素(也就是'5'这个元素)开始查找元素'2'
console.log(arr.includes(2, -1)); //false

//由于第二个参数的绝对值大于数组长度,所以从第一个元素(也就是'1'这个元素)开始查找元素'2'
console.log(arr.includes(2, -6)); //true

SetMap数据结构有一个has()方法,与includes()区别:

  • Set 结构的has方法,是用来查找值
    has(value):返回一个布尔值,表示该值是否为Set的成员。
  • Map 结构的has方法,是用来查找键名
    has(key)has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中。

二、对象的扩展

1.概念

对象的解构赋值用于从一个对象取值,相当于将目标对象自身的所有可遍历的(enumerable)、但尚未被读取的属性,分配到指定的对象上面。所有的键和它们的值,都会拷贝到新对象上面。

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 }

注意:

  • 由于解构赋值要求等号右边是一个对象,所以如果等号右边是undefinednull,就会报错,因为它们无法转为对象。
  • 解构赋值必须是最后一个参数,否则会报错。

解构赋值的拷贝是浅拷贝,即如果一个键的值是复合类型的值(数组、对象、函数)、那么解构赋值拷贝的是这个值的引用,而不是这个值的副本。

let obj = { a: { b: 1 } };
let {...x } = obj;
console.log(x); //{ a: { b: 1 } }
console.log(x.a.b); //1

x.a.b = 2;
console.log(obj.a.b); //2

扩展运算符的解构赋值,不能复制继承自原型对象的属性

let o1 = { a: 1 };
let o2 = { b: 2 };
o2.__proto__ = o1;
let {...o3 } = o2;
console.log(o3); //{ b: 2 }
console.log(o3.b); //2
console.log(o3.a); //undefined


const o = Object.create({ x: 1, y: 2 });
o.z = 3;
let { x, ...newObj } = o;
let { y, z } = newObj;
console.log(x); //1
console.log(y); //undefined
console.log(z); //3


let { x, ... { y, z } } = o;
// SyntaxError: ... must be followed by an identifier in declaration contexts
2.属性简写

ES6允许直接写入变量和函数,作为对象的属性和方法。这时,属性名为变量名, 属性值为变量的值。

var baz = {foo}; 
//等同于 
var baz = {foo: foo};
3.方法简写
var o = { method() { return "Hello!"; } };
//等同于 
var o = { method: function() { return "Hello!"; } };
4.属性名表达式

ES6 允许字面量定义对象时,可以把表达式放在方括号内。

let propKey = 'foo'; 
let obj = { [propKey]: true, ['a' + 'bc']: 123 };
5.方法的name属性

函数的name属性,返回函数名。

const person = { 
	sayName() { 
		console.log('hello!');
	} 
};
console.log(person.sayName.name); //"sayName"
6.Object.is(value1,value2)

同值相等,与===类似,不同之处在于:一是+0不等于-0,二是NaN等于自身

console.log(+0 === -0);  //true
console.log(NaN === NaN);  //false
console.log(Object.is(+0,-0));  //false
console.log(Object.is(NaN,NaN));  //true
console.log(Object.is({}, {})); //false
console.log(Object.is(true,'true')); //false
console.log(Object.is(123,123)); //true
console.log(Object.is(456,'456')); //false
7.Object.assign(target,o1,o2…)

用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。Object.assign方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。具有以下作用:

  • 为对象添加属性和方法
  • 克隆对象
  • 为属性提供默认值
//为对象添加属性和方法
Object.assign(SomeClass.prototype, { someMethod(arg1, arg2) { ··· }, anotherMethod() { ··· } });

//克隆对象
function clone(origin) { return Object.assign({}, origin); }

//为属性提供默认值
function processContent(options) { 
	options = Object.assign({}, DEFAULTS, options); 
	...
}
//DEFAULTS对象是默认值,options对象是用户提供的参数

Object.assign() 对象的浅复制和对象的合并

let obj = {
    name: 'hb',
    friends: [],
    count: {}
};
//浅复制
let obj1 = obj;
let obj2 = Object.assign(obj);
console.log(obj1 === obj); //true
console.log(obj2 === obj); //true

//对象的合并

let obj3 = Object.assign({}, obj);
console.log(obj3); //{ name: 'hb', friends: [], count: {} }
console.log(obj3 === obj); //false
console.log(obj3.friends === obj.friends); //true
console.log(obj3.count === obj.count); //true

let obj4 = {};
let obj5 = {
    name: 'lm'
};
//存在相同属性时后面的属性值会覆盖前面的属性值
Object.assign(obj4, obj, obj5);
console.log(obj4); //{ name: 'lm', friends: [], count: {} }
8.__proto__属性

本质上属于内部属性,指向当前对象的prototype对象,一般不直接使用。

let obj = { a:1 };
console.log(obj.__proto__ === Object.prototype); //true
9.Object. getPrototypeOf(obj)

用于读取一个对象的原型对象。

let obj = { a:1 };
console.log(Object.getPrototypeOf(obj)); //{}
console.log(Object.getPrototypeOf(obj) === Object.prototype); //true
10.Object. setPrototypeOf(obj,prototype)

用来设置一个对象的prototype对象,返回参数对象本身。它是 ES6 正式推荐的设置原型对象的方法。

let obj = { a:1 };
//新建一个对象objPro
let objPro = {
	toString(){
		return 'hello';
	}
};
//设置原型对象
Object.setPrototypeOf(obj,objPro); 
console.log(Object.getPrototypeOf(obj) === objPro); //true
console.log(obj.toString()); //hello
//obj可以调用原来的原型中的属性和方法
console.log(obj.valueOf()); //{ name: 'hb' }
11.Object.keys(obj)

返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名

let obj = {
    name: 'Tom',
    age: 25
};

//返回属性名组成的数组
let keys = Object.keys(obj);
console.log(keys); //[ 'name', 'age' ]
12.Object.values(obj)

返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值

let obj = {
    name: 'Tom',
    age: 25
};

//返回属性值组成的数组
let values = Object.values(obj);
console.log(values); //[ 'Tom', 25 ]
13.Object. entries(obj)

返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。

let obj = {
    name: 'Tom',
    age: 25
};

//返回key-values键值对组成的数组
let entries = Object.entries(obj);
console.log(entries); //[ [ 'name', 'Tom' ], [ 'age', 25 ] ]

//for-of遍历entries
for (let [key, value] of entries) {
    console.log(key, value);
}
//name Tom
//age 25
8.扩展运算符的应用
  • 扩展某个函数的参数,引入其他操作
function baseFunction({ a, b }) {
	...
}
function wrapperFunction({ x, y, ...restConfig }) {
	// 使用 x 和 y 参数进行操作
	// 其余参数传给原始函数
	return baseFunction(restConfig);
}
  • 取出参数对象的所有可遍历属性,拷贝到当前对象之中
let z = { a: 3, b: 4 };
let n = {...z };
console.log(n); //{ a: 3, b: 4 }


let aClone = { ...a };
// 等同于
let aClone = Object.assign({}, a);

//上面的例子只是拷贝了对象实例的属性,如果想完整克隆一个对象,还拷贝对象原型的属性,可以采用下面的写法。
// 写法一
const clone1 = Object.assign(Object.create(Object.getPrototypeOf(obj)),obj);
// 写法二
const clone2 = Object.create(Object.getPrototypeOf(obj),Object.getOwnPropertyDescriptors(obj));
  • 合并两个对象
let ab = { ...a, ...b };
// 等同于
let ab = Object.assign({}, a, b);

//如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉。
let aWithOverrides = { ...a, x: 1, y: 2 };
// 等同于
let aWithOverrides = { ...a, ...{ x: 1, y: 2 } };
// 等同于
let x = 1, y = 2, aWithOverrides = { ...a, x, y };
// 等同于
let aWithOverrides = Object.assign({}, a, { x: 1, y: 2 });
  • 修改现有对象部分的属性
let newVersion = {
	...previousVersion,
	name: 'New Name' // Override the name property
};
  • 其他

1.如果把自定义属性放在扩展运算符前面,就变成了设置新对象的默认属性值。
2.与数组的扩展运算符一样,对象的扩展运算符后面可以跟表达式。
3.如果扩展运算符后面是一个空对象,则没有任何效果。
4.如果扩展运算符的参数是nullundefined,这两个值会被忽略,不会报错。
5.扩展运算符的参数对象之中,如果有取值函数get,这个函数是会执行的。

三、函数的扩展

1.函数参数的默认值
1.基本用法

ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。

function fun(x, y = 'World') {
    console.log(x, y);
}
fun(); //undefined 'World'
fun(1); //1 'World'
fun(1, 2); //1 2
  • 通常情况下,定义了默认值的参数,应该是函数的尾参数
  • 函数的length属性,将返回没有指定默认值的参数个数。如果遇到有默认值的参数就停止。
function test(a, b, c = '1', d, e = '2') {}
console.log(test.name); //test
//形参个数,遇到默认值直接停止,返回长度
console.log(test.length); //2
2.与解构赋值默认值结合使用

参数默认值可以与解构赋值的默认值,结合起来使用。

function foo({ x, y = 5 }) {
    console.log(x, y);
}
foo({}) // undefined 5 
foo({ x: 1 }) // 1 5 
foo({ x: 1, y: 2 }) // 1 2
3.参数默认值的位置

通常情况下,定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的。

// 例一
function f(x = 1, y) {
	return [x, y];
}

f() // [1, undefined]
f(2) // [2, undefined])
f(, 1) // 报错
f(undefined, 1) // [1, 1]

// 例二
function f(x, y = 5, z) {
	return [x, y, z];
}

f() // [undefined, 5, undefined]
f(1) // [1, 5, undefined]
f(1, ,2) // 报错
f(1, undefined, 2) // [1, 5, 2]
4.作用域

一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的。

//例子一
function f(x, y = x) {
	console.log(y);
}

f(2) // 2

//例子二
let x = 1;
function f(y = x) {
	let x = 2;
  	console.log(y);
}

f() // 1
5.应用

利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出一个错误。

function throwIfMissing() {
	throw new Error('Missing parameter');
}

function foo(mustBeProvided = throwIfMissing()) {
  	return mustBeProvided;
}

foo()
// Error: Missing parameter
//上面代码的foo函数,如果调用的时候没有参数,就会调用默认值throwIfMissing函数,从而抛出一个错误。
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 参数代替arguments变量的例子。

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

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

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

function push(array, ...items) {
	items.forEach(function(item) {
    	array.push(item);
    	console.log(item);
  	});
}
var a = [];
push(a, 1, 2, 3); //1 2 3
//注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
3.箭头函数

ES6 允许使用“箭头”(=>)定义函数。

var f = v => v;

// 等同于
var f = function (v) {
	return v;
};

如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。

var f = () => 5;
// 等同于
var f = function () { return 5 };

var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
	return num1 + num2;
};

如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。

var sum = (num1, num2) => { return num1 + num2; }

由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。

// 报错
let getTempItem = id => { id: id, name: "Temp" };

// 不报错
let getTempItem = id => ({ id: id, name: "Temp" });

箭头函数可以与变量解构结合使用。

const full = ({ first, last }) => first + ' ' + last;

// 等同于
function full(person) {
	return person.first + ' ' + person.last;
}

箭头函数使得表达更加简洁。

const isEven = n => n % 2 === 0;
const square = n => n * n;

箭头函数的一个用处是简化回调函数。

// 正常函数写法
[1,2,3].map(function (x) {
	return x * x;
});

// 箭头函数写法
[1,2,3].map(x => x * x);

rest 参数与箭头函数结合的例子

const numbers = (...nums) => nums;
console.log(numbers(1, 2, 3, 4, 5)); // [1,2,3,4,5]

const headAndTail = (head, ...tail) => [head, tail];
console.log(headAndTail(1, 2, 3, 4, 5)); // [1,[2,3,4,5]]

箭头函数使用注意点:

  • 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。因为箭头函数里面没有自己的this,而是引用外层的this
  • 不能作为构造函数。
  • 有内部属性arguments,不保存实参。
  • 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
    不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
  • 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
function foo() {
	setTimeout(() => {
    	console.log('id:', this.id);
  	}, 100);
}
var id = 21;
foo.call({ id: 42 }); // id: 42

上面代码中,setTimeout的参数是一个箭头函数,这个箭头函数的定义生效是在foo函数生成时,而它的真正执行要等到 100 毫秒后。如果是普通函数,执行时this应该指向全局对象window,这时应该输出21。但是,箭头函数导致this总是指向函数定义生效时所在的对象(本例是{id: 42}),所以输出的是42

//普通函数
function test(){
	console.log(this);
}
test(); //Object [global]

//箭头函数
let test2 = ()=>{
	console.log(this);
}
test2();  //{}

let obj = {
	test,
	test2
};
obj.test();  //{ test: [Function: test], test2: [Function: test2] }   
obj.test2();  //{}

//arguments
let test3 = function() {
    console.log(arguments);
}
test3(1, 2, 3, 4); //[Arguments] { '0': 1, '1': 2, '2': 3, '3': 4 }

let test4 = () => {
    console.log(arguments);
}
test4(1, 2, 3, 4); //[Arguments]

普通函数与箭头函数的混合使用

//例子一
function test() {
    console.log(this);
    //箭头函数  (IIFE)
    (() => {
        console.log(this);
    })();
}
test(); //两个都是 Object [global]

let obj = {
    test
};
obj.test(); //两个都是 { test: [Function: test] }

//例子二
function test1() {
    console.log(this, '----'); 
    return () => {
        console.log(this, '++++'); 
    }
}
test1(); //Object [global] ----

let obj1 = {
    say: test1(),
};
obj1.say(); //Object [global] ----   Object [global] ++++
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值