【JS基础】一些个人积累的原生JS编码设计思想,和大家一起开拓下思维

2023-11-12

前言

以下都是我个人遇到的前端JS原生编码设计上的一些案例记录,希望能帮助新手开拓写代码的思想,并且能够结合自己的想法应用在实际的项目中,写出更加易读,拓展,维护的代码。

在其中会有一些案例展示,并不是说某个写法只能用于该案例上,要学会举一反三。

还有一点就是不要死记这些东西(我自己也记不住,叫我重写都未必能写出),留有个印象就好,等到某天你发现某个场景可以使用到下面的写法时,再对应的拿取用。


对象配置

就是一个总函数,可以通过传入的对象配置项,开启函数内部的一些特定模块的处理,例子如下:

// 总函数
function fn(target, config = {}) {
    // A模块处理默认开启
    if (config.handleA === true || config.handleA === undefined) {
        handleA()
    }
    // B模块手动开启
    if (config?.handleA === true) {
        handleB()
    }
}

// handleA处理模块
function handleA(){

}

// handleB处理模块
function handleB(){

}

// ...

举个使用在项目上的例子,例如我封装axios的时候,如果是一些简单的项目,就只需要做一层封装即可,然后在写接口请求方法的时候只需要:

import request from "@/utils/request";

/**
 * 登请求
 */
export const login = (data) => {
  return request({
    url: "/sys/login",
    method: "POST",
    data,
    needLoading: true, // 是否有请求动画,默认给true
    handleErrer: false, // 是否要手动处理错误信息,默认会自动报出接口错误信息
    // ...
  });
};

然后在axios的请求和响应拦截器中实现对应的功能即可。


链式调用

这是我个人认为最好理解和记忆的编写方法:

class _Print {
    // 初始化
    constructor() {
        this.queue = [this.init] // 执行栈
        this.next()
    }

    // 初始化钩子
    init() {
        console.log('初始化钩子')
        // 这里要开启下一轮事件循环再执行栈中的任务,保证链式调用的任务已推入
        setTimeout(() => {
            this.next();
        }, 0)
    }

    // 同步执行
    print(msg) {
        let fn = function () {
            console.log(msg);
            this.next()
        }
        this.queue.push(fn)
        return this
    }

    // 延迟
    delay(time) {
        let fn = function () {
            setTimeout(() => {
                this.next()
            }, time)
        }

        this.queue.push(fn)
        return this
    }

    // 弹出栈任务并执行
    next() {
        let fn = this.queue.shift() // 这里重新定义了函数,不再是指向实例了
        fn && fn.call(this)
    }
}

new _Print().print('1').delay(3000).delay(3000).print('2')

问题来了,这种链式调用能用在什么场景下呢?暂时没想到哈哈。


队列调用

就是把要经过的任务都推入到任务队列里,然后挨个执行,例子如下:

function p() {
    let promise = Promise.resolve()

    function fn1(result) { // 功能封装1
        console.log('fn1');
        return Promise.resolve('fn1')
    }

    function fn2(result) { // 功能封装2
        console.log('fn2');
        return Promise.resolve('fn2')
    }

    let arr = [fn1, fn2]

    while (arr.length) {
        promise = promise.then(arr.shift())
    }

    return promise
}

p('1') // fn1 fn2 轮流执行

可以用在对axios进行更深入的封装,可以参考我这篇文章:【场景方案】如何去设计并二次封装一个好用的axios,给你提供一个另类写法,另加一些思考


并发执行

并发执行任务的时候,我们要做好每次的并发量。一般这种并发场景都是异步请求,所以必然涉及到Promise,这里也就拿Promise去写示例:

// 模拟100个异步请求
const arr = [];
for (let i = 0; i < 100; i++) {
    arr.push(() => new Promise((resolve) => {
        setTimeout(() => {
            console.log('done', i);
            resolve();
        }, 100 * i);
    }));
};

const parallelRun = () => {
    const runingTask = new Map(); // 记录正在发送的异步请求(闭包存储)
    const inqueue = (totalTask, max) => { // 异步请求队列,每组请求的最大数量
        // 当正在请求的任务数量小于每组请求的最大数量,并且还有任务未发起时,就推入请求
        while (runingTask.size < max && totalTask.length) {
            const newTask = totalTask.shift(); // 弹出新任务
            const tempName = totalTask.length; // 以长度命名?
            runingTask.set(tempName, newTask);
            newTask().finally(() => {
                runingTask.delete(tempName);
                inqueue(totalTask, max); // 每次一个任务完成后就继续塞入新任务
            });
        }
    }
    return inqueue;
};

parallelRun()(arr, 6);

有人会问为啥不直接用all方法呢?因为只要期中一个任务失败了,整个队列都没用了。详细可以看【es6入门】好好捋一捋Promise与Async的异步写法,细节满满


条件判断优化

我们会经常遇到一种场景,在一个函数中有很多的判断路线:

function process(item) {
	if (item.status === 'a' && isFinished()) {
		// ...
	}
	if (['a', 'b'].includes(item.status) && !isFinished()) {
		// ...
	}
}

