es6语法之函数的扩展总结 常用

版权声明:一只会飞的狼 https://blog.csdn.net/sinat_40697723/article/details/85927348

                                                             函数的扩展

1.函数参数的默认值;

2.rest参数;

3.严格模式;

4.name属性;

5.箭头函数;

6.双冒号运算符;

7.尾调用优化;


一.函数参数的默认值:

1.es6之前,由于不能直接为参数添加默认值,如果需要添加默认值的话代码量较多。

function log(x, y) {
  y = y || 'World';
  console.log(x, y);
}

log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello World

注意:上面最后一行代码,我们给y赋值'',但是还是使用默认值'world'。上面给参数赋默认值的缺陷是:当我们给y赋的值的布尔值是false时,也会使用默认值。为了避免这种问题,我们需要先判断一下。

if (typeof y === 'undefined') {
  y = 'World';
}

可以发现,es6之前给函数参数赋默认值的代码量过多。es6提供了直接给函数参数赋默认值的方法。

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

log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello

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

2.参数变量是默认声明的,所以不能用let和const再次声明。

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

3.使用参数默认值时,函数不能有同名参数。

// 不报错
function foo(x, x, y) {
  // ...
}

// 报错
function foo(x, x, y = 1) {
  // ...
}
// SyntaxError: Duplicate parameter name not allowed in this context

4.参数默认值不是传值的,而是每次都重新计算默认值表达式的值。也就是说,参数默认值是惰性求值的。

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

foo() // 100

x = 100;
foo() // 101

5.与解构赋值默认值结合使用。

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

上面最后一行代码报错的原因:上面的代码中使用了解构赋值的默认值,没有使用函数参数的默认值。只有当调用的函数的参数是一个对象的时候,x和y才会生成。最后一行没有传入参数,x和y就不会生成,所以会报错。

解决办法就是给函数参数也使用默认值:

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

foo() // undefined 5

通过下面的代码熟悉一下这节的知识点:

// 写法一
function m1({x = 0, y = 0} = {}) {
  return [x, y];
}

// 写法二
function m2({x, y} = { x: 0, y: 0 }) {
  return [x, y];
}
// 函数没有参数的情况
m1() // [0, 0]
m2() // [0, 0]

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

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

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

m1({z: 3}) // [0, 0]
m2({z: 3}) // [undefined, undefined]

6.在使用过程中,最好在函数的尾参数使用默认值,这样容易看出来到底省略了哪些参数。

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

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

从上面结果可以看出来,当我们在前面的参数使用默认值,调用函数传参比较麻烦,还得给有参数默认值的参数传入undefined。所以建议最好在函数的尾参数处使用默认值。

7.指定了参数默认值以后,函数的length属性的返回值等于函数的参数个数减去指定了默认值的参数个数。

(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2

8.同理,rest参数也不会计入length属性。

(function(...args) {}).length // 0

9.如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了。

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

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

var x = 1;

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

f(2) // 2

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

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

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

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

foo()
// Error: Missing parameter

12.可以将参数默认值设为undefined,表明这个参数是可以省略的。

function foo(optional = undefined) { ··· }

二.rest参数:

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

1.rest参数的用法示例:一个求和函数。

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

  for (var val of values) {
    sum += val;
  }

  return sum;
}

add(2, 5, 3) // 10

2.rest 参数代替arguments变量的例子:一个排序函数。

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

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

arguments不是数组,而是一个类数组的对象,

3.利用 rest 参数改写数组push方法的例子。

function push(array, ...items) {
  items.forEach(function(item) {
    array.push(item);
    console.log(item);
  });
}

var a = [];
push(a, 1, 2, 3)

4.rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。

// 报错
function f(a, ...b, c) {
  // ...
}

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

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

三.严格模式:

具体了解严格模式点击此链接:严格模式介绍

从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;
  };
}());

四.name属性:

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

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

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

var f = function () {};

// ES5
f.name // ""

// ES6
f.name // "f"

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

const bar = function baz() {};

// ES5
bar.name // "baz"

// ES6
bar.name // "baz"

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

(new Function).name // "anonymous"

5.bind返回的函数,name属性值会加上bound前缀。

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

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

五.箭头函数:

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

