JavaScript 函数新增知识

280 阅读12分钟

JavaScript 函数新增知识

JavaScript 函数对象的两个默认属性

首先我们的普通对象而言的话,我们是可以发现一点的就是,我们的对象是可以通过 . 操作符实现向一个对象中添加属性或者方法

/**
 * 定义一个对象
 * @type {{}}
 */
var obj = {}
​
/**
 * 开始为我们的对象添加属性和方法
 * 那么我们该如何进行添加呐???
 * 这个时候就是通过的是我们的 . 操作符实现的
 * 添加属性或者方法
 */
obj.name = "76433"
obj.message = "我们对于对象的一种描述"
obj.running = function() {
    console.log(`${this.name} is running on ${obj.name}`)
}
​
console.log(obj)

那么这个时候,作为我们的函数也是可以进行这样的操作

因为我们的函数本身实际上的话也是一种对象,只不过是一种给比较特殊的对象罢了

/**
 * 定义函数
 * @returns undefined
 */
function foo() {}
​
/**
 * 开始实现为我们的函数添加属性和方法
 */
foo.info = "76433"
foo.massge = "这是一个 foo 函数"console.dir(foo)
​
console.log(foo.info)

通过我们的这种 . 操作符实现添加的属性实际上的话只是我们的自定义属性

除了我们的通过上面的自定义属性的话,我们的函数有没有属于自己的一些属性呐???

当然是有的:

  • name 属性 : 这个属性是为了获取我们的函数的名称的一个属性,我们可以实现的是将两个函数实现添加到是数组中去

    • 然后来实现使用,以及获取元素的名称
  • length 属性 : 这个属性就是实现的是获取我们函数本来可以接收参数的个数,函数的柯里化是会利用这个属性的

/**
 * 定义函数
 * @returns undefined
 */
function foo() {}
​
console.log(foo.name)  // foo
console.log(foo.length)  // 0
​
​
/**
 * 用来表示的是含有参数的函数
 * @param {any} param01
 * @param {any} param02
 * @returns undefined
 */
function bar(param01, param02) {}
​
console.log(bar.name)  // bar
console.log(bar.length)  // 2/**
 * 我们需要注意一点的是,实际上我们 JavaScript 是十分自由的
 * 不像其他的编程语言,JavaScript 的函数定义了几个参数,但是我们进行传参的时候,还是可以多传的
 * 并且实现不报错,但是只不过,这个是一把双刃剑的,合理利用这一特点吧
 */

JavaScript 函数的arguments对象

注意,对于我们的函数而言的话,我们的箭头函数是没有这一个属性的呐!!!

arguments 属性是一个类数组对象(array-like)

function foo() {
 console.log(arguments)
 return arguments
}
​
foo().length  // 0
foo(11, 13, 16).length  // 3

函数的中的 arguments 参数的话实现的就是得到函数执行时的传入的参数的信息

image-20241110233508155.png

但是在我们的实际开发中,我们是很不好对我们的类数组对象进行操作的

所以说就会有需求,将类数组对象转化在我们的数组进行操作

那么会有那些方式来帮助我们进行将这个类数组的 arguments 转化为数组呐???

(function() {
 /**
     * 方式一,通过 for 循环和 push 方法
     */
    var newArgus = []
    for(var item of arguments) {
        newArgus.push(item)
    }
    console.log(newArgus)
​
    /**
     * 方式二:使用 ES6 为我们提供的两种方式来实现
     */
    var newArgu2 = Array.from(arguments)
    var newArgu3 = [...arguments]
    console.log(newArgu2)
    console.log(newArgu3)
​
    /**
     * 方式三: 调用 slice 方法
     */
    var newArgs4 = [].slice.apply(arguments)
    var newArgs5 = Array.prototype.slice.apply(arguments)
    console.log(newArgs4)
    console.log(newArgs5)
})()

上面的方法三的话,实际上就涉及了 slice 方法的一些原理性的使用了

