JavaScript < ES5、ES6、ES7、… >Web前端之路让前端飞

ES6学习笔记——函数的拓展

2017-09-07  本文已影响44人  island_0d48

函数的拓展

函数默认值

到了ES6,函数终于有默认值了,在ES5中其实是可以用一些变通的方法来完成参数默认的,但是有一些缺点,比如说如果参数赋值了,但是对应的布尔值为false,则该赋值不起作用。ES6就解决了这个问题。

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

log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello World 出现问题

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

log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello 没毛病

参数变量是默认声明的,所以不能用let或者const再次声明

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

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

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

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

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

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

foo() // 100

x = 100;
foo() // 101

每次调用都重新计算

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

参数默认值可以和解构赋值的默认值,结合起来使用。
更骚气的是如果参数时一个对象,对象里面做了默认值,比如{x, y=5},这个时候如果啥也不传就会报错,这个时候可以通过提供函数参数的默认值,避免报错。

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

foo() // undefined 5

function fetch(url, { body = '', method = 'GET', headers = {} }) {
  console.log(method);
}

fetch('http://example.com', {})
// "GET"

fetch('http://example.com')

function fetch(url, { method = 'GET' } = {}) {
  console.log(method);
}

fetch('http://example.com')
// "GET"

出现了双重默认值。

参数默认值的位置

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

// 例一
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]

上述情况都不能省略参数,只能设置undefined来触发等于默认值。但是null没有这个效果。

函数的长度(length)

函数的属性length返回函数没有指定默认值的参数的个数,如果指定了默认参数,那么将不计入length,length永远返回没有默认参数的传入参数个数。

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

有趣的是这个length的计数是根据默认参数的位置来计算的,果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了。

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

作用域

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

var x = 1;

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

上面两种情况,第一种就是指向第一个x,外面全局的x不会影响y的赋值。但是第二种情况因为x本身没有定义,所以会指向外部全局的x,但是在内部x的操作,并不会影响到y的值。

应用

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

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

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

foo()
// Error: Missing parameter

rest参数

ES5中函数传参可以用arguments,在ES6加入了rest参数来使得传参更加地优雅,用法为...变量名。

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

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

  return sum;
}

add(2, 5, 3) // 10

注意,rest参数必须是最后一个参数,否则会报错

严格模式

ES5开始函数内部可以设定为严格模式,但是ES6引入默认值、解构赋值、扩展运算符之后,函数内部就不可以使用严格模式了,否则就会报错。

因为函数内部的严格模式,同样适用于函数体和函数参数,但是函数执行的时候,先执行函数参数,然后执行函数体,这就不合理了,只有从函数体之中,才能知道参数是否应该以严格模式执行,但是参数应该先初始化。

当然了,你可以吧“use strict”放在外面或者用一个立即执行的函数包住函数。

'use strict';

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

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

name属性

函数的name属性,直接返回函数的名字,有很多种情况,比如匿名函数还有bind的函数等,详见代码。

var f = function () {};

// ES5
f.name // ""

// ES6
f.name // "f"

const bar = function baz() {};

// ES5
bar.name // "baz"

// ES6
bar.name // "baz"

(new Function).name // "anonymous"

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

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

箭头函数

ES6的箭头表达式是非常有用的一个东西。哇,真的方便。

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

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" });

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

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

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

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

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

箭头函数使得表达更加简洁。
箭头函数的一个用处是简化回调函数。

// 正常函数写法
[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]]

注意点

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

绑定this

箭头函数可以绑定this对象,这就可以减少bind显式的调用。

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

如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上面。

由于双冒号运算符返回的还是原对象,因此可以采用链式写法。

foo::bar;
// 等同于
bar.bind(foo);

foo::bar(...arguments);
// 等同于
bar.apply(foo, arguments);

const hasOwnProperty = Object.prototype.hasOwnProperty;
function hasOwn(obj, key) {
  return obj::hasOwnProperty(key);
}

var method = obj::obj.foo;
// 等同于
var method = ::obj.foo;

let log = ::console.log;
// 等同于
var log = console.log.bind(console);

// 例一
import { map, takeWhile, forEach } from "iterlib";

getPlayers()
::map(x => x.character())
::takeWhile(x => x.strength > 100)
::forEach(x => console.log(x));

// 例二
let { find, html } = jake;

document.querySelectorAll("div.myClass")
::find("p")
::html("hahaha");

尾逗号

ES6支持参数的最后一个后面可以接一个逗号

function clownsEverywhere(
  param1,
  param2,
) { /* ... */ }

clownsEverywhere(
  'foo',
  'bar
上一篇下一篇

猜你喜欢

热点阅读