var f = v => v;

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

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

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

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

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

	  var total = (num1, num2) => {num1++; return num1 + num2;};
	  console.log(total(1,2));    //4

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

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

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

5.如果箭头函数只有一行语句,且不需要返回值。可以在前面加void。

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

6.箭头函数使用注意点:

  1. 函数体内的this对象,就是定义时所在的对象,不是使用时所在的对象。(最重要)
  2. 不可以当作构造函数,也就是说不能用new调用,否则会抛出一个错误。
  3. 不可以使用arguments对象,该对象在函数体内不存在,可以使用rest参数代替。

例1:

		//普通函数
    function foo() {
      setTimeout(function() {
        console.log(this.id);
      }, 100);
    }

    var id = 11;
    foo.call({id: 22});    //11

    //箭头函数
    function foos() {
      setTimeout(() => {
        console.log(this.ids)
      }, 100);
    }

    var ids = 1;
    foos.call({ids: 2});  //2

上面的箭头函数,定义生效是在foos.call()的时候,箭头函数导致this总是指向函数定义生效时所在的对象(本例是{id: 2}),所以输出的是2。对于普通函数,js高程规定超时调用在非严格模式下都是在全局环境调用,所以打印出11,在严格模式下应该打印出undefined。

例子2:

    function Timer() {
      this.s1 = 0;
      this.s2 = 0;
      //箭头函数
      setInterval(() => {
        this.s1 ++;
      }, 1000);
      //普通函数
      setInterval(function() {
        this.s2 ++;
      }, 1000);
    }

    var timer = new Timer();
    setTimeout(() => {
      console.log(timer.s1);
    }, 3100);    //3
    setTimeout(() => {
      console.log(timer.s2);
    }, 3100);    //0

上面代码中,Timer函数内部设置了两个定时器,分别使用了箭头函数和普通函数。前者的this绑定定义时所在的作用域(即Timer函数),后者的this指向运行时所在的作用域(即全局对象)。所以,3100 毫秒之后,timer.s1被更新了 3 次,而timer.s2一次都没更新。

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

例3:

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

    //ES5
    function foo() {
      var _this = this;
      setTimeout(function() {
        console.log(_this.id);
      }, 1000);
    }

上面代码中,转换后的 ES5 版本清楚地说明了,箭头函数里面根本没有自己的this,而是引用外层的this

除了this,以下三个变量在箭头函数之中也是不存在的,指向外层函数的对应变量:argumentssupernew.target。另外,由于箭头函数没有自己的this,所以当然也就不能用call()apply()bind()这些方法去改变this的指向。

7.不适用场合:

  1. 定义函数的方法 ,且该方法内部包括this。
    const cat = {
      id: '001',
      //普通函数
      jump: function() {
        console.log(this);
      },
      //箭头函数
      down: () => {
        console.log(this);
      }
    }
    cat.jump();  //{id: "001", jump: ƒ}
    cat.down();  //Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}

上面代码中,普通函数的this指向的是cat。但是箭头函数指向的是window这个全局对象,结果是达不到预期的效果。所以定义函数的方法 ,且该方法内部包括this。

      2.需要动态this的时候,也不要使用箭头函数

    const tg = document.getElementById('press');
    tg.addEventListener('click', () => {
      console.log(this);      //Window {postMessage: ƒ, blur: ƒ, …}
    });

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

另外,如果函数体很复杂,有许多行,或者函数内部有大量的读写操作,不单纯是为了计算值,这时也不应该使用箭头函数,而是要使用普通函数,这样可以提高代码可读性。


六.双冒号运算符:

箭头函数可以绑定this对象,大大减少了显式绑定this对象的写法(callapplybind)。但是,箭头函数并不适用于所有场合,所以现在有一个提案,提出了“函数绑定”(function bind)运算符,用来取代callapplybind调用。

函数绑定运算符是并排的两个冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边的函数上面。


三.尾调用优化:

尾调用(Tail Call)是函数式编程的一个重要概念,本身非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数。

function f(x){
  return g(x);
}

以下三种情况都不属于尾调用:

// 情况一
function f(x){
  let y = g(x);
  return y;
}

// 情况二
function f(x){
  return g(x) + 1;
}

// 情况三
function f(x){
  g(x);
}

猜你喜欢

转载自blog.csdn.net/sinat_40697723/article/details/85927348