核心前提:JavaScript 中函数是一等公民 (可作为参数 / 返回值 / 赋值),这是所有高阶技巧的基础;

AOP(面向切面编程)

  1. 概念:AOP (Aspect Oriented Programming) 核心是不修改原函数代码,在函数执行的「前置 / 后置 / 异常」阶段插入额外逻辑 (如日志、埋点、性能监控),实现业务逻辑与通用逻辑解耦;

  2. 实现原理:通过重写函数的 prototype (或直接包装函数),在原函数执行前后注入逻辑;

  3. 手写实现:

    // 给 Function 原型扩展前置/后置切面方法
    Function.prototype.before = function (beforeFn) {
      const self = this; // 保存原函数引用
      return function (...args) {
        beforeFn.apply(this, args); // 执行前置逻辑
        return self.apply(this, args); // 执行原函数并返回结果
      };
    };
    
    Function.prototype.after = function (afterFn) {
      const self = this;
      return function (...args) {
        const result = self.apply(this, args); // 先执行原函数
        afterFn.apply(this, args); // 执行后置逻辑
        return result; // 返回原函数结果
      };
    };
    
    // 测试
    function coreFn(name) {
      console.log(`核心逻辑:处理 ${name} 的请求`);
      return `结果:${name} 处理完成`;
    }
    
    // 包装核心函数:前置埋点,后置日志
    const wrappedFn = coreFn
      .before((name) => console.log(`[前置] 埋点:${name} 请求开始`))
      .after((name) => console.log(`[后置] 日志:${name} 请求结束`));
    
    console.log(wrappedFn("用户A"));
    // 输出:
    // [前置] 埋点:用户A 请求开始
    // 核心逻辑:处理 用户A 的请求
    // [后置] 日志:用户A 请求结束
    // 结果:用户A 处理完成
    
  4. 应用场景:

    1. 接口请求的前置参数校验、后置响应格式化;
    2. 埋点统计 (如按钮点击前记录用户行为)
    3. 性能监控 (记录函数执行耗时)

单例模式

  1. 概念:保证一个类全局只有一个实例,且提供唯一访问入口 (如 Vuex 的 store、浏览器的 window 对象)

  2. 实现方式:

    const Singleton = (function () {
      let instance = null; // 闭包保存实例
    
      function createInstance() {
        // 真实业务逻辑:创建实例(如初始化配置、连接数据库)
        return {
          name: "单例实例",
          doSomething() {
            console.log("执行单例方法");
          },
        };
      }
    
      return {
        getInstance() {
          if (!instance) {
            instance = createInstance();
          }
          return instance;
        },
      };
    })();
    
    // 测试:多次获取实例是否为同一个
    const a = Singleton.getInstance();
    const b = Singleton.getInstance();
    console.log(a === b); // true
    
    function createModal() {
      // 模拟创建弹窗(重量级操作,惰性执行)
      const div = document.createElement("div");
      div.innerHTML = "全局唯一弹窗";
      div.style.display = "none";
      document.body.appendChild(div);
      return div;
    }
    
    // 通用单例包装函数
    function getSingleton(fn) {
      let instance = null;
      return function () {
        return instance || (instance = fn.apply(this, arguments));
      };
    }
    
    // 包装创建弹窗的函数
    const createSingleModal = getSingleton(createModal);
    
    // 测试:两次调用返回同一个DOM元素
    const modal1 = createSingleModal();
    const modal2 = createSingleModal();
    console.log(modal1 === modal2); // true
    
  3. 应用场景:

    1. 全局弹窗、loading 组件;
    2. 数据库连接池、Redis 客户端;
    3. Vuex/Piniastore 实例;