想这种,我们可以用一个对象去整改,使得整个易读性更好:

function process(item) {
	const statusMap = {
		a: isFinished(),
		b: isFinished()
	}
	if (statusMap[item.status]) {
		// ...
	}
}

要学会灵活写statusMap,例如:

function process(item) {
	if ((item.status === 'a' && isFinished()) || (item.status === 'b' && !isFinished())) {
		// ...
	}
}

可以改成:

function process(item) {
	const statusMap = {
		a: isFinished(),
		b: !isFinished()
	}
	if (statusMap[item.status]) {
		// ...
	}
}

如果场景变得更复杂,每个if判断条件是不一样的,并且执行的内容也不一样,那么可以设计成:

const statusMap = [
	[
		()=>{ /* 条件1 */ },
		()=>{ /* 执行内容1 */ },
	],
	[
		()=>{ /* 条件2 */ },
		()=>{ /* 执行内容2 */ },
	],
]
const target = map.find(status => status[0]) // 找到条件满足的那一项数组
if (target) {
	target[1]()
} else {
	// 没有命中的逻辑
}

给数据做封装

啥意思呢,就是当我们有一个数据要维护时,可以选择给这个数据做一层封装,让他拥有一些能力,还能给他再拓展其他信息等等。

具体如何做的可以参考如下,一个购物车里有很多商品,每一个商品的数据是以一个对象的形式去储存的,将来会对这些对象做一些操作。那么我们就可以封装成这样:

function createUIGoods(aGoods) {
  class UIGoods {
    // 获取总价格
    get totalPrice() { // 在class中Object.defineProperty的get可以这样简写
      return this.choose * this.data.price;
    }

    // 是否选中
    get isChoose() {
      return this.choose > 0;
    }

    constructor(g) {
      // 通过拷贝后冻结拷贝后的对象,让外部无法直接修改其中的属性
      g = { ...g };
      Object.freeze(g);

      // 定义商品所有信息
      Object.defineProperty(this, 'data', {
        get: function () {
          return g;
        },
        set: function () {
          throw new Error('data 属性是只读的,不能重新赋值');
        },
        configurable: false,
      });

      // 选中后的数量值
      var internalChooseValue = 0; // 用个临时变量存放值,防止get返回值的无限递归
      Object.defineProperty(this, 'choose', {
        configurable: false,
        get: function () {
          return internalChooseValue;
        },
        set: function (val) {
          if (typeof val !== 'number') {
            throw new Error('choose属性必须是数字');
          }
          let temp = parseInt(val);
          if (temp !== val) {
            throw new Error('choose属性必须是整数');
          }
          if (val < 0) {
            throw new Error('choose属性必须大于等于 0');
          }
          internalChooseValue = val;
        },
      });

      // 这里写其他可以修改的属性
      this.fromWhere = 'China';
      Object.seal(this); // 这里用seal代替freeze保证其他属性能够不被冻结,能够修改
    }
  }

  Object.freeze(UIGoods.prototype); // 禁止外部修改原型

  let g = new UIGoods(aGoods);

  return g
}

let g = createUIGoods(aGoods)
g.data.price = 100; // 无法修改data里的属性
g.fromWhere = 'USA' // 其他属性可以修改

console.log(g);

每个商品数据经过createUIGoods的处理,变得既安全又功能强大,为我们为后续购物车的功能实现打下了良好的基础。

看到没数据再也不是普普通通简单的数据了。

此思想从渡一前端那里看来的


尝试先写JS再写界面

我相信我们大多数开发的时候都是先把UI设计稿搭建起来,再去填充对应的JS内容。

其实我们有条件的时候可以尝试先写JS再写界面,大致就是从获取到的数据开始设计,从里到外的去写代码,例如获取数据--->数据处理---->功能封装---->界面搭建---->样式完善,这种方式去写代码,就比较容易让JS与界面解耦,很有可能把每个JS函数单独拿出来可以独立运行。

可以这样概括:要由JS决定HTML,而不是HTML决定JS。

当然这只是一个建议,可以去尝试。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【JS基础】一些个人积累的原生JS编码设计思想,和大家一起开拓下思维 的相关文章

