ES6学习-函数的扩展

说明:本文参考阮一峰的ECMAScript 6 入门

概要

  1. 函数参数默认值(直接赋值、解构默认赋值);
  2. rest参数:(…参数名)将函数多余参数放进一个数组,可使用数组特有方法;
  3. 严格模式:只要函数参数使用了默认值、解构赋值、rest参数就不能在在函数内部显示指定严格模式;
  4. 箭头函数: => ,箭头函数没有自己的this,其内部this指向永远都是其定义时所在的对象。两种不适用场合:定义对象的方法,该方法内部包含this;需要使用动态this,例如事件监听的回调函数如果使用了箭头函数,则该箭头函数内部this会指向全局对象window,而不会指向监听对象。

1.函数参数的默认值

function add(x = 1, y = 2 ){
	return x + y
}
add() //3

与结构赋值默认值结合使用

function foo({x, y = 5}) {
  console.log(x, y);
}

foo({}) // undefined 5
foo({x: 1}) // 1 5
foo({x: 1, y: 2}) // 1 2
foo() // TypeError: Cannot read property 'x' of undefined

上面代码只使用了对象的解构赋值默认值,没有使用函数参数的默认值。只有当函数foo的参数是一个对象时,变量x和y才会通过解构赋值生成。如果函数foo调用时没提供参数,变量x和y就不会生成,从而报错。通过提供函数参数的默认值,就可以避免这种情况。

function foo({x, y = 5} = {}) {
  console.log(x, y);
}

foo() // undefined 5

2.rest参数

ES6引入了rest参数(形式为…变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest参数搭配的变量是一个数组,该变量将多余的参数放进数组中。

function add(...values) {
	let sum = 0
	for (val of values) {
		sum += val
	}
	return sum
}
add(2, 3, 5) //10

上面代码,是一个求和函数,利用rest参数,可以向函数传入任意数目的参数。

下面是一个rest参数代替arguments对象的一个例子。

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

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

上面代码的两种写法,很明显rest参数的写法更简洁些。
arguments对象,不是一个真正的数组,它是一个类似数组的对象,不具有数组特有的方法。因此需要使用call()方法将其转化为数组,再使用数组的方法。而rest参数是一个真正的数组,数组特有的方法都可以使用。

注意,rest参数只能是最后一个参数,否则会报错。

//报错 SyntaxError: Rest parameter must be last formal parameter
function f(a, ...b,  c) {
	//...
}

函数的length属性,不包括rest参数。

(function(a) {}).length  // 1
(function(...a) {}).length  // 0
(function(a, ...b) {}).length  // 1

3.严格模式

从ES5开始,函数内部可以设定为严格模式。

function foo (a, b) {
	'use strict'
	//code
}

ES6对此做了修改,只要函数参数使用了默认值、解构赋值或者扩展运算符,就不能显示指定严格模式,否则会报错。

//报错
function d(a = 1){
	'use strict'
}
//报错
function e({a, b}) {
	'use strict'
}
//报错
function e(...a) {
	'use strict'
}

这样规定的原因是,函数内部指定严格模式,同时适用于函数参数和函数体。但函数参数先于函数体执行,这就会导致函数参数不符合严格模式下时,只会在进入函数体内部,发现需要使用严格模式,才会报错。

两种方法可以规避这种限制。
第一种是设定全局的严格模式。

'use strict'

function doSomething(a, b = a) {
  // code
}

第二种是放在立即执行函数里。

const doSomething = (function () {
	'use strict'
	return function (value = 2) {
		return value
	}
}())

4.箭头函数

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

var f = v => v

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

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

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

var sum = (a, b) => a + b
//等同于
var sum  = function (a, b) {
	return a + b
}

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

var sum = (a, b) => { return a + b }

由于大括号被解释为代码块,当函数返回一个对象时,就需要使用圆括号将对象括起来,否则会报错。

//报错
let item = id => {id: id, name: "temp"}

//不报错
let item = id => ({id: id, name: "temp"})

如果箭头函数只有一条语句,且没有返回值,可以采用下面的写法,就不用写大括号了。

let fn = () => void doesNotReturn()

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

var sum = ({ x,  y }) => x + y

下面是rest参数和箭头函数结合使用。

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

使用注意点
箭头函数有几个使用注意点。

  1. 函数体内的this对象,不是使用时所在的对象,而是函数定义时所在的对象。
  2. 不可以当做构造函数,使用new命令会报错。
  3. 不可以使用arguments对象,该对象在箭头函数里不存在。如果要使用,可以用rest参数代替。
  4. 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

上面四点中,尤其第一点需要注意。this对象的指向是可变的,但是在箭头函数中,它的指向是固定的。

function foo() {
	setTimeout(() => {
		console.log(this.id)
	}, 100)
}
let id = 21

foo.call({id: 42})

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

this指向的固定化,并不是箭头函数内部有绑定this的机制,而是因为箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有自己的this,所以就不能当做构造函数使用。

所以,箭头函数转成 ES5 的代码如下。

// ES6
function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}

// ES5
function foo() {
  var _this = this;

  setTimeout(function () {
    console.log('id:', _this.id);
  }, 100);
}

另外由于箭头函数没有自己的this,所以也就不能使用call()、apply()、bind()这些方法改变this的指向。

(function() {
  return [
    (() => this.x).bind({ x: 'inner' })()
  ];
}).call({ x: 'outer' });
// ['outer']

上面代码中,箭头函数没有自己的this,所以bind方法无效,内部的this指向外部的this。

不适用场合
由于箭头函数的this指向是固定的,下面两种场合就不适合使用箭头函数。

第一个场合是定义对象的方法,且该方法内部包含this。

const cat = {
	lives: 9,
	jumps: () => {
		this.lives--
	}
}

上面代码中,cat.jumps()方法是一个箭头函数,这是错误的。调用cat.jumps()时,如果是普通函数,该方法内部的this指向cat;如果写成上面那样的箭头函数,使得this指向全局对象,因此不会得到预期结果。这是因为对象不构成单独的作用域,导致jumps箭头函数定义时的作用域就是全局作用域。

第二个场合是需要动态this的时候,也不应使用箭头函数。

var button = document.getElementById('press');
button.addEventListener('click', () => {
  this.classList.toggle('on');
});

上面代码运行时,点击按钮会报错,因为button的监听函数是一个箭头函数,导致里面的this就是全局对象。如果改成普通函数,this就会动态指向被点击的按钮对象。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值