JS 常用的六种设计模式介绍

article/2025/10/25 16:06:57

常用设计模式

前言

我们经常听到一句话,“写代码要有良好的封装,要高内聚,低耦合”。究竟怎样的代码才算得上是良好的代码。

什么是高内聚,低耦合?

即五大基本原则(SOLID)的简写

  • 层模块不依赖底层模块,即为依赖反转原则。
  • 部修改关闭,外部扩展开放,即为开放封闭原则。
  • 合单一功能,即为单一功能原则。
  • 知识要求,对外接口简单,即为迪米特法则。
  • 合多个接口,不如独立拆分,即为接口隔离原则。
  • 成复用,子类继承可替换父类,即为里式替换原则

我们为什么要封装代码?

其实封装代码有这些好处:

  1. 良好的封装,不会让内部变量污染外部
  1. 封装好的代码可以作为一个模块给外部调用。外部无需了解细节,只需按约定的规范调用。
  1. 对扩展开放,对修改关闭,即开放关闭原则。外部不能修改内部代码,保证了内部的正确性;又留出扩展接口,提高了灵活性。

我们可以观察React、Vue、EventEmitter、Axios等等这些优秀的源码,会发现其实他们封装的模块都是有迹可循的。这些规律总结起来就是设计模式。

什么是设计模式?

借用鲁迅先生说过的一句话,世上本没有路,走的人多了也便成了路。所谓设计模式,是前辈们总结下来的,在软件设计、开发过程中,针对特定场景、特定问题的较优解决方案。

为什么需要设计模式?

实际上,不使用设计模式,照样可以进行需求开发。但是这造成的后果是:因设计缺陷、代码实现缺陷,给后期维护、开发、迭代带来了麻烦。

设计模式

设计模式一共分为3大类23种,主要介绍常用的几种

模式类型设计模式
创建型模式单例模式、工厂模式、建造者模式
结构型模式适配器模式、装饰器模式、代理模式
行为型模式策略模式、观察者模式、发布订阅模式、职责链模式、中介者模式

单例模式

单例模式:一个类只有一个实例,并提供一个访问他的全局访问点。

  • Singleton :特定类,这是我们需要访问的类,访问者要拿到的是它的实例;
  • instance :单例,是特定类的实例,特定类一般会提供 getInstance 方法来获取该单例;
  • getInstance :获取单例的方法;

在这里插入图片描述

class Singleton {let _instance = null;static getInstance() {if (!Singleton._instance) {Singleton.instance = new Singleton()}// 如果这个唯一的实例已经存在,则直接返回return Singleton._instance}
}const s1 = Singleton.getInstance()
const s2 = Singleton.getInstance()console.log(s1 === s2)  // true

源码实例

Vuex 源码中的单例模式

Vuex:实现了一个全局的store用来存储应用的所有状态。这个store的实现就是单例模式的典型应用。

// 安装vuex插件
Vue.use(Vuex)// store注入Vue实例
new Vue({el:"$app",store
})

通过调用Vue.use方法,安装Vuex插件。Vuex插件本质上是一个对象,内部实现了一个install方法,这个方法在插件安装时被调用,从而把Store注入到Vue实例中。

let Vue // instance 实例export function install (_Vue) {// 判断传入的Vue实例对象是否已经被install过(是否有了唯一的state)if (Vue && _Vue === Vue) {if (process.env.NODE_ENV !== 'production') {console.error('[vuex] already installed. Vue.use(Vuex) should be called only once.')}return}// 若没有,则为这个Vue实例对象install一个唯一的VuexVue = _Vue// 将Vuex的初始化逻辑写进Vue的钩子函数里applyMixin(Vue)
}

通过这种方式,可以保证一个 Vue 实例只会被 install 一次 Vuex 插件,所以每个 Vue 实例只会拥有一个全局的 Store。

优缺点

优点: 节约资源,保证访问的一致性。

缺点: 扩展性不友好,因为单例模式一般自行实例化,没有接口。

使用场景

  1. 如果一个类实例化过程消耗资源比较多,可以使用单例避免性能浪费
  1. 需要公共状态,可以使用单例保证访问一致性。

工厂模式

工厂模式:根据不同的参数,返回不同类的实例。

