JavaScript开发:函数在实际开发中的使用总结(2)

本文以《JavaScript高级程序设计》第4版作为基础参考,整理使用JavaScript开发过程中,函数使用相关的知识点。

本文是开发知识点系列第九篇。

  1. 第一篇:JavaScript开发中变量、常量声明的规矩总结
  2. 第二篇:JavaScript开发:数据类型知识总结
  3. 第三篇:JavaScript开发:使用Number数据类型需要注意的问题
  4. 第四篇:JavaScript开发:操作符在实际开发中的使用总结
  5. 第五篇:JavaScript开发:流程控制语句在实际开发中的使用总结
  6. 第六篇:JavaScript开发:函数在实际开发中的使用总结(1)
  7. 第七篇:JavaScript开发:日期对象在开发中的使用总结
  8. 第八篇:JavaScript开发:正则表达式在开发中的使用总结

函数第一篇,也就是上面的第六篇,主要讲的是函数使用的内置规则。内置规则讲完,本篇正式开始写实际开发过程中,属于函数的那些有意思的部分(变化多端的部分)。

函数自身属性变化

函数柯里化-部分求值

函数柯里化是一种函数式编程技术,利用函数递归将函数原有参数分化的技术,又叫部分求值。

一个通用的函数柯里化(currying)实现通常会接受一个函数作为参数,并返回一个新的函数,这个新的函数可以逐步接受参数,直到收集齐原函数所需的所有参数后,再执行原函数

javascript 复制代码
function curry(fn) {
    return function curried(...args) {
        if (args.length >= fn.length) {
            return fn.apply(this, args);
        } else {
            return function(...args2) {
                return curried.apply(this, args.concat(args2));
            }
        }
    };
}

// 使用示例
function sum(a, b, c) {
    return a + b + c;
}

let curriedSum = curry(sum);

console.log(curriedSum(1, 2, 3)); // 输出6
console.log(curriedSum(1)(2, 3)); // 输出6
console.log(curriedSum(1, 2)(3)); // 输出6
console.log(curriedSum(1)(2)(3)); // 输出6

上例curry函数接受一个函数fn作为参数,返回一个新的函数curriedcurried函数会检查它已经收到的参数数量是否足够。如果足够,它就会调用fn函数。如果不足,它就会返回一个新的函数,这个新的函数会收集更多的参数。这样就可以逐步地提供参数,直到收集齐所有参数为止。

函数柯里化-等待求值

下面是来自《JavaScript设计模式与开发实践》书中的例子,立即执行函数,返回一个新函数,等到没有参数时再求值

js 复制代码
  var cost = (function () {
            var args = [];
            return function () {
                if (arguments.length === 0) {
                    var money = 0;
                    for (var i = 0, l = args.length; i < l; i++) {
                        money += args[i];

                    }
                    return money;
                } else {
                    [].push.apply(args, arguments);
                }
            }
        })();
        cost(100); // 未真正求值
        cost(200); // 未真正求值
        cost(300); // 未真正求值
        console.log(cost()); // 求值并输出:600

反函数柯里化

柯里化(Currying)是一种将多个参数的函数转换成一系列一个参数的函数的技术。反柯里化(Uncurrying)则是柯里化的逆过程,它将一个柯里化的函数转换为一个接受多个参数的函数

javascript 复制代码
// 柯里化的加法函数
function curriedAdd(x) {
    return function(y) {
        return x + y;
    };
}

// 反柯里化的函数
function uncurry(func) {
    return function(x, y) {
        return func(x)(y);
    };
}

// 使用反柯里化
var add = uncurry(curriedAdd);
console.log(add(1, 2)); // 输出: 3

curriedAdd是一个柯里化的函数,uncurry函数是一个反柯里化的函数,可以使用uncurry函数来反柯里化curriedAdd函数,得到一个新的函数add。这个新的函数接受两个参数,然后返回它们的和。

函数种类

根据不同的划分标准有不同种类的函数,这里只是列举以下几种。

纯函数

纯函数是函数式编程的基础,它有两个主要的特点:

  1. 给定相同的输入,总是返回相同的输出。
  2. 没有任何副作用,也就是说,它不改变程序的状态,也不影响其他函数的执行。