惰性函数

  1. 概念:函数首次执行时动态重写自身逻辑,后续执行直接使用重写后的逻辑,避免重复判断 (如环境检测、兼容性处理)

  2. 实现原理:利用函数的可重写特性,首次执行时将函数体替换为目标逻辑,后续调用无需再走判断分支;

  3. 手写实现:

    function addEvent(element, type, handler) {
      // 首次执行:检测环境并重写函数
      if (element.addEventListener) {
        addEvent = function (element, type, handler) {
          element.addEventListener(type, handler, false);
        };
      } else if (element.attachEvent) {
        addEvent = function (element, type, handler) {
          element.attachEvent(`on${type}`, handler);
        };
      } else {
        addEvent = function (element, type, handler) {
          element[`on${type}`] = handler;
        };
      }
      // 执行重写后的函数
      addEvent(element, type, handler);
    }
    
    // 测试:首次调用重写函数,后续直接执行对应逻辑
    const btn = document.getElementById("btn");
    addEvent(btn, "click", () => console.log("点击了")); // 首次执行重写
    addEvent(btn, "click", () => console.log("再次点击")); // 直接执行 addEventListener
    
    function getViewportSize() {
      // 首次执行:重写函数
      getViewportSize = function () {
        return {
          width: window.innerWidth || document.documentElement.clientWidth,
          height: window.innerHeight || document.documentElement.clientHeight,
        };
      };
      // 执行重写后的逻辑
      return getViewportSize();
    }
    
    // 测试:首次调用重写,后续直接返回结果
    console.log(getViewportSize()); // 首次调用:执行 “重写函数 + 兼容性判断 + 返回结果”,这一步和常规写法耗时一致
    console.log(getViewportSize()); // 后续调用:直接执行重写后的函数(仅返回视口大小,无任何兼容性判断),省去了每次的 || 逻辑运算
    
  4. 应用场景:

    1. 浏览器兼容性处理 (如事件绑定、Promise 降级)
    2. 高频调用的工具函数 (如获取设备信息、计算位置)
    3. 避免重复的环境检测 (如判断是否为移动端)

手写 bind 函数

  1. 概念:bind 是函数原型方法,作用是绑定函数的 this 指向,并支持预设参数,返回一个新函数(执行时才调用原函数);

  2. 核心特性:

    1. 绑定 this 指向,不受调用方式影响;
    2. 支持柯里化传参 (预设参数)
    3. 绑定后的函数可被 new 调用 (此时 this 指向实例,忽略绑定的 this)
  3. 手写实现:

    Function.prototype.myBind = function (context, ...bindArgs) {
      // 1. 校验调用者是否为函数
      if (typeof this !== "function") {
        throw new TypeError("Bind must be called on a function");
      }
    
      const self = this; // 保存原函数
    
      // 2. 定义返回的新函数(使用剩余参数接收调用时的参数)
      const boundFn = function (...callArgs) {
        // 合并bind时的参数和调用时的参数
        const finalArgs = [...bindArgs, ...callArgs];
        // 关键:new调用时this指向实例,否则指向bind的context
        return self.apply(this instanceof boundFn ? this : context, finalArgs);
      };
    
      // 3. 继承原函数的原型(保证new调用时原型链正确)
      boundFn.prototype = Object.create(self.prototype);
      // 修复constructor指向
      boundFn.prototype.constructor = boundFn;
    
      return boundFn;
    };
    
    // 测试
    const person = { name: "张三" };
    function sayHi(age, gender) {
      console.log(`我是${this.name},${age}岁,${gender}`);
    }
    
    // 绑定 this 和预设参数
    const boundSayHi = sayHi.myBind(person, 20);
    boundSayHi("男"); // 输出:我是张三,20岁,男
    
    // new 调用(忽略绑定的this)
    const instance = new boundSayHi("女"); // 输出:我是 undefined,20岁,女(this指向instance)
    console.log(instance instanceof sayHi); // true
    console.log(instance.constructor === sayHi); // 验证原型链正确性(true)
    
  4. 应用场景

    1. 固定函数的 this 指向 (如 React 类组件中绑定事件处理函数)
    2. 预设函数参数 (柯里化场景)
    3. 延迟执行函数 (如定时器中绑定 this)

