ECMAScript6之函数的扩展

ECMAScript6之函数的扩展

1. 函数参数的默认值

ES6之前为函数参数设置默认值,需要使用变通的方法才能实现。

function add(x, y) {
	x = x || 1;
	y = y || 2;
	return x+y; 
}

// 使用参数默认值参与运算
add(); // 3
add(2); // 4

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

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

add(); // 3

给函数参数设置默认值时,函数不能有同名参数。

// 给函数参数设置默认值时,不能包含同名参数
function add (x = 1, x = 3, y = 1) {} // SyntaxError: Duplicate parameter name not allowed in this context
function add (x, x, y = 2) {} // SyntaxError: Duplicate parameter name not allowed in this context

函数参数默认值可以是表达式,也可以调用对象属性。但是参数默认值是惰性求值的,只有使用的时候才会求值。

let person = {
	name: 'jidi',
	age: 22,
	sex: 1
}
let x = 12;

function printStr(y = x + 1, z = person) {
	console.log(y + z.age);
}

// 函数参数默认值时惰性求值的
printStr(); // 35
printStr(1); // 23

1.1 函数参数默认值与解构赋值结合使用

函数参数可以进行解构赋值,解构赋值默认值与函数参数默认值可以结合使用。

// 单独使用解构赋值默认值
function printStr ({x, y = 5}) {
	console.info(x, y);
}

// 不提供参数,参数x和y不会通过解构赋值产生,从而报错
printStr(); // TypeError: Cannot destructure property 'x' of 'undefined' as it is undefined.

printStr({x:1}); // 1 5
printStr({}); // undefined 5
printStr({X:1, y:2}); // 1 2

在上面代码中,只使用了对象的解构赋值默认值,只有当函数printStr 的参数是一个对象时,变量xy才会通过解构赋值生成。如果函数printStr调用时没提供参数,变量xy就不会生成,从而报错。

// 解构赋值默认值与函数参数默认值结合使用
function printStr({x = 1 , y = 2 } = {}) {
	console.info(x, y);
}

// 不添加参数,正常使用
printStr(); // 1 2

上面代码中,给函数参数设置了默认值,函数调用时不添加参数,会调用默认值进行解构赋值。

下面是一个更明显的例子。

// 解构赋值设置默认值,函数参数默认值为{}
function m1 ({x = 0, y = 0} = {}) {
	return [x, y];
}
// 解构赋值没有设置默认值,函数参数设置默认值为一个对象
function m2 ({ x, y }  = { x:1, y: 2 }) {
	return [x, y];
}

// 参数都为空
m1(); // [0, 0]
m2(); // [1, 2]

// x 和 y 都有值的情况
m1({x: 2, y: 4}) // [2, 4]
m2({x: 2, y: 4}) // [2, 4]

// x 有值,y 无值的情况
m1({x: 8}) // [8, 0]
m2({x: 3}) // [3, undefined]

// x 和 y 都无值的情况
m1({}) // [0, 0];
m2({}) // [undefined, undefined]

在上面例子中,m1设置了对象解构赋值默认值,函数参数默认值为空对象,不管函数调用时是否设置了参数,解构赋值都会发生,且会根据实际情况取到值,如果解构失败会使用解构赋值默认值;m2只设置了函数参数默认值,没有设置对象解构赋值默认值,当函数调用时,如果函数有参数,则会根据实际情况进行解构,由于解构没有设置默认值,解构不成功,变量值会变为undefined

1.2 函数参数默认值的位置

一般来说定义了默认值的参数是函数的尾参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的,除非用undefined占位。

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

f(); // [1, undefined]
f(2); // [2, undefined])

// 省略设置了默认值的参数,报错
f(, 1); // Uncaught SyntaxError: Unexpected token ','

// 要能正确调用,必须显示的用undefined占位
f(undefined, 1) // [1, 1]

1.3 函数的 length 属性

函数参数指定了默认值后,函数的length属性,将返回没有指定默认值的参数个数。这是因为length属性的含义是,该函数预期传入的参数个数。

// 设置默认值后,预期传入的参数个数会与实际参数个数不一致
(function (x) {}).length; // 1
(function (x = 5) {}).length; // 0
(function (x, y, z = 5) {}).length; // 2

1.4 作用域

一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域。等到初始化结束,这个作用域就会消失。

var x = 1;

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

test(2); // 2

上面代码,函数test调用时,参数形成一个单独的作用域,在此作用域里面,默认值变量x指向第一个参数x,并不会指向全局变量x,所以输出结果为2

var x = 1;

function test(y = x) {
  let x = 2;
  console.log(y);
}

test(); // 1

上面代码,函数test调用时,参数形成一个单独的作用域,在此作用域里面,默认值变量x没有被定义,此时指向全局变量x,而函数内部的局部变量x不会影响默认值变量x,所以输出结果为1

2. rest 参数

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

function add(...values) {
  let total= 0;

  for (let value of values) {
    total+= value;
  }
  return total;
}

add(1, 2, 3); // 6

rest 参数是一个真正的数组,数组特有的方法都可以使用。

function printStr(...items) {
  items.forEach((item) => {
    console.log(item);
  });
}

printStr(1, 2, 3); // 1 2 3

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

function f(x, ...y, z) { } // Uncaught SyntaxError: Rest parameter must be last formal parameter

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

(function(...x) {}).length;  // 0

