函数的扩展

函数的默认值

在 ES6 之前,不能直接为函数指定默认值,只能采用变通的方法:

1
2
3
4
5
6
7
8
function log(x, y) {
if (y === "undefined") {
y = "world"
}
console.log(x, y)
}
log("hello") //hello test
log("hello", "") //hello

ES6 允许为函数的参数设置默认值:

1
2
3
4
5
6
7
8
9
10
11
12
function log(x, y = "world") {
console.log(x, y)
}
log("hello") //hello world
log("hello", "") //hello

eg: function Point(x = 0, y = 0) {
this.x = x
this.y = y
}
let p = new Point()
console.log(p.x + " " + p.y) //0 0

参数的默认值是 lazy evaluation 的,每次都重新计算默认值

1
2
3
4
5
6
7
let x = 1
function foo(f = x + 1) {
console.log(f)
}
foo() //2
x = 3
foo() //4

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

1
2
3
4
5
6
7
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 destructure property `x` of 'undefined' or 'null'

写法不同,默认值不同

1
2
3
4
5
6
7
8
9
10
11
12
13
function m1({ x = 0, y = 0 } = {}) {
//默认值是空对象
console.log(x, y)
}
function m2({ x, y } = { x: 0, y: 0 }) {
console.log(x, y)
}
m1() //0 0
m2() //0 0
m1({ x: 1, y: 2 }) //1 2
m2({ x: 1, y: 2 }) //1 2
m1({}) //0 0
m2({}) //undefined undefined

如果传入 undefined,将触发该参数等于默认值,null 没有效果

1
2
3
4
function foo(x = 1, y = 2) {
console.log(x, y)
}
foo(undefined, null) //1 null

利用默认参数可以指定某一个参数不得省略,省略就报错

1
2
3
4
5
6
7
function throwIfMissing() {
throw new Error("Missing parameter")
}
function foo(mustBeProvided = throwIfMissing()) {
return mustBeProvided
}
foo() //Error: Missing parameter

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

1
function foo(optional = undefined) {}

函数的 length 属性

length 不计从默认参数开始的参数个数。

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

rest 参数

ES6 引入了 rest 参数,用于获取函数的多余参数,这样就不用使用 arguments 对象了。

1
2
3
4
5
6
7
8
9
10
11
12
13
function add(...values) {
let sum = 0
for (let value of values) sum += value
return sum
}
add(1, 2, 3, 4) //10

//arguments变量的写法
function sortNumbers() {
return Array.prototype.slice.call(arguments).sort()
}
//rest参数的写法
const sortNumbers = (...numbers) => numbers.sort()

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

name 属性

函数的 name 属性返回该函数的函数名

1
2
3
4
5
6
7
8
9
10
11
12
13
function foo() {}
foo.name //'foo'

var f = function () {}

//ES5
f.name //''

//ES6
f.name //'f'

const bar = function baz() {}
bar.name //'baz'

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

1
new Function().name //'anonymous'

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

1
2
3
function foo() {}
foo.bind({}).name //'bound foo'
;(function () {}).bind({}).name //'bound '

箭头函数

1
2
3
4
5
var f = (v) => v
//等同于
var f = function (v) {
return v
}

如果箭头函数的代码块多于一条语句,就要使用大括号括起来。

1
2
3
var sum = (num1, num2) => {
return num1 + num2
}

由于大括号被解释为代码块,所以如果直接返回一个对象,必须在对象外加上括号。

1
var get = (id) => ({ id: id, name: "get" })

可以与变量解构结合使用

1
2
3
4
5
const full = ({ first, last }) => first + "," + last
//等同于
function full(person) {
return person.first + "," + person.last
}

注意事项

  • 函数体内的 this 对象就是定义时所在的对象,而不是使用时所在的对象。
  • 不可当做构造函数,不能使用 new 命令。
  • 不可使用 arguments 对象,该对象在函数体内不存在,可以使用 rest 参数代替。
  • 不可以使用 yield 命令,因此不能做 Generator 函数。
1
2
3
4
5
6
7
function foo() {
setTimeout(() => {
console.log("id:", this.id)
}, 1000)
}
let id = 21
foo.call({ id: 4 }) //id: 4

嵌套的箭头函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function insert(value) {
return {
into: function (array) {
return {
after: function (afterValue) {
array.splice(array.indexOf(afterValue) + 1, 0, value)
return array
},
}
},
}
}
insert(2).into([1, 3]).after(1) //[1, 2, 3]
//箭头函数写法
const insert = (value) => ({
into: (array) => ({
after: (afterValue) => {
array.splice(array.indexOf(afterValue) + 1, 0, value)
return array
},
}),
})

apply 的简易实现

Function.apply(thisArg: any, argArray?: any)第一个参数用于接受 this 对象,剩余参数为arguments
所以可以根据思路:

  • this挂载到thisArg.fn
  • 通过thisArg.fn传递参数,获取返回值
  • 删除thisArg.fn
  • 返回

在浏览器中,如果第一个参数为this,那么需要将thisArg设置为window对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Function.prototype.my_apply = function (context, array) {
context = Object(context) || window
context.fn = this

let result
if (!array) {
result = context.fn()
} else {
let args = []
for (let i = 0, len = array.length; i < len; i++) {
args.push(`array[${i}]`)
}
result = eval(`context.fn(${args})`) // context.fn(array[0], array[1])
}

delete context.fn
return result
}

call 的简易实现

apply的实现类似,区别在于参数的获取方式不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
Function.prototype.my_call = function (context) {
let args = []
context = Object(context) || window // if call(null) then point to window
context.fn = this // put current function into context.fn

for (let i = 1, len = arguments.length; i < len; i++) {
args.push(`arguments[${i}]`) // push the arguments into args
}
let result = eval(`context.fn(${args})`) // call function with parameters

delete context.fn
return result
}

bind 的简易实现

bind有个最大的特点,就是绑定后的函数也可以使用new操作符创建对象,这时bind中指定的this就会被忽略,转而指向被构造的对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let age = 2
let person = {
age: 12,
}

function foo(text) {
this.first = "Edward"
console.log(this.age)
console.log(text)
}
foo.prototype.last = "Snowden"

let bindFoo = foo.bind(person, "some text")

let obj = bindFoo()
// 12
// some text

let newObj = new bindFoo()
// undefined
// some text
console.log(newObj.first, newObj.last) // Edward Snowden

在上面这个例子中,不管是person中的age还是Global中的age,都没有被打印出来,因为此时的this已经指向了newObj。所以需要通过修改返回值来实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Function.prototype.my_bind = function (context) {
if (typeof this !== "function") {
throw new Error(`need to be function`)
}

let _this = this
let args = Array.prototype.slice.call(arguments, 1) // get the rest of arguments

let fTemp = function () {}

let fBound = function () {
let bindArgs = Array.prototype.slice.call(arguments) // get the arguments from the return function
return _this.apply(
this instanceof fTemp ? this : context,
args.concat(bindArgs)
) // check if it is be a constructor
}

fTemp.prototype = this.prototype
fBound.prototype = new fTemp()

return fBound
}