函数增强

arguments转为Array

查看

JS中函数也是个对象,它也有自己的属性和方法,常见的属性有name和length。

  • name: 函数名。

  • length: 函数未使用参数个数(定义时的形参)。

    • 注意:rest参数不参与length的计算。

函数的EC(执行上下文)中关联的VO(variable object)其在堆内存中对应的AO(activation object)创建时会初始化arguments引用的类数组对象,但它只是个类数组对象,虽然可以迭代和通过索引访问参数值,但像数组中的filter等方法是不能使用的,所以,我们通常需要将其转化为数组。

方法一:for 循环遍历

1
2
3
4
5
6
7
function foo(arg1, arg2) {
var arr = []
for (var item of arguments) {
arr.push(item)
}
console.log(arr)
}

方法二:使用数组的slice方法

Array.prototype.slice(start, end)用于截取数组,内部通过this进行截取,所以我们可以改变this的指向,从而实现arguments向Array的转换。

使用原型方法
1
2
3
4
function foo(arg1, arg2) {
var arr = Array.prototype.slice.call(arguments)
console.log(arr)
}
使用实例对象处理
1
2
3
4
function foo(arg1, arg2) {
var arr = [].slice.call(arguments)
console.log(arr)
}

以上两种方式归根结底都是获取数组原型方法进行处理Array.prototype.slice(start, end)

方法三:使用Array.from()

ES6中新增了Array.from()方法,该方法可以将类数组对象转化为数组。

1
2
3
4
5
function foo(arg1, arg2) {
var arr = Array.from(arguments)
console.log(arr)
}

JS纯函数

查看

在函数式编程中有一个非常重要的概念叫纯函数,JS符合函数式编程的范式,所以JS存在纯函数的概念。

什么纯函数

  • 确定的输入,一定产生确定的输出。

    • 在函数调用中,加入输入的参数一致,返回的结果不是能改变的。
    • 即不能调用并改变外部变量,如在全局作用域下使用闭包的特性。
  • 函数在执行过程中,不能产生副作用。

    • 不能在调用的同时影响外部变量,以影响后续的使用,导致结果的不一致。

副作用:在计算机科学中,副作用表示在执行一个函数的时候,除了返回函数值外,还对调用函数产生了附加的影响,比如修改了全局变量,修改参数或者改变外部的存储。

如下,获取obj的name属性时,操作了obj,导致后续obj的使用出现了变化。

1
2
3
4
5
6
7
8
9
10
var obj = { name: '大梦想家' }

function foo(obj) {
obj.age = 18
return obj.name
}

foo(obj)

console.log(obj) // {name: '大梦想家', age: 18}

副作用往往是产生bug的温床。

纯函数的案例

  • 如数组的slice方法就是个纯函数,因为slice方法不会改变原数组,而是返回一个新的数组。

  • 而数组的splice方法则会改变原数组,所以splice方法不是纯函数。

纯函数的好处

  • 可以安心编写和使用,不必担心对外部的影响。

  • React中就要求我们无论是函数还是class声明一个组件,这个组件都必须像纯函数一样,保护它们的props不被修改。

函数柯里化

查看

什么是函数柯里化

  • 只传递一部分参数,返回一个函数,用返回的函数去处理剩余的参数,此过程就称为柯里化(Currying)。

  • 柯里化是一种对函数的转化,不会对函数进行调用。

  • 柯里化将一个可调用的函数f(a, b, c)转化为可调用的f(a)(b)©。

柯里化优势

  • 函数的职责单一

    • 在函数式编程中,我们其实往往希望一个函数处理的问题尽可能单一,而不是将一大堆的处理过程交给一个函数来处理。
    • 那么我们是否可以将每次传入的参数在单一的函数中处理,处理完后在下一个函数中再使用处理后的结果。
1
2
3
4
5
6
7
function add(x) {
x += 2;
return function (y) {
y *= 2;
return x + y;
}
}
  • 函数的参数复用

1
2
3
4
5
6
7
8
9
10
11
12
13
function createAdder(num){
return function(count) {
return num + count
}
}

var adder5 = createAdder(5)
adder5(10)
adder5(100)

var adder10 = createAdder(10)
adder10(10)
adder10(100)

自动柯里化函数

将普通函数转换为柯里化函数。

以下代码仅可在最后一次调用时改变this指向有效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function foo(args1, arg2, arg3) {
return args1 + arg2 + arg3
}

function gCurrying(fn) {
function currying(...args1) {
if (args1.length >= fn.length) {
return fn.apply(this, args1)
} else {
return function (...args2) {
return currying.apply(this, [...args1, ...args2])
}
}
}
return currying
}

var currying = gCurrying(foo)
console.log(currying(1)(2)(3))

组合函数

查看

组合函数的概念

我们需要对一个数据进行操作,需要执行两个以上的函数,而且这两个函数是依次执行的。如下代码:

1
2
3
4
5
6
7
8
9
function mul(num) {
return num * 2
}

function square(num) {
return num ** 2
}

console.log(square(mul(4))) // 64

实现组合函数

组合函数一般是最初的函数接受较多参数,然后将其返回值交予其他函数进行操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function compose(...fns) {
var length = fns.length
if (length === 0) return

fns.forEach((item, index) => {
if (typeof item !== 'function') {
throw new TypeError(`The ${index} arguments is not a function`)
}
})

return function (...args) {
var result = fns[0].apply(this, args)
var index = 0
while (++index < length) {
result = fns[index].call(this, result)
}
return result
}
}