高级JS-函数的增强(纯函数-柯里化-组合函数-手写apply、call、bind)

56 阅读3分钟
  • 属性name: 一个函数的名词我们可以通过name来访问
function foo() {}
console.log(foo.name); // foo

var baz = function () {}
console.log(baz.name); // baz
  • 属性length: 属性length用于返回函数参数的个数
// 不会将剩余参数算在里面
function test(a,b,c) {}
console.log(test.length); // 3

arguments

arguments 是传递给函数参数的类数组对象,它不是一个数组类型,而是一个对象类型

  1. 它拥有数组的一些特性,比如说length,比如可以通过index索引来访问
  2. 但是它却没有数组的一些方法,比如filter、map等
function bar(a,b,) {
    console.log(arguments); // Arguments(4)[1, 2, 3, 4] 类数组对象

    // 可迭代对象
    for (var i of arguments) {
        console.log(i);
    }
}
bar(1,2,3,4)
  • arguments 转 Array
function bar(a,b,) {
    // 方式一:
    var newArg = []
    for (var i of arguments) {
       newArg.push(i)
    }

    // 方式二(es6):
    var newArg1 = Array.from(arguments)
    var newArg2 = [...arguments]

    // 方式三:
    var newArg3 = [].slice.apply(arguments)
}
bar(1,2,3,4)

箭头函数是不绑定arguments的,在箭头函数中使用arguments会去上层作用域查找

function bar(a,b,) {
   var foo = () => {
       console.log(arguments); // [122,2]
   }
   foo()
}
bar(122,2)
  • 函数的剩余(rest)参数

    剩余参数必须放到最后一个位置,否则会报错

function bar(a,b,...arg) {
    console.log(a, b); // 122 2
    console.log(arg); // [98, 67]
}
bar(122,2,98,67)
  • 剩余参数和arguments的区别
  1. arguments对象不是一个真正的数组,剩余参数是一个真正的数组,可以进行数组的所有操作
  2. arguments 对象包含了传给函数的所有实参,剩余参数只包含那些没有对应形参的实参

纯函数

  1. 确定的输入,一定会产生确定的输出

  2. 函数在执行过程中,不能产生副作用

    副作用:表示在执行一个函数时,除了返回函数值之外,还对调用函数产生了附加的影响,比如修改了全局变量,修改参数或者改变外部的存储

var names = ['www', 'qqq', 'wqq']

// 没有对原数组进行修改(纯函数)
console.log(names.slice(0, 2)); // ['www', 'qqq']

// 会对原数组进行修改(不纯函数)
console.log(names.splice(1, 2)); // ['qqq', 'wqq']

// 以来外层变量(不是纯函数)
var foo = 3
function add(num) {
    return foo + num
}
console.log(add(2)); // 5
foo=10
console.log(add(2)); // 12
  • 纯函数的作用和优势
  1. 编写的时候不需要关心传入的内容是如何获得的或者依赖外部变量是否已经发生了修改
  2. 用的时候确定输入内容不会被任意篡改,并且自己确定的输入,一定会有确定的输出

柯里化

只传递给函数一部分参数来调用它,让它返回一个函数去处理剩余参数,这个过程就称为柯里化

  • 柯里化的代码转换

手动转换

function foo1(x, y, z) {
    console.log(x + y + z);
}
foo1(10,20,30)

// 柯里化函数
function foo2(x) {
    return function (y) {
        return function (z) {
            console.log(x + y + z);
        }
    }
}
foo2(10)(20)(30)

// 箭头函数的写法
var foo3 = x => y => z => console.log(x + y + z)
foo3(10)(20)(30)

自动转换

function foo(x, y, z) {
    console.log(x + y + z);
}
function sum(n1, n2) {
    console.log(n1 + n2);
}

