函数是什么?

41.JS函数 - 图1

数学里的函数

  • 函数在数学中为两个非空集合间的一种对应关系
  • 定义域中的每项元素皆对应一项值域中的元素
    • 假设函数为f
    • 假设输入为x1,那么必有一个输出y1:f(x1)=y1
    • 假设输入为x2,那么对应输出可以是y1也可以不是
    • 不管你输入多少次x1,输出都是有
    • f()在数学里不合法,因为没有输入,除非另说明
  • 函数式编程
    1. - 如果你的函数符合数学含义的函数,那么就是函数式的

JS的函数与环境

函数的返回值由什么确定

  • 调用时输入的参数params
  • 定义时的环境env

举例

  1. let x = 'x'
  2. let a = '1'
  3. function f1(x){
  4. return x+a
  5. }
  6. {
  7. let a = '2'
  8. f1('x')//值为x1 这个a是定义时的a而不是执行时的a
  9. }
  1. let x = 'x'
  2. let a = '1'
  3. function f1(x){
  4. return x+a
  5. }
  6. a = '3'
  7. {
  8. let a = '2'
  9. f1('x')//值为x3 a在定义环境中是3
  10. }
  1. let x = 'x'
  2. let a = '1'
  3. function f1(x){
  4. return x+a
  5. }
  6. a = '3'
  7. {
  8. let a = '2'
  9. f1('x')//值为x3 执行在a变成4之前
  10. }
  11. a = '4'
  1. let x = 'x'
  2. let a = '1'
  3. function f1(c){
  4. c()
  5. }
  6. {
  7. let a = '2'
  8. function f2(){
  9. console.log(x + a)
  10. }
  11. f1(f2)//打印出x2 f2定义的环境中 a = '2',x会从外层找到'x'
  12. }

面试题

  1. for(var i = 0;i<6;i++){
  2. setTimeout(
  3. ()=>console.log(i)
  4. )
  5. }
  6. 打印出66,因为6个函数共用一个i
  7. for(let i = 0;i<6;i++){
  8. setTimeout(
  9. ()=>console.log(i)
  10. )
  11. }
  12. 打印出0 1 2 3 4 5 ,因为6个函数各自一个i
  13. for(var i = 0;i<6;i++){
  14. !function(j){
  15. setTimeout(
  16. ()=>console.log(i)
  17. )
  18. }(i)//通过立即执行函数也可以解决这个问题
  19. }

闭包

特点

  • 能让一个函数维持住一个变量
  • 但并不能维持这个变量的值
  • 尤其是变量的值会变化的时候

对象是穷人的闭包

  • 对象也可以来维持住一个闭包
  • 如果一门语言不支持闭包,可以用对象处理

闭包是穷人的对象

  • 如果一门语言不支持对象,你可以用闭包代理

    this的值

    声明一个函数
  1. const f1 = new Function( ‘x’,’y’,’return x + y’ )
  2. function f2(x,y){ return x + y }
  3. const f3 = function (x,y){ return x + y }
  4. const f4 = (x,y)=> x + y

其中,1 2 3 都是ES6之前的语法,支持this/arguments/new
而4是ES6的新语法,不支持this/arguments/new

箭头函数不支持this

const a = 233
const f1 = ()=>console.log(a) /233

console.log(this)
const f2 = ()=>console.log(this) //Window

  • 箭头函数把this当做外部的变量,仅此而已
  • 但非剪头函数有很多特殊处理
  • 箭头函数不支持this指的就是剪头函数对this与其他变量一视同仁,不会特殊对待

非箭头函数的this

this其实就是一个隐式的参数

显式this

  • fn.call(asThis,1,2)
  • fn.bind(asThis,1,2)()
  • obj.method.call(obj,'hi')

隐式this

  • fn(1,2) //fn.call(undefined,1,2)
  • obj.method('hi') //obj.method.call(obj,'hi')
  • array[0]('hi') //array[0].call(array,'hi')
  1. button.onclick = function(e){
  2. console.log(this)
  3. }
  4. //this是参数,不同情况不一样
  5. const vm = new Vue({
  6. date:{
  7. message:'hi'
  8. },
  9. methods:{
  10. sayHi(){
  11. console.log(this.message)
  12. //不确定 也是参数
  13. //正常使用是vm
  14. }
  15. }
  16. })

面试题

  1. let length = 10
  2. function fn(){console.log(this.length)}
  3. let obj = {
  4. length:5,
  5. method(fn){
  6. fn()
  7. arguments[0]()
  8. }
  9. }
  10. obj.method(fn,1)//输出什么?答案密码 123456

小总结

  • this是call的第一个参数
  • new重新设计了this
  • 剪头函数不接受this

递归与调用栈

递归阶乘

41.JS函数 - 图2

  1. const j = (n) => n === 1 ? 1 : n * j(n-1)

代入求值

41.JS函数 - 图3 先递进,后回归
image.png

斐波那契数列

