这是一份面向前端初学者的函数使用指南
JS_函数
这是一份面向前端初学者的函数使用指南,在这里穷尽所能的介绍了函数的常用使用方法及相关知识点,阅读前建议具备js基础。文章有点长 坐稳准备发车了~
1. JS 函数的 this 指向
全局作用域下的 this 指向:
浏览器环境:指向 window 全局对象,即 GO(GlobalObject)
Node环境:指向一个空对象 {}
this 绑定规则
默认绑定
场景:独立函数调用
- 函数没有被绑定到某个对象,而是直接调用;
- this 指向 window
// 案例一
function foo() {
console.log(this)
}
foo() // window
// 案例二
function foo1() {
console.log(this)
}
function foo2() {
console.log(this);
foo1()
}
foo2() // 2次 window
// 案例三
var obj = {
name: "why",
foo: function() {
console.log(this)
}
}
var bar = obj.foo
bar() // window
// 案例四
function foo() {
console.log(this)
}
var obj = {
name: "why",
foo: foo
}
var bar = obj.foo
bar() // window
// 案例五
function foo() {
function bar() {
console.log(this)
}
return bar
}
var fn = foo()
fn() // window
隐式绑定
场景:通过某个对象发起函数调用;
- object.fn();
- Object 对象会被 js 引擎绑定到 fn 函数的 this;
- this 指向所调用函数的对象
限制条件:函数必须作为对象的方法来调用,即对象的某个属性上必须存在对函数的引用;
// 案例一
function foo() {
console.log(this)
}
var obj = {
name: "why",
foo: foo
}
obj.foo() // {name: 'why', foo: ƒ}
// 2.案例二:
var obj = {
name: "why",
eating: function() {
console.log(this.name + "在吃东西")
},
running: function() {
console.log(obj.name + "在跑步")
}
}
obj.eating() // why在吃东西
obj.running() // why在跑步
var fn = obj.eating
fn() // 在吃东西(此时 this 指向 window)
// 案例三
var obj1 = {
name: "obj1",
foo: function() {
console.log(this)
}
}
var obj2 = {
name: "obj2",
bar1: obj1.foo,
bar2: obj1
}
obj2.bar1() // {name: 'obj2', bar: ƒ}
obj2.bar2.foo() // {name: 'obj1', foo: ƒ}
显示绑定
在对象内部不包含对函数的引用,通过 call、apply、bind 方法强制指定 this 的绑定对象;
function foo(...args) {
console.log(this, args)
}
var obj = {
name: "obj"
}
// (1) fn.call(obj, param1, param2) 逐个传参
foo.call(obj, 1, 2) // {name: 'obj'} [1, 2]
// (2) fn.apply(obj, [param1, param2]) 数组传参
foo.apply(obj, [1, 2]) // {name: 'obj'} [1, 2]
foo.apply("aaa", [1, 2]) // String {'aaa'} [1, 2]
// (3) fn.bind(obj, param1, param2)() 逐个传参;返回一个 this 指向为 obj 的新函数
var newFoo = foo.bind("aaa", 1, 2)
console.log(newFoo === foo) // false
newFoo(3) // String {'aaa'} [1, 2, 3]
new 绑定
对函数使用 new 关键字,执行构造函数
function Person(name, age) {
// 当前作用域执行时 this 指向的对象不会有具体的内容;
// 使用 new 关键字将 当前作用域代码块 当作构造函数执行时 才能确定具体内容;
console.log(this) // Person {}
this.name = name
}
var p1 = new Person("why")
console.log(p1, p1.name) // Person {name: 'why'} 'why'
var p2 = new Person("kobe")
console.log(p2, p2.name) // Person {name: 'kobe'} 'kobe'
几个特殊场景
// (1) 定时器:this 指向 window
setTimeout(function() {
console.log(this) // window
}, 500)
// (2) forEach:若传了第二个参数,this 指向该参数;若没有传则指向 window
let arr = [1, 2]
arr.forEach(function(item) {
console.log(this) // 两次 window
})
arr.forEach(function(item) {
console.log(this) // 两次 String {'aaa'}
}, 'aaa')
// (3) 事件监听:this 指向监听对象
let box = document.querySelector('.box')
box.onclick = function() {
console.log(this === box) // true
}
this 绑定规则的优先级
new 绑定 > bind显示绑定 > 隐式绑定(obj.fn()) > 默认绑定(独立函数调用)
默认绑定的优先级最低
显示绑定 > 隐式绑定
new 绑定 > 隐式绑定
new 绑定 > 显示绑定(bind)
- 注意:new 绑定不允许和 call、apply 同时使用
var obj = {
name: "obj",
foo: function() {
console.log(this)
}
}
// 隐式绑定 > 默认绑定
obj.foo() // {name: 'obj', foo: ƒ}
// 显示绑定 > 隐式绑定
obj.foo.apply('111') // String {'111'}
obj.foo.call('222') // String {'222'}
var bar1 = obj.foo.bind("333")
bar1() // String {'333'}
// new 绑定 > 隐式绑定
var f = new obj.foo() // foo {}
// new 绑定 > 显示绑定(bind);new 绑定不允许和 call、apply 同时使用
var bar2 = obj.foo.bind("aaa")
var obj = new bar2() // foo {}
this 规则之外
忽略显示绑定
显示绑定传入一个 null 或 undefined 时,显示绑定会被忽略;此时会使用默认绑定,this 指向 window;
function foo() {
console.log(this)
}
foo.apply("abc") // String {'abc'}
foo.apply(null) // window
foo.apply(undefined) // window
var bar = foo.bind(null)
bar() // window
间接函数引用
创建一个函数的间接引用(默认绑定)
(obj2.foo = obj1.foo) 返回的结果 foo 函数;
foo函数被直接调用,那么是默认绑定,指向 window;
争论:该代码书写方式不规范
var obj1 = {
name: "obj1",
foo: function() {
console.log(this)
}
}
var obj2 = {
name: "obj2"
}
obj1.foo() // {name: 'obj1', foo: ƒ}
(obj2.bar = obj1.foo)() // window
箭头函数
什么是箭头函数
() => {}:箭头函数是 ES6 之后增加的一种编写函数的方法,它比函数表达式要更加简洁;
-
简写
- 优化一:如果只有一个参数 () 可以省略;
- 优化二:若函数执行体只有一行代码,可以省略大括号,并且该行代码的返回值会作为整个函数的返回值
- 优化三:如果函数执行体返回一个对象,那么需要给对象加上()
let fn1 = item => {} let fn2 = item => console.log('aaa') let fn3 = item => ({ name: 'aaa' })
-
注意点:
- 箭头函数不会绑定 this、arguments 属性;
- 箭头函数不能作为构造函数来使用(不能和new一起来使用,会抛出错误)
箭头函数的 this 指向
-
箭头函数不使用 this 的四种标准规则(不绑定this),this 指向箭头函数的外层作用域 ;
-
如果存在多层箭头函数的嵌套,所有箭头函数的 this 都指向最外层箭头函数的外层作用域;
- 区别:不同层级的箭头函数查找次数不同;
- 显示绑定对箭头函数无效
let arr = [1, 2]
arr.forEach(() => {
console.log(this) // 两次 window
}, 'aaa')
var foo = () => {
console.log(this)
}
foo.call("abc") // window
var obj = {
bar1: foo,
bar2: function () {
console.log('bar2', this) // bar2 {bar1: ƒ, bar2: ƒ}
let outer = () => {
console.log('outer', this) // outer {bar1: ƒ, bar2: ƒ}
let inner = () => {
console.log('inner', this) // inner {bar1: ƒ, bar2: ƒ}
}
inner()
}
outer()
}
}
obj.bar1() // window
obj.bar2() // ↑
this 指向面试题
题一
var name = "window"
var person = {
name: "person",
sayName: function () {
console.log(this.name)
}
}
function sayName() {
var sss = person.sayName
sss() // window:独立函数调用
person.sayName(); // person:隐式调用
// 使用这种写法,前后代码必须用 ; 隔开
(person.sayName)(); // person:隐式调用
(b = person.sayName)() // window:赋值表达式(独立函数调用)
}
sayName()
题二
var name = 'window'
// 此处的 person1 是一个对象,有自己的作用域但没有 this
var person1 = {
name: 'person1',
foo1: function () {
console.log(this.name)
},
foo2: () => console.log(this.name),
foo3: function () {
return function () {
console.log(this.name)
}
},
foo4: function () {
return () => {
console.log(this.name)
}
}
}
var person2 = { name: 'person2' }
person1.foo1() // person1(隐式绑定)
person1.foo1.call(person2) // person2(显示绑定优先级大于隐式绑定)
person1.foo2() // window(不绑定作用域,上层作用域是全局)
person1.foo2.call(person2) // window(显示绑定对箭头函数无效)
// 普通函数有自己的 this,优先使用自己的 this(默认绑定 window)
person1.foo3()() // window(独立函数调用)
person1.foo3.call(person2)() // window(独立函数调用)
person1.foo3().call(person2) // person2(最终调用返回函数式, 使用的是显示绑定)
// 箭头函数没有自己的 this,直接使用外层作用域的 this
person1.foo4()() // person1(箭头函数不绑定this, 上层作用域this是person1)
person1.foo4.call(person2)() // person2(上层作用域被显示的绑定了一个person2)
person1.foo4().call(person2) // person1(上层找到person1)
题三
var name = 'window'
// 此处的 Person 是一个函数,有自己的 this 和作用域
function Person(name) {
this.name = name
this.foo1 = function () {
console.log(this.name)
},
this.foo2 = () => console.log(this.name),
this.foo3 = function () {
return function () {
console.log(this.name)
}
},
this.foo4 = function () {
return () => {
console.log(this.name)
}
}
}
var person1 = new Person('person1')
var person2 = new Person('person2')
person1.foo1() // person1
person1.foo1.call(person2) // person2
person1.foo2() // person1
person1.foo2.call(person2) // person1
person1.foo3()() // window
person1.foo3.call(person2)() // window
person1.foo3().call(person2) // person2
person1.foo4()() // person1
person1.foo4.call(person2)() // person2
person1.foo4().call(person2) // person1
题四
var name = 'window'
function Person (name) {
this.name = name
this.obj = {
name: 'obj',
foo1: function () {
return function () {
console.log(this.name)
}
},
foo2: function () {
return () => {
console.log(this.name)
}
}
}
}
var person1 = new Person('person1')
var person2 = new Person('person2')
person1.obj.foo1()() // window
person1.obj.foo1.call(person2)() // window
person1.obj.foo1().call(person2) // person2
person1.obj.foo2()() // obj(foo2由obj隐式绑定)
person1.obj.foo2.call(person2)() // person2
person1.obj.foo2().call(person2) // obj
2. 深入 call、apply、bind
call
Function.prototype.myCall = function(thisArg, ...args) {
// 传入 undefined | null 时,忽略显示绑定,否则指向转换的对象
// 如果第一个参数是基本类型,通过 Object 转换为对象类型,便于将函数当作对象的方法调用
thisArg = (thisArg !== null && thisArg !== undefined) ? Object(thisArg): window
// 获取要执行的函数:函数调用了 call,this 隐式绑定了函数对象,即 this = 要执行的函数
var fn = this
// 判断调用对象
if(typeof fn !== "function") {
console.error("type error")
return
}
// 将函数作为第一个参数(对象)的方法调用,使函数的 this 指向传入的第一个参数(对象)
thisArg.fn = fn
var result = thisArg.fn(...args)
// 删除调用方法时第一个参数对象绑定的函数属性,使对象状态初始化
delete thisArg.fn
// 返回函数执行结果
return result
}
function sum(...args) {
console.log(this, args)
return args[0] + args[1]
}
sum.myCall(undefined) // window []
var result = sum.myCall("abc", 1, 2) // String {'abc', fn: ƒ} [1, 2]
console.log(result) // 3
sum.myCall(0) // Number {0, fn: ƒ} []
apply
Function.prototype.myApply = function(thisArg, argArray) {
thisArg = (thisArg !== null && thisArg !== undefined) ? Object(thisArg): window
var fn = this
if(typeof fn !== "function") {
console.error("type error")
}
thisArg.fn = fn
// 判断是否传参
argArray = argArray || []
// 执行函数
var result = thisArg.fn(...argArray)
delete thisArg.fn
return result
}
function sum(...args) {
console.log(this, args)
return args[0] + args[1]
}
sum.myApply(undefined) // window []
var result = sum.myApply("abc", [1, 2]) // String {'abc', fn: ƒ} [1, 2]
console.log(result) // 3
sum.myApply(0) // Number {0, fn: ƒ} []
bind
Function.prototype.myBind = function(thisArg, ...argArray) {
thisArg = (thisArg !== null && thisArg !== undefined) ? Object(thisArg): window
var fn = this
if(typeof fn !== "function") {
console.error("type error")
}
// 定义函数并返回
function proxyFn(...args) {
thisArg.fn = fn
// 对两次传入的参数进行合并
var finalArgs = [...argArray, ...args]
// 执行函数
var result = thisArg.fn(...finalArgs)
delete thisArg.fn
return result
}
return proxyFn
}
function sum(...args) {
console.log(this, args)
return args[0] + args[1]
}
var bar1 = sum.myBind(undefined)
bar1() // window []
var bar2 = sum.myBind("abc", 1)
var result = bar2(2) // String {'abc', fn: ƒ} [1, 2]
console.log(result) // 3
var bar3 = sum.myBind(0)
bar3() // Number {0, fn: ƒ} []
3. arguments & 剩余参数
arguments
arguments 是一个用于接收普通函数参数的 类数组(array-like)对象;
自动内置无需声名,只能在普通函数中使用;
-
什么是 类数组(array-like)
-
类数组不是一个数组,而是一个具有 index 索引和 length 属性的对象;通过索引可以访问对象内容;
-
类数组拥有数组的一些特性:index 索引、length 属性;但是并没有数组的一些方法:forEach、map;
-
深层思考:数组是一个具有 symbol.iterator 属性的可迭代对象;而类数组不具有 symbol.iterator 属性,是一个不可迭代对象,所以类数组无法使用一些数组方法;
function foo() { // Arguments(3) [1, 2, 3, callee: ƒ, length: 3, Symbol(Symbol.iterator): ƒ] console.log(arguments) // (1) 获取参数长度 console.log(arguments.length) // 3 // (2) 根据索引值获取某一个参数 console.log(arguments[1]) // 2 // (3) 根据 callee 获取当前 arguments 所在的函数 console.log(arguments.callee) // 将当前函数以字符串的形式打印 } foo(1, 2, 3)
-
-
如何将一个 类数组 转换为 数组
function foo() { // (1) 遍历然后存到一个空数组中 var newArr1 = [] for (var i = 0; i < arguments.length; i++) { newArr1.push(arguments[i]) } console.log('newArr1', newArr1) // (2) Array.prototype.slice var newArr2 = Array.prototype.slice.call(arguments) var newArr3 = [].slice.call(arguments) console.log('newArr2', newArr2) console.log('newArr3', newArr3) // (3) ES6:Array.from & 展开运算符 var newArr4 = Array.from(arguments) var newArr5 = [...arguments] console.log('newArr4', newArr4) console.log('newArr5', newArr5) } foo(1, 2, 3)
-
补充:Array.prototype.slice 的内部实现
- 传参:slice(m, n) 根据索引截取 m ~ n-1 的数据,以数组形式返回;
- 不传参:默认截取全部数据,以数组形式返回;
Array.prototype.mySlice = function(start, end) { var arr = this start = start || 0 end = end || arr.length var newArray = [] for (var i = start; i < end; i++) { newArray.push(arr[i]) } return newArray } var newArray1 = Array.prototype.mySlice.call(["aaa", "bbb", "ccc"]) var newArray2 = Array.prototype.mySlice.call(["aaa", "bbb", "ccc"], 1, 3) console.log(newArray1) // ['aaa', 'bbb', 'ccc'] console.log(newArray2) // ['bbb', 'ccc'] var names = ["aaa", "bbb", "ccc"] console.log(names.slice()) // ['aaa', 'bbb', 'ccc'] console.log(names.slice(1, 3)) // ['bbb', 'ccc']
剩余参数
箭头函数是不绑定 arguments 的,使用 arguments 只能去上层作用域查找;
同时,当函数参数个数不确定时,ES6 利用展开运算符的特性提供了剩余参数的写法;
需要声名接收,在普通函数和箭头函数中都能使用;
const bar = () => {
console.log(arguments) // arguments is not defined
}
bar()
const foo = function(...args) {
// Arguments(3) [1, 2, 3, callee: (...), Symbol(Symbol.iterator): ƒ]
console.log(arguments)
console.log(args) // [1, 2, 3]
const bar = (...args) => {
// Arguments(3) [1, 2, 3, callee: (...), Symbol(Symbol.iterator): ƒ]
console.log(arguments)
console.log(args) // ['a', 'b', 'c']
}
bar('a', 'b', 'c')
}
foo(1, 2, 3)
4. 惰性函数
表示函数的惰性载入,函数的逻辑分支只会在第一次调用时执行;
在第一次调用过程中,函数被覆盖为另一个按照合适方式执行的函数;
再次调用函数就不再重复执行分支,而是直接使用之前的结果;
优势:避免重复判断,优化函数性能;
场景:适用于函数调用频繁、外部状态固定的应用环境;
- 比如浏览器兼容时对 API 的初次判断,兼容环境的 API 可能会在不同位置多次使用;
// 原函数
function addEvent(type, element, fun) {
if (element.addEventListener) {
element.addEventListener(type, fun, false);
} else if(element.attachEvent){
element.attachEvent('on' + type, fun);
} else {
element['on' + type] = fun;
}
}
// 惰性载入
function addEvent(type, element, fun) {
if (element.addEventListener) {
addEvent = function (type, element, fun) {
element.addEventListener(type, fun, false);
}
} else if(element.attachEvent){
addEvent = function (type, element, fun) {
element.attachEvent('on' + type, fun);
}
} else {
addEvent = function (type, element, fun) {
element['on' + type] = fun;
}
}
return addEvent(type, element, fun);
}
5. 纯函数
纯函数需要同时满足以下两点:
确定的输入,一定产生确定的输出;
函数在执行过程中,不能产生副作用;
-
什么是副作用:
- 在计算机科学中,副作用表示在一个函数执行时,除了返回函数值以外,还对函数外部的状态产生了附加影响,比如修改了全局变量,修改参数或者改变外部存储;
- 纯函数在执行的过程中不能产生副作用,而副作用也往往是产生 bug 的 “温床”;
-
纯函数案例:slice & splice
/** * slice 函数是一个纯函数 * > 只要传入确定的 start/end 参数, 对于同一个数组就会返回确定的值 * > slice 函数本身并不会修改调用 slice 的数组 */ var eArr1 = ["a", "b", "c", "d", 'e'] var resArr1 = eArr1.slice(0, 3) console.log(resArr1) // ["a", "b", "c"] console.log(eArr1) // ["a", "b", "c", "d", 'e'] /** * splice 不是一个纯函数 * > splice 在执行时会修改调用 splice 函数的数组, 产生副作用 */ var eArr2 = ["a", "b", "c", "d", 'e'] var resArr2 = eArr2.splice(2) console.log(resArr2) // ['c', 'd', 'e'] console.log(eArr2) // ['a', 'b']
-
开发者使用纯函数的优势
- 在纯函数中,不需要关心传入的内容如何获得,也无需关注依赖的其他外部变量是否发生更改;
- 确定的输入,一定会有确定的输出;使开发者可以将重心迁移到对业务逻辑的实现;而不必关注调用一个纯函数时其内部的执行;
6. 高阶函数
高阶函数:接受一个或多个函数作为参数;或返回值是一个函数;
常见高阶函数
1. filter
过滤 返回符合条件的项
const nums = [1, 2, 3, 4, 5]
let newNums = nums.filter(function(item) {
return item % 2 === 0
})
console.log(newNums) // [ 2, 4 ]
2. map
映射 批量处理并返回
const nums = [1, 2, 3, 4, 5]
let newNums2 = nums.map(function(item) {
return item * 10
})
console.log(newNums2) // [ 10, 20, 30, 40, 50 ]
3. forEach
迭代 批量处理不返回
const nums = [1, 2, 3, 4, 5]
nums.forEach(function(item) {
console.log(item) // 依次打印 1 2 3 4 5
})
4. find & findIndex
查找符合条件的一项并返回
let friends = [
{name: 'james', age: 35},
{name: "why", age: 18}
]
// find
let findFriend = friends.find(function(item) {
return item.name === 'james'
})
console.log(findFriend) // {name: 'james', age: 35} 未找到就返回 undefined
// findIndex
var friendIndex = friends.findIndex(function(item) {
return item.name === 'why'
})
console.log(friendIndex) // 1 未找到就返回 -1
5. reduce
累加
const nums = [1, 2, 3, 4, 5]
var total = nums.reduce(function(count, item) {
return count + item
}, 0) // 如果未指定初始值,就将第一个数值作为初始值 count,第二个数值作为第一个 item
console.log(total) // 15
AOP 面向切面编程
场景:当我们需要在一个公共函数执行前后添加自己的逻辑;通常为了避免污染公共逻辑而影响复用,不能直接修改公共函数;此时可以通过 AOP 的方法利用高阶函数和原型链的特点进行处理;
过程:封装公共逻辑,将非公共逻辑抽离出来,通过动态植入的方法,与公共逻辑一同使用;
优势:保证公共逻辑模块的纯净和高内聚,方便复用;
Function.prototype.mixins = function(...callback) {
return (...args) => {
const before = typeof callback[0] == 'function' ? callback[0] : undefined
const after = typeof callback[0] == 'function'? callback[1] : undefined
// 前置执行
if(before) before()
this(...args)
// 后置执行
if(after) after()
}
}
function foo(...args) {
console.log('公共逻辑', args)
}
function before(){
console.log('前置非公共逻辑');
}
function after(){
console.log('后置非公共逻辑');
}
let whoSay = foo.mixins(before, after)
whoSay('why') // 前置非公共逻辑 --》 公共逻辑 ['why'] --》 后置非公共逻辑
组合函数
接收多个函数作为参数,前一个函数的返回结果 作为后一个函数的参数,依次调用;
最简单的组合函数
function composeFn(m, n) {
return function(count) {
return n(m(count))
}
}
function double(num) {
return num * 2
}
function square(num) {
return num ** 2
}
var result = square(double(2))
console.log(result) // 16
var newFn = composeFn(double, square)
console.log(newFn(2)) // 16
实现通用组合函数
function hyCompose(...fns) {
var length = fns.length
// 判断参数类型
for (var i = 0; i < length; i++) {
if (typeof fns[i] !== 'function') {
throw new TypeError("Expected arguments are functions")
}
}
function compose(...args) {
var index = 0
// 执行第一个函数并返回结果
var result = length ? fns[index].apply(this, args): args
// 从第二个函数开始逐个执行;比较时 ++index 先自增1再比较
while(++index < length) {
// 前一个函数的返回结果 作为 后一个函数的参数 继续返回结果
result = fns[index].call(this, result)
}
return result
}
return compose
}
function double(m) {
return m * 2
}
function square(n) {
return n ** 2
}
var newFn = hyCompose(double, square)
console.log(newFn(2)) // 16
++前置 & 后置++
// ++前置:先自增 1 再执行当前代码
// 第一次:++index1 === 1 --》 1 < 2 --》 打印 ++i 1
// 第二次:++index1 === 2 --》 2 < 2 --》 false
let index1 = 0
while(++index1 < 2) {
console.log('++i', index1) // ++i 1
}
// 后置++:先执行当前代码再自增 1
// 第一次:index2++ === 0 --》 0 < 2 --》 打印 i++ 1
// 第二次:index2++ === 1 --》 1 < 2 --》 打印 i++ 2
// 第二次:index2++ === 2 --》 2 < 2 --》 false
let index2 = 0
while(index2++ < 2) {
console.log('i++', index2) // i++ 1 --> i++ 2
}
偏函数
固定了函数的一个或多个参数,返回一个新的函数来接收剩下的参数,简化函数调用;
bind 函数就是一个偏函数的典型代表,从第二个参数开始,作为添加到绑定函数的参数;
function sum(a, b, c) {
return a + b + c
}
function partial(sum, a) {
return function (b, c) {
return sum(a, b, c)
}
}
let partialSum = partial(sum, 1)
console.log(partialSum(2, 3)) // 6
7. 柯里化函数
与偏函数不同,柯里化是把接收多个参数的函数转换成多个只接收一个参数的函数;
传递给函数一部分参数来调用它,让它返回一个函数去处理剩余的参数;
这个过程就称之为柯里化;
柯里化结构
// 原始函数
function add(x, y, z) {
return x + y + z
}
var result = add(1, 2, 3)
console.log(result) // 6
// 柯里化
function sum1(x) {
return function(y) {
return function(z) {
return x + y + z
}
}
}
console.log(sum1(1)(2)(3)) // 6
// 柯里化简写
var sum3 = x => y => z => x + y + z
console.log(sum3(1)(2)(3)) // 6
为什么要柯里化
-
让函数的职责单一,便于对混乱复杂的逻辑进行分层处理;
- 在函数式编程中,我们往往希望一个函数处理的问题尽可能的单一,而不是将一大堆的处理过程交给一个函数来处理;
- 每次传入的参数在单一函数中进行处理后,还可以结果交给下一个函数使用;
-
复用参数逻辑:
function add(x, y, z) { x = x + 2 y = y * 2 z = z * z return x + y + z } console.log(add(1, 2, 3)) // 16 function sum(x) { x = x + 2 return function(y) { y = y * 2 return function(z) { z = z * z return x + y + z } } } const foo = sum(1)(2) console.log(foo(3)) // 3 + 4 + 9 = 16 console.log(foo(5)) // 3 + 4 + 25 = 32 复用
柯里化函数延迟执行
函数执行后不立即返回结果,而是调用返回值的一个方法来获取最终结果;
- 支持以任意方式传递任意个数的参数;
- 根据开发者需要,随时调用获取返回值方法,得到最终结果;
function add(...args) {
let inner = function () {
args.push(...arguments);
return inner
}
inner.toRes = function () {
return args.reduce((prev, cur) => {
return prev + cur
})
}
return inner
}
console.log(add(1, 2, 3).toRes()) // 6
console.log(add(1, 2)(3).toRes()) // 6
console.log(add(1)(2)(3).toRes()) // 6
console.log(add(1)(2).toRes()) // 3
console.log(add(1)(2)(3)(4).toRes()) // 10
函数柯里化
传入一个普通函数,返回一个柯里化函数;
- 返回的柯里化函数支持以任意方式传递任意个数的参数;
- 参数个数达到原函数需要的参数个数,立即返回结果;否则继续返回一个柯里化函数;
function myCurrying(fn) {
function curried(...args) {
// 已传入参数个数 >= 函数需要的参数个数, 就执行函数
if (args.length >= fn.length) {
return fn.apply(this, args)
} else {
// 已传入参数个数 < 函数需要的参数个数,就返回一个新的函数, 继续接收剩余的参数
function curried2(...args2) {
// 继续接收参数, 递归调用 curried 检查 已传入参数个数 是否达到 函数需要的参数个数
return curried.apply(this, [...args, ...args2])
}
return curried2
}
}
return curried
}
function add(x, y, z) {
console.log(this)
return x + y + z
}
var curryAdd = myCurrying(add)
console.log(curryAdd(1, 2, 3)) // window 6
console.log(curryAdd(1, 2)(3)) // window 6
console.log(curryAdd(1)(2)(3)) // window 6
下一篇:js_原型 ing...
转载自:https://juejin.cn/post/7272749016756486196