JavaScript函数

160 阅读3分钟

在JavaScript里,函数被称为一等公民。Javascript函数涉及到的内容有很多,其中包含函数声明、函数调用、赋值、传参、构造函数、立即执行等。

JS编程特点

函数式编程和面向对象编程的混编语言,可扩展性强

优点:编程灵活、易学

缺点:不可控

采用三种不同的方法来实现计算的函数

1、普通函数

function add(a, b) {
    return a + b
}

function minus(a, b) {
    return a - b
}

fucntion compute(a, b, fn) {
    return fn(a, b)
}

compute(1, 2, add)

2、构造函数

function Compute(a, b) {
    this.a = a
    this.b = b

    this.add = function() {
        return this.a + this.b
    }

    this.minus = function() {
        return this.a - this.b
    }
}

var compute = new Compute(1, 2)

compute.add()

3、立即执行函数

var test = (
    function () {
        function Compute(a, b) {
            this.a = a
            this.b = b

            this.add = function() {
                return this.a + this.b
            }

            this.minus = function() {
                return this.a - this.b
            }
        }
        return new Compute(1, 2)
    }
)()

test.add()

函数式编程

函数式第一类对象,不依赖任何其他对象独立存在

纯函数

相同的输入得到相同的输出,不依赖且不影响外部环境也不产生任何副作用;输出完全取决于输入。 test函数就是纯函数,行参取决于全局变量a。

var a = 1

function test(num) {
    console.log(num)
}

test(a)

凡事产生了副作用的函数就不是纯函数。只要和函数外部环境发生了交互就是副作用。 常见的副作用主要包括:发送数据请求、改变数据、console.log、 DOM操作、数据存储等等。

函数组合

若干个纯函数、偏函数、柯里化函数组合成一个新的函数、形成数据传递、并实现一种有序的执行效果。

function toUpperCase(str) {
    return str.toUpperCase()
}

function exclaim(str) {
    return str + '!'
}

function compose(f, g) {
    return function(x) {
        return f(g(x))
    }
}

var fn = compose(exclaim, toUpperCase)
fn('hello')

exclaim(toUpperCase('hello'))

组合函数的参数不定,采用循环的方式来封装compose,遍历的项由参数的最后一项自右向左,每次执行的结果作为下一次调用的参数。

function compose() {
    var args = [].slice.call(arguments)
        len = args.length - 1
    return function(x) {
        var res = args[len](x)

        while(len--) {
            res = args[len](res)
        }

        return res
    }
}

可以采用数组的reduceRight方法,将上面的compose进行一步改造。

function compose() {
    var args = [].slice.call(arguments)

    return function(x) {
        return args.reduceRight(function(res, cb) {
            return cb(res)
        }, x)
    }
}

高阶函数

一个函数接受另一个函数作为参数变量

 function test(a, b, pow) {
     return pow(a, 2) + pow(b, 2)
 }

 function pow(x, n) {
     return Math.pow(x, n)
 }

 var res = test(2, 3, pow)

柯里化

函数式编程的思想,将一个多参数的函数转成多个单参数的函数,将n元函数转换成n个一元函数。主要作用:1、简化代码;2、提高维护性;3、功能单一化

function add(a, b, c, d) {
    return a + b + c + d
}

function curry(fn) {
    var _arg = [].slice.call(arguments, 1)
    return function() {
        var newArgs = _arg.concat([].slice.call(arguments))
        return fn.apply(this. newArgs)
    }
}

var add2 = curry(add, 1, 2)
var res = add2(3, 4)

简版的柯里化大体封装,但是有个弊端,参数的个数只能在有限次数凑齐,如果在调用add2时传入的参数只有一个将会出错。

将柯里化进一步优化封装,当调用时传入的参数小于fn的形参时,递归调用柯里化函数本身。

function curry(fn, len) {
    var len = len || fn.length

    var func = function(fn) {
        var _arg = [].slice.call(arguments, 1)
        return function() {
            var newArgs = _arg.concat([].slice.call(arguments))
            return fn.apply(this, newArgs)
        }
    }

    return function() {
        var argLen = arguments.length
        if (argLen < len) {
            var formatedArr = [fn].concat([].slice.call(arguments))
            return curry(func.apply(this, formatedArr), len - argLen)
        } else {
            return func.apply(this, arguments)
        }
    }
}

偏函数

英文:partial Application,将n元函数转换成n-x元的函数

Function.prototype.partial = function() {
    var _self = this
        _args = [].slice.call(arguments)

    return function() {
        var newArgs = _args.concat([].slice.call(arguments))
        return _self.apply(null, newArgs)
    }
}

函数记忆

var time = 0
var cache = []

function factorial(n) {
    time++

    if (cache[n]) {
        return cache[n]
    }
    if (n === 0 || n === 1) {
        cache[0] = 1
        cache[1] = 1
        return 1
    }
    return cache[n] = n * factorial(n - 1)
}

function memorize(fn) {
    var cache = {}

    return fucntion() {
        var key = [].join.call(arguments, ',')

        return cache[key] = cache[key] || fn.apply(this, arguments)
    }
}

var f = memorize(factorial)

防抖

1.是否首次延迟执行

2.n秒之内 触发事件不执行事件处理函数(n秒之内频繁触发事件,计时器会频繁重新开始计时)

function debounce(fn, time, triggleNow) {
    var t = null

    var debounced = function() {
        var _self = this
            args = arguments
        
        if (t) {
            clearTimeout(t)
        }

        if (triggerNow) {
            var exec = !t

            t = setTimeout(function() {
                t = null
            }, 1000)

            if (exec) {
                fn.apply(_self, args)
            }
        } else {
            t = setTimeout(function() {
                fn.apply(_self, args)
            }, time)
        }
    }

    debounced.remove = function() {
        clearTimeout(t)
        t = null
    }

    return debounced
}

节流

事件被触发,n秒之内只执行一次事件处理函数

function throttle(fn, delay) {
    var t = null
        begin = new Date().getTime()
    
    return function() {
        var self = this
            args = arguments
            cur = new Date().getTime()
        
        clearTimeout(t)

        if (cur - begin >= delay) {
            fn.apply(this, _args)
            begin = cur
        } else {
            t = setTimeout(function() {
                fn.apply(_self, args)
            }, delay)
        }
    }
}

数组扁平

多维数组变为一维数组

function flatten(arr) {
    var _arr = arr || []
        fArr = []
        len = _arr.length
        item

    for(var i = 0; i < len; i++) {
        item = _arr[i]
        if (_isArr(item)) {
            fArr = fArr.concat(flatten(item))
        } else {
            fArr.push(item)
        }
    }

    function _isArr(obj) {
        return {}.toString.call(obj) === '[object Array]'
    }

    return fArr
}