javascript 复制代码
function add(a, b) {
    return a + b;
}

console.log(add(1, 2)); // 输出: 3
console.log(add(1, 2)); // 输出: 3

add函数就是一个纯函数。它接受两个参数ab,然后返回它们的和。给定相同的输入,它总是返回相同的输出。而且它没有任何副作用,不改变程序的状态,也不影响其他函数的执行。

我的理解纯函数就是和外部环境没有任何其它(除了输入参数)耦合关系的程序单元。

函数如果有副作用(比如改变全局变量的值,或者修改了函数参数的状态等),或者它的返回值依赖于外部状态(比如依赖于全局变量,或者系统时间等),那这个函数就不是纯函数

javascript 复制代码
let count = 0;

function increment() {
    count += 1;
    return count;
}

console.log(increment()); // 输出: 1
console.log(increment()); // 输出: 2

increment函数就不是一个纯函数。它没有接受任何参数,而是直接修改了全局变量count的值。这是一个副作用,因为它改变了程序的状态。

闭包

闭包首先是一种函数。

闭包是一个非常重要的概念,在许多编程语言中都存在,包括JavaScript。闭包是指一个函数有访问自己被创建时所在的词法作用域内的变量的能力。

当一个函数嵌套在另一个函数内部时,内部函数可以访问外部函数的变量,即使外部函数已经执行完毕。这就是闭包。

我之前总结:闭包就是具备作用域链保持能力的函数。

一个简单的示例

javascript 复制代码
function outerFunction() {
    var outerVariable = 'I am from outer function!';

    function innerFunction() {
        console.log(outerVariable);
    }

    return innerFunction;
}

var myFunction = outerFunction();
myFunction(); // 输出:'I am from outer function!'

outerFunction是一个外部函数,它有一个局部变量outerVariableinnerFunction是一个内部函数,它可以访问outerVariable

当调用outerFunction时,它返回innerFunction。将返回的函数存储在myFunction变量中,然后调用myFunction。即使outerFunction已经执行完毕,myFunction仍然可以访问outerVariable。这就是闭包。

闭包在许多场合都非常有用,比如创建私有变量,实现工厂函数,模块等。

私有变量

使用闭包来创建私有变量。这些变量只能被特定的函数访问和修改,而不能在函数外部被直接访问

javascript 复制代码
function createCounter() {
    var count = 0; // 私有变量

    return {
        increment: function() {
            count++;
        },
        decrement: function() {
            count--;
        },
        getCount: function() {
            return count;
        }
    };
}

var counter = createCounter();
counter.increment();
counter.increment();
console.log(counter.getCount()); // 输出2
console.log(counter.count); // 输出undefined

示例中createCounter函数创建了一个私有变量count,并返回了一个包含三个方法的对象。这三个方法都是闭包,它们可以访问和修改count变量。

increment方法和decrement方法用于增加和减少count的值,getCount方法用于获取count的当前值。不能直接访问count变量,只能通过这三个方法来操作它。如果试图直接访问count变量,会得到undefined,因为count变量在函数外部是不可见的。

工厂函数

下面是一个使用闭包的工厂函数的例子

javascript 复制代码
function createPerson(name, age, job) {
    var _name = name;
    var _age = age;
    var _job = job;

    return {
        getName: function() {
            return _name;
        },
        getAge: function() {
            return _age;
        },
        getJob: function() {
            return _job;
        }
    };
}

var person1 = createPerson('John', 30, 'Engineer');
console.log(person1.getName()); // Outputs: "John"

_name_age_job是私有变量,它们只能通过getNamegetAgegetJob方法来访问。

模块

包括很早之前给Jquery添加插件,将window、document、Jquery等作为输入参数,最后将插件挂载在Jquery插件上。或者一些库将库本身挂载在window对象,都是闭包在模块上的应用。

还有一种比较知名的,就是Webpack的打包产物,Webpack打包的产物是一个自执行函数,也可以被视为一个闭包。

这个函数包含了所有的模块代码。这个函数在执行时会创建一个私有的作用域,防止模块间的全局变量冲突。这个函数接收一个参数,通常是一个对象,这个对象映射了所有模块的引用。这样,模块就可以通过这个对象来引用其他模块。

因为这个立即执行函数可以访问并操作其外部的变量(即那个映射对象),所以它形成了一个闭包。

