ES6 函数的拓展

来源:互联网 发布:linux 返回上级目录 编辑:程序博客网 时间:2024/05/06 02:50

1.函数参数的默认值

1.1 基本用法

ES5函数参数没有默认值,通常这样传默认值

function log(x,y) {    if(typeof y === 'undefined') {        y = 'world';    }    console.log(x,y);};log('hello');       // hello worldlog('hello',false); //hello falselog('hello','');    //hello

ES6引入了函数默认值,使函数在需要使用默认值的情况下代码更加简洁。

function log(x,y = 'world') {        console.log(x,y);}log('hello');       // hello worldlog('hello',false); //hello falselog('hello','');    //hello

这里需要注意两点

1)函数参数在函数体内不能用let 或者const再次声明导致重复声明变量

function foo(x = 5) {  let x = 1; // error  const x = 2; // error}

2)如果函数参数的传的默认值是一个表达式,表达式每次需要重新计算

let x = 99;function foo(p = x + 1) {  console.log(p);}foo() // 100x = 100;foo() // 101

另一个例子

function point(x = 0, y = 0) {    this.x = x;    this.y = y;}const p = new point(3,4);console.log(p);   //point {x: 3, y: 4}const q = new point();console.log(q);   //point {x: 0, y: 0}

ES6 引入函数默认值的好处在于:首先,阅读代码的人,可以立刻意识到哪些参数是可以省略的,不用查看函数体或文档;其次,有利于将来的代码优化,即使未来的版本在对外接口中,彻底拿掉这个参数,也不会导致以前的代码无法运行。

1.2 与解构赋值默认值结合使用
code one

function foo ({x, y = 3}) {        console.log(x,y);}foo();   //Cannot destructure property `x` of 'undefined' or 'null'.foo({}); //undefined 3foo({x:1}) //1 3foo({x:4,y:5}); //4 5

同样与也可以与数组解构赋值结合使用

code two

function foo ([x,y]=[1,3]) {    console.log(x,y);}foo([]);  //undefined undefinedfoo([3,4]); // 3 4

code one中上只使用了对象的解构赋值默认值,没有使用函数参数的默认值。只有当函数foo的参数是一个对象时,变量x和y才会通过解构赋值生成。如果函数foo调用时没提供参数,变量x和y就不会生成,从而报错。通过提供函数参数的默认值,如何提供函数的参数默认值?只需等于一个空对象即可。

function foo ({x, y = 3} = {}) {    console.log(x,y);}foo();  //undefined 3

下面是另一个解构赋值默认值的例子。

function fetch(url,{body = '', method = 'GET', headers = {} }) {    console.log(method);};fetch('http://www.baidu.com',{});  //GETfetch('http://www.baidu.com');     // Cannot destructure property `body` of 'undefined' or 'null'.

给第二个参数传入默认值,这样在给函数传值时可以省略第二个参数而不会报错。

function fetch(url,{body = '', method = 'GET', headers = {} } = {}) {    console.log(method);};fetch('http://www.baidu.com');  //GET

考虑下面两种函数参数的区别

//函数参数的默认值是空对象,但是设置了对象解构赋值的默认值;function fun1({x=0,y=0} = {}) {        console.log ([x,y]);}//函数参数的默认值是一个有具体属性的对象,但没有设置对象解构赋值的默认值function fun2 ({x,y} = {x: 0, y: 0}) {    console.log ([x,y]);}//函数没有传参数fun1();   //[0,0]fun2();   //[0,0]//函数传参数且x,y都有值fun1({x:1, y:3});  //[1, 3]fun2({x:1, y:3});  //[1, 3]//x 有值,y 无值的情况fun1({x:1}); //[1,0]fun2({x:1}); //[1,undefined]//函数有参数,但x,y都无值fun1({});  //[0,0]fun2({});  //[undefined, undefined]

1.2 有默认值参数的位置

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

```function f1(x=1,y) {    console.log([x,y]);}f1(2); //[2, undefined]function f2(y,x=1) {    console.log([[x,y]]);}f2(2); //[1,2]```

所以如果设置参数默认值,应该将参数默认值写在后面,没有默认值的参数写在前面。

1.3函数的length属性

指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。

function f1(x, y, z = 3) {    console.log(f1.length);};f1();  //2function f2(x=1, y=2, z = 3) {    console.log(f2.length);};f2();   //0//如果函数的参数有默认值且不是尾参数,那么含有默认值的参数后面的参数不计入lengthfunction f3(x=1, y=2, z) {    console.log(f3.length);};f3();  //0

1.4 作用域

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

var x = 1;function f(x,y = x) {    console.log(y);}f(2);  //2