函数柯里化

  1. 概念:柯里化 (Currying) 是将多参数函数拆解为一系列单参数函数,逐步接收参数,直到参数齐全后执行原函数;

  2. 核心:分步传参延迟执行

  3. 实现原理:利用闭包保存已传入的参数,返回新函数接收剩余参数,直到参数数量满足原函数要求;

  4. 手写实现:

    function curry(fn) {
      const argLength = fn.length; // 原函数的参数个数
    
      // 改用递归思路,每次传入已收集的参数
      function curried(...collectedArgs) {
        // 若参数已凑齐,执行原函数
        if (collectedArgs.length >= argLength) {
          return fn.apply(this, collectedArgs.slice(0, argLength)); // slice 防止参数过多
        }
        // 未凑齐则返回新函数,新函数调用时合并参数
        return function (...newArgs) {
          return curried.apply(this, collectedArgs.concat(newArgs));
        };
      }
    
      return curried;
    }
    
    // 测试
    function sum(a, b, c) {
      return a + b + c;
    }
    
    const curriedSum = curry(sum);
    console.log(curriedSum(1)(2)(3)); // 6
    console.log(curriedSum(1, 2)(3)); // 6
    console.log(curriedSum(1, 2, 3)); // 6
    
    function curryAdvanced(fn) {
      const execute = (...collectedArgs) => {
        // 返回一个新函数,用于接收下一批参数
        return (...newArgs) => {
          // 若传入空参数(调用()),执行原函数并返回结果
          if (newArgs.length === 0) {
            return fn(...collectedArgs);
          }
          // 否则拼接参数,继续返回接收函数
          return execute(...collectedArgs, ...newArgs);
        };
      };
      return execute();
    }
    
    // 测试
    const sumAdvanced = (...args) => args.reduce((a, b) => a + b, 0);
    const curriedSumAdv = curryAdvanced(sumAdvanced);
    
    console.log(curriedSumAdv(1)(2)(3)()); // 6(最后传空参数执行)
    console.log(curriedSumAdv(1, 2)(3, 4)()); // 10
    
  5. 核心场景:

    // 基础请求函数
    function request(method, baseURL, url, data) {
      console.log(`[${method}] ${baseURL}${url}`, data);
      // 实际业务中:return axios({ method, url: baseURL + url, data, headers });
    }
    
    // 柯里化请求函数
    const curryRequest = curry(request);
    
    // 1. 绑定 GET 方法 + 固定 baseURL(复用:所有GET请求都用这个baseURL)
    const getRequest = curryRequest('GET', 'https://api.example.com');
    
    // 2. 复用:不同接口只需传 url + data
    const getUser = getRequest('/user');
    const getOrder = getRequest('/order');
    
    // 调用:无需重复传 method 和 baseURL
    getUser({ id: 1 }); // [GET] https://api.example.com/user {id:1}
    getOrder({ orderId: 100 }); // [GET] https://api.example.com/order {orderId:100}
    
    // 基础验证函数:(规则, 值) => 布尔值
    function validate(rule, value) {
      const rules = {
        mobile: /^1[3-9]\d{9}$/,
        email: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
        required: (v) => !!v.trim()
      };
      return rules[rule].test ? rules[rule].test(value) : rules[rule](value);
    }
    
    // 柯里化验证函数
    const curryValidate = curry(validate);
    
    // 1. 绑定规则:生成专用验证函数(复用)
    const validateMobile = curryValidate('mobile');
    const validateEmail = curryValidate('email');
    const validateRequired = curryValidate('required');
    
    // 2. 调用:只需传校验值
    console.log(validateMobile('13800138000')); // true
    console.log(validateEmail('test@example.com')); // true
    console.log(validateRequired('')); // false
    
    // 基础筛选函数:(分类, 时间范围, 状态, 列表) => 筛选结果
    function filterList(category, timeRange, status, list) {
      return list.filter(item => 
        item.category === category && 
        item.time >= timeRange.start && 
        item.time <= timeRange.end && 
        item.status === status
      );
    }
    
    // 柯里化筛选函数
    const curryFilter = curry(filterList);
    
    // 分步收集参数:
    // 1. 第一步:选分类(如「商品」)
    const filterGoods = curryFilter('goods');
    
    // 2. 第二步:选时间范围(如近7天)
    const filterGoods7d = filterGoods({ start: Date.now() - 7*24*3600*1000, end: Date.now() });
    
    // 3. 第三步:选状态(如「已上架」),参数齐全,执行筛选
    const filterGoods7dOnSale = filterGoods7d('onSale');
    
    // 调用:传入列表,完成筛选
    const list = [{ category: 'goods', time: Date.now(), status: 'onSale' }, ...];
    console.log(filterGoods7dOnSale(list));
    
    // 基础计算函数:(单价, 数量, 折扣) => 最终价格
    function calcPrice(price, count, discount) {
      return price * count * discount;
    }
    
    const curryCalc = curry(calcPrice);
    
    // 第一步:绑定单价(如 99 元)
    const calc99 = curryCalc(99);
    
    // 第二步:绑定数量(如 2 件)
    const calc99x2 = calc99(2);
    
    // 第三步:绑定折扣(如 0.8),执行计算
    console.log(calc99x2(0.8)); // 99*2*0.8 = 158.4
    
    // 柯里化工具函数
    const add = curry((a, b) => a + b);
    const multiply = curry((a, b) => a * b);
    const toNumber = (str) => Number(str);
    
    // 函数组合:pipe(从左到右执行)
    const pipe = (...fns) => (x) => fns.reduce((acc, fn) => fn(acc), x);
    
    // 组合逻辑:转数字 → 加10 → 乘2
    const processNum = pipe(toNumber, add(10), multiply(2));
    
    // 调用
    console.log(processNum('5')); // (5 → 5+10=15 → 15*2=30) → 30
    
    import React from 'react';
    
    // 柯里化点击处理函数:(id, e) => 逻辑
    const handleItemClick = curry((id, e) => {
      console.log(`点击了ID为${id}的项`, e);
    });
    
    function ItemList() {
      const list = [{ id: 1, name: '项1' }, { id: 2, name: '项2' }];
      
      return (
        <ul>
          {list.map(item => (
            // 提前绑定 id,渲染时不创建新函数(优化性能)
            // 若不用柯里化,需写 onClick={(e) => handleItemClick(item.id, e)},每次渲染都会创建新匿名函数,影响性能
            <li key={item.id} onClick={handleItemClick(item.id)}>
              {item.name}
            </li>
          ))}
        </ul>
      );
    }
    