一个简化的Webpack打包结果

javascript 复制代码
(function(modules) {
    // ...Webpack bootstrap logic...
})([
    /* 0 */
    (function(module, exports) {
        // Module 0 code...
    }),
    /* 1 */
    (function(module, exports, __webpack_require__) {
        // Module 1 code...
        var module0 = __webpack_require__(0);
    }),
    // ...More modules...
]);

闭包还有其它一些应用。包括后面要介绍的高阶函数、设计模式等。只是出于分类标准的不同没有放在这里。

高阶函数

高阶函数是函数中的一个重要概念,它指的是至少满足下列一条的函数:

  1. 接受一个或多个函数作为输入
  2. 输出一个函数

在JavaScript中,高阶函数非常常见,例如数组的mapfilterreduce方法,以及函数的bind方法等。

一个简单的高阶函数示例

javascript 复制代码
// 定义一个高阶函数,它接受一个函数作为参数,并返回一个新的函数
function higherOrderFunction(fn) {
    return function() {
        console.log('Before');
        fn();
        console.log('After');
    }
}

// 定义一个普通函数
function sayHello() {
    console.log('Hello, world!');
}

// 使用高阶函数创建一个新的函数
let newFunction = higherOrderFunction(sayHello);

// 调用新的函数
newFunction();
// 输出:
// Before
// Hello, world!
// After

higherOrderFunction是一个高阶函数,它接受一个函数fn作为参数,并返回一个新的函数。这个新的函数在调用fn函数之前和之后,都会打印一条消息。

同时,高阶函数是函数式编程的一个重要工具,它可以让代码更加模块化和可重用。

防抖节流函数

防抖(debounce)和节流(throttle)本身都是高阶函数,返回一个函数,同时参数中也有函数。

防抖是指在一定时间内,事件被触发多次,但只执行一次(最后一次)回调函数。如果在这个时间内又被触发,则重新计算时间

javascript 复制代码
function debounce(func, wait) {
    var timeout;
    return function() {
        var context = this;
        var args = arguments;
        clearTimeout(timeout);
        timeout = setTimeout(function() {
            func.apply(context, args);
        }, wait);
    };
}

// 使用示例
var myEfficientFn = debounce(function() {
    // 执行一些高开销的操作
}, 250);

window.addEventListener('click', myEfficientFn);

节流是指在一定时间内只执行一次回调函数,保证稳定触发,降低触发频率

javascript 复制代码
const throttle = function (fn, interval) {
  var __self = fn // 保存需要被延迟执行的函数引用
  var timer // 定时器
  var firstTime = true // 是否是第一次调用
  return function () {
    var args = arguments
    var __me = this
    if (firstTime) { // 如果是第一次调用,不需延迟执行
      __self.apply(__me, args)
      return firstTime = false
    }
    if (timer) { // 如果定时器还在,说明前一次延迟执行还没有完成
      return false
    }
    timer = setTimeout(function () { // 延迟一段时间执行
      clearTimeout(timer)
      timer = null
      __self.apply(__me, args)
    }, interval || 500)
  }
}

// 使用示例
const myEfficientFn = throttle(function() {
    // 执行一些高开销的操作
}, 250);

window.addEventListener('resize', myEfficientFn);

缓存函数

高阶函数可以用来创建一个缓存函数,该函数可以缓存其他函数的结果,以提高性能

javascript 复制代码
function memoize(func) {
    var cache = {};
    return function() {
        var key = JSON.stringify(arguments);
        if (cache[key]) {
            return cache[key];
        } else {
            var val = func.apply(this, arguments);
            cache[key] = val;
            return val;
        }
    };
}

// 使用示例
var complexCalculation = function(x, y) {
    // 假设这是一个耗时的计算
    return x * y;
};

var memoizedCalculation = memoize(complexCalculation);

console.log(memoizedCalculation(5, 3)); // 计算结果并存入缓存
console.log(memoizedCalculation(5, 3)); // 从缓存中获取结果,不进行计算

memoize是一个高阶函数,它接受一个函数func作为参数,返回一个新的函数。这个新函数在被调用时会检查缓存中是否已经有这个函数调用的结果,如果有,就直接返回结果,否则,就调用func进行计算,并把结果存入缓存。