上面代码中,参数y的默认值等于变量x。调用函数f时,参数形成一个单独的作用域。在这个作用域里面,默认值变量x指向第一个参数x,而不是全局变量x,所以输出是2。

let x = 1;function f(y=x) {    let x = 2;    console.log(y);}f();  //1f(3); //3, y值为3,默认值失效

上面代码中,函数f调用时,参数y = x形成一个单独的作用域。这个作用域里面,变量x本身没有定义,所以指向外层的全局变量x。函数调用时,函数体内部的局部变量x影响不到默认值变量x。

这里特别说明一点:在ES6中 let x = x;无论在声明变量,还是在函数给定默认参数都会报错。

var x = x;   //不报错,x为undefinedlet x = x;   //报错,x is not defined

再看

let x = 3;function f(x = x) {    console.log(x + 1);};f(); //  ReferenceError: x is not definedf(3); //4

所以在项目中尽量避免let x = x;这种写法。

函数的参数也可以是一个默认值,同样遵守作用域规则,这种情况在实际中很少用到,这里就不作讨论,想了解更多细节请参考ES6入门。

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

function throwIfMissing() {  throw new Error('Missing parameter');}function foo(mustBeProvided = throwIfMissing()) {  console.log(mustBeProvided+1);}foo();   // Missing parameterfoo(1);  //2

2.rest参数

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

function add(...values) {let sum = 0;    for(let item of values) {        sum += item ;    }    console.log(sum);}add(1,2,3);

上面代码的add函数是一个求和函数,利用 rest 参数,可以向该函数传入任意数目的参数。
注意,这里let item不能写成let var,ES6入门中是let var of values
会报错:Unexpected token var 。var是一个关键词,不能用作变量名。

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 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。

function f1(...items,a) {    return;}f1(); //Rest parameter must be last formal parameter

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

function f1(a,...items) {        return;    }function f2(a=3,...items) {    return;}console.log(f1.length); //1console.log(f2.length); //0

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

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

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

// 报错function doSomething(a, b = a) {  'use strict';  // code}// 报错const doSomething = function ({a, b}) {  'use strict';  // code};// 报错const doSomething = (...a) => {  'use strict';  // code};const obj = {  // 报错  doSomething({a, b}) {    'use strict';    // code  }};

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

1)设定全局性的严格模式

'use strict';function doSomething(a, b = a) {  // code}

2)函数包在一个无参数的立即执行函数里面

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

4.name属性
函数的name属性,返回该函数的函数名。

function foo() {}foo.name // "foo"

如果将一个匿名函数赋值给一个变量,ES5 的name属性,会返回空字符串,而 ES6 的name属性会返回实际的函数名。

var f = function () {};// ES5f.name // ""// ES6f.name // "f"

如果将一个具名函数赋值给一个变量,则 ES5 和 ES6 的name属性都返回这个具名函数原本的名字。

const bar = function baz() {};// ES5bar.name // "baz"// ES6bar.name // "baz"

Function构造函数返回的函数实例,name属性的值为anonymous。

function foo() {};foo.bind({}).name // "bound foo"(function(){}).bind({}).name // "bound "

5.箭头函数

5.1 基本用法
ES6 允许使用“箭头”(=>)定义函数。一个参数可以省略圆括号

var f = v => v;<=>var f = function(v) {    return v;}

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

//一个参数var f = () => 6;<=>var f = function() {    return 6;}console.log(f());  //6----------//多个参数,参数用圆括号包括起来var f = (num1,num2) => num1 + num2;<=>var f = function(num1, num2) {    return num1 + num2;}console.log

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

var f = (num1,num2) => {    let num3 = num1 + 1;    let num4 = num2 + 2;;    return [num3,num4];}console.log(f(1,2));  //[2,4]

如果箭头函数返回一个对象,需要在对象外面加上圆括号否则会报错。

var f = (x,y) => ({x:1,y:2});console.log(f());  //{x: 1, y: 2}

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

let fn = () => void doesNotReturn();

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

const full = ({first,last}) => first + ' ' + last;<=>function full(person) {  return person.first + ' ' + person.last;}full({first:'harry',last:'potter'}) //harry potter

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

const isEven = n => n % 2 == 0;const square = n => n * n;console.log(isEven(3));   //falseconsole.log(square(2));   //4

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

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

另一个例子是

// 正常函数写法var result = values.sort(function (a, b) {  return a - b;});// 箭头函数写法var result = values.sort((a, b) => a - b);

rest 参数与箭头函数结合

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

5.2 使用注意点
(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。

(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。

(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

上面四点中,第一点尤其值得注意。this对象的指向是可变的,但是在箭头函数中,它是固定的。
this在JS中是一个比较复杂的问题,我会在另一篇文章中介绍传统函数的this和箭头函数this的区别。

原创粉丝点击