随机推荐

  • react动态给组件添加事件_React实战—组件行为:React中的事件(4)

    网页是组件构成的 组件是一系列标签形成的单一功能 所以考虑组件无非也就三件事 结构 表现 和行为 结构JSX搞定了 当然JSX不仅仅干这个 表现是CSS的事儿 我们重点说下行为 也就是事件和钩子 事件好说 就是户干了啥事儿 然后有什么样的相
  • Fiddler / Charles - 夜神模拟器证书安装App抓包

    Fiddler Charles 夜神模拟器证书安装App抓包 文章目录 Fiddler Charles 夜神模拟器证书安装App抓包 前言 一 软件安装 1 Openssl安装 1 1下载安装 1 2配置环境变量 1 3查看openssl版
  • 基于AWS的云服务架构最佳实践 #CSDN博文精选# #IT# #云服务实践#

    大家好 小C将继续与你们见面 带来精选的CSDN博文 在这里 你将收获 将系统化学习理论运用于实践 系统学习IT技术 学习内容涵盖数据库 软件测试 主流框架 领域驱动设计和第三方生态等 离全栈工程师更近一步 精心整理的CSDN技术大咖博文
  • Java版工程项目管理系统源码 工程项目源码

    数 据 库 MySQL 开发语言 Java 开发工具 MyEclipse 源码类型 WebForm 以甲方项目管理为中心 包括项目启动 计划 执行 控制与收尾阶段的全生命周期管理 并对范围 预算 进度 质量 资源 风险等管理要素进行控制与预
  • 06FFMPEG的AVCodecContext结构体分析

    06FFMPEG的AVCodecContext结构体分析 概述 该结构体位于libavcodec库中的avcodec h中 1 AVCodecContext编解码上下文结构体 位于libavcodec库里 AVFormatContext A
  • C++的sort函数如何实现从大到小排序

    C 的sort函数如何实现从大到小排序 一 sort的基本用法 1 所在的头文件 2 包含的参数 3 时间复杂度 二 具体示例 USACO07DEC Bookshelf B 题目描述 输入格式 输出格式 样例 1 样例输入 1 样例输出 1
  • CMake应用:生成器表达式

    目录 一 概述 二 常用的生成器表达式 1 布尔生成器表达式 2 字符串值生成器表达式 3 调试 CMake的生成器表达式不算是特别常用 但是有一些场景可能是必须要使用的 或者在针对不同编译类型设置不同编译参数的时候可以巧妙应用 从而减少配
  • 数据结构顺序表的基本操作—插入

    include
  • uniapp history打包 解决公众号支付的问题

    微信公众号支付的时候 会在商户平台中配置支付目录 如 http www xxx com mypro 上面的路径是支付的url 页面地址 但是我们使用 uniapp 或者vue 的时候 默认的是 router 的 mode 是 hash 模式
  • [转载]使用@value注解注入properties配置信息

    首先 value需要参数 这里参数可以是两种形式 Value configProperties t1 msgname 或者 Value t1 msgname 其次 下面我们来看看如何使用这两形式 在配置上有什么区别 1 Value conf
  • Centos 服务器禁止 IP访问/ IP黑名单

    本文结合上文的一起使用体验更佳 Centos实时网络带宽占用查看工具iftop 一 iptables 防火墙 yum install iptables services 2 安装 iptables 防火墙 systemctl enable
  • IDEA控制台中文乱码解决

    关于IDEA中文乱码的解决方法 如下 1 打开idea安装目录 选择 打开文件 末尾添加 Dfile encoding UTF 8 2 打开IntelliJ IDEA gt File gt Setting gt Editor gt File
  • js获取当前时间和倒计时

    一 当前时间 获取当前时间显示年月日 时分秒 function newDate var date new Date var Y date getFullYear var M date getMonth 1 lt 10 0 date getM
  • RAID介绍及RAID5配置实例

    一 RAID磁盘阵列介绍 1 1RAID磁盘阵列介绍 RAID是Redundant Array of Independent Disks的缩写 中文简称为独立冗余磁盘阵列 把多块独立的物理硬盘按不同的方式组合起来形成一个硬盘组 逻辑硬盘 从
  • python制作网络社交图

    python制作网络社交图 制作说明 python语言是可以制作网络社交图的 在制作之前需要先导入python内部的一个库 import networkx as nx 该库是一个用python语言开发的图论与复杂网络建模工具 内置了常用的图
  • Redis-关于RDB的几点顿悟-COW(Copy On Write)

    文章目录 摘要 问题概述 问题解决 使用Copy On Write 写时复制 详细 解答 Copy On Write 机制 Linux中CopyOnWrite实现原理 CopyOnWrite的好处 Redis中的CopyOnWrite 摘要
  • Linux(Centos6.5)下如何解压.zip和.rar文件

    Windows下常见的压缩文件类型是 zip和 rar 在Linux下如何压缩和解压缩呢 1 zip linux下提供了zip和unzip命令 zip是压缩 unzip是解压缩 举例如下 zip myFile zip jpg 将所有jpg文
  • Linux--权限管理

    学习目标 1 Linux权限管理 1 1 用户分类 2 用户类型和访问权限 2 1 理解什么是权限 3 文件类型和权限操作 3 1 修改权限 3 2 关于root 3 3 更改文件拥有者 3 4 修改组权限 3 5 目录权限 3 5 1 粘
  • 基于jsp的学生网上报名系统

    学生网上报名系统分为五大模块 信息查询模块 信息修改模块 信息录入模块和导出印模块以及信息发布模块 其中信息录入模块是进行信息的添加 存储和删除 是系统运行的基础 信息查询模块是学生通过自己的姓名和学号登陆系统 进入报名界面时由系统按照预先
  • 【JS基础】一些个人积累的原生JS编码设计思想,和大家一起开拓下思维

    文章目录 前言 对象配置 链式调用 队列调用 并发执行 条件判断优化 给数据做封装 尝试先写JS再写界面 前言 以下都是我个人遇到的前端JS原生编码设计上的一些案例记录 希望能帮助新手开拓写代码的思想 并且能够结合自己的想法应用在实际的项目