跳到主要内容

编程思想

函数式编程

基础

函数
  • 关注程序的输入和输出;
  • 忽略程序的过程;
  • 将程序的执行过程抽象为一个函数;
// 命令式编程
const array = [0, 1, 2, 3];
for (let i = 0; i < array.length; i++) {
array[i] = Math.pow(array[i], 2);
}

// 函数式方式
[0, 1, 2, 3].map((num) => Math.pow(num, 2));
纯函数
  • 数据不可变 + 无状态性 + 固定输入-输出;
    • 无状态性:不依赖也不会改变外部作用域的变量;
    • 数据不可变:函数不会改变输入数据;
    • 固定输入-输出:相同的输入必然是相同的输出;
副作用函数
  • 函数本身进行与函数返回值无关的操作,即修改外部状态;
    • 发送网络请求;
    • 修改 DOM;
    • 事件请求;
    • 本地存储;
    • 状态管理;

柯里化

思想
  • 把传递多个参数的函数 A 变换成一系列只接受一个参数的函数 B;
  • 函数 B 并返回一个函数 C,函数 C 接受函数 A 的第二个参数,以此类推;
  • 常用于函数式编程;
自定义柯里化
const curry = (fn, ...args) => {
if (fn.length <= args.length) {
return fn.apply(this, args);
} else {
return curry.bind(this, fn, ...args);
}
};

function sum(a, b, c) {
return a + b + c;
}

let curriedSum = curry(sum);
console.log(sum(1, 2, 3));
console.log(curriedSum(1, 2, 3));
console.log(curriedSum(1, 2)(3));
console.log(curriedSum(1)(2)(3));

组合和管道

组合
  • 将多个函数组合成一个函数;
  • 从右向左执行他的组成函数,并将上一个的输出作为下一个的输入;
const compose = (...fns) => {
return (x) => {
return fns.reduceRight((beforeRes, fn) => {
return fn(beforeRes);
}, x);
};
};

const addOne = (x) => x + 1;
const double = (x) => x * 2;
const subtractThree = (x) => x - 3;

const composedFunc = compose(subtractThree, double, addOne);
console.log(composedFunc(3)); // 5
管道
  • 将多个函数组合成一个函数;
  • 从左向右执行他的组成函数,并将上一个的输出作为下一个的输入;
const pipe = (...fns) => {
return (x) => {
return fns.reduce((beforeRes, fn) => {
return fn(beforeRes);
}, x);
};
};

const addOne = (x) => x + 1;
const double = (x) => x * 2;
const subtractThree = (x) => x - 3;
const pipedFunc = pipe(addOne, double, subtractThree);
console.log(pipedFunc(3)); // 5

函数缓存

  • 基于柯里化,闭包和哈希表实现函数缓存;
const memoize = function (fn, context) {
let cache = Object.create(null);
context = context || window;
return (...args) => {
if (!cache[args]) {
cache[args] = fn.apply(context, args);
}
return cache[args];
};
};

const add = (a, b) => a + b;
const calc = memoize(add);
const num1 = calc(100, 200);
const num2 = calc(100, 200); // 缓存得到的结果

编码技巧

if 优化

口诀
  • 互斥条件表驱动;
  • 嵌套条件校验链;
    • 同时达成的条件使用 && 结合在一起;
  • 短路条件早 return;
  • 零散条件可组合;
    • 上面三种条件自定义组合;
