裴大头-秦可爱

裴大头-秦可爱

Pei你看雪 Blog

甲之蜜糖,乙之砒霜。

文章一览

  • 发布于 2023-12-07

    Element UI 级联选择器 el-cascader 实现懒加载和搜索功能

    当代的 Web 应用程序通常需要提供高度灵活和易用的用户界面,以满足用户对数据选择和搜索的需求。在前端开发中,级联选择器(Cascader)是一种常见的组件,可以帮助用户从多层级的数据中进行选择。 Element UI 是一个流行的 Vue.js 组件库,提供了丰富的 UI 组件,其中包括了 <el-cascader> 组件。该组件可以实现级联选择功能,但默认情况下不支持懒加载和搜索的结合。在本篇博客中,我们将重点介绍如何结合懒加载和搜索来增强 <el-cascader> 的功能。 先看效果 基础功能:级联、懒加载!、搜索 后端代码 Controller.java /** 获取地区信息列表 @param pxRegion 地区信息 @return 地区信息列表 / @GetMapping(value = "/getRegionList") public AjaxResult getRegionList(PxRegion pxRegion) { return AjaxResult.success(pxRegionService.getRegionList(pxRegion)); } 地区管理Service接口 /* 地区管理Service接口 @author 裴浩宇 @date 2023-12-06 / public interface IPxRegionService { /* 查询地区管理列表 @param pxRegion 地区管理 @return 地区管理集合 / List<PxRegion> getRegionList(PxRegion pxRegion); } 地区管理Service业务层处理 /* 地区管理Service业务层处理 @author 裴浩宇 @date 2023-12-06 */ @Service public class PxRegionServiceImpl implements IPxRegionService { @Resource private PxRegionMapper pxRegionMapper; /** 查询地区管理列表 @param pxRegion 地区管理 @return 地区管理 / @Override public List<PxRegion> getRegionList(PxRegion pxRegion) { List<PxRegion> regionList = pxRegionMapper.getRegionList(pxRegion); // 如果名称不为空,说明是模糊查询需要处理数据 if (StringUtils.isNotEmpty(pxRegion.getName())) { // 遍历所有地区 for (PxRegion region : regionList) { if (region.getLb() == 1) { // 市的话默认带出该市的区 // 参数 PxRegion param = new PxRegion(); param.setLb(2L); param.setSsdqdm(region.getId()); // 获取该市下的区 List<PxRegion> children = pxRegionMapper.getRegionList(param); region.setChildren(children); } else if (region.getLb() == 2) { region.setChildren(null); // 区的话 // 参数 PxRegion param = new PxRegion(); // 找到区的所属市 param.setId(region.getSsdqdm()); List<PxRegion> city = pxRegionMapper.getRegionList(param); city.get(0).setChildren(Collections.singletonList(region)); // 将市加入 regionList.add(city.get(0)); regionList.remove(region); } } } return regionList; } } 地区管理Mapper接口 /* 地区管理Mapper接口 @author 裴浩宇 @date 2023-12-06 */ public interface PxRegionMapper { /** 查询地区管理列表 @param pxRegion 地区管理 @return 地区管理集合 */ public List<PxRegion> getRegionList(PxRegion pxRegion); } 地区管理Mapper.xml <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.pnkx.mapper.PxRegionMapper"> <resultMap type="PxRegion" id="PxRegionResult"> <result property="id" column="id"/> <result property="name" column="name"/> <result property="py" column="py"/> <result property="ssdqdm" column="ssdqdm"/> <result property="lb" column="lb"/> <result property="yb" column="yb"/> <result property="jlzt" column="jlzt"/> </resultMap> <sql id="selectPxRegionVo"> select id, name, py, ssdqdm, lb, yb, jlzt from yy_dq_ybdmk </sql> <select id="getRegionList" parameterType="PxRegion" resultMap="PxRegionResult"> <include refid="selectPxRegionVo"/> <where> <if test="id != null "> and id = #{id} </if> <if test="name != null and name != ''"> and name like concat('%', #{name}, '%') </if> <if test="py != null and py != ''"> and py = #{py} </if> <if test="ssdqdm != null "> and ssdqdm = #{ssdqdm} </if> <if test="lb != null "> and lb = #{lb} </if> <if test="yb != null "> and yb = #{yb} </if> <if test="jlzt != null "> and jlzt = #{jlzt} </if> </where> </select> </mapper> 前端代码 Region组件 <template> <div class="cascader"> <el-cascader v-model="selectedValues" :options="options" @change="handleChange" :before-filter="handleFilter" :props="props" separator=" - " filterable clearable placeholder="请选择省市区" /> </div> </template> <script> import {getRegionList} from "@/api/px/blog/region"; export default { name: "Region", props: { address: { type: Array, default: "" } }, watch: { address: { async handler() { // 获取省列表 await this.getRegionList(0); // 获取市列表 if (this.address[0]) await this.getRegionList(1, this.address[0]); // 获取区列表 if (this.address[1]) await this.getRegionList(2, this.address[1]); // 赋值 this.selectedValues = [ Number(this.address[0]), Number(this.address[1]), Number(this.address[2]), ]; }, immediate: true, deep: true }, }, data() { const _this = this; return { // 选中的省市区值 selectedValues: [], // 数据源,省市区的JSON数据 options: [], // 映射关系 props: { // value对应属性 value: "id", // label对应属性 label: "name", // 开启懒加载 lazy: true, lazyLoad: function (node, resolve) { const {level} = node; getRegionList({ lb: level, ssdqdm: node.value }).then(res => { resolve(res.data.filter(item => { if (level === 0) { // 判断是否已存在省 return !_this.options?.find(province => province.id === item.id) } else if (level === 1) { // 判断是否已存在市 return !_this.options.find(province => province.id === item.ssdqdm)?.children.find(city => city.id === item.id) } else if (level === 2) { // 找到省 const theProvince = _this.options.find(province => province.children.find(city => city.id === item.ssdqdm)); // 判断是否已存在区 return !theProvince?.children.find(city => city.id === item.ssdqdm)?.children.find(area => area.id === item.id) } }).map(item => { return { ...item, children: [], leaf: level >= 2 } })); }) } } }; }, methods: { /** 搜索 @param searchValue / async handleFilter(searchValue) { if (searchValue) { await this.getRegionList(undefined, undefined, searchValue); this.selectedValues = Number(searchValue); } }, /* 选择省市区 @param value / handleChange(value) { this.$emit("update:address", value); }, /* 获取地区列表 @param level @param parentCode @param name */ async getRegionList(level, parentCode, name) { await getRegionList({ lb: level, ssdqdm: parentCode, name }).then(res => { res.data.forEach(item => { if (item.lb === 0) { // 省 this.options.find(province => province.id === item.id) || this.options.push(item); } else if (item.lb === 1) { // 市 this.options.find(province => province.id === item.ssdqdm)?.children?.find(city => city.id === item.id) || this.options.find(province => province.id === item.ssdqdm).children.push({ ...item, children: item.children.map(area => { return { ...area, leaf: true } }) }); } else if (item.lb === 2) { // 区 // 找到省 const theProvince = this.options.find(province => province.children.find(city => city.id === item.ssdqdm)); // 找到市放入区 theProvince.children.find(city => city.id === item.ssdqdm)?.children?.find(area => area.id === item.id) || theProvince.children.find(city => city.id === item.ssdqdm)?.children.push({ ...item, leaf: true }); } }) }) } } } </script> 使用 <template> <region :address.sync="address"/> </template> <script> import Region from "@/components/Region/index.vue"; export default { components: { Region }, data() { return { // 地址 address: [140000, 140400, 140406] } }, watch: { address(val) { this.$message.success('选中: ' + val.join('-')); } } } </script> 在上述代码中,接口大致有三个参数:level(层级)、parentCode(父级编号)、name(名称用于模糊查询) 前端组件在初始时回显选中的地区,改变时触发emit事件使父组件修改,并且支持懒加载和搜索。 源码下载 点击下载源码

  • 发布于 2023-12-01

    Java 23种设计模式——组合模式(Composite Pattern)

    什么是组合模式? 组合模式是一种结构型设计模式,它允许我们将对象组织成树状结构,并以统一的方式处理这些对象。组合模式通过将对象分为两个主要角色来实现这一目标:叶节点和容器节点。叶节点表示树结构中的最底层对象,而容器节点则表示由叶节点或其他容器节点组成的对象。 组合模式的结构 组合模式的结构包含以下几个核心组件: Component(组件):定义叶节点和容器节点的共同接口,可以在该接口上定义默认行为和访问子节点的方法。 Leaf(叶节点):表示树结构中的最底层对象,没有子节点。 Composite(容器节点):由叶节点或其他容器节点组成的对象,可以包含子节点,并实现在Component接口中定义的方法。 下面是一个简化的类图,展示了组合模式的结构: +------------------+| Component | +------------------+ | +operation() | | +add(Component) | | +remove(Component) | | +getChild(int) | +------------------+ / / / / / +--------------+ +--------------+ | Leaf | | Composite | +--------------+ +--------------+ | +operation() | | +operation() | +--------------+ +--------------+ 组合模式的应用场景 组合模式适用于以下情况: 当你希望以统一的方式处理对象集合时,可以使用组合模式。这种方式使得客户端可以将单个对象和组合对象一视同仁。 当你有一个对象树,且对象之间具有层次关系时,组合模式可以帮助你更好地管理和操作这些对象。 当你希望添加或移除树中的对象时,组合模式能够提供一种一致的方式来执行这些操作。 示例代码 shapes shapes/Shape.java: 通用形状接口 package com.pnkx.designpatterns.composite.shapes; import java.awt.*; public interface Shape { int getX(); int getY(); int getWidth(); int getHeight(); void move(int x, int y); boolean isInsideBounds(int x, int y); void select(); void unSelect(); boolean isSelected(); void paint(Graphics graphics); } shapes/BaseShape.java: 提供基本功能的抽象形状 package com.pnkx.designpatterns.composite.shapes; import java.awt.*; abstract class BaseShape implements Shape { public int x; public int y; public Color color; private boolean selected = false; BaseShape(int x, int y, Color color) { this.x = x; this.y = y; this.color = color; } @Override public int getX() { return x; } @Override public int getY() { return y; } @Override public int getWidth() { return 0; } @Override public int getHeight() { return 0; } @Override public void move(int x, int y) { this.x += x; this.y += y; } @Override public boolean isInsideBounds(int x, int y) { return x > getX() && x < (getX() + getWidth()) && y > getY() && y < (getY() + getHeight()); } @Override public void select() { selected = true; } @Override public void unSelect() { selected = false; } @Override public boolean isSelected() { return selected; } void enableSelectionStyle(Graphics graphics) { graphics.setColor(Color.LIGHT_GRAY); Graphics2D g2 = (Graphics2D) graphics; float[] dash1 = {2.0f}; g2.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 2.0f, dash1, 0.0f)); } void disableSelectionStyle(Graphics graphics) { graphics.setColor(color); Graphics2D g2 = (Graphics2D) graphics; g2.setStroke(new BasicStroke()); } @Override public void paint(Graphics graphics) { if (isSelected()) { enableSelectionStyle(graphics); } else { disableSelectionStyle(graphics); } // ... } } shapes/Dot.java: 点 package com.pnkx.designpatterns.composite.shapes; import java.awt.*; public class Dot extends BaseShape { private final int DOT_SIZE = 3; public Dot(int x, int y, Color color) { super(x, y, color); } @Override public int getWidth() { return DOT_SIZE; } @Override public int getHeight() { return DOT_SIZE; } @Override public void paint(Graphics graphics) { super.paint(graphics); graphics.fillRect(x - 1, y - 1, getWidth(), getHeight()); } } shapes/Circle.java: 圆形 package com.pnkx.designpatterns.composite.shapes; import java.awt.*; public class Circle extends BaseShape { public int radius; public Circle(int x, int y, int radius, Color color) { super(x, y, color); this.radius = radius; } @Override public int getWidth() { return radius * 2; } @Override public int getHeight() { return radius * 2; } public void paint(Graphics graphics) { super.paint(graphics); graphics.drawOval(x, y, getWidth() - 1, getHeight() - 1); } } shapes/Rectangle.java: 三角形 package com.pnkx.designpatterns.composite.shapes; import java.awt.*; public class Rectangle extends BaseShape { public int width; public int height; public Rectangle(int x, int y, int width, int height, Color color) { super(x, y, color); this.width = width; this.height = height; } @Override public int getWidth() { return width; } @Override public int getHeight() { return height; } @Override public void paint(Graphics graphics) { super.paint(graphics); graphics.drawRect(x, y, getWidth() - 1, getHeight() - 1); } } shapes/CompoundShape.java: 由其他形状对象组成的复合形状 package com.pnkx.designpatterns.composite.shapes; import java.awt.*; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class CompoundShape extends BaseShape { protected List<Shape> children = new ArrayList<>(); public CompoundShape(Shape... components) { super(0, 0, Color.BLACK); add(components); } public void add(Shape component) { children.add(component); } public void add(Shape... components) { children.addAll(Arrays.asList(components)); } public void remove(Shape child) { children.remove(child); } public void remove(Shape... components) { children.removeAll(Arrays.asList(components)); } public void clear() { children.clear(); } @Override public int getX() { if (children.size() == 0) { return 0; } int x = children.get(0).getX(); for (Shape child : children) { if (child.getX() < x) { x = child.getX(); } } return x; } @Override public int getY() { if (children.size() == 0) { return 0; } int y = children.get(0).getY(); for (Shape child : children) { if (child.getY() < y) { y = child.getY(); } } return y; } @Override public int getWidth() { int maxWidth = 0; int x = getX(); for (Shape child : children) { int childsRelativeX = child.getX() - x; int childWidth = childsRelativeX + child.getWidth(); if (childWidth > maxWidth) { maxWidth = childWidth; } } return maxWidth; } @Override public int getHeight() { int maxHeight = 0; int y = getY(); for (Shape child : children) { int childsRelativeY = child.getY() - y; int childHeight = childsRelativeY + child.getHeight(); if (childHeight > maxHeight) { maxHeight = childHeight; } } return maxHeight; } @Override public void move(int x, int y) { for (Shape child : children) { child.move(x, y); } } @Override public boolean isInsideBounds(int x, int y) { for (Shape child : children) { if (child.isInsideBounds(x, y)) { return true; } } return false; } @Override public void unSelect() { super.unSelect(); for (Shape child : children) { child.unSelect(); } } public boolean selectChildAt(int x, int y) { for (Shape child : children) { if (child.isInsideBounds(x, y)) { child.select(); return true; } } return false; } @Override public void paint(Graphics graphics) { if (isSelected()) { enableSelectionStyle(graphics); graphics.drawRect(getX() - 1, getY() - 1, getWidth() + 1, getHeight() + 1); disableSelectionStyle(graphics); } for (Shape child : children) { child.paint(graphics); } } } editor editor/ImageEditor.java: 形状编辑器 package com.pnkx.designpatterns.composite.editor; import com.pnkx.designpatterns.composite.shapes.CompoundShape; import com.pnkx.designpatterns.composite.shapes.Shape; import javax.swing.; import javax.swing.border.Border; import java.awt.; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; public class ImageEditor { private EditorCanvas canvas; private CompoundShape allShapes = new CompoundShape(); public ImageEditor() { canvas = new EditorCanvas(); } public void loadShapes(Shape... shapes) { allShapes.clear(); allShapes.add(shapes); canvas.refresh(); } private class EditorCanvas extends Canvas { JFrame frame; private static final int PADDING = 10; EditorCanvas() { createFrame(); refresh(); addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { allShapes.unSelect(); allShapes.selectChildAt(e.getX(), e.getY()); e.getComponent().repaint(); } }); } void createFrame() { frame = new JFrame(); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); frame.setLocationRelativeTo(null); JPanel contentPanel = new JPanel(); Border padding = BorderFactory.createEmptyBorder(PADDING, PADDING, PADDING, PADDING); contentPanel.setBorder(padding); frame.setContentPane(contentPanel); frame.add(this); frame.setVisible(true); frame.getContentPane().setBackground(Color.LIGHT_GRAY); } public int getWidth() { return allShapes.getX() + allShapes.getWidth() + PADDING; } public int getHeight() { return allShapes.getY() + allShapes.getHeight() + PADDING; } void refresh() { this.setSize(getWidth(), getHeight()); frame.pack(); } public void paint(Graphics graphics) { allShapes.paint(graphics); } } } Main.java: 客户端代码 package com.pnkx.designpatterns.composite; import com.pnkx.designpatterns.composite.editor.ImageEditor; import com.pnkx.designpatterns.composite.shapes.Circle; import com.pnkx.designpatterns.composite.shapes.CompoundShape; import com.pnkx.designpatterns.composite.shapes.Dot; import com.pnkx.designpatterns.composite.shapes.Rectangle; import java.awt.*; public class Main { public static void main(String[] args) { ImageEditor editor = new ImageEditor(); editor.loadShapes( new Circle(10, 10, 10, Color.BLUE), new CompoundShape( new Circle(110, 110, 50, Color.RED), new Dot(160, 160, Color.RED) ), new CompoundShape( new Rectangle(250, 250, 100, 100, Color.GREEN), new Dot(240, 240, Color.GREEN), new Dot(240, 360, Color.GREEN), new Dot(360, 360, Color.GREEN), new Dot(360, 240, Color.GREEN) ) ); } } 执行结果 总结 组合模式是一种强大且灵活的设计模式,它允许我们以统一的方式处理对象集合,并将对象组织成树状结构。通过定义共同接口和使用叶节点和容器节点,组合模式使得处理复杂对象结构变得简单而一致。在开发具有层次结构的系统时,组合模式是一个有用的工具。 源码 组合模式源码下载 Java 23种设计模式(含源码)

  • 发布于 2023-11-23

    Java 23种设计模式——适配器模式(Adapter)

    适配器模式是一种常见的设计模式,它可以用来将一个类的接口转换成客户希望的另外一个接口,从而让原本由于接口不兼容而不能一起工作的类能够协同工作。在Java中,适配器模式通常涉及三个角色:目标接口、适配器和被适配者。 目标接口 目标接口是客户端所期望使用的接口,也可以是一个抽象类。它定义了客户端需要调用的方法,但是这些方法的参数类型或返回值类型可能与原有的接口不符。目标接口的存在是为了让客户端可以通过统一的接口来访问不同的子系统,而不需要关注具体的实现细节。 下面是一个示例代码,演示了如何定义一个目标接口: public interface Target {void request(); } 被适配者 被适配者是需要被适配的类,它定义了客户端不关心的接口。被适配者通常是一个已经存在的类,它的接口与目标接口不兼容,需要通过适配器进行转换。被适配者的存在是为了提供一些功能,但是其接口与目标接口不匹配,无法直接使用。 以下是一个示例代码,演示了如何定义一个被适配者类: public class Adaptee { public void specificRequest() { System.out.println("Adaptee's specific request"); } } 适配器 适配器是实现了目标接口的类,并且包装了一个需要适配的类的实例,以便将其接口转换成客户端所期望的接口。适配器通常包含一个被适配者对象的引用,在目标接口方法中调用被适配者对象的方法,并将其返回值转换成客户端期望的类型。 以下是一个示例代码,演示了如何定义一个适配器类: public class Adapter implements Target { private Adaptee adaptee; public Adapter(Adaptee adaptee) { this.adaptee = adaptee; } public void request() { adaptee.specificRequest(); } } 在这个示例中,Adapter 类实现了 Target 接口,并且包装了 Adaptee 的实例。在 request 方法中调用了 Adaptee 的 specificRequest 方法。 客户端代码 客户端代码需要创建一个适配器对象,并且通过适配器对象来调用被适配者的方法。客户端不需要知道适配器和被适配者之间的具体关系,只需要知道目标接口即可。 以下是一个示例代码,演示了如何在客户端代码中使用适配器模式: public class Main { public static void main(String[] args) { Adaptee adaptee = new Adaptee(); Target adapter = new Adapter(adaptee); adapter.request(); } } 在这个示例中,客户端首先创建一个 Adaptee 对象,然后创建一个适配器对象,并将 Adaptee 对象作为参数传递给适配器。最后,客户端通过适配器来调用目标接口的方法。 总结 适配器模式是一种非常有用的设计模式,它可以帮助我们解决接口不兼容的问题。通过使用适配器模式,我们可以将一个类的接口转换成客户端所期望的另外一个接口,从而让原本由于接口不兼容而不能一起工作的类能够协同工作。在Java中,实现适配器模式通常需要定义目标接口、被适配者和适配器三个角色。 Java 23种设计模式(含源码)

  • 发布于 2023-11-22

    SSR、SSG和 SPA 是什么,有什么优缺点?

    当谈论 Web 应用程序开发的时候,你可能会听到一些术语,如 SSR、SPA 和 SSG。这些术语代表了不同的开发模式,每种模式都有其独特的优点和适用场景。在本文中,我们将深入探讨 SSR、SPA 和 SSG,并比较它们之间的异同。   SSR(Server-Side Rendering,服务器端渲染 SSR 是一种将页面在服务器端进行渲染的开发模式。在 SSR 中,服务器会将页面渲染为完整的 HTML 内容,然后将其发送到客户端。这意味着在客户端收到 HTML 页面之前,页面的初始内容已经存在,可以提供更快的首屏加载速度。SSR 还具有良好的 SEO(Search Engine Optimization)性能,因为搜索引擎能够直接读取页面的 HTML 内容。 SSR 的一些优点包括: 更快的首屏加载速度:由于页面的初始内容已在服务器端生成,用户可以更快地看到页面。 良好的 SEO 性能:搜索引擎可以直接读取和索引服务器端生成的完整 HTML 页面。 然而,SSR 也存在一些限制和挑战: 更高的服务器负载:由于服务器需要处理页面的渲染和数据请求,因此需要更强大的服务器性能。 部署复杂度增加:SSR 的部署相对来说更加复杂,需要考虑服务器环境和后端数据交互等问题。 SSG(Static Site Generation,静态网站生成) SSG 是一种在构建时生成所有 HTML 页面的开发模式。在 SSG 中,开发人员在构建过程中将页面内容预先生成为静态 HTML 文件,并将其提供给服务器或托管在 CDN 上。当用户请求页面时,服务器直接返回相应的静态 HTML 文件,无需动态生成页面。这可以提供快速的加载速度、低成本和良好的 SEO 性能。 SSG 的一些优点包括: 快速的加载速度:由于页面已经预先生成为静态 HTML 文件,无需动态生成,因此可以提供快速的加载速度。 低成本:由于不需要动态服务器渲染,托管静态文件的成本较低。 良好的 SEO 性能:静态 HTML 页面对搜索引擎更友好,有利于索引和排名。 然而,SSG 也存在一些限制和挑战: 对于动态内容的支持较差:由于页面在构建时生成,并且不会根据用户的请求动态更新,因此对于某些动态内容(如用户生成内容)的支持较差。 更新内容需要重新构建:当需要更新页面内容时,需要重新构建整个应用程序并重新部署。 SPA(Single-Page Application,单页应用) SPA 是一种在客户端进行渲染的开发模式。在 SPA 中,页面的内容和路由控制通过 JavaScript 在客户端进行管理。SPA 的主要优点是提供了更好的用户体验和交互性。由于只需要加载一次页面,之后的页面切换都是通过异步加载数据和更新视图实现的,用户可以快速地浏览页面,无需频繁地与服务器进行交互。 SPA 的一些优点包括: 更好的用户体验:页面切换快速,无需等待服务器返回完整的 HTML 页面。 丰富的交互性:SPA 可以使用前端框架(如 Vue.js 或 React)来处理用户交互,实现复杂的功能和动画效果。 然而,SPA 也存在一些限制和挑战: 更多的前端资源:由于整个应用程序的逻辑和渲染都在客户端完成,因此需要下载更多的前端资源(如 JavaScript、CSS 和图像),可能导致页面加载速度变慢。 SEO 不友好:由于搜索引擎通常只读取和索引初始 HTML 页面,SPA 对于搜索引擎的抓取和索引性能较差。这需要额外的工作来实现服务端渲染或预渲染以改善 SEO。 综上所述,SSR、SPA 和 SSG 是三种不同的 Web 应用程序开发模式,每种模式都有其独特的优点和适用场景。选择合适的模式取决于您的应用程序需求和目标。如果您追求更好的首屏加载速度和 SEO 性能,可以选择 SSR 或 SSG;如果您注重用户体验和交互性,可以选择 SPA。在实际开发中,您也可以根据具体情况结合使用这些模式来达到最佳效果。

  • 发布于 2023-11-15

    JavaScript 的事件循环机制

    JavaScript 的事件循环机制(Event Loop)是一种管理和调度异步任务执行的机制,它确保 JavaScript 代码能够以非阻塞的方式执行。 JavaScript 运行时包含以下几个重要的组成部分: 调用栈(Call Stack):用于存储执行上下文(函数调用)的栈结构。当执行一个函数时,将其执行上下文推入调用栈,函数执行完成后将其弹出。调用栈是单线程执行的,同一时间只能执行一个任务。 消息队列(Message Queue):用于存储待执行的消息(任务)。每个消息都与一个或多个回调函数相关联。当异步任务完成后,会将其对应的回调函数推入消息队列。 事件循环(Event Loop):负责监听调用栈和消息队列,并决定何时将消息队列中的任务推入调用栈执行。事件循环不断地检查调用栈和消息队列的状态,并根据一定的规则进行调度。 事件循环的基本流程如下: 执行同步任务:从全局上下文开始,按顺序执行同步任务,直到遇到第一个异步任务。 处理异步任务:当遇到异步任务时,会将其挂起并注册回调函数。异步任务可以是定时器、网络请求、事件监听等。 异步任务完成后:将其对应的回调函数推入消息队列。 判断调用栈是否为空:当调用栈为空时,事件循环会检查消息队列。 执行回调函数:将消息队列中的第一个任务(回调函数)推入调用栈执行。 重复上述过程:循环执行上述流程,保证异步任务得到处理并执行回调函数。 需要注意的是,事件循环机制中还存在微任务(Microtask)和宏任务(Macrotask)的概念,它们影响着异步任务的执行顺序和优先级。微任务具有比宏任务更高的优先级,会在当前宏任务执行完毕后立即执行。 通过事件循环机制,JavaScript 可以处理异步操作,并确保代码执行的非阻塞性,提供了一种灵活的方式来处理异步任务。 微任务(Microtask)和宏任务(Macrotask)都是在事件循环中用于处理异步操作的机制,它们之间有一些重要的区别。 微任务(Microtask): 执行时机:微任务会在当前宏任务执行完成后立即执行。也就是说,当一个宏任务执行完毕后,在开始下一个宏任务之前,会先检查是否存在微任务队列,如果有,则依次执行微任务队列中的所有任务。 优先级:微任务具有比宏任务更高的优先级。在事件循环中,微任务总是优先于宏任务执行,即使它们是在同一个事件循环阶段中产生的。 触发方式:常见的触发微任务的方式包括Promise的resolve/reject、MutationObserver以及Node.js中的process.nextTick等。 示例应用:微任务适合处理一些优先级较高、需要尽快执行的任务,例如处理状态更新、执行DOM操作后的回调等。 宏任务(Macrotask): 执行时机:宏任务会在当前事件循环的下一个阶段执行。在事件循环中,每个宏任务之间会有一个渲染帧的间隔,这样可以保证浏览器在执行宏任务时能够进行页面渲染和响应用户交互。 优先级:宏任务的优先级相对较低,在事件循环中会在微任务之后执行。 触发方式:常见的触发宏任务的方式包括setTimeout、setInterval、I/O操作、UI交互事件(如点击、滚动)等。 示例应用:宏任务适合处理一些耗时较长、优先级较低的任务,例如网络请求、定时器回调、用户交互事件回调等。 需要注意的是,微任务和宏任务的执行顺序是固定的:一个宏任务执行完毕后,会先检查并执行所有的微任务,然后再执行下一个宏任务。这种机制保证了微任务的即时性和优先级。 在实际开发中,合理使用微任务和宏任务可以提高代码的性能和响应能力。微任务适合处理一些重要的、需要立即执行的任务,而宏任务则适合处理一些延时较长或者对实时性要求不高的任务。 事件循环经典案例 参考资料 什么是Event Loop?

  • 发布于 2023-11-13

    Vue 3 中的 provide 和 inject

    在 Vue 3 中,provide 和 inject 是一对用于跨级组件传递数据的高级选项。它们提供了一种方式来在父组件中提供数据,并在子组件中使用 inject 来接收这些数据。本文将详细介绍这两个选项的用法、示例和最佳实践。 什么是 provide 和 inject 在 Vue 3 中,provide 和 inject 是一对配套的 API,用于解决组件之间跨层级传递数据的问题。通过 provide 在父组件中提供数据,然后在子组件中使用 inject 来接收这些数据,可以实现组件树中的数据共享与传递。 provide 的用法 在父组件中,通过 provide 方法来提供数据。provide 方法接受一个键值对,其中键是用于标识数据的唯一标识符,值可以是任意类型的数据,包括基本类型、对象、函数等。通常情况下,我们可以使用响应式对象或者 ref 来提供数据,以便在子组件中进行更改时能够触发响应式更新。 import { provide, reactive } from 'vue';// 在父组件中提供一个响应式对象 const sharedData = reactive({ message: 'Hello, this is shared data' }); provide('sharedData', sharedData); 在上面的示例中,我们在父组件中使用 provide 提供了一个名为 sharedData 的响应式对象,子组件可以通过 inject 来获取并使用这个数据对象。 inject 的用法 在子组件中,通过 inject 来接收父组件中提供的数据。inject 接受一个键,并从父组件的 provide 中查找对应的数据。如果找到了,则返回该数据,否则返回默认值(可选)。 import { inject } from 'vue'; // 在子组件中使用 inject 获取提供的数据 const sharedData = inject('sharedData', /* 可选的默认值 */ null); // 现在子组件中可以访问 sharedData.message console.log(sharedData.message); // 输出 'Hello, this is shared data' 在上面的示例中,我们在子组件中使用 inject 获取了父组件中提供的名为 sharedData 的数据,然后可以直接使用该数据对象中的属性。 最佳实践和注意事项 在使用 provide 和 inject 时,有一些最佳实践和注意事项需要我们牢记: 避免直接修改 inject 获取的值:尽管 inject 获取的值是响应式的,但为了避免潜在的问题,通常不建议直接修改 inject 获取的值。而是建议在父组件中通过 provide 提供一个可响应的对象,然后在子组件中直接修改该对象的属性。 命名规范:为了避免命名冲突,建议在使用 provide 和 inject 时使用唯一的标识符作为键,例如使用组件名作为前缀。 数据传递和响应式更新:通过 provide 和 inject 可以实现数据在组件树中的传递和响应式更新,这对于多层级的复杂组件通信非常有用。 组合式 API 中的使用:在使用组合式 API 创建自定义逻辑时,也可以在 setup 函数中使用 provide 和 inject,以便在组合式 API 中实现跨组件通信。 合理管理 key 使用 Symbol 作为提供的键:如之前所述,使用 Symbol 可以提供更好的唯一性保证,避免了命名冲突的风险。您可以创建一个专门用于依赖注入的 Symbol 集合,以便在整个应用程序中共享和管理这些 Symbol。 // 创建一个专门用于依赖注入的 Symbol 集合 const mySymbols = { sharedData1: Symbol('sharedData1'), sharedData2: Symbol('sharedData2'), // 可以继续添加其他 Symbol }; // 在 provide 中使用上述的 Symbol provide(mySymbols.sharedData1, sharedData1); provide(mySymbols.sharedData2, sharedData2); // 在 inject 中使用上述的 Symbol const data1 = inject(mySymbols.sharedData1); const data2 = inject(mySymbols.sharedData2); 统一管理 key:如果不使用 Symbol,而是直接使用字符串作为提供的键,可以在单独的文件中统一定义这些字符串键,然后在需要使用的地方进行导入。这样可以确保键名的统一和管理。 // keys.js export const SHARED_DATA_1 = 'sharedData1'; export const SHARED_DATA_2 = 'sharedData2'; // 在组件中使用 import { provide } from 'vue'; import { SHARED_DATA_1, SHARED_DATA_2 } from './keys'; provide(SHARED_DATA_1, sharedData1); provide(SHARED_DATA_2, sharedData2); 通过以上方法,您可以合理地管理依赖注入的 key,确保其唯一性和统一性。这种方式能够在大型应用程序中更好地组织和管理依赖注入的键。 总结 在 Vue 3 中,provide 和 inject 提供了一种灵活而强大的方式来实现跨组件层级的数据传递和共享。通过提供数据的父组件和接收数据的子组件之间建立了一种松耦合的关系,使得组件之间的通信变得更加灵活和可维护。 希望本文能够帮助您更好地理解和使用 Vue 3 中的 provide 和 inject,并能在实际项目中发挥它们的作用。

  • 发布于 2023-11-08

    Java 23种设计模式——单例模式(Singleton)

    单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一,有以下特点: - 单例类只能有一个实例。 - 单例类必须自己创建自己的唯一实例。 - 单例类必须给所有其他对象提供这一实例。 实现单例模式 在 Java 中,实现单例模式有多种方式,以下是其中两种比较常见的方式: 1. 饿汉式单例模式 public class Singleton {private static Singleton instance = new Singleton(); private Singleton() { } public static Singleton getInstance() { return instance; } } 在饿汉式单例模式中,实例在类加载的时候就创建好。这样做的优点是实现简单,线程安全,但缺点是如果这个实例很大却没有被使用,会造成资源的浪费。 2. 懒汉式单例模式 public class Singleton { private static Singleton instance; private Singleton() { } public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } 懒汉式单例模式在第一次获取实例的时候才进行实例化,它的优点是避免了资源浪费,但缺点是在多线程环境下会存在性能问题,因为在getInstance方法上加了synchronized关键字,导致每次调用都要进行同步操作。 当在懒汉式单例模式中加入双重检查锁(Double-Checked Locking)时,可以更好地解决多线程环境下的性能问题。以下是加入双重检查锁的懒汉式单例模式示例: public class Singleton { private volatile static Singleton instance; private Singleton() { } public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } } 应用场景 单例模式通常在以下场景中使用: 对于频繁使用的对象,可以省去创建对象所消耗的时间,提高性能。 控制资源的使用,例如数据库连接池等。 总结 单例模式是设计模式中的经典之一,它可以确保一个类只有一个实例,提供全局访问点,以及控制资源的有效利用。在实际应用中,需要根据具体的业务场景和性能要求选择合适的单例模式实现方式。 希望本文对你理解 Java 单例模式有所帮助。 Java 23种设计模式(含源码)

  • 发布于 2023-11-06

    Vue 3中自定义指令的使用方法

    在Vue 3中,自定义指令的使用方法有了一些改变。本文将介绍如何在Vue 3中编写和使用自定义指令。 1. 自定义指令的注册 在Vue 3中,我们可以使用app.directive方法来注册自定义指令。例如,我们要注册一个名为"my-directive"的指令,可以按照以下方式进行: const app = createApp(App)app.directive('my-directive', { // 指令选项 }) 2. 自定义指令的选项 自定义指令的选项和Vue 2中基本保持一致。常用的选项包括: beforeMount:在元素挂载到DOM之前调用。 mounted:在元素挂载到DOM后调用。 beforeUpdate:在元素更新之前调用。 updated:在元素更新之后调用。 beforeUnmount:在元素卸载之前调用。 unmounted:在元素卸载之后调用。 除了上述选项,还可以包含其他选项,如bind、unbind、beforeUnmount等,具体可以根据自己的需求进行配置。 3. 自定义指令的示例 下面我们通过一个简单的例子来演示如何在Vue 3中编写和使用自定义指令。 首先,在注册自定义指令之前,我们需要创建一个Vue实例。例如: const app = createApp(App) 然后,我们可以通过app.directive方法来注册自定义指令。假设我们要创建一个自动聚焦的指令,可以按照以下方式进行: app.directive('focus', { mounted(el) { el.focus() } }) 在上述代码中,我们注册了名为"focus"的自定义指令,并在mounted选项中调用el.focus()来实现自动聚焦效果。 最后,在模板中使用自定义指令。例如: <template> <div> <input v-focus /> </div> </template> 在上述代码中,我们通过v-focus将自定义指令应用到了input元素上。 4. 自定义指令的修饰符 Vue 3中的自定义指令也支持修饰符的使用。修饰符可以通过.语法来添加,例如v-my-directive.modifier。具体的修饰符使用方式可以根据自己的需求进行定义和扩展。 总结 本文介绍了在Vue 3中如何编写和使用自定义指令。我们通过注册指令、设置选项、示例代码等方式详细讲解了自定义指令的使用方法。希望本文能够对你理解和应用Vue 3中的自定义指令有所帮助!

  • 发布于 2023-11-03

    Java 23种设计模式——原型模式(Prototype)

    原型模式(Prototype) 一、问题 在某些情况下,需要创建对象的副本,但复制一个对象的成本可能很高,或者希望避免与对象的具体类耦合。例如,当创建对象的过程较为复杂,或者对象包含大量共享的状态时,使用常规的创建方法可能会导致性能下降。 二、解决方案 原型模式的解决方案是通过复制现有对象来创建新对象,而不是从头开始构建。这允许我们以更高效的方式创建新对象,同时避免了与对象类的直接耦合。核心概念是在原型对象的基础上进行克隆,使得新对象具有与原型相同的初始状态。 在原型模式中,通常会有以下几个角色: 抽象原型(Prototype):声明克隆方法,作为所有具体原型的基类或接口。 具体原型(Concrete Prototype):实现克隆方法,从自身创建一个副本。 客户端(Client):使用原型对象的客户端代码,在需要新对象时通过克隆现有对象来创建新实例。 三、效果 原型模式的应用可以带来以下效果: 减少对象创建的成本:避免了复杂对象的重复初始化过程,提高了创建对象的效率。 避免与具体类耦合:客户端可以通过克隆方法创建新对象,而无需知道具体类的细节,降低了耦合度。 灵活性增加:可以在运行时动态地添加或删除原型,适应不同的对象创建需求。 支持动态配置:可以通过克隆来定制对象的不同配置,而无需修改其代码。 然而,也需要注意一些限制,如: 深克隆问题:原型模式默认进行浅克隆,即复制对象本身和其引用。如果对象内部包含其他对象的引用,可能需要实现深克隆来复制整个对象结构。 克隆方法的实现:某些对象可能不容易进行克隆,特别是涉及到文件、网络连接等资源的情况。 总之,原型模式是一种在需要创建对象副本时非常有用的设计模式,它提供了一种灵活且高效的方法来处理对象的复制需求。 四、代码 1.Person.java /** Person @author 裴浩宇 @version 1.0 @date 2023/11/3 17:13 @description 人 */ public class Person implements Cloneable{ // 姓名 private String name; // 年龄 private Integer age; // 住址 private Address address; @Override public Person clone() { Person clone = null; try { clone = (Person) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return clone; } @Override public String toString() { return "Person{" + "name='" + name + ''' + ", age=" + age + ", address=" + address + '}'; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } } 2.Address.java /** Address @author 裴浩宇 @version 1.0 @date 2023/11/3 17:14 @description 地址 */ public class Address { // 省 private String province; // 市 private String city; // 区 private String area; @Override public String toString() { return "Address{" + "province='" + province + ''' + ", city='" + city + ''' + ", area='" + area + ''' + '}'; } public String getProvince() { return province; } public void setProvince(String province) { this.province = province; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } public String getArea() { return area; } public void setArea(String area) { this.area = area; } } 3.Main.java /** Main @author 裴浩宇 @version 1.0 @date 2023/11/3 17:18 @description 客户端 */ public class Main { public static void main(String[] args) { Person p = new Person(); p.setName("Pei你看雪"); p.setAge(18); Address address = new Address(); address.setProvince("山东省"); address.setCity("济南市"); address.setArea("历区城"); p.setAddress(address); Person q = p; Person z = p.clone(); p.setAge(19); p.getAddress().setArea("高新区"); System.out.println("q" + q); System.out.println("z" + z.toString()); } } 4.打印结果 从输出结果可以看到,使用 = 赋值方式创建对象时,两个对象共享相同的引用类型字段;而使用原型模式创建对象时,新对象是独立于原对象的,不会共享引用类型字段。 因此,在需要创建新对象并避免共享引用类型字段的情况下,建议使用原型模式。 当然,对于对象中的引用类型字段,只会复制引用,新对象与原对象将共享同一个引用类型对象。 源码 源码下载 Java 23种设计模式(含源码)

  • 发布于 2023-10-27

    Java 23种设计模式——建造者模式(Builder)

    建造者模式(Builder) 一、问题 在某些情况下,一个对象的创建过程非常复杂,涉及多个步骤,每个步骤都可能有不同的实现方式。如果将所有创建逻辑放在一个类中,会导致该类变得庞大且难以维护。此外,如果需要创建不同的变体对象,就需要在该类中添加更多的逻辑,使得代码变得混乱。 二、解决方案 建造者模式提供了一种将一个复杂对象的构建过程与其表示分离的方法。它将对象的构建过程封装在一个独立的"建造者"类中,由该类负责逐步构建对象。这样,可以根据需要创建不同的建造者来构建不同的对象变体。通常,建造者模式涉及以下角色: 产品(Product):表示正在构建的复杂对象。建造者模式的目标是构建这个产品。 抽象建造者(Abstract Builder):定义了构建产品的步骤和方法,但没有具体的实现。不同的具体建造者可以实现不同的构建步骤,从而创建不同的产品变体。 具体建造者(Concrete Builder):实现了抽象建造者定义的方法,完成了产品的构建过程。每个具体建造者负责构建特定的产品变体。 指导者(Director):负责控制建造的过程。它通过将客户端与具体建造者分离,确保产品的构建是按照一定顺序和规则进行的。 三、效果 建造者模式的效果包括: 分离构建过程和表示:通过建造者模式,可以将复杂对象的构建过程与其最终表示分离,使得构建过程更加清晰可控。 支持不同的表示:通过使用不同的具体建造者,可以创建不同的产品表示,而不改变客户端的代码。 更好的可扩展性:如果需要添加新的产品变体,只需创建一个新的具体建造者即可,而无需修改已有的代码。 隐藏产品的内部结构:客户端只需与抽象建造者和指导者交互,无需关心产品的内部构建细节。 总之,建造者模式适用于需要构建复杂对象,且构建过程涉及多个步骤或变体的情况。通过将构建过程分解为可重用的步骤,建造者模式提供了一种结构化的方法来创建对象。 一个简单的例子,设想你正在建造一个汽车,汽车需要引擎,轮胎,座椅等等组成部分,这些部分的制作过程和步骤都是固定的,但是每一个部分在具体的表现上可能有所不同。使用建造者模式,你可以创建一个"汽车"的指挥者,把"建造引擎","建造轮胎","建造座椅"等等任务交给不同的建造者去完成,最后由指挥者来拼装得到汽车。 四、代码 1.Car.java /** Car @author 裴浩宇 @version 1.0 @date 2023/10/27 14:20 @description 产品类——车 */ public class Car { // 发动机 private String engine; // 轮胎 private String tire; // 座位 private String seat; public String getEngine() { return engine; } public void setEngine(String engine) { this.engine = engine; } public String getTire() { return tire; } public void setTire(String tire) { this.tire = tire; } public String getSeat() { return seat; } public void setSeat(String seat) { this.seat = seat; } @Override public String toString() { return "Car{" + "engine='" + engine + ''' + ", tire='" + tire + ''' + ", seat='" + seat + ''' + '}'; } } 2.Builder.java /** Builder @author 裴浩宇 @version 1.0 @date 2023/10/27 14:23 @description 建造者 */ public abstract class Builder { // 车 protected Car car = new Car(); /** 组装发动机 */ public abstract void setEngine(); /** 组装轮胎 */ public abstract void setTire(); /** 组装座位 */ public abstract void setSeat(); /** 输出车 @return 车 / public Car outputCar() { return car; } } 3.BYDBuilder.java /* BYDBuilder @author 裴浩宇 @version 1.0 @date 2023/10/27 15:15 @description BYD汽车建造类 */ public class BYDBuilder extends Builder { @Override public void setEngine() { System.out.println("开始组装比亚迪发动机"); car.setEngine("比亚迪发动机"); System.out.println("比亚迪发动机组装完成"); } @Override public void setTire() { System.out.println("开始组装比亚迪轮胎"); car.setTire("比亚迪轮胎"); System.out.println("比亚迪轮胎组装完成"); } @Override public void setSeat() { System.out.println("开始组装比亚迪座位"); car.setSeat("比亚迪座位"); System.out.println("比亚迪座位组装完成"); } } 4.OSHANBuilder.java /** OSHANBuilder @author 裴浩宇 @version 1.0 @date 2023/10/29 9:24 @description 欧尚汽车建造类 */ public class OSHANBuilder extends Builder { @Override public void setEngine() { System.out.println("开始组装欧尚发动机"); car.setEngine("欧尚发动机"); System.out.println("欧尚发动机组装完成"); } @Override public void setTire() { System.out.println("开始组装欧尚轮胎"); car.setTire("欧尚轮胎"); System.out.println("欧尚轮胎组装完成"); } @Override public void setSeat() { System.out.println("开始组装欧尚座位"); car.setSeat("欧尚座位"); System.out.println("欧尚座位组装完成"); } } 5.Director.java /** Director @author 裴浩宇 @version 1.0 @date 2023/10/29 9:29 @description 指导者类 */ public class Director { // 建造者 private final Builder builder; /** 构造方法 @param builder 建造者 */ public Director(Builder builder) { this.builder = builder; } /** 开始造车 @return 车 / public Car buildCar() { builder.setEngine(); builder.setTire(); builder.setSeat(); return builder.outputCar(); } } 6.Main.java /* Main @author 裴浩宇 @version 1.0 @date 2023/10/29 9:33 @description 客户端 */ public class Main { public static void main(String[] args) { // 初始化比亚迪汽车指导类 Director BYDDirector = new Director(new BYDBuilder()); // 开始制造比亚迪汽车 Car BYDcar = BYDDirector.buildCar(); System.out.println(BYDcar.toString()); // 初始化欧尚汽车指导类 Director OSHANDirector = new Director(new OSHANBuilder()); // 开始制造欧尚汽车 Car OSHANCar = OSHANDirector.buildCar(); System.out.println(OSHANCar.toString()); } } 7.打印结果 源码 源码下载 Java 23种设计模式(含源码)

Crafted with by Pei你看雪

小破站居然运行了 779 天访客 20508

© 2023 Pei你看雪鲁ICP备19037910号-2