likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 61

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
评论
请登录