核心思想:将对象的创建与对象的实现分离。实现复杂,但使用简单。工厂会给我们提供一个工厂方法,我们直接去调用即可。

  • Visitor :访问者,访问工厂方法。
  • Factory :工厂,负责返回产品实例;
  • Product :产品,访问者从工厂拿到的产品实例;

在这里插入图片描述

🌰 例子

我们去环球影城的餐厅吃饭,点了一份“牛肉拉面”、“馄饨云吞面”,面煮好了,就直接端到桌子上,我们只管吃,不用在乎煮面的过程。

这个过程中,我们扮演访问者的角色,餐厅扮演的就是工厂的角色,“xxx”面就是产品。

class Restaurant{constructor(){this.menuData = {};}// 获取菜品getDish(dish){if(!this.menuData[menu]){console.log("菜品不存在,获取失败");return;}return this.menuData[menu];},// 添加菜品addMenu(menu,description){if(this.menuData[menu]){console.log("菜品已存在,请勿重复添加");return;}this.menuData[menu] = menu;}// 移除菜品removeMenu(menu){if(!this.menuData[menu]){console.log("菜品不存在,移除失败");return;}delete this.menuData[menu];},
}class Dish{constructor(name,description){this.name = name;this.description = description;}eat(){console.log(`I'm eating ${this.name},it's ${`this.description);}
}

这些场景都有一些特点:使用者只需要知道产品名字就可以拿到实例,不关心创建过程。所以我们可以把复杂的过程封装在一块,更便于使用。

源码实例

Vue、React 源码中的工厂模式

document.createElement 创建 DOM 元素。这个方法采用的就是工厂模式,方法内部很复杂,但外部使用很简单。只需要传递标签名,这个方法就会返回对应的 DOM 元素。

和原生的 document.createElement 类似,VueReact 这种具有虚拟 DOM 树机制的框架在生成虚拟 DOM 的时候,都提供了 createElement 方法用来生成 VNode,用来作为真实 DOM 节点的映射。上面实现一致,调用createEle``ment后,返回VNode元素。

// 使用
ccreateElement('h3', { class: 'main-title' }, [createElement('p', { class: 'main-content' }, '真有意思')
])
// 函数大致结构如下
export function createElement(tag,data,children){//....各种判断 判断生成什么样的Vnodeif(!tag){return createEmptyVNode()}return new VNode(tag,data,children);
}

Vue-Router 源码中的工厂模式

vue-router 中使用了工厂模式的思想来获得响应路由控制类的实例,this.history 用来保存路由实例。

export default class VueRouter{constructotr(options){const mode = options.mode || "hash";switch (mode) {case 'history':this.history = new HTML5History(this, options.base)breakcase 'hash':this.history = new HashHistory(this, options.base, this.fallback)breakcase 'abstract':this.history = new AbstractHistory(this, options.base)breakdefault:if (process.env.NODE_ENV !== 'production') {assert(false, `invalid mode: ${mode}`)}}}
}

优缺点

优点:

  1. 良好的封装,访问者无需了解创建过程,代码结构清晰。
  1. 扩展性良好,通过工厂方法隔离了用户和创建流程,符合开闭原则。
  1. 解耦了高层逻辑和底层产品类,符合最少知识原则,不需要的就不要去交流;

缺点:

给系统增加了抽象性,带来了额外的系统复杂度,不能滥用。(合理抽象能提高系统维护性,但可能会提高阅读难度,还是需要合理看待)

使用场景

  • 对象创建比较复杂,访问者无需了解创建过程。
  • 需要处理大量具有相同/类似属性的小对象。

滥用只是增加了不必要的系统复杂度,过犹不及。

适配器模式

适配器模式:用于解决兼容问题,接口/方法/数据不兼容,将其转换成访问者期望的格式进行使用。

生活案例:

  1. 耳机转接线。圆孔耳机插不进TypeC的手机里,只需一个TypeC转接头,此时TypeC转接头就是“适配器”。
  1. 翻译官。老板张三去国外谈业务,带上翻译官李四,李四这个时候就是作为不同语言的人之间交流的“适配器”。

场景特点:

  1. 同时存在多种格式,旧有接口格式不满足现在需要。
  1. 增加适配器可以更好使用旧接口。