3. 严格模式

从 ES5 开始,函数内部就可以设定严格模式。但是ES2016 做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。

// 函数参数使用默认值,内部再使用严格模式会报错 Uncaught SyntaxError: Illegal 'use strict' directive in function with non-simple parameter list
function test(x, y = x) {
  'use strict';
}

// 函数参数使用默认值,内部再使用严格模式会报错
function test ({x, y}) {
  'use strict';
};

// 函数参数使用扩展运算符,内部再使用严格模式会报错
function test (...a) => {
  'use strict';
};

4. 箭头函数

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

var f = function(x) {
	return x;
} 

// 上面例子用箭头函数可以改写成下面样子
var f = x => x;

如果函数没有参数或者有多个参数,需要使用圆括号。

// 没有参数 
var f = () => "jidi";
// 等价于
var f = function() {
	return "jidi";
}

// 有多个参数
var f = (x, y) => x + y;
// 等价于
var f = function (x, y) {
	return x + y;
}

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

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

如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。

// 返回的如果是对象,需要在对象外面加括号,否则报错。
let getPerson = id => { id: id, name: "jidi" }; // Uncaught SyntaxError: Unexpected token ':'

// 加括号,不报错
let getPerson = id => ({ id: id, name: "jidi" });

getPerson(1); // {id: 1, name: "jidi"}

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

// 使用解构赋值的箭头函数
const getStr= ({ name = "jidi", age = 22 } = {}) => name+ ' ' + age;

// 等同于
function getStr({ name = "jidi", age = 22 } = {}) {
  return name+ ' ' + age;
}

getStr(); // "jidi 22"
getStr({name: "java", age: 20}); // "java 20"

箭头函数可以与rest 参数结合使用。

const numbers = (...number) => number;
// rest参数是数组,结果返回数组
numbers(1, 2, 3, 4, 5); // [1,2,3,4,5]

// 使用箭头函数和rest参数计算n个数之和
const add = (...items) => {
	let total = 0;
	items.forEach(item => total += item)
	return total;
}

add(1, 2, 3); // 6

4.1 箭头函数使用注意点

箭头函数虽然使用起来更加方便,但是也有一些需要值的注意的地方。

  • 函数体内的this对象,就是函数定义时所在的对象,而不是使用时所在对象。
  • 不可以当做构造函数使用,即不能使用new命令。
  • 不可以使用arguments对象,因为在箭头函数内部,该对象是不存在的,可以用 rest参数进行替代。
  • 不可以使用yield命令,则箭头函数不能用作 Generator 函数。

this对象的指向是可变的,但是在箭头函数中,它是固定的。

var id = 21;

// 箭头函数形式
function f1() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}
f1(); // id: 21
f1.call({ id: 42 }); // id: 42

// 普通函数形式
function f2() {
  setTimeout(function() {
    console.log('id:', this.id);
  }, 100);
}
f2();  // id: 21
f2.call({ id: 42 }); // id: 21

上面代码中,setTimeout的参数是一个箭头函数,这个箭头函数的定义生效是在f1函数生成时,箭头函数导致this总是指向函数定义生效时所在的对象,所以输出的是42。而普通函数,执行时this指向全局对象,这时应该输出21。

this指向的固定,实际是箭头函数没有自己的this,导致内部的this就是外层代码块的this。所以上面的例子中的代码如果转换成ES5代码,就是下面形式。

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

// ES5
function f1() {
  var _this = this;
  setTimeout(() => {
    console.log('id:', _this.id);
  }, 100);
}

除了thisargumentssupernew.target三个变量在箭头函数之中也是不存在的,也指向外层函数的对应变量。

function f() {
  setTimeout(() => {
    console.log('arguments:', arguments);
  }, 100);
}

f(2, 4, 6, 8); // arguments: [2, 4, 6, 8]

上面的例子中,箭头函数内部的srguments变量指向的是外层函数f的变量arguments

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

4.2 箭头函数不适用场合

箭头函数能够固定this,所有有两个地方不能用箭头函数。

  • 定义对象的方法,且对象方法内部包含this
  • 需要动态使用this的时候,也不能使用箭头函数。

5. 函数参数的尾逗号

ES2017 允许函数的最后一个参数有尾逗号。

// ES2017允许函数参数后面跟逗号
function add(x, y,) {
	return x + y;
}

6. Function.prototype.toString()

ES2019 对函数实例的toString()方法做出了修改。之前toString()方法会返回省略了注释和空格的函数代码,修改后的toStirng()方法,规定返回一模一样的代码。

function add(...items) {
    // 求和函数
    let total = 0;
    items.forEach(item => total += item);
    return total;
} 

add.toString();	// "function add(...items) {
				//	   // 求和函数
				//	   let total = 0;
				//	   items.forEach(item => total += item);
				//	   return total;
				//	}"

7. catch 命令的参数省略

try...catch结构,以前要求catch命令后面必须跟参数,接受try代码块抛出的错误对象。ES2019 做出了改变,允许catch语句省略参数。

8. 参考链接

本篇博文是我自己学习笔记,原文请参考:ECMAScript 6 入门
如有问题,请及时指出!
欢迎沟通交流,邮箱:[email protected]

发布了15 篇原创文章 · 获赞 3 · 访问量 3622

猜你喜欢

转载自blog.csdn.net/qq_41863849/article/details/104517904