函数属性和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.箭头函数不绑定argumentsvar 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 parametersfunction 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