互斥条件表驱动
// 相互冲突的条件使用表驱动优化
// 利用 key-handler, 通过 key 执行对应 handler;
const day = new Date().getDay()
let day_zh;
if(day === 0){
day_zh = '星期日'
}else if(day === 1) {
day_zh = '星期一'
}
...
else{
day_zh = '星期六'
}
// 表驱动
const week = ['星期日', '星期一',..., '星期六']
const day = new Date().getDay(
const day_zh = week[day]

递归函数

/**
* 该函数用于数组形式的规则递归节点, 思想很巧妙
* data 为数据, key 可作为操作中止的判断条件, callback 为操作
* 首先根据 key (可选) 判断是否满足判断条件
* 满足则使用 callback 进行操作
* callback 有 value, index, data 三个参数, 这也算是一个最佳实践, 记住即可
* 不满足则递归使用 loop 函数, 将其子节点作为新的 data
* @params data 数据
* @params key 节点标识属性
* @params callback 操作
*/
const loop = (
data: Layer[],
key: string,
callback: (value: Layer, index: number, data: Layer[]) => void
) => {
for (let i = 0; i < data.length; i++) {
if (data[i].key === key) {
return callback(data[i], i, data);
}
if (data[i].children) {
loop(data[i].children, key, callback);
}
}
};

API 设计

前端 API

API 格式
interface ApiResponse<T> {
status: "pending" | "success" | "error";
data: T;
message: string;
}
自定义 fetch
  • fetch 封装一层;
  • 便于后续添加逻辑 (鉴权,登录。。。);
防腐层
  • api 调用中间抽象出一层防腐层;
  • 将数据获取的数据或逻辑分类;
  • 以便 api 的修改导致业务代码报错;
api 错误处理
  • 前端只在防腐层中进行错误处理;

后端 api

API 格式
interface ApiResponse<T> {
code: number;
data: T;
message: string;
}
错误处理
  • 后端只在 controller 层面进行错误处理;
  • 同时写一个全局错误处理处理未捕获的异常;
数据库读写逻辑
  • 读写数据,先操作数据,再读写数据库;
  • 数据读写错误,要有对应的错误处理;

面向对象

封装性

  • 对象方法和属性包装在对象内部;
  • 外界无法直接访问对象内部,隐藏对象内部细节;
  • 对外提供固定接口;

继承性

  • 子类继承父类,进而复用父类代码;
  • 子类可以重载父类;

多态性

  • 同一操作作用于不同对象,产生不同的结果;
  • 可通过方法重载,方法重写实现编译时和运行时的多态性;
  • 可通过组合加委托,在运行时中传入不同接口实现,实现多态性;

前端工程化

目的
  • 通过一系列工具和标准规范前端开发流程;
  • 目的是提高前端开发效率,提高代码质量和项目可维护性;
主要内容
  • 项目搭建;
    • 自动化项目依赖管理,构建,打包,压缩,优化等;
      • vite,pnpm 等;
    • 使用 cli 脚手架工具快速生成项目结构;
  • 代码质量;
    • 定义编码规范,编码风格,提高代码可读性;
    • eslint,prettier 约束代码风格;
    • Typescript 约束代码类型;
  • 模块化/组件化;
    • 模块化代码,高内聚,低耦合,提高代码复用性;
    • ESM,CommonJS;
  • 测试;
    • 单元测试:Jest,Vitest 单元测试;
    • 端到端测试:Cypress 模拟用户操作;
  • 版本控制;
    • 版本控制系统:git 管理代码版本;
    • 分支管理策略:Git Flow,Github Flow 管理开发流程;
  • CI/CD;
    • 持续集成和持续部署;
    • 自动化代码检查,构建,测试,部署;

软件架构模式

mvc

组成部分
  • Model:模型;
    • 应用程序的数据模型或业务逻辑;
    • 用于数据的读写操作;
  • View:视图;
    • 用户界面的可视化部分,负责展示数据;
  • Controller:控制器;
    • Model 和 View 交流媒介;
    • 负责 UI 逻辑和读写 Model;
处理流程
  • View 发送指令至 Controller;
  • Controller 完成业务逻辑后改变 Model;
  • Model 将数据更新至 View;

mvc处理流程

目标
  • 将 UI ,数据和业务逻辑分离;
  • 降低代码耦合度,提高应用程序的可维护性和可测试性;

mvvm

mvvm
  • 用于构建用户界面的软件架构模式;
组成部分
  • Model:模型;
    • 应用程序的数据模型或业务逻辑;
    • 用于数据的读写操作;
  • View:视图;
    • 用户界面的可视化部分,负责展示数据;
  • ViewModel:视图模型;
    • Model 和 View 交流媒介;
    • 负责在 Model 提取并转换数据用于 View;
    • 通过数据绑定机制将数据与 View 绑定;
    • 负责 UI 逻辑;
  • 数据绑定;
    • ViewModel 与 View 两层之间,任一一层数据的变化自动反映至另一层;
处理流程
  • View 和 ViewModel 的更新通过数据绑定机制双向同步;
  • View 无需手动通信 Model;

mvvc处理流程

目标
  • 将 UI 逻辑与数据逻辑分离;
  • 通过数据绑定实现数据和 UI 的同步;
  • 降低代码耦合度,提高应用程序的可维护性和可测试性;
与 mvc 的区别
  • 低耦合;
    • mvvm 将 UI 和数据完全解耦,view 和 Model 不知道对方存在;
    • View 和 Model 可以独立于对方变化;
  • 数据绑定;
    • 具有数据绑定机制,实现了 View 和 ViewModel 的自动同步;
    • 无需手动更新 View;