扁平化(数组 / 函数)

前置知识

  1. reduce 的语法

    /**
     * callback 回调函数
     *  - accumulator 累计器
     *  - currentValue 当前值
     *  - index 当前索引
     *  - array 调用 reduce 的数组
     * initialValue 为第一次调用 callback 函数时的第一个参数的值,若没有提供初始值,则将使用数组中第一个元素
     */
    arr.reduce(callback(accumulator, currentValue, index, array), initialValue)
    
  2. 使用 reduce 的第一个参数,每次调用的参数和返回值如下表:

    [0, 1, 2, 3, 4].reduce(function (accumulator, currentValue, currentIndex, array) {
      return accumulator + currentValue;
    });
    
    callback accumulator currVal Index array return value
    第一次调用 0 1 1 [0, 1, 2, 3, 4] 1
    第二次调用 1 2 2 [0, 1, 2, 3, 4] 3
    第三次调用 3 3 3 [0, 1, 2, 3, 4] 6
    第四次调用 6 4 4 [0, 1, 2, 3, 4] 10
  3. 提供一个初始值作为 reduce 方法的第二个参数,每次调用的参数和返回值如下表:

    [0, 1, 2, 3, 4].reduce((accumulator, currentValue, currentIndex, array) => {
      return accumulator + currentValue
    }, 10)
    
    callback accumulator currVal Index array return value
    第一次调用 10 0 0 [0, 1, 2, 3, 4] 10
    第二次调用 10 1 1 [0, 1, 2, 3, 4] 11
    第三次调用 11 2 2 [0, 1, 2, 3, 4] 13
    第四次调用 13 3 3 [0, 1, 2, 3, 4] 16
    第五次调用 16 4 4 [0, 1, 2, 3, 4] 20