react的高阶组件

React的高阶组件(Higher-Order Component,HOC)就是一个高阶函数。它接受一个组件作为参数,返回一个新的组件

jsx 复制代码
// 高阶组件
function withLogging(WrappedComponent) {
    return class extends React.Component {
        componentDidMount() {
            console.log(`${WrappedComponent.name} is mounted`);
        }

        componentWillUnmount() {
            console.log(`${WrappedComponent.name} is unmounted`);
        }

        render() {
            return <WrappedComponent {...this.props} />;
        }
    }
}

// 原始组件
class MyComponent extends React.Component {
    render() {
        return <div>My Component</div>;
    }
}

// 使用高阶组件包装原始组件
const MyComponentWithLogging = withLogging(MyComponent);

// 使用新的组件
ReactDOM.render(<MyComponentWithLogging />, document.getElementById('root'));

withLogging就是一个高阶组件。它接受一个组件WrappedComponent作为参数,返回一个新的组件。这个新的组件在挂载和卸载时会打印日志,然后渲染WrappedComponent

可以使用withLogging来包装任何组件,例如MyComponent,得到一个新的组件MyComponentWithLogging。这个新的组件具有日志功能,其他方面和MyComponent完全一样。

vue的高阶组件

Vue的高阶组件(Higher-Order Component,HOC)也是一个高阶函数。它接受一个组件作为参数,返回一个新的组件

javascript 复制代码
// 高阶组件
function withLogging(WrappedComponent) {
    return {
        mounted() {
            console.log(`${WrappedComponent.name} is mounted`);
        },
        beforeDestroy() {
            console.log(`${WrappedComponent.name} is unmounted`);
        },
        render(createElement) {
            return createElement(WrappedComponent, {
                props: this.$props,
            });
        }
    }
}

// 原始组件
const MyComponent = {
    name: 'MyComponent',
    props: ['message'],
    template: '<div>{{ message }}</div>'
}

// 使用高阶组件包装原始组件
const MyComponentWithLogging = withLogging(MyComponent);

// 使用新的组件
new Vue({
    el: '#app',
    components: {
        MyComponentWithLogging
    }
});

例子中,withLogging是一个高阶组件。它接受一个组件WrappedComponent作为参数,返回一个新的组件。这个新的组件在挂载和卸载时会打印日志,然后渲染WrappedComponent

使用withLogging来包装任何组件,例如MyComponent,得到一个新的组件MyComponentWithLogging。这个新的组件具有日志功能,其他方面和MyComponent完全一样。

常用的容易被忽视的函数

还有许多知名且常用的函数

  1. console.log(): 在控制台打印信息,常用于调试。

  2. alert(): 弹出一个警告框。

  3. parseInt()parseFloat(): 将字符串转换为整数或浮点数。

  4. isNaN(): 检查一个值是否是非数字。

  5. Array.prototype.map(): 对数组的每个元素执行一个函数,并返回一个新的数组。

  6. Array.prototype.filter(): 返回一个新的数组,数组中的元素都满足一个条件。

  7. Array.prototype.reduce(): 对数组的每个元素执行一个函数,并将结果累积起来。

  8. Array.prototype.forEach(): 对数组的每个元素执行一个函数。

  9. Object.keys(): 返回一个对象的所有键。

  10. Object.values(): 返回一个对象的所有值。

  11. Object.entries(): 返回一个对象的所有键值对。

  12. JSON.parse(): 将一个JSON字符串转换为一个JavaScript对象。

  13. JSON.stringify(): 将一个JavaScript对象转换为一个JSON字符串。

  14. Promise: 用于处理异步操作,可以用thencatchfinally方法来处理成功、失败和结束的情况。

以上只是众多函数的一部分,还有许多其他的内置函数和对象方法,这里就不一一列举了。

设计模式

什么是设计模式,有人说设计模式是因为开发语言有缺陷,所有才有了设计模式。我不敢苟同,我觉得设计模式是一系列成熟的套路或者解决方案。开发语言不能什么都做。

单例设计模式

单例设计模式保证一个类只有一个实例,并提供一个全局访问点来获取这个唯一的实例