适配器模式必须包含目标(Target)、源(Adaptee)和适配器(Adapter)三个角色。

在这里插入图片描述

🌰 例子

场景:我们要获取通过多个接口获取列表数据,拼接在一起,在一个组件内进行展示。因历史遗留原因,这些列表数据的格式不太一样。

// 格式 1
{book_id: 1001status: 0,create: '2021-12-12 08:10:20',update: '2022-01-15 09:00:00',
},// 格式 2
{id: 1002status: 0,createTime: 16782738393022,updateAt: '2022-01-15 09:00:00',
},// 格式 3
{book_id: 1003status: 0,createTime: 16782738393022,updateAt: 16782738393022,
}

三个数据来源,三种时候数据结构,这时候我们有几种实现方式。

  1. 组件针对不同的数据来源分别采用不同的渲染。(需要对数据类型进行区分)
  1. 在外部将数据转换成一种数据格式的列表传入,组件单一职责:展示。

对比之下,使用适配器模式 ,将不同的数据结构适配成展示组件所能接受的数据结构。保持了组件的单一职责,更优。

  1. 定义一个统一的数据结构
interface bookData {book_id: number;status: number;createAt: string; // 时间戳updateAt: string; // 时间戳
}
  1. 通过适配器模块,适配成访问者所要数据格式
interface bookDataType1 {book_id: number;status: number;create: string;update: string;
}interface bookDataType2 {id: number;status: number;createTime: number;updateAt: string;
}interface bookDataType3 {book_id: number;status: number;createTime: number;updateAt: number;
}const getTimeStamp = function (str: string): number {//.....转化成时间戳return timeStamp;
};//适配器
export const bookDataAdapter = {adapterType1(list: bookDataType1[]) {const bookDataList: bookData[] = list.map((item) => {return {book_id: item.book_id,status: item.status,createAt: getTimeStamp(item.create),updateAt: getTimeStamp(item.update),};});return bookDataList;},adapterType2(list: bookDataType2[]) {const bookDataList: bookData[] = list.map((item) => {return {book_id: item.id,status: item.status,createAt: item.createTime,updateAt: getTimeStamp(item.updateAt),};});return bookDataList;},adapterType3(list: bookDataType3[]) {const bookDataList: bookData[] = list.map((item) => {return {book_id: item.book_id,status: item.status,createAt: item.createTime,updateAt: item.updateAt,};});return bookDataList;},
};
  1. 将数据经过适配器处理后进行整合,组件展示
//整合数据
const bookDataList = [...bookDataAdapter.adapterType1(type1MatailList),...bookDataAdapter.adapterType2(type2MatailList),...bookDataAdapter.adapterType3(type3MatailList),
];

优缺点

优点: 可以使原有逻辑得到更好的复用,有助于避免大规模改写现有代码;

缺点: 会让系统变得零乱,明明调用 A,却被适配到了 B,如果滥用,那么对可阅读性不太友好。

使用场景

  • 想要使用一个已经存在的对象,但是接口不满足需求,那么可以使用适配器模式转换成你需要的接口。
  • 想要创建一个可以复用的对象,而且确定需要和一些不兼容的对象一起工作,这种情况可以使用适配器模式。

装饰器模式

装饰器模式:在不改变原对象的基础上,增加新属性/方法/功能。

一个对象被另一个对象包装,形成一条包装链,在原对象上增加功能。

在这里插入图片描述

🌰 例子

暂时想不到什么好的例子和实际应用场景。

优缺点

优点:

  1. 对象的核心职责和装饰功能区分开,可以通过动态增删装饰去除目标对象中的装饰逻辑。

策略模式

策略模式:定义一系列算法,根据输入的参数决定使用哪个算法。

在这里插入图片描述

重点:算法的实现和算法的使用分开。

  • Context :封装上下文,根据需要调用需要的策略,屏蔽外界对策略的直接调用,只对外提供一个接口,根据需要调用对应的策略;
  • Strategy :策略,含有具体的算法,其方法的外观相同,因此可以互相代替;
  • StrategyMap :所有策略的合集,供封装上下文调用;

🌰 例子

场景:双十一满减活动。满200-20、满300-50、满500-100。这个需求,怎么写?