// 自动转换
function wqCurrying(fn) {
    function curryFn(...args) {
        if (args.length >= fn.length) {
            // 2. 直接执行fn的函数
          return fn(...args)
        } else {
            // 1. 继续返回一个函数,继续接收参数
            return function (...newArgs) {
                return curryFn(...args.concat(newArgs))
            }
        }
    }
    return curryFn
}

var fooCurry = wqCurrying(foo)
fooCurry(10)(20)(30)

var sumCurry = wqCurrying(sum)
sumCurry(10)(20)

组合函数

var num = 10

function double(num){
    return num * 2
}

function pow(num) {
    return num ** 2
}
console.log(pow(double(num))); // 400


// 通用的组合函数的封装:将这两个函数组合起来,自动依次调用
function composeFn(...fns) {
    var len = fns.length
    if(len <= 0 ) return
    // 1. 边界判断
    for (var i = 0; i < len; i++){
        var fn = fns[i]
        if(typeof fn !== "function") {
            throw new Error(`index position${i} must be function`)
        }
    }
    // 2. 返回的新函数
    return function (...args) {
        var result = fns[0].apply(this,args)
        for (var i = 1; i<len;i++){
            var fn = fns[i]
            result = fn.apply(this,[result])
        }
        return result
    }
}
var newFn = composeFn(double,pow,console.log)
newFn(10); // 400

严格模式

  1. 支持在js文件中开启严格模式;
  2. 也支持对某一个函数开启严格模式
<script>
    // js开启严格模式
    "use strict";

    // 函数开启严格模式
    function foo() {
        "use strict";
    }
</script>

手写apply-call-bind

手写封装apply、call

function foo(name, age) {
    console.log(this,name,age);
}

// 1. 封装到独立的函数中
function exeFn(thisArg,otherArgs,fn) {
/*
this:指向当前调用的函数对象
thisArg:传入的第一个参数
otherArgs: 其他的参数
*/

// 获取thisArg,并且确保是一个对象类型
thisArg = (thisArg === null || thisArg === undefined) ? window : Object(thisArg)

// thisArg.fn = this
Object.defineProperty(thisArg, 'fn', {
   configurable: true,
   value: fn
})
   thisArg.fn(...otherArgs)
   delete thisArg.fn
}

// 2. 封装到原型中
Function.prototype.wqExec = function (thisArg,otherArgs) {
    thisArg = (thisArg === null || thisArg === undefined) ? window : Object(thisArg)

    // thisArg.fn = this
    Object.defineProperty(thisArg, 'fn', {
        configurable: true,
        value: this
    })
    thisArg.fn(...otherArgs)
    delete thisArg.fn
}

// 实现 wqapply 方法
Function.prototype.wqapply = function(thisArg,otherArgs) {
    // 使用封装的独立函数
    exeFn(thisArg,otherArgs,this)
}
foo.wqapply({name: 'www'},['wqq',19])
foo.wqapply(123,['zzz',20])
foo.wqapply(null,['fff',12])

// 实现 wqcall 方法
Function.prototype.wqcall = function (thisArg,...otherArgs) {
    // 使用原型封装的方法
    this.wqExec(thisArg,otherArgs)
}
foo.wqcall({name: 'www'},'wqq',19)
foo.wqcall(123,'zzz',20)
foo.wqcall(null,'fff',12)

手写bind

function foo(name, age,num,address) {
    console.log(this, name, age,num,address);
}

Function.prototype.wqbind = function (thisArg,...otherArgs) {
    thisArg = (thisArg === null || thisArg === undefined) ? window : Object(thisArg)
    Object.defineProperty(thisArg, 'fn', {
        configurable: true,
        value: this
    })
    return (...newArgs) => {
        // var allArgs = otherArgs.concat(newArgs)
        var allArgs = [...otherArgs,...newArgs] // 浅拷贝
        thisArg.fn(...allArgs)
    }
}

var newFoo = foo.wqbind({name: 'www'},'zzz',90)
newFoo(122,'成都')