slice 方法原本是对数组进行操作的一种方法(函数)

但是只不过这里就需要进行一个升级了,实际上这个方法的利用场景是在可迭代对象中的,但是只不过只可以通过数组调用

内部实现的是,对可迭代对象实现了遍历,然后进行的操作

同时还需要注意的一点是: Array 实际上是我们的构造函数,对于函数而言的话,是具有显式原型和隐式原型的

构造函数的显式原型 和 实例对象的执行是一致的,指向是同一个东西

JavaScript 函数的剩余(rest)参数

在 ES6 中引入了剩余参数 rest paramsters

  • 剩余参数的表现形式为: 在参数的前面有: ... 的标识符,剩余参数实现的时候,是将对于的参数保存在数组中的

    • 同时我们还可以利用这个标识符来实现展开数组(平铺数组)的操作
/**
 * 函数本身没有参数的剩余参数函数
 * @param {Array} args
 */
function foo(...args) {
    console.log(args)
    console.log(...args)
}
​
foo(11, 12, 32)
​
/**
 * 函数本身含有参数的剩余参数函数
 * @param param01
 * @param param02
 * @param {Array} args
 */
function bar(param01, param02, ...args) {
    console.log(args)
    console.log(args instanceof Array)  // true
    console.log(...args)
}
bar(1, 2, 11, 12, 32)

注意: 剩余参数必须写在每一个参数的最后

为什么会出现 剩余参数呐???

  • arguments 是我们的早期的 ECMAScript中为了获取函数传入的参数提供的一种数据结构
  • 但是ES6 为了简化操作,所以说就提供了剩余参数 rest paramsters 来替代 arguments
  • 同时需要注意的一点是,我们的 arguments 不可以用在箭头函数中,但是剩余参数是可以的哟!!!

JavaScript 纯函数的理解(Pure Function)

函数式的编程里面是含有一个十分重要的概念的,JavaScript 符合我们的函数式的编程思想。所以说也是具有纯函数的概念

  • 在 react 的组件的开发模式中多次被提及
  • react 的组件化的开发模式中就含有我们的纯函数的概念,redux 中就有一个 reducer 的概念
  • 有很多的框架的底层实现就是使用的是纯函数的涉及模式