if-else暴力法直接梭哈。

通过判断输入的折扣类型来计算商品总价的方式

function priceCalculate(discountType,price){if(discountType === 'discount200-20'){return price - Math.floor(price/200) * 20;}else if(discountType === 'discount300-50'){return price - Math.floor(price/300) * 50;}else if(userType === 'discount500-100'){return price - Math.floor(price/500) * 100;}
}

缺点:

  1. 随着折扣类型的增加,if-else会变得越来越臃肿。
  1. 折扣活动算法改变或折扣类型增加时,都需要改动priceCalculate方法,违反开闭原则。
  1. 复用性差,如果其他地方有类似的算法,但规则不一样,上述代码不能复用。

使用策略模式对代码改写

// 算法的实现
const discountMap = {'discount200-20': function(price) {return price - Math.floor(price / 200) * 20;},'discount300-50': function(price) {return price - Math.floor(price/300) * 50;},'discount500-100': function(price) {return price - Math.floor(price/500) * 100;},
}// 算法的使用
function priceCalculate(discountType,price){return discountMap[discountType] && discountMap[discountType](price);
}

以上代码就将算法的实现和算法的使用分开,以后不管增加或修改了算法,都无需对priceCalculate方法进行改动。

当然以上代码的抽象程度并不高,如果我们想隐藏计算算法,可以借助 IIFE 使用闭包的方式,提供一个添加策略的方法。

const priceCalculate = (function(){const discountMap = {'discount200-20': function(price) {return price - Math.floor(price / 200) * 20;},'discount300-50': function(price) {return price - Math.floor(price/300) * 50;},'discount500-100': function(price) {return price - Math.floor(price/500) * 100;},};return {addStategy(stategyName,fn){if(discountMap[stategyName]) return;discountMap[stategyName] = fn;},priceCal(discountType,price){return discountMap[discountType] && discountMap[discountType](price);}}
})()// 使用
priceCalculate.priceCal('discount200-20',250); // 230priceCalculate.addStategy('discount800-200',function(price){return price - Math.floor(price/800) * 200;
})

这样算法就被隐藏起来,并且预留了增加策略的入口,便于扩展。

场景:表单验证。

表单验证项一般会比较复杂,所以需要给每个表单项增加 validator 自定义校验方法。以ElementUIForm 表单为例。

我们可以像官网示例一样把表单验证都写在组件的状态 data 函数中。

缺点:不好复用使用频率比较高的表单验证方法,造成代码冗余。

使用策略模式和函数 柯里化 对代码进行改写。

utils/validates.js为通用验证规则,即为StrategyMap

utils/index.js中的formValidateGene通过柯里化动态选择表单验证方法,即为Context

// src/utils/validates.js
// 姓名校验 由2-10位汉字组成 
export function validateUsername(str) {const reg = /^[\u4e00-\u9fa5]{2,10}$/return reg.test(str)
}// 手机号校验 由以1开头的11位数字组成
export function validateMobile(str) {const reg = /^1\d{10}$/return reg.test(str)
}// 邮箱校验 
export function validateEmail(str) {const reg = /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(.[a-zA-Z0-9_-]+)+$/return reg.test(str)
}// src/utils/index.js
import * as Validates from './validates.js';export function formValidateGene = (key,msg) => {return function(rule,value,cb){if(Validates[key] && Validates[key](value)){cb()}else{cb(new Error(msg))}}
}// 使用
<script>import { formValidateGene } from '@/utils'export default {data() {return {ruleForm: {username: '',telephone: '',},rules: {username: [{ validator: formValidateGene('validateUsername','用户名格式为2~10个汉字,请重新输入'), trigger: 'blur' }],telephone: [{ validator: formValidateGene('validateMobile','手机号格式错误,请重新输入'), trigger: 'blur' }],}};}}
</script>

优缺点

优点:

  1. 策略相互独立,可以互相切换。提高了灵活性以及复用性。
  1. 不需要使用if-else进行策略选择,提高了维护性。
  1. 可扩展性好,满足开闭原则。

缺点:

  1. 策略相互独立,一些复杂的算法逻辑无法共享,造成资源浪费。
  1. 用户在使用策略时,需要了解具体的策略实现。不满足最少知识原则,增加了使用成本。