每一项都是前两项的和

  1. const fib = (n) =>
  2. n === 0 ? 0 :
  3. n === 1 ? 1 :
  4. fib(n-1) + fib(n-2)

代入求值

41.JS函数 - 图5

降低压栈/计算次数

尾递归优化(迭代)

  1. f = (n) => f_inner(2,n,1,0)
  2. f_inner = (start,end,prev1,prev2)=>
  3. start === end ? prev1 + prev2 :
  4. f_inner(start + 1, end, prev1 + prev2, prev1)

代入求值

41.JS函数 - 图6

这就是尾递归,可以不用“回头”,不压栈从而达到优化效果
但是js中,没有尾递归优化,因为js还是会压栈去创造一些环境栈
在有尾递归优化的语言中就可以使用

所有的递归都可以改成循环

  1. f = (n) => {
  2. let array = [0,1]
  3. for(let i = 0; i <= n-2; i++){
  4. array[i+2] = array[i+1] + array[i]
  5. }
  6. return array[array.length-1]
  7. }

数组把每一次的计算结果记录在数组中,就不需要压栈了

记忆化函数与React优化

记忆化可以减少重复计算,大大降低压栈次数

  1. const memo = (fn) => {
  2. const memoed = function(key) {
  3. if (!(key in memoed.cache)) {
  4. memoed.cache[key] = fn.apply(this, arguments)
  5. // 注意 memo 只会以第一个参数作为记忆点,但是还是会用 arguments 来调用 fn
  6. }
  7. return memoed.cache[key]
  8. }
  9. memoed.cache = {}
  10. return memoed
  11. }
  12. const x2 = memo((x) => {
  13. console.log('执行了一次')
  14. return x * 2
  15. })
  16. // 第一次调用 x2(1)
  17. console.log(x2(1)) // 打印出执行了,并且返回2
  18. // 第二次调用 x2(1)
  19. console.log(x2(1)) // 不打印执行,并且返回上次的结果2
  20. // 第三次调用 x2(1)
  21. console.log(x2(1)) // 不打印执行,并且返回上次的结果2

Lodash的实现

  1. _.memoize = function(func, hasher) {
  2. var memoize = function(key) {
  3. var cache = memoize.cache;
  4. var address = '' + (hasher ? hasher.apply(this, arguments) : key)
  5. if (!has(cache, address))
  6. cache[address] = func.apply(this, arguments)
  7. return cache[address];
  8. };
  9. memoize.cache = {};
  10. return memoize;
  11. };

React.memo

点击查看【codepen】

React.useCallback

这个函数需要根据m的变化来进行更新,所以不会在没变化的时候去产生新的onClick函数
点击查看【codepen】

柯里化Currying

让所有函数都只接受一个函数,基于单一参数函数衍生出非常多理论知识,比如λ运算
用闭包(对象)来让函数支持两个参数

单参数函数接受两个参数

  • 用对象的形式

    1. const add = ({a,b})=> a + b
    2. add({a:1,b:2})
  • 柯里化

    1. const add = a => b => a + b
    2. add(1)(2)

    面试题

    如何把三参数函数add(1,2,3)变成curriedAdd(1)(2)(3)形式?

  1. const curriedArr =
  2. a =>
  3. b =>
  4. c =>
  5. add(a,b,c)

假设 addTwo接受两个参数 addThree接受三个参数 addFore接受四个参数 请写出一个currify函数,使得它们分别接受2,3,4次参数,比如 currify(addTwo)(1)(2) //3 currify(addThree)(1)(2)(3) //6 currify(addFore)(1)(2)(3)(4) //10 也就是说 currify 能将任意接受固定个参数的函数变成单一参数的函数

  1. currify = (fn, params = [])=>
  2. (...args) =>
  3. params.length+args.length === fn.length
  4. ? fn(...params, ...args)
  5. : currify(fn, [...params, ...args])
  6. addTwo = (a,b)=>a+b
  7. addThree = (a,b,c)=>a+b+c
  8. newAddTwo = currify(addTwo)
  9. newAddThree = currify(addThree)
  10. newAddTwo(1)(2)
  11. newAddThree(1)(2)(3)

高阶函数

JS内置的高阶函数

  • Function.prototype.bind
  • Function.prototype.apply
  • Function.prototype.call
  • Array.prototype.sort
  • Array.prototype.map
  • Array.prototype.filter
  • Array.prototype.reduce

image.png

函数的组合

  1. function doubleSay (str) {
  2. return str + ", " + str;
  3. }
  4. function capitalize (str) {
  5. return str[0].toUpperCase() + str.substring(1);
  6. }
  7. function exclaim (str) {
  8. return str + '!';
  9. }
  10. let result = exclaim(capitalize(doubleSay("hello")));
  11. result //=> "Hello, hello!"

使用pipe操作

  1. let result = "hello"
  2. |> doubleSay
  3. |> capitalize
  4. |> exclaim;
  5. result //=> "Hello, hello!"

React高阶组件

点击查看【codepen】