js函数增强

目录

  • 函数的属性
    • arguments
    • 将arguments转换成数组
    • rest
  • 纯函数
  • 柯里化函数
  • 自动实现函数柯里化
  • 组合函数
  • 自动实现组合化
  • with与eval
    • with
    • eval
  • 严格模式
    • 严格模式的限制

函数的属性

函数其实也是一个对象
是对象就会有对应的方法与属性
以下是几个常用属性

  1. name
    name属性主要用于访问给定函数的名字,在匿名函数中这个值为空
  2. length
    length属性中存放的则是函数参数的个数
    注意,如果是rest参数的话则不会计入
  3. prototype
    这个属性指向了函数的原型对象,当函数作为构造函数生成新对象时新对象会继承原型对象中的属性
    关于原型与继承可以看我这篇文章
    (未动笔,未来可寄)

arguments

arguments是一个对应于传递给函数的参数的类数组对象
即你调用函数时传入的参数都存放在arguments
arguments是一个类数组(like-Array)对象
它有着length属性和能通过索引访问元素
但没有数组对应的方法

将arguments转换成数组

arguments转换成数组有以下三种办法

  1. 遍历整个arguments,将其中的元素一个一个push到新数组中
        function foo() {
            var newArguments = [];
            for (var i = 0; i < arguments.length; i++) {
                newArguments.push(arguments[i])
            }
            console.log(newArguments)
        }
        foo(1, 2, 3, 4, 5, 6, 7, 8, 9, 0)
    
    这种方法最简单,同样的代码也很多
  2. 使用ES6提供的新语法
        function foo() {
            var newArguments = Array.from(arguments);
            console.log(newArguments)
        }
        foo(1, 2, 3, 4, 5, 6, 7, 8, 9, 0)
    
    这种方法使用ES6提供的from方法实现
            function foo() {
            var newArguments = [...arguments];
            console.log(newArguments)
        }
        foo(1, 2, 3, 4, 5, 6, 7, 8, 9, 0)
    
    也可以通过展开运算符来实现
  3. 使用slice方法加显式绑定this实现
        function foo() {
            var newArguments = [].slice.apply(arguments);
            console.log(newArguments)
        }
        foo(1, 2, 3, 4, 5, 6, 7, 8, 9, 0)
    
    如果需要兼容ES5以下环境的话可以使用这种方法或第一种方法
    注意,不能直接Array.slice,因为slice实例方法
    可以通过Array.prototype.slice实现

注意,arguments只存在于非箭头函数中,在箭头函数argumentsrest替代

rest

箭头函数中不绑定arguments,在箭头函数中使用arguments会在上层作用域中查找
作为替代,ES6中引入了了rest parameter,可以将不定数量的参数放入数组中
arguments不同的是,rest是一个数组
rest只包含那些没有形参对应的参数
如果一个函数有形参,那么rest必须放到最后面

        function foo(a, b, ...args) {
            console.log(a, b)
            console.log(args)
        }
        foo(1, 2, 3, 4, 5, 6, 7, 8, 9, 0)

控制台打印结果
结果

纯函数

在函数式编程中有个概念叫纯函数
只要一个函数符合以下条件就是一个纯函数

  1. 函数在有相同的输入时,需要有相同的输出
  2. 函数不能有副作用

副作用即函数除了返回值之外还对函数调用产生了附加影响
修改全局变量修改参数或外部存储
纯函数的最大好处就是你可以安心的编写以及安心的使用
只需要关心业务逻辑,不需要关心传入的值是否依赖外部变量
在用的时候也可以确定输入内容不会被任意篡改,并且自己确定的输入,一定会有确定的输出
以下是几个简单的纯函数例子

        function sum(num1, num2) {
            return num1 + num2
        }
        var num = sum(1, 2)

下面就不是一个纯函数,因为确定的输入并不会导致确定的输出

        var flag = true
        function sum(num1, num2) {
            if (flag) {
                return num1 + num2
            } else {
                return num1 * num2
            }
        }
        var num = sum(1, 2)
        flag = false
        num = sum(1, 2)

柯里化函数

函数柯里化即将函数一次接收多个参数变成一次接收一个并且返回一个新函数接收剩下的参数
这个过程就称之为柯里化
柯里化的优势有以下几点

  1. 函数职责单一
    函数式编程中,我们其实往往希望一个函数处理的问题尽可能的单一,而不是将一大堆的处理过程交给一个函数来处理
    那么我们就可以将每次传入的参数在单一的函数中进行处理,处理完后在下一个函数中再使用处理后的结果
  2. 函数的参数复用
    如下代码所示
    	function addCurried(a) {
    		return function(b) {
    			return add(a, b);
    		}
    	}
    	const add5 = addCurried(5);
    	console.log(add5(3)); // 输出 8
    	console.log(add5(7)); // 输出 12
    
    如果我们想计算多次5+7和5+3的话就可以利用柯里化的这个特点来实现每次只输入一个参数,剩下的参数进行复用

以下是一个简单的柯里化函数

        function sum(x, y, z) {
            return x + y + z
        }
        function sum2(x) {
            return function sum3(y) {
                return function sum4(y) {
                    return x + y + z
                }
            }
        }
        console.log(sum(1, 2, 3))
        console.log(sum2(1)(2)(3))

在这其中,sum为改造之前的函数,sum2为改造后的柯里化函数

自动实现函数柯里化