javascript 复制代码
var getSingle = function( fn ){ 
   var result; 
   return function(){    
     return result || ( result = fn .apply(this, arguments )); 
   } 
 };

这是来自《JavaScript设计模式与开发实践》的通用单例设计模式。

工厂模式

工厂模式是一种创建对象的设计模式,它提供了一种方式来封装创建特定类型对象的逻辑

javascript 复制代码
function CarMaker() {}

CarMaker.prototype.drive = function() {
    return "Vroom, I have " + this.doors + " doors";
};

CarMaker.factory = function(type) {
    var constr = type,
        newcar;

    if (typeof CarMaker[constr] !== "function") {
        throw {
            name: "Error",
            message: constr + " doesn't exist"
        };
    }

    if (typeof CarMaker[constr].prototype.drive !== "function") {
        CarMaker[constr].prototype = new CarMaker();
    }

    newcar = new CarMaker[constr]();
    return newcar;
};

CarMaker.Compact = function() {
    this.doors = 4;
};

CarMaker.Convertible = function() {
    this.doors = 2;
};

CarMaker.SUV = function() {
    this.doors = 24;
};

var corolla = CarMaker.factory('Compact');
var solstice = CarMaker.factory('Convertible');
var cherokee = CarMaker.factory('SUV');

console.log(corolla.drive()); // "Vroom, I have 4 doors"
console.log(solstice.drive()); // "Vroom, I have 2 doors"
console.log(cherokee.drive()); // "Vroom, I have 24 doors"

CarMaker函数是一个工厂,它有一个静态方法factory,方法接受一个类型参数,然后创建并返回一个相应类型的新对象。CarMaker还定义了几个子类型:CompactConvertibleSUV,每个子类型都有一个doors属性,表示车门的数量。

可以使用CarMaker.factory方法来创建不同类型的车辆对象,例如corollasolsticecherokee。这些对象都有一个drive方法,这个方法会返回一个字符串,表示这个车辆的类型和车门的数量。

命令模式

命令模式是一种行为设计模式,它将一个请求封装为一个对象,从而可以用不同的请求对客户进行参数化

javascript 复制代码
// 命令接收者
var Light = {
    turnOn: function() {
        console.log("The light is on");
    },
    turnOff: function() {
        console.log("The light is off");
    }
};

// 命令对象
var LightOnCommand = {
    execute: function() {
        Light.turnOn();
    }
};

var LightOffCommand = {
    execute: function() {
        Light.turnOff();
    }
};

// 命令调用者
var Switch = {
    executeCommand: function(command) {
        command.execute();
    }
};

// 使用命令
Switch.executeCommand(LightOnCommand); // 输出: "The light is on"
Switch.executeCommand(LightOffCommand); // 输出: "The light is off"

Light对象是命令接收者,它有两个方法:turnOnturnOffLightOnCommandLightOffCommand是命令对象,它们有一个execute方法,这个方法会调用Light对象的方法。Switch对象是命令调用者,它有一个executeCommand方法,这个方法会调用命令对象的execute方法。

然后可以使用Switch.executeCommand方法来执行不同的命令,例如LightOnCommandLightOffCommand。这样就可以通过命令对象来封装对Light对象的操作,使得Switch对象不需要直接操作Light对象。

设计模式总结

实际因为函数是一等公民缘故,设计模式没有不涉及函数的。这里只是写了几个,还有其它:代理模式、中介者模式、模板方法模式、装饰者模式等等。感兴趣可以阅读《JavaScript设计模式和开发实践》这本书。

开发范式

开发范式是软件开发过程中所采用的一种方法或模式,用于指导和规范开发人员在设计、编码和测试等环节中的行为和决策。它是一种规范,旨在提高开发效率、代码质量和可维护性。

其他领域同样存在范式,比如教育范式、经济范式、设计范式等。

面向切面编程

面向切面编程(Aspect-Oriented Programming,AOP)是一种编程范式,其目标是提高模块化程度,以提高代码的可重用性和可维护性。

在传统的面向对象编程(OOP)中,通过类和对象来组织和封装数据和行为。然而,有些行为是跨越多个类和对象的,例如日志记录、事务管理、安全检查等。这些行为被称为"横切关注点"(cross-cutting concerns),因为它们"横切"了多个模块。

