函数属性和arguments以及剩余参数
函数属性name与length
◼ 我们知道JavaScript中函数也是一个对象,那么对象中就可以有属性和方法。
◼ 属性name:一个函数的名词我们可以通过name来访问;
// 自定义属性
foo.message = "Hello Foo"
console.log(foo.message)
// 默认函数对象中已经有自己的属性
// 1.name属性
console.log(foo.name)
console.log(bar.name)
// 将两个函数放到数组中(了解)
var fns = [foo, bar]
for (var fn of fns) {
console.log(fn.name)
}
◼ 属性length:属性length用于返回函数参数的个数;
注意:rest参数是不参与参数的个数的;
// 2.length属性: 参数的个数
function test() {
}
test(111, 222, 333)
console.log(foo.length)
console.log(bar.length)
console.log(test.length)
// 额外补充
// function demo(...args) {
// }
// demo("abc", "cba", "nba")
函数的arguments
◼ arguments 是一个 对应于传递给函数的参数 的 类数组(array-like)对象。
◼ array-like意味着它不是一个数组类型,而是一个对象类型:
但是它却拥有数组的一些特性,比如说length,比如可以通过index索引来访问传递给函数的参数;
但是它却没有数组的一些方法,比如filter、map等;
function foo(m, n) {
// arguments 类似数组对象
console.log(arguments)
// 1.默认用法:
// 通过索引获取内容
// console.log(arguments[0])
// console.log(arguments[1])
// // 遍历
// for (var i = 0; i < arguments.length; i++) {
// console.log(arguments[i])
// }
// for (var arg of arguments) {
// console.log(arg)
// }
// 2.需求获取所有参数中的偶数
// 数组 filter
// for (var arg of arguments) {
// if (arg % 2 === 0) {
// console.log(arg)
// }
// }
// var evenNums = arguments.filter(item => item % 2 === 0)
// console.log(eventNums)
}
foo(10, 25, 32, 41)
arguments转成Array
◼ 在开发中,我们经常需要将arguments转成Array,以便使用数组的一些特性。
常见的转化方式如下
◼ 转化方式一:
遍历arguments,添加到一个新数组中;
◼ 转化方式二:较难理解(有点绕),了解即可
调用数组slice函数的call方法;
◼ 转化方式三:ES6中的两个方法
Array.from
[...arguments]
// 2.1.将arguments转成数组方式一:
// var newArguments = []
// for (var arg of arguments) {
// newArguments.push(arg)
// }
// console.log(newArguments)
// 2.2.将arguments转成数组方式三: ES6中方式
// var newArgs1 = Array.from(arguments)
// console.log(newArgs1)
// var newArgs2 = [...arguments]
// console.log(newArgs2)
// 2.3.将arguments转成数组方式二: 调用slice方法
var newArgs = [].slice.apply(arguments)
// var newArgs = Array.prototype.slice.apply(arguments)
console.log(newArgs)
箭头函数不绑定arguments
箭头函数是不绑定arguments的,所以我们在箭头函数中使用arguments会去上层作用域查找:
// 1.箭头函数不绑定arguments
var bar = () => {
console.log(arguments)
}
bar(11, 22, 33)//报错
// 2.函数的嵌套箭头函数
function foo() {
var bar = () => {
console.log(arguments)
}
bar()
}
foo(111, 222)//正常运行,因为foo有argument
函数的剩余参数
◼ ES6中引用了rest parameter,可以将不定数量的参数放入到一个数组中:
如果最后一个参数是 ... 为前缀的,那么它会将剩余的参数放到该参数中,并且作为一个数组;
也可以一个函数只有剩余参数
◼ 那么剩余参数和arguments有什么区别呢?
剩余参数只包含那些没有对应形参的实参,而 arguments 对象包含了传给函数的所有实参;
arguments对象不是一个真正的数组,而rest参数是一个真正的数组,可以进行数组的所有操作;
arguments是早期的ECMAScript中为了方便去获取所有的参数提供的一个数据结构,而rest参数是ES6中提供并且希望以此来替代arguments的;
◼ 剩余参数必须放到最后一个位置,否则会报错。
// 剩余参数: rest parameters
function foo(num1, num2, ...otherNums) {
// otherNums数组
console.log(otherNums)
}
foo(20, 30, 111, 222, 333)
// 默认一个函数只有剩余参数
function bar(...args) {
console.log(args)
}
bar("abc", 123, "cba", 321)
// 注意事项: 剩余参数需要写到其他的参数最后
纯函数的理解和应用
◼ 函数式编程中有一个非常重要的概念叫纯函数,JavaScript符合函数式编程的范式,所以也有纯函数的概念;
在react开发中纯函数是被多次提及的;
比如react中组件就被要求像是一个纯函数(为什么是像,因为还有class组件),redux中有一个reducer的概念,也是要求必须是一个纯函数;
所以掌握纯函数对于理解很多框架的设计是非常有帮助的;
◼ 纯函数的维基百科定义:
在程序设计中,若一个函数符合以下条件,那么这个函数被称为纯函数:
此函数在相同的输入值时,需产生相同的输出。
函数的输出和输入值以外的其他隐藏信息或状态无关,也和由I/O设备产生的外部输出无关。
该函数不能有语义上可观察的函数副作用,诸如“触发事件”,使输出设备输出,或更改输出值以外物件的内容等。
◼ 当然上面的定义会过于的晦涩,所以简单总结一下:
确定的输入,一定会产生确定的输出;(不能使用闭包,否则全局变量可能会影响值的输出)
函数在执行过程中,不能产生副作用;
◼ 那么这里又有一个概念,叫做副作用,什么又是副作用呢?
在计算机科学中,也引用了副作用的概念,表示在执行一个函数时,除了返回函数值之外,还对调用函数产生了附加的影响,比如修改了全局变量,修改参数或者改变外部的存储;
◼ 纯函数在执行的过程中就是不能产生这样的副作用:
副作用往往是产生bug的 “温床”。
function sum(num1, num2) {
return num1 + num2
}
// 不是一个纯函数
var address = "广州市"
function printInfo(info) {
console.log(info.name, info.age, info.message)
info.flag = "已经打印结束"
address = info.address
}
var obj = {
name: "why",
age: 18,
message: "哈哈哈哈"
}
printInfo(obj)
console.log(obj)
if (obj.flag) {
}
◼ 我们来看一个对数组操作的两个函数:
slice:slice截取数组时不会对原数组进行任何操作,而是生成一个新的数组;
splice:splice截取数组, 会返回一个新的数组, 也会对原数组进行修改;
◼ slice就是一个纯函数,不会修改数组本身,而splice函数不是一个纯函数;
纯函数的作用与优势
为什么纯函数在函数式编程中非常重要呢?
因为你可以安心的编写和安心的使用;
你在写的时候保证了函数的纯度,只是单纯实现自己的业务逻辑即可,不需要关心传入的内容是如何获得的或者依赖其他的
外部变量是否已经发生了修改;
你在用的时候,你确定你的输入内容不会被任意篡改,并且自己确定的输入,一定会有确定的输出;
◼ React中就要求我们无论是函数还是class声明一个组件,这个组件都必须像纯函数一样,保护它们的props不被修改:
柯里化的理解和应用
◼ 柯里化也是属于函数式编程里面一个非常重要的概念。
是一种关于函数的高阶技术;
它不仅被用于 JavaScript,还被用于其他编程语言;
◼ 我们先来看一下维基百科的解释:
在计算机科学中,柯里化(英语:Currying),又译为卡瑞化或加里化;
是把接收多个参数的函数,变成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数,而且返回结果的新函数的技术;
柯里化声称 “如果你固定某些参数,你将得到接受余下参数的一个函数”;
◼ 维基百科的结束非常的抽象,我们这里做一个总结:
只传递给函数一部分参数来调用它,让它返回一个函数去处理剩余的参数;
这个过程就称之为柯里化;
◼ 柯里化是一种函数的转换,将一个函数从可调用的 f(a, b, c) 转换为可调用的 f(a)(b)(c)。
柯里化不会调用函数。它只是对函数进行转换。
// 普通的函数
function foo1(x, y, z) {
console.log(x + y + z)
}
// foo1(10, 20, 30)
// foo1(20, 33, 55)
// 因为foo不是一个柯里化的函数, 所以目前是不能这样调用
// 柯里化函数
function foo2(x) {
return function(y) {
return function(z) {
console.log(x + y + z)
}
}
}
foo2(10)(20)(30)
foo2(20)//无输出值,因为只调用了最外层
// 另外一种写法: 箭头函数的写法
// function foo3(x) {
// return y => {
// return z => {
// console.log(x + y + z)
// }
// }
// }
// 另外一种写法: 箭头函数的简化写法
var foo3 = x => y => z => {
console.log(x + y + z)
}
foo3(10)(20)(30)
柯里化优势(没懂)
◼ 那么为什么需要有柯里化呢?
在函数式编程中,我们其实往往希望一个函数处理的问题尽可能的单一,而不是将一大堆的处理过程交给一个函数来处理;
那么我们是否就可以将每次传入的参数在单一的函数中进行处理,处理完后在下一个函数中再使用处理后的结果;
◼ 比如上面的案例我们进行一个修改:传入的函数需要分别被进行如下处理
第一个参数 + 2
第二个参数 * 2
第三个参数 ** 2
◼ 另外一个使用柯里化的场景是可以帮助我们可以复用参数逻辑:
makeAdder函数要求我们传入一个num(并且如果我们需要的话,可以在这里对num进行一些修改);
在之后使用返回的函数时,我们不需要再继续传入num了
柯里化案例练习
这里我们在演示一个案例,需求是打印一些日志:
日志包括时间、类型、信息;
// 案例一: 打印一些日志
// 信息一: 日志的时间
// 信息二: 日志的类型: info/debug/feature
// 信息三: 具体的信息
// 1.没有柯里化的时候做法
function logInfo(date, type, message) {
console.log(`时间:${date} 类型:${type} 内容:${message}`)
}
// // 打印日志
// logInfo("2022-06-01", "DEBUG", "修复界面搜索按钮点击的bug")
// // 又修复了一个bug
// logInfo("2022-06-01", "DEBUG", "修复了从服务器请求数据后展示的bug")
// logInfo("2022-06-01", "DEBUG", "修复了从服务器请求数据后展示的bug")
// logInfo("2022-06-01", "DEBUG", "修复了从服务器请求数据后展示的bug")
// logInfo("2022-06-01", "FEATURE", "增加了商品的过滤功能")
// 2.对函数进行柯里化: 柯里化函数的做法
// var logInfo = date => type => message => {
// console.log(`时间:${date} 类型:${type} 内容:${message}`)
// }
function logInfo(date) {
return function(type) {
return function(message) {
console.log(`时间:${date} 类型:${type} 内容:${message}`)
}
}
}
var logToday = logInfo("2022-06-01")
var logTodayDebug = logToday("DEBUG")
var logTodayFeature = logToday("FEATURE")
// 打印debug日志
logTodayDebug("修复了从服务器请求数据后展示的bug")
logTodayDebug("修复界面搜索按钮点击的bug")
logTodayDebug("修复界面搜索按钮点击的bug")
logTodayDebug("修复界面搜索按钮点击的bug")
logTodayDebug("修复界面搜索按钮点击的bug")
logTodayFeature("新建过滤功能")
logTodayFeature("新建搜索功能")
function sum(num1, num2) {
return num1 + num2
}
sum(5, 10)
sum(5, 15)
sum(5, 18)
// makeAdder函数就是对sum的柯里化
function makeAdder(count) {
function add(num) {
return count + num
}
return add
}
// 1.数字和5相加
var adder5 = makeAdder(5)
adder5(10)
adder5(15)
adder5(18)
// 2.数组和10相加
var adder10 = makeAdder(10)
adder10(10)
adder10(16)
adder10(19)
// adder5 = null
// adder10 = null