使用场景

  1. 算法需要自由切换的场景。
  1. 多个算法只有行为上有些不同,可以考虑策略模式动态选择算法。
  1. 需要多重判断,可以考虑策略模式规避多重条件判断。

观察者模式

观察者模式:一个对象(称为subject)维持一系列依赖于它的对象(称为observer),将有关状态的任何变更自动通知给它们(观察者)。

观察者模式中的角色有两类:观察者和被观察者。

  • 观察者必须订阅内容改变的事件,定义一对多的依赖关系。

在这里插入图片描述

生活中的例子:领导(被观察者)在台上介绍防疫政策,底下的工作人员(观察者)“观察”领导说的防疫政策的变化,当政策变化时,通知(update)到街道。

工作人员(观察者)必须订阅内容改变的事件,即用耳朵去听政策的变化。

🌰 例子

简单的代码基本实现

// 观察者模式 被观察者Subject 观察者Observer Subject变化 notify观察者
let observerIds = 0;// 被观察者Subject
class Subject {constructor() {this.observers = [];}// 添加观察者addObserver(observer) {this.observers.push(observer);}// 移除观察者removeObserver(observer) {this.observers = this.observers.filter((obs) => {return obs.id !== observer.id;});}// 通知notify观察者notify() {this.observers.forEach((observer) => observer.update(this));}
}// 观察者Observer
class Observer {constructor() {this.id = observerIds++;}update(subject) {// 更新}
}

优缺点

优点:目标变化就会通知观察者,这是观察者模式最大的优点。

缺点: 不灵活。目标和观察者是耦合在一起的,要实现观察者模式,必须同时引入被观察者和观察者才能达到响应式的效果。

使用场景

发布-订阅模式

发布/订阅模式:基于一个主题/事件通道,希望接收通知的对象(称为subscriber)通过自定义事件订阅主题,被激活事件的对象(称为publisher)通过发布主题事件的方式被通知。

发布-订阅模式的角色有两类:发布者和订阅者

  • 使用一个主题通道,这个通道介于发布者和订阅者之间。

在这里插入图片描述

🌰 例子

我们微信会关注很多公众号,公众号有新文章发布时,就会有消息及时通知我们文章更新了。

这个时候公众号为发布者,用户为订阅者,用户将订阅公众号的事件注册到事件调度中心,当发布者发布新文章时,会发布事件至事件调度中心,调度中心会发消息告诉订阅者。

class Event {constructor() {this.eventEmitter = {};}// 订阅on(type, fn) {if (!this.eventEmitter[type]) {this.eventEmitter[type] = [];}this.eventEmitter[type].push(fn);}// 取消订阅off(type, fn) {if (!this.eventEmitter[type]) {return;}this.eventEmitter[type] = this.eventEmitter[type].filter((event) => {return event !== fn;});}// 发布emit(type) {if (!this.eventEmitter[type]) {return;}this.eventEmitter[type].forEach((event) => {event();});}
}

源码实例

Vue 双向绑定中的发布订阅模式

在这里插入图片描述

Vue 双向绑定通过数据劫持和发布-订阅模式实现。

  1. 通过DefineProperty劫持各个数据的settergetter,并为每个数据添加一个订阅者列表,这个列表将会记录所有依赖这个数据的组件。响应式后的数据相当于消息的发布者。
  1. 每个组件都对应一个Watcher订阅者,当组件渲染函数执行时,会将本组件的Watcher加入到所依赖的响应式数据的订阅者列表中。相当于完成了一次订阅,这个过程叫做“依赖收集”。
  1. 当响应式数据发生变化时,会出settersetter负责通知数据的订阅者列表中的WatcherWatcher触发组件重新渲染来更新视图。视图层相当于消息的订阅者。

EventEmitter

跟 🌰 差不多,略。

优缺点

优点:

  1. 时间解耦:注册的订阅行为由发布者决定何时调用,订阅者无需持续关注,由发布者负责通知。
  1. 对象解耦:发布者无需知道消息的接受者,只需遍历订阅该消息类型的订阅者发送消息,解耦了发布者和订阅者之间的联系,互不持有,都依赖于抽象。

缺点:

  1. 资源消耗:创建订阅者需要一定的时间和内存。
  1. 增加复杂度:弱化了联系,难以维护调用关系,增加了理解成本。

使用场景

  • 各模块相互独立
  • 存在一对多的依赖关系
  • 依赖模块不稳定、依赖关系不稳定
  • 各模块由不同的人员开发

http://chatgpt.dhexx.cn/article/eJbuvQ9N.shtml

相关文章

git工具统计项目的代码行数

1、git 查看代码的项目总行数 &#xff08;1&#xff09;打开Git终端&#xff0c;进入项目的根目录 git log --prettytformat: --numstat | awk { add $1; subs $2; loc $1 - $2 } END { printf "added lines: %s, removed lines: %s, total lines: %s\n", add,…

小技巧之统计代码行数

欢迎关注我的微信公众号“人小路远”哦&#xff0c;在这里我将会记录自己日常学习的点滴收获与大家分享&#xff0c;以后也可能会定期记录一下自己在外读博的所见所闻&#xff0c;希望大家喜欢&#xff0c;感谢支持&#xff01; 搞了两个月&#xff0c;连搬带抄写出来的代码&a…

MAC代码下统计代码行数工具

作为一名程序员在很多的时候需要统计代码行数&#xff1a; 支持windows系统的代码行数统计方法以及软件很多&#xff0c;但是MAC系统的统计代码行数的真的不太多。 大家都知道用 wc -l 命令进行代码行数统计&#xff0c;但是它会将代码中的注释、空行所占用的文本行都统计在内…

cloc工具 命令行 统计代码行数

基本用法 &#xff1a;cloc后面跟目录名&#xff0c;文件名&#xff0c;或压缩文件名 例如&#xff1a; cloc ./application 1.安装(参考官网http://cloc.sourceforge.net/#apt-get) 根据操作系统不同&#xff0c;选择以下任意安装方法 sudo npm install -g cloc …

程序代码行数统计

程序写完了&#xff0c;提交著作权的时候不知道代码行数是多少怎么办&#xff1f; 介绍 软件名称兼容系统下载地址代码统计工具Windows软件下载 下面我们开始教程 打开主应用程序点击加号添加程序项目所在的目录点击按钮选择需要统计文件的文件后缀&#xff0c;看个人需求如…

计代码行数cloc,一个代码统计行数很好用的工具

分为window、mac系统区分&#xff0c;基本是一样的&#xff0c;一个代码统计行数很好用的工具。 CLOC简介 Cloc是一款使用Perl语言开发的开源代码统计工具&#xff0c;支持多平台使用、多语言识别&#xff0c;能够计算指定目标文件或文件夹中的文件数&#xff08;files&#x…

统计代码量-代码统计工具 CLOC | gitlab统计代码量

文章目录 一、代码统计工具 CLOC什么是CLOC?下载安装clocs使用 二、gitlab统计代码量命令行统计图形化统计IDE Statistic统计代码插件 一、代码统计工具 CLOC 什么是CLOC? github: https://github.com/AlDanial/cloc CLOC是Count Lines of Code的意思&#xff0c;可以计算…

Python实现一个代码行数统计工具(以C/C++为例)

前几天在网上看到一个有意思的题&#xff0c;题目是设计一个代码行数统计工具。这类工具我经常会用到&#xff0c;但是具体是如何实现的呢&#xff1f;这个问题我还从未思考过&#xff0c;于是便试着做出这种工具。 题目描述是这样的&#xff1a; 题目要求&#xff1a;   请…

统计项目代码行数工具cloc

Ubuntu用户 使用cloc在ubuntu内统计代码行数 安装cloc工具 sudo apt-get install cloc进入需要统计的目录内&#xff0c;然后执行 cloc .然后就会显示文件目录中的文件数(files)、空白行数(blank)、注释行数(comment)和代码行数(code)。 Windows 用户 也是使用cloc工具 …

Win10 代码行数统计工具CLOC的安装和使用

简介 CLOC(Count Lines of Code)&#xff0c;是一个可以统计多种编程语言中空行、评论行和物理行的工具。这个工具还是蛮实用的&#xff0c;可以帮我们快速了解一个项目中代码的信息。 注&#xff1a;底下这个命令可以实现统计代码行数的功能&#xff0c;只是不排除空行和注释…

代码行数统计小工具

一、先下载好SourceCounter小工具。解压&#xff0c;然后直接打开文件夹中的SourceCounter.exe。如果没有找到此工具的下载链接&#xff0c;点这里下载 二、选择代码类型&#xff0c;勾选上所有类型 三、双击点开后&#xff0c;选择文件夹&#xff0c;就可以直接统计出字…

在项目开发中统计代码行数的6种方式

文章目录 一、使用find和wc命令统计代码行数进行参数的过滤筛选命令参数简要说明 二、PowerShell工具统计代码行数条件过滤输出所有文件的行数PowerShell相关命令的简要说明 三、git命令git ls-filesgit log 四、代码编辑器插件五、jscpd六、自己实现一个注释和空行忽略目录和文…

chatgpt赋能python:Python代码行数统计-统计Python代码行数的常用工具与使用方法

Python代码行数统计 - 统计Python代码行数的常用工具与使用方法 Python编程语言是当今最流行的编程语言之一&#xff0c;在数据科学、人工智能、Web应用程序等许多领域都得到了广泛应用。当我们开发Python项目时&#xff0c;我们经常需要统计代码行数以管理代码库并监视进度。…

局域网电脑使用同一台鼠标键盘控制

问题又来了,我现在有两个电脑&#xff0c;局域网相连&#xff0c;但是我只有一套键盘鼠标啊&#xff0c;办公特别不方便&#xff0c;现在有工具可以让我们达到这个目的。 微软推出的 Mouse without Borders (无界鼠标)&#xff0c;这是一个免费的工具大家可以放心。下载地址为…

用一套键鼠控制两台主机

自己最近做一个项目涉及到以下场景&#xff1a;需要在一台window10系统的电脑上看一些文档&#xff0c;同时在一台装Ubuntu1804的电脑上跑一些代码。因此需要同时用两套鼠标键盘&#xff0c;切换起来十分麻烦&#xff0c;而且经常会拿错。因此参考博客解决了用一套键鼠控制两台…

Synergy两台电脑使用同一个鼠标和键盘

###分享一款共享鼠标和键盘的软件&#xff0c;即两台电脑使用同一个鼠标和键盘&#xff01; Synergy是一款跨平台的键盘鼠标共享软件&#xff0c;日前我们提供了Synergy 和Synergy 64位的Win版本、Synergy Mac版&#xff0c;Synergy 能够让使用者仅用一套键盘鼠标&#xff0c;就…

如何把笔记本做台式机的副屏(一套键鼠控制两台电脑)

通过一套键鼠控制两台电脑 前提安装所需的软件一、 简介二、 安装2.1 小技巧 前提 两台电脑在同一个局域网内&#xff0c;并且均为windows操作系统 例如两台电脑链接的同一个WIFI&#xff0c;或者笔记本无线连接路由&#xff0c;台式机插网线链接路由 只有在同一个局域网内才能…

用一个键盘和鼠标控制两台双显示器计算机的最佳方法是什么?

If you have two awesome dual-monitor computers and want to easily switch back and forth between them using a single keyboard and mouse, then what is the best way to go about it? Today’s SuperUser Q&A post provides some great suggestions for a reader’…

怎么用一个计算机控制两个屏幕,一台电脑带2个显示器,这样操作就对了,双屏显示操作更方便...

为了提高操作电脑的工作效率,许多用户决定将两台显示器连接到台式电脑或笔记本电脑,在两个显示器上工作,可以扩展桌面空间,在需要的时候还可以将图像从其中一个屏幕复制到另一个屏幕上。你可以将两台显示器连接到几乎任何台式计算机或笔记本电脑,在Windows 7和Windows 10操…

python两台电脑文件传输_两台电脑怎么互相连接传送文件?

相信许多购买了新电脑的朋友最头疼的事儿就是如何把旧电脑里的资料拷贝到新电脑里。因为,文件太大用移动硬盘实在麻烦,网络社交工具在线传送速度太慢,网线对拷既要求交叉线又搞不懂IP设置,难!难!难! 那么,有没有一种简单又粗暴的方式可以实现电脑数据对拷呢? 绿联USB …