在OOP中,处理横切关注点通常需要在多个地方复制和粘贴代码,这会导致代码冗余和难以维护。而AOP的目标就是将这些横切关注点模块化,使它们可以被独立地添加到程序中。

AOP的主要优点是提高了代码的模块化,使得核心关注点和横切关注点可以独立地开发和测试,从而提高了代码的可重用性和可维护性。然而,AOP也有一些缺点,例如增加了代码的复杂性,可能会导致性能问题,以及可能会引入新的bug。

方法拦截

在Vue 2中,对数组的pushsplice等方法的拦截是通过重写这些方法来实现的。Vue 2在内部创建了一个改写过的数组方法的版本,当这些方法被调用时,它们不仅会执行原生的数组操作,还会通知Vue进行依赖追踪和视图更新。

以下是一个简化的示例,展示了Vue是如何重写数组的push方法的:

javascript 复制代码
// 获取数组原型
const arrayProto = Array.prototype;

// 创建一个新的对象,继承自数组原型
const arrayMethods = Object.create(arrayProto);

// 重写数组的push方法
arrayMethods.push = function (...items) {
  // 先调用原生的push方法
  const result = arrayProto.push.apply(this, items);

  // 获取到观察者实例
  const ob = this.__ob__;

  // 对新添加的元素进行观察
  ob.observeArray(items);

  // 通知依赖进行更新
  ob.dep.notify();

  // 返回push方法的结果
  return result;
};

// 在Vue中,会将数据的__proto__指向arrayMethods,从而实现对数组方法的拦截

示例中首先获取到数组的原型,然后创建一个新的对象,这个对象继承自数组的原型。然后在这个新的对象上重写push方法。在重写的push方法中,首先调用原生的push方法,然后获取到观察者实例,对新添加的元素进行观察,最后通知依赖进行更新。

当对一个数组进行观察时,Vue会将这个数组的__proto__指向arrayMethods,从而实现对数组方法的拦截。当调用数组的push方法时,实际上调用的是arrayMethods上的push方法。

添加after和before

在JavaScript中,可以通过修改Function.prototype来给所有的函数添加afterbefore方法

javascript 复制代码
Function.prototype.before = function(beforeFn) {
    var self = this; // 保存原函数的引用
    return function() { // 返回包含了原函数和新函数的"代理"函数
        beforeFn.apply(this, arguments); // 执行新函数
        return self.apply(this, arguments); // 执行原函数
    }
};

Function.prototype.after = function(afterFn) {
    var self = this;
    return function() {
        var ret = self.apply(this, arguments);
        afterFn.apply(this, arguments);
        return ret;
    }
};

// 使用示例
function log() {
    console.log('Hello, world!');
}

log = log.before(function() {
    console.log('Before');
}).after(function() {
    console.log('After');
});

log(); 
// 输出:
// Before
// Hello, world!
// After

示例中before方法接受一个函数作为参数,返回一个新的函数。这个新的函数在执行原函数之前,先执行传入的函数。after方法类似,只不过它是在执行原函数之后,再执行传入的函数。

函数式编程

函数式编程(Functional Programming)是一种编程范式,它的核心思想是使用函数来抽象数据计算。函数式编程语言最重要的基础是λ演算(lambda calculus),而且λ演算的函数可以接受函数当作输入(引数)和输出(返回值)。

函数式编程的一些主要原理和特点:

  1. 纯函数(Pure functions):纯函数是这样的函数,给定相同的输入,总是返回相同的输出,而且没有任何可观察的副作用,比如网络请求,输入和输出设备,或者数据突变。

  2. 不可变性(Immutability):在函数式编程中,数据是不可变的,也就是说,一旦一个数据结构被创建,那么不能再改变它。如果需要修改这个数据结构,必须创建一个新的数据结构。

  3. 函数是一等公民(First-class functions):在函数式编程语言中,函数被视为一等公民。这意味着函数可以作为其他函数的参数,也可以作为其他函数的返回值,还可以赋值给变量。

  4. 高阶函数(Higher-order functions):高阶函数是接受其他函数作为参数,或者返回其他函数作为结果的函数。

  5. 递归(Recursion):由于函数式编程避免使用循环和迭代,因此递归在函数式编程中非常重要,它是实现循环的主要方式。

  6. 引用透明(Referential transparency):如果一个函数对于相同的输入总是产生相同的结果,那么它就是引用透明的。这意味着函数的调用可以被它的返回值所替代,而不会改变程序的行为。

