深入理解 ES6 之函数

带参数默认值的函数

通常在封装公共方法的时候,为了考虑方法的通用性,我们会给它配置对应的参数。为了方便调用,会配置一份初始的参数,当调用者不传该参数的时候,方法也可以正常使用。

下面以封装一个 ajax 方法为例,对比 ES5 和 ES6 对默认参数的使用方法。
ES5 中的模拟默认参数

function request(url, method, timeout, callback) {
  method = method || 'POST'
  timeout = timeout || 5000
  callback = callback || function () {}
  // 剩下的逻辑...
}

/**
 * 此时,如果 timeout 为 0,依然会走后面的逻辑即 5000
 */

// 更安全的版本
function request(url, method, timeout, callback) {
  method = typeof method !== 'undefined' ? method : 'POST'
  timeout = typeof timeout !== 'undefined' ? timeout : 5000
  callback = typeof callback !== 'undefined' ? callback : function () {}
  console.log(method, timeout, callback)
  // 剩下的逻辑...
}

ES6 中参数默认值

// 如果没有传参数,则使用参数默认值
function request(url, method='POST', timeout=5000, callback=function () {}) {
}

参数默认值如何影响 arguments 对象

在 ES5 中,我们可以通过 arguments 对象获取实参列表(类数组)

function getParams(one, two) {
  console.log(arguments)    // { '0': 1, '1': 2 }
  one = 3    // 非严格模式下通过修改形参标识符会映射到 arguments 对象上
  two = 4
  console.log(arguments)    // { '0': 3, '1': 4 }
}

// 严格模式下,则不会发生这样的情况,更加符合我们的期望
function getParams(one, two) {
  'use strict'
  console.log(arguments)    // { '0': 1, '1': 2 }
  one = 3    // 非严格模式下通过修改形参标识符会映射到 arguments 对象上
  two = 4
  console.log(arguments)    // { '0': 1, '1': 2 }
}
getParams(1, 2)

ES6在使用了默认参数值的情况下,基本和 ES5 严格模式下表现一致

使用不具名参数

JavaScript 中的函数调用,在实参数量大于形参,或者形参数量大于实参的时候,都可以正常运行。

如何获取不定项形参的实参

// 一个累加器,用于计算传入的所有形参之和
function calculateCount() {
  var count = 0
  for (var prop in arguments) {
    count += arguments[prop]
  }
  return count
}

// ES6 的剩余参数
function calculateCount(...numArr) {
  // numArr 是一个实参列表的数组(不同于 arguments 的类数组)
  return numArr.reduce((accumulator, currentValue) => accumulator + currentValue)
}

剩余参数的限制条件:

  • 函数只能有一个剩余参数,并且它必须被放在最后
  • 是剩余参数不能在对象字面量的 setter 属性中使用
let obj = {
  get name() {
    return 1
  },
  set name(...value) {
    obj.name = value
  }
}
obj.name = '123'    // SyntaxError: Setter function argument must not be a rest parameter
console.log(obj.name)

冷知识

设计剩余参数是为了替代 ES 中的arguments。原先在 ES4 中就移除了arguments并添加了剩余参数,以便允许向函数传入不限数量的参数。尽管 ES4 从未被实施,但这个想法被保持下来并在 ES6 中被重新引入,虽然arguments仍未在语言中被移除。

扩展运算符

与剩余参数关联最密切的就是扩展运算符。剩余参数允许你把多个独立的参数合并到一个数组中;而扩展运算符则允许将一个数组分割,并将各个项作为分离的参数传给函数。

基本使用

var a = [1, 2, 3, 4]
var b = [...a]    // [1, 2, 3, 4]
var c = {...a}    // {0: 1, 1: 2, 2: 3, 3: 4}

ES6 的 name 属性

定义函数有各种各样的方式,在 JS 中识别函数就变得很有挑战性。此外,匿名函数表达式的流行使得调试有点困难,经常导致堆栈跟踪难以被阅读与解释。正因为此, ES6 给所有函数添加了name属性。

function foo() {}
console.log(foo.name)  // 'foo'
const bar = function () {}
console.log(bar.name)  // 'bar'

函数的双重用途:new 调用 & 直接调用

如何保证构造函数只能通过 new 的方式调用

JS 为函数提供了两个不同的内部方法:[[Call]]与[[Construct]]。当函数未使用new进行调用时,[[call]]方法会被执行,运行的是代码中显示的函数体。而当函数使用new进行调用时,[[Construct]]方法则会被执行,负责创建一个被称为新目标的新的对象,并且使用该新目标作为this去执行函数体。拥有[[Construct]]方法的函数被称为构造器。

function Person(name) {
  console.log(this)
  if (this instanceof Person) {
    this.name = name
  } else {
    throw new Error('You must use new with Person.')
  }
}

const person = new Person('Jack')    // this => Person
const student = Person('Tom')    // this => window or undefined(严格模式下)

new.target 元属性

元属性指的是“非对象”(例如new)上的一个属性,并提供关联到它的目标的附加信息。当函数的[[Construct]]方法被调用时,new.target会被填入new运算符的作用目标,该目标通常是新创建的对象实例的构造器,并且会成为函数体内部的this值。而若[[Call]]被执行,new.target的值则会是undefined。

function Person(name) {
  console.log(new.target)
  if (typeof new.target !== 'undefined') {
    this.name = name
  } else {
    throw new Error('You must use new with Person.')
  }
}

new Person('java')    // new.target 为 Person
Person('java')    // new.target 为 undefined

块级函数

箭头函数

特征:

  • 没有this、super、arguments,也没有new.target绑定:this、super、arguments、以及函数内部的new.target的值由所在的、最靠近的非箭头函数来决定
  • 不能被使用new调用:箭头函数没有[[Construct]]方法,因此不能被用为构造函数,使用new调用箭头函数会抛出错误。
  • 没有原型:既然不能对箭头函数使用new,那么它也不需要原型,也就是没有prototype属性。
  • 不能更改this:this的值在函数内部不能被修改,在函数的整个生命周期内其值会保持不变。this的值由声明时的环境决定
var someHandler = {
  init: function () {
    console.log(this, 'init')
  },
  doSomething: () => {
    console.log(this, 'doSomething')
  }
}

someHandler.init()    // this => someHandler
someHandler.doSomething()    // this => window
  • 没有arguments对象:既然箭头函数没有arguments绑定,你必须依赖于具名参数或剩余参数来访问函数的参数。
  • 不允许重复的具名参数:箭头函数不允许拥有重复的具名参数,无论是否在严格模式下;而相对来说,传统函数只有在严格模式下才禁止这种重复。
var func = function (a, a) {}

var arrowFunc = (a, a) => {}    // SyntaxError: Duplicate parameter name not allowed in this context

基本形式

var foo = (val) => {
  // doSomething...
  return val
}
// 等效于
var foo = val => val
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。