封装一个函数来帮我们自动实现柯里化操作

        function sum(x, y, z) {
            return x + y + z
        }
        function logInfo(error, msg) {
            console.log(`${error}:${msg}`)
        }
        function hyCurrying(fn) {
            function curryFn(...args) {
                if (args.length >= fn.length) {
                    return fn(...args)
                } else {
                    return function (...rest) {
                        return curryFn(...args.concat(rest))
                    }
                }
            }
            return curryFn
        }
        var sumCurry = hyCurrying(sum)
        var infoCurry = hyCurrying(logInfo)
        console.log(sumCurry(1)(2)(3))
        console.log(sum(1, 2, 3))
        infoCurry("ERROR")("not defined")
        logInfo("ERROR", "not defined")

这里使用hyCurrying构造一个柯里化函数curryFn
curryFn中主要做两件事

  1. 当传入的参数大于等于原本函数所需的参数时,就执行原函数,并将原函数可能得返回值返回
  2. 当传入的参数小于原本函数所需的参数时就需要再次调用函数(即以这样的形式func()()),定义一个新函数并将这个新函数返回给sumCurry调用,将再次传进去的参数rest合并到原本的args参数中然后调用curryFn函数,并将curryFn的返回值返回

最后来看看结果

结果
有些时候,我们可能在使用函数的时候给他显式绑定this,这个时候以上的代码就需要进行更改

        function hyCurrying(fn) {
            function curryFn(...args) {
                if (args.length >= fn.length) {
                    return fn.apply(this,args)
                } else {
                    return function (...rest) {
                        return curryFn.apply(this,args.concat(rest))
                    }
                }
            }
            return curryFn
        }

组合函数

有时候我们需要对某一个数据进行函数调用,会执行两个函数
每调用次函数,就要执行两个函数,操作上就显得重复了
将两个函数组合起来,自动调用,这个过程就是组合函数
以下是一个组合函数的例子

        function double(num) {
            return num * 2
        }
        function square(num) {
            return num ** 2
        }
        function compose(fn1, fn2) {
            return function (x) {
                return fn2(fn1(x))
            }
        }
        var calcFn = compose(double, square)
        console.log(calcFn(20))

自动实现组合化

上面列举的代码只能实现两个函数的组合,有时候我们需要传入更多的函数与参数时就需要对以上代码进行优化

        function double(num) {
            return num * 2
        }
        function square(num) {
            return num ** 2
        }
        function compose(...fns) {
            for (var fn of fns) {
                if (typeof fn !== "function") {
                    throw new TypeError(`${fn} Expected a function`)
                }
            }
            if (fns.length == 0) return
            return function (...args) {
                var result = fns[0].apply(this, args)
                for (var i = 1; i < fns.length; i++) {
                    result = fns[i].call(this, result)
                }
                return result
            }
        }
        var calcFn = compose(double, square)
        console.log(calcFn(20))

在函数compose中我们先对传入的参数进行判断,如果传入的不是函数,或者传入的参数为空就直接抛出异常结束执行
在之后我们返回一个新函数
新函数首先会调用fns中的第一个函数来得到result以此作为以后函数传入的参数
每次函数调用完毕后都将更新result里的值,直到fns里的函数运行完毕返回result的值

with与eval

无论是with还是eval都会造成兼容和性能问题,真实开发中一般并不常用仅做了解

with

with语句主要用于延长语句的作用域链
它会在包裹的语句上额外添加一层作用域链
with会造成许多兼容性问题

        var obj = {
            message: "hello world"
        }
        with (obj) {
            console.log(message)
        }

打印结果

结果

eval

eval可以将传入的字符串当做JavaScript代码来运行
eval会将最后一句执行语句的结果,作为返回值
eval代码必须加上分号
eval代码的可读性十分差
eval代码因为是字符串容易被刻意篡改,造成安全问题
eval代码无法被浏览器优化

        var str = 'var message = "hello world"; console.log(message)'
        eval(str)

结果

结果

严格模式

JavaScript长久以来的发展或多或少的留下了一些错误或者不完善的问题
为了兼容旧代码,这些问题会永远的留在了JavaScript
为了解决这个问题,在ES5中正式提出了严格模式的概念
严格模式是一种具有限制性的JavaScript模式
在这种模式运行下的代码会受到更为严格的检测
严格模式对正常的JavaScript语义进行了一些限制

  1. 严格模式会通过抛出错误来替换以前的静默错误
  2. 严格模式也会让代码得到更好的优化
  3. 严格模式也会禁用ES未来版本中可能定义的一些语法

开启严格模式只需要在代码最上方加一行

"use strict"

严格模式支持在js文件中开启
在文件中使用即代表整个文件开启严格模式
也支持在函数中开启
在函数中使用即代表整个函数开启严格模式
必须将此代码写在最上方才能生效
注意,开启严格模式无法返回默认模式

严格模式的限制

  1. 无法意外的创建全局变量
  2. 严格模式会使引起静默失败的赋值操作抛出异常
  3. 严格模式下不允许删除不可删除的属性
  4. 4.严格模式不允许函数参数有相同的名称
  5. 不允许0开头的八进制语法
  6. 在严格模式下,不允许使用with
  7. 在严格模式下,eval不再为上层引用变量
  8. 严格模式下,this绑定不会默认转成对象

你可能感兴趣的:(网页,javascript,前端,开发语言)