纯函数的详细介绍:

  • 在函数中是具有相同的一群输入值的,同时需要进行相同的输出

  • 输入输出值的以外的其他概念的话和其他隐藏信息状态无关,同时也是和 I/O 设备的外部输出是无关的

  • 就是我们的一个函数有了一个确定输出后,那么就含有确定的输出 | 同时一个函数的运行是没有副作用的(side effect

    • 即是说:我们的函数的输入值和输出值是相互确定的,有了确定的输出,那就会含有确定的输入
/**
 * 先实现编写一个纯函数
 * @param num01
 * @param num02
 * @returns {number}
 */
function foo(num01, num02) {
    return num01 * num02
}
​
foo(10, 20)
​
/**
 * 为什么这个是我们的纯函数呐???是因为最终的函数功能在实现的时候,并没有进行对我们的输入输出值照成影响
 */
​
​
/**
 * 开始实现书写一个不是纯函数的函数
 * @param obj
 @ruturn undefined
 */
function bar(obj) {
    console.log(obj)
    obj.flag = true
}
​
var obj = {
    name: "76433",
    age: 18,
    message: "我是一个 obj 对象"
}
bar(obj)
console.log(obj)
​
/**
 * 为什么不是纯函数呐???
 * 是因为在函数的内部对我们的输入的值进行了一定的改变,产生了副作用
 */

JavaScript 数组方法slice 和 splice 对比

JavaScript 数组的操作slice

var names = ["juwenzhang", "76433", "水逆信封"]
​
/**
 * 开始我们的数组操作 slice
 * 含有第一个参数 start
 * 第二个参数为: end
 */
var newNames = Array.prototype.slice.call(names, 1)
console.log(newNames, names)
​
​
var newNames01 = Object.getPrototypeOf(names).slice.call(names, 1)
console.log(newNames01, names)
​
​
/**
 * 我们的slice 方法实际上的话是对我们的原本的输入值是没有进行修改的,所以说这个是一个纯函数
 */

JavaScript 数组的操作 splice

/**
 * splice 的书写
 * 参数一: 确定进行修改的位置
 * 参数二: 确定我们的需要进行修改的个数
 * 参数三: 进行修改的时候的替换的内容
 */var names = ["juwenzhang", "76433", "水逆信封"]
var newNames = Array.prototype.splice.call(names, 1, 1, "hahaha")
​
console.log(newNames, names)  // [ '76433' ] [ 'juwenzhang', 'hahaha', '水逆信封' ]
/**
 * splice 就不是一个纯函数,因为他对我们的原本的输入值进行了一定的转化的
 * 产生了对应的副作用,返回的是我们的需要进行修改的对象
 */

JavaScript 柯里化和柯里化函数(currying)

柯里化函数实现的就是我们的一种函数式编程的一种十分重要的概念

可以进行书写,或者说不进行书写也是可以的,只是一种书写规范而已,没有其他的

柯里化函数实现的就是将我们的可以传入多个参数的函数实现基本的一些转化为每次只是传入一个参数进行调用的函数格式

柯里化我们在实现的时候,我们是需要进行的是返回的是一个新的参数来实现的

只传递一部分的参数给我们的函数进行调用,然后返回一个函数去处理其他的参数的函数的格式书写,这个过程就是函数的柯里化

不使用柯里化的函数的书写形式

function foo(param01, param02, param03, ...params) {
    var initValue = param01 * param02 + param03
    return params.reduce((preValue, item) => preValue + item, initValue)
}
​
console.log(foo(1, 2, 3, 44, 55))

使用了柯里化的函数格式

function foo(param01) {
    console.log(param01)
    return function(param02) {
        console.log(param02 + param01)
        return function(param03) {
            var initValue = param01 + param02 + param03
            console.log(initValue)
            return function(...params) {
                var sum = [].reduce.call(params, (preValue, value) => preValue + value, initValue);
                console.log(sum)
            }
        }
    }
}
​
foo(11)(12)(13)(111)

柯里化和箭头函数

var foo = param01 => param02 => param03 => (...params) => {
    var initValue = param01 + param02 + param03
    var sum = [].reduce.call(params,(preValeue, currentValue) => preValeue + currentValue, initValue)
    return sum
}
​
console.log(foo(11)(22)(33)(44, 55, 66))  // 231

自动柯里化函数

实现函数的柯里化的时候,我们需要注意的就是进行判断函数的参数的问题

如果需要进行柯里化的函数需要的参数和进行柯里化函数的时候的比较是相等或者说是小于等于的话

那就直接调用函数

否则就是对参数一个一个的传递进行比较即可

但是在进行这些操作的时候,一定要保证this 的指向问题

在我们进行后面的组件化的开发模式中,我们是可以直接把进行柯里化的函数抽象成我们的一个工具函数,直接来使用的(utils)

/**
 * 抽离出一个函数来实现自动柯里化函数
 * @param {Function} func
 * @returns {CurryFunc}
 */
function seleniumCurrying(func) {
    function CurryFunc(...params) {
        if (params.length >= func.length) {
            func.apply(this, params)
        } else {
            return function(...newParams) {
                return CurryFunc.apply(this, params.concat(newParams))
            }
        }
    }
    return CurryFunc
}
​
/**
 * 在我们的实现自动化的柯里化函数的实现的思路就是:
 * 对处理柯里化的函数里面接收一个参数,即是说需要进行柯里化的原始的函数
 * 先进行第一步的判断: 即是说进行我们返回的第一个参数接收的参数的数量和原始的函数接收的参数的数量进行
 * 总和的判断
 *
 * 如果说我们的参数小于调用函数所需要的参数,那就直接在进行接收我们的参数来实现判断即可
 *//**
 * 需要进行函数柯里化的函数
 * @param {number} param01
 * @param {number} param02
 * @param {...number} params
 */
function sum(param01, param02, ...params) {
    console.log([].reduce.call(params,(preValue, curValue) => preValue + curValue, param01 + param02))
}
​
var curry_sum = seleniumCurrying(sum)
curry_sum(10)(20)
​
_sum = curry_sum(30)
_sum(30)

JavaScript 组合函数

  • 组合函数(Compose Function)是JavaScript 中的一种开发思想。即是说函数的一种使用的技巧以及模式

    • 比如说我们的需要对一个数据进行处理的时候,需要进行函数的调用,执行两个函数,这两个函数就是依次执行的
    • 如果说我们需要进行两个函数的单独调用的话,这个时候,从操作上就十分的复杂了
    • 这个时候,我们就需要使用到组合函数的使用(Compose Function),对我们的函数进行整合
var num = 100/**
 * 开始实现两部的操作,第一步的就是实现我们的数字 * 2
 * @param num
 * @returns {number}
 */
function getNumMulti(num) {
    return num * 2
}
​
/**
 * 第二部实现的就是我们的对一个数据进行平方
 * @param num
 * @returns {number}
 */
function getNumPow(num) {
    return num * num
}
​
console.log(getNumPow(getNumMulti(num)))
// 这个是以前的实现的方法,就是通过两个函数来实现我们的书写方法,这两就是两个函数的依次进行调用了
​
​
// 开始实现将上面的两个函数实现整合到一起,形成我们的组合函数
/**
 * 组合函数的初体验
 * @param num
 * @returns {number}
 * @constructor
 */
function ComposeFunc(num) {
    return getNumPow(getNumPow(num))
}
​
console.log(ComposeFunc(num))

同时我们也是可以进行我们的组合函数的自动化的组合的

我们进行书写我们的组合函数的时候,我们进行书写的时候,我们是可以进行一些操作的

就是先进行对剩余参数的判断,就是判断是否含有别人传入的参数

然后进行的是对传入的参数进行一个边界的判断,如果是函数就进行下面的继续执行,否则也停止运行函数

/**
 * 用来实现两个函数的整合
 * @param argsFn
 * @returns {function(...[*]): *}
 * @constructor
 */
function ComposeFunc(...argsFn) {
    // 先进行判断我们的传入的参数是否含有,不含有直接终止函数的继续运行
    if (argsFn.length <= 0) return;
​
    // 开始实现判断我们的参数是不是我们的函数: edge case (边界判断)
    for(const func of argsFn) {
        if (typeof func !== 'function') {
            throw new Error(`${func} not a function`)
        }
    }
​
    return function(...paramFn) {
        let res = argsFn[0].apply(this, paramFn)
        for(let i = 1; i < argsFn.length; i++) {
            res = argsFn[i].call(this, res)
        }
        return res
    }
}
​
​
// 开始实现定义我们的几个需要进行组合式调用的函数的书写
function getDoubleNum(num) {
    return num * 2
}
​
function getPowNum(num) {
    return num * num
}
​
​
const deal_num = ComposeFunc(getDoubleNum, getPowNum)
console.log(deal_num(100))  // 40000

通过这一节的学习以及了解,我们就可以理解到一些关于我们的JavaScript 中的一些更加高级的函数的书写了

在这一节中,我们需要进行强化记忆的就是我们的 纯函数的概念和 函数的柯里化操作

以及了解组合函数的基本的书写

在我们的现在的 vue3 或者 react 的开发模式中,我们会常进行抽象我们的一大坨的工具函数的,这样来实现一些函数的抽离

直接通过前端工程化的开发实现暴露给外面实现使用即可