这些原理和特点共同构成了函数式编程的基础,它们使得函数式编程具有高度的模块化和可重用性,也使得代码更加清晰和易于理解。

不可变性

在函数式编程中,数据是不可变的,一旦创建,不能改变它。

javascript 复制代码
const arr = [1, 2, 3, 4, 5];
const newArr = arr.map(x => x * 2);

例子中没有改变原始的数组,而是创建了一个新的数组newArr

函数组合

函数组合是一种将多个函数组合成一个新函数的技术,新函数的输出是由原函数按照一定顺序计算得出的。

javascript 复制代码
function compose(f, g) {
    return function(x) {
        return f(g(x));
    };
}

例子中,compose函数就是一个函数组合的例子,它接受两个函数fg,然后返回一个新的函数,这个新的函数的输出是由fg按照一定顺序计算得出的。

引用透明

如果一个函数对于相同的输入总是产生相同的结果,那么它就是引用透明的。这意味着函数的调用可以被它的返回值所替代,而不会改变程序的行为

javascript 复制代码
function add(a, b) {
    return a + b;
}

var sum = add(1, 2); // sum is 3

例子中,add函数只要输入相同(在这个例子中,输入是1和2),它总是返回相同的结果(在这个例子中,结果是3)。因此可以用add(1, 2)的返回值3来替代sum,而不会改变程序的行为。

函数的链式调用

在JavaScript中,实现函数的链式调用通常需要返回一个对象,这个对象包含了可以被链式调用的方法。每个方法在执行完毕后,都需要返回这个对象,以便进行下一次的链式调用。

一个简单的示例

javascript 复制代码
function MyObject() {
    this.value = 0;
}

MyObject.prototype.add = function(number) {
    this.value += number;
    return this; // 返回this以支持链式调用
};

MyObject.prototype.subtract = function(number) {
    this.value -= number;
    return this; // 返回this以支持链式调用
};

MyObject.prototype.print = function() {
    console.log(this.value);
    return this; // 返回this以支持链式调用
};

// 使用示例
var obj = new MyObject();
obj.add(5).subtract(3).print(); // 输出2

MyObject是一个构造函数,它创建了一个包含value属性的对象。addsubtractprint方法都是MyObject的原型方法,它们可以被MyObject的实例链式调用。每个方法在执行完毕后,都返回this,即当前的MyObject实例,以便进行下一次的链式调用。

总结一下

函数的应用还有很多,上面算是个人的一个总结。

  1. 函数的参数变化可以有函数柯里化、反柯里化
  2. 函数的种类根据不同划分标准:纯函数、闭包、高阶函数。三者都需要理解
  3. 闭包的应用以及高阶函数的应用除了上面还有很多
  4. 设计模式是一系列成熟的套路和解决方案,不仅仅限于JavaScript语言
  5. 开发范式:除了面相切面编程和函数式编程,面向对象编程和面向过程编程也会用到函数,这是因为函数是JavaScript一等公民,因为函数是功能单元

本文完。

相关推荐
熊的猫14 分钟前
webpack 核心模块 — loader & plugins
前端·javascript·chrome·webpack·前端框架·node.js·ecmascript
速盾cdn21 分钟前
速盾:vue的cdn是干嘛的?
服务器·前端·网络
四喜花露水1 小时前
Vue 自定义icon组件封装SVG图标
前端·javascript·vue.js
前端Hardy1 小时前
HTML&CSS: 实现可爱的冰墩墩
前端·javascript·css·html·css3
web Rookie2 小时前
JS类型检测大全:从零基础到高级应用
开发语言·前端·javascript
Au_ust2 小时前
css:基础
前端·css
帅帅哥的兜兜2 小时前
css基础:底部固定,导航栏浮动在顶部
前端·css·css3
工业甲酰苯胺2 小时前
C# 单例模式的多种实现
javascript·单例模式·c#
yi碗汤园2 小时前
【一文了解】C#基础-集合
开发语言·前端·unity·c#
就是个名称2 小时前
购物车-多元素组合动画css
前端·css