这是一份面向初学者的函数使用指南

JS_函数

这是一份面向前端初学者的函数使用指南,在这里穷尽所能的介绍了函数的常用使用方法及相关知识点,阅读前建议具备js基础。文章有点长 坐稳准备发车了~

1. JS 函数的 this 指向

全局作用域下的 this 指向:

  • 浏览器环境:指向 window 全局对象,即 GO(GlobalObject)

  • Node环境:指向一个空对象 {}

this 绑定规则

默认绑定

场景:独立函数调用

  • 函数没有被绑定到某个对象,而是直接调用;
  • this 指向 window
js 复制代码
// 案例一
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 指向所调用函数的对象

限制条件:函数必须作为对象的方法来调用,即对象的某个属性上必须存在对函数的引用;

js 复制代码
// 案例一
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 的绑定对象;

js 复制代码
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 关键字,执行构造函数

js 复制代码
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'

几个特殊场景

js 复制代码
// (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 绑定 > 显示绑定(?apply & ?call & bind) > 隐式绑定(obj.fn()) > 默认绑定(独立函数调用)

  • 默认绑定的优先级最低

  • 显示绑定 > 隐式绑定

  • new 绑定 > 隐式绑定

  • new 绑定 > 显示绑定(bind)

    • 注意:new 绑定不允许和 call、apply 同时使用
js 复制代码
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;

js 复制代码
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;

  • 争论:该代码书写方式不规范

js 复制代码
var obj1 = {
  	name: "obj1",
  	foo: function() {
    	console.log(this)
  	}
}
var obj2 = {  
    name: "obj2"	
}

obj1.foo()					// {name: 'obj1', foo: ƒ}
(obj2.bar = obj1.foo)()		// window

箭头函数

什么是箭头函数

() => {}:箭头函数是 ES6 之后增加的一种编写函数的方法,它比函数表达式要更加简洁;

  • 简写

    • 优化一:如果只有一个参数 () 可以省略;
    • 优化二:若函数执行体只有一行代码,可以省略大括号,并且该行代码的返回值会作为整个函数的返回值
    • 优化三:如果函数执行体返回一个对象,那么需要给对象加上()
    js 复制代码
    let fn1 = item => {}
    let fn2 = item => console.log('aaa')
    let fn3 = item => ({ name: 'aaa' })
  • 注意点:

    • 箭头函数不会绑定 this、arguments 属性;
    • 箭头函数不能作为构造函数来使用(不能和new一起来使用,会抛出错误)

箭头函数的 this 指向

  • 箭头函数不使用 this 的四种标准规则(不绑定this),this 指向箭头函数的外层作用域 ;

  • 如果存在多层箭头函数的嵌套,所有箭头函数的 this 都指向最外层箭头函数的外层作用域;

    • 区别:不同层级的箭头函数查找次数不同;
    • 显示绑定对箭头函数无效
js 复制代码
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 指向面试题

题一

js 复制代码
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()

题二

js 复制代码
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)

题三

js 复制代码
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

题四

js 复制代码
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

js 复制代码
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

js 复制代码
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

js 复制代码
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 属性,是一个不可迭代对象,所以类数组无法使用一些数组方法;

    js 复制代码
    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)
  • 如何将一个 类数组 转换为 数组

    js 复制代码
    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 的数据,以数组形式返回;
    • 不传参:默认截取全部数据,以数组形式返回;
    js 复制代码
    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 利用展开运算符的特性提供了剩余参数的写法;

需要声名接收,在普通函数和箭头函数中都能使用;

js 复制代码
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 可能会在不同位置多次使用;
js 复制代码
// 原函数
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

    js 复制代码
    /**
     * 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

过滤 返回符合条件的项

js 复制代码
const nums = [1, 2, 3, 4, 5]
let newNums = nums.filter(function(item) {
	return item % 2 === 0
})
console.log(newNums)	// [ 2, 4 ]

2. map

映射 批量处理并返回

js 复制代码
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

迭代 批量处理不返回

js 复制代码
const nums = [1, 2, 3, 4, 5]
nums.forEach(function(item) {
  	console.log(item)	// 依次打印 1 2 3 4 5
})

4. find & findIndex

查找符合条件的一项并返回

js 复制代码
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

累加

js 复制代码
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 的方法利用高阶函数和原型链的特点进行处理;

  • 过程:封装公共逻辑,将非公共逻辑抽离出来,通过动态植入的方法,与公共逻辑一同使用;

  • 优势:保证公共逻辑模块的纯净和高内聚,方便复用;

js 复制代码
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'] --》 后置非公共逻辑

组合函数

接收多个函数作为参数,前一个函数的返回结果 作为后一个函数的参数,依次调用;

最简单的组合函数

js 复制代码
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

实现通用组合函数

js 复制代码
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

++前置 & 后置++

js 复制代码
// ++前置:先自增 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 函数就是一个偏函数的典型代表,从第二个参数开始,作为添加到绑定函数的参数;

js 复制代码
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. 柯里化函数

与偏函数不同,柯里化是把接收多个参数的函数转换成多个只接收一个参数的函数;

  • 传递给函数一部分参数来调用它,让它返回一个函数去处理剩余的参数;

  • 这个过程就称之为柯里化;

柯里化结构

js 复制代码
// 原始函数
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

为什么要柯里化

  • 让函数的职责单一,便于对混乱复杂的逻辑进行分层处理;

    • 在函数式编程中,我们往往希望一个函数处理的问题尽可能的单一,而不是将一大堆的处理过程交给一个函数来处理;
    • 每次传入的参数在单一函数中进行处理后,还可以结果交给下一个函数使用;
  • 复用参数逻辑

    js 复制代码
    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	复用

柯里化函数延迟执行

函数执行后不立即返回结果,而是调用返回值的一个方法来获取最终结果;

  • 支持以任意方式传递任意个数的参数;
  • 根据开发者需要,随时调用获取返回值方法,得到最终结果;
js 复制代码
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

函数柯里化

传入一个普通函数,返回一个柯里化函数;

  • 返回的柯里化函数支持以任意方式传递任意个数的参数;
  • 参数个数达到原函数需要的参数个数,立即返回结果;否则继续返回一个柯里化函数;
js 复制代码
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...

相关推荐
OEC小胖胖5 小时前
告别 undefined is not a function:TypeScript 前端开发优势与实践指南
前端·javascript·typescript·web
行云&流水6 小时前
Vue3 Lifecycle Hooks
前端·javascript·vue.js
老虎06276 小时前
JavaWeb(苍穹外卖)--学习笔记04(前端:HTML,CSS,JavaScript)
前端·javascript·css·笔记·学习·html
三水气象台6 小时前
用户中心Vue3网页开发(1.0版)
javascript·css·vue.js·typescript·前端框架·html·anti-design-vue
烛阴6 小时前
Babel 完全上手指南:从零开始解锁现代 JavaScript 开发的超能力!
前端·javascript
CN-Dust7 小时前
[FMZ][JS]第一个回测程序--让时间轴跑起来
javascript
全宝8 小时前
🎨前端实现文字渐变的三种方式
前端·javascript·css
yanlele9 小时前
前端面试第 75 期 - 2025.07.06 更新前端面试问题总结(12道题)
前端·javascript·面试
妮妮喔妮9 小时前
【无标题】
开发语言·前端·javascript
fie88899 小时前
浅谈几种js设计模式
开发语言·javascript·设计模式