数组扁平化

  1. 概念:将多维数组转换为一维数组 (如 [1, [2, [3]]] → [1, 2, 3])

  2. 手写实现:

    function flatten(arr) {
      let result = [];
      for (let item of arr) {
        if (Array.isArray(item)) {
          result = result.concat(flatten(item)); // 递归处理子数组
        } else {
          result.push(item);
        }
      }
      return result;
    }
    
    console.log(flatten([1, [2, [3, 4], 5]])); // [1,2,3,4,5]
    
    function flattenReduce(arr) {
      return arr.reduce((acc, cur) => {
        return acc.concat(Array.isArray(cur) ? flattenReduce(cur) : cur);
      }, []);
    }
    console.log(flattenReduce([1, [2, [3, 4], 5]])); // [1,2,3,4,5]
    
    function flattenES6(arr) {
      while (arr.some(item => Array.isArray(item))) {
        arr = [].concat(...arr);
      }
      return arr;
    }
    console.log(flattenES6([1, [2, [3, 4], 5]])); // [1,2,3,4,5]
    
    // flat(depth):depth 为扁平化深度,Infinity 表示无限深度
    console.log([1, [2, [3, 4], 5]].flat(Infinity)); // [ 1, 2, 3, 4, 5 ]
    

函数扁平化(函数组合)

  1. 概念:将多个嵌套执行的函数「扁平化」为线性执行 (如 fn3(fn2(fn1(x))) → compose(fn3, fn2, fn1)(x))

  2. 手写实现:

    // compose:从右到左执行(如 compose(f,g,h) → x => f(g(h(x))))
    function compose(...fns) {
      if (fns.length === 0) return (x) => x; // 无函数时返回原值
      if (fns.length === 1) return fns[0]; // 单个函数直接返回
      return fns.reduce((a, b) => (...args) => a(b(...args)));
    }
    
    // 测试
    const add1 = (x) => x + 1;
    const mul2 = (x) => x * 2;
    const sub3 = (x) => x - 3;
    
    // compose:sub3(mul2(add1(10))) → (10+1)*2-3 = 19
    console.log(compose(sub3, mul2, add1)(10)); // 19
    
    // pipe:从左到右执行(如 pipe(f,g,h) → x => h(g(f(x))))
    function pipe(...fns) {
      if (fns.length === 0) return (x) => x; // 无函数时返回原值
      if (fns.length === 1) return fns[0]; // 单个函数直接返回
      return fns.reduce((a, b) => (...args) => b(a(...args)));
    }
    
    // 测试
    const add1 = (x) => x + 1;
    const mul2 = (x) => x * 2;
    const sub3 = (x) => x - 3;
    
    // pipe:add1(mul2(sub3(10))) → (10-3)*2+1 = 15
    console.log(pipe(sub3, mul2, add1)(10)); // 15
    
  3. 应用场景:

    1. 数组扁平化:处理后端返回的多维数据、树形结构转一维;
    2. 函数扁平化:React 中间件、Reduxcompose 增强器、函数式编程中的逻辑组合;
打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

你好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. AOP(面向切面编程)
  2. 2. 单例模式
  3. 3. 惰性函数
  4. 4. 手写 bind 函数
  5. 5. 函数柯里化
  6. 6. 扁平化(数组 / 函数)
    1. 6.1. 前置知识
    2. 6.2. 数组扁平化
    3. 6.3. 函数扁平化(函数组合)