JS案例:前端Iframe及Worker通信解决思路

news2024/11/16 5:26:17

目录

前言

Iframe通信

Worker通信

实现思路

实现过程

MessageCenter类

IPC类

Server类

Client类

PeerToPeer

功能演示

基础功能

父子通信

兄弟通信

父子兄弟通信

其他功能

函数调用

索引标识

卸载页面

重置页面

批量执行

批量操作

总结


前言

在前端开发中,经常会使用iframe和worker来实现一些特殊的需求,比如将第三方的页面嵌入到自己的页面中,或者在同一页面中显示多个不同的内容,后台运行JS代码等。然而,由于iframe和Worker具有独立的文档结构和执行环境,所以在多个页面及线程之间进行数据交互和通信变得困难。此时文件之间的通信就非常重要,为了让子页面与父级或其他页面共享数据和状态或使页面间达到联动的目的,我用JS实现了一个插件包,这里做个分享

Iframe通信

首先我们需要熟悉iframe的通信方式

window对象提供了postMessage函数,使用postMessage给子页面,父页面或者自己发送消息,通过在window对象上监听message事件获取收到的消息

window.postMessage("父页面发送的消息"); // 发给了当前页面

window.addEventListener(
  "message",
  console.log.bind(this, "父页面收到信息") // 父页面收到信息 MessageEvent
);

在postMessage(message, targetOrigin, transfer)函数中可以传递3个参数,分别是

  • message:需要发送的消息
  • targetOrigin:目标源,如:"http://127.0.0.1:5500/","*" 表示全部通配符
  • transfer:取消深拷贝的数据,通过message发送对象是深拷贝的数据,会在目标页面和当前页面产生两个对象,如果直接发送消息会十分损耗性能,使用transfer可以达到保存数据的功能

下面是个简单的父子通信的例子

     // parent
      sonIframe.onload = () => {
        sonIframe.contentWindow.postMessage(data, "*");
      };
      window.addEventListener("message", (e) => {
        console.log("父页面收到信息", e.data);
      });
    // son
      window.addEventListener("message", (e) => {
        console.log("子页面收到信息", e.data);
        window.parent.postMessage(e.data, "*");
      });

Worker通信

worker是js中的多线程,其通信方式与iframe类似,通过使用worker实例化对象进行传递消息,但是需要注意的是,iframe可以通过parent访问父页面,worker只能通过self对象将消息传递给父页面中的实例worker,因此功能实现需要对其做兼容。下面是一段使用worker的代码

// 父页面
const worker = new Worker("./worker.js", { type: "module" });
worker.onmessage = (e) => {
  console.log("parent收到了消息:", e.data);
};
worker.postMessage({ type: "msg", msg: "你也好" });

// 子页面
self.onmessage = (e) => {
  console.log("worker收到了消息:", e.data);
};
self.postMessage({ type: "msg", msg: "你好" });

 

实现思路

有了上面的例子我们就可以使用api实现页面之间的通信,下面是思维导图

上述设计中有Server,PeerToPeer,Client三个工具类,它们之间通过观察者进行通信,然后通过MessageCenter传递异步任务

其中Server部署在父页面中,实现监听及发送消息的作用;Client部署在子页面中,作用与Server类似,担任消息发送与接收的职务;PeerToPeer的作用有两个:一是担任广播群发消息的任务,二是收集广播的消息。此外,Server与Client之间也可以直接通过message通信,子页面之间通信通过PeerToPeer进行绑定

实现过程

了解了上面的基本概念与思路后,我们来将这个功能实现一下

MessageCenter类

作为消息收发的核心,许多地方用到了消息中心,具体实现方式可以参照之前的这篇文章

相对应Promise,其优点是可以触发多段异步操作,既规避了回调函数的耦合,又解决了异步操作,是通信过程不可或缺的一个部分,我们的核心函数可以继承该类并将其具象化,基于其中部分功能实现具体操作,那么整个程序设计可以使用多个链式调用的方式执行函数,如:

  server
    .mount()
    .on("msg", console.log)
    .on("msg1", console.log)
    .on("msg2", console.log)
    .load()
    .catch(console.log);

IPC类

整个工具的核心是IPC(进程通信),其集合了前面说到的send发送消息,handleMessage接收消息,此外基于这两点,我在类中增加了一些可能用的到的函数,来提升类灵活性与高可用性,如:mount函数用于手动挂载当前页面监听消息;unmount与前者相反,取消消息监听卸载页面;reset重置页面信息,reset后需要重新实例化类;load用来监听当前页面是否加载完成;watchHandler和invokeHandler分别是“监听函数类型消息并执行对应函数”与“发送函数类型消息触发函数”

import type { MessageCenter as TypeMessageCenter } from "utils-lib-js"
import { PeerToPeer } from "./p2p"
const { defer, MessageCenter, getType } = UtilsLib
export namespace IPCSpace {
    export type IObject<T = any> = {
        [key: string | number | symbol]: T
    }
    export type IHandler<T = any> = {
        (...args: any[]): Promise<T> | void
    }

    export type IOptions<Target = ITarget> = {
        target: Target // 目标页面,一般指Iframe或者window.parent
        origin: string // 发送消息给哪个域名
        source: Window | Worker // 当前window对象或Worker
        handlers: IObject<IHandler> // 钩子函数,等对方触发
        id: string // 标识,用来区分调用者
        handlersFixStr: string // 动态修改函数type关键字
        transfer: any // 需要传递的较大的数据,避免message的深复制导致两边的性能损耗较大
    }
    export type ISendParams = {
        type: string // 消息类型
        data?: unknown // 传递数据
        id?: number | string // 消息标识
    }
    export type ITarget = Window | HTMLIFrameElement | Worker
}
export class IPC extends (MessageCenter as typeof TypeMessageCenter) {
    constructor(protected opts: Partial<IPCSpace.IOptions> = {}) {
        super()
        const {
            origin = "*",
            source = window,
            target = null,
            transfer,
            handlers = {},
            id = "",
            handlersFixStr = "@invoke:ipc:handlers:"
        } = opts
        this.opts = {
            origin,
            source,
            target,
            transfer,
            handlers,
            id,
            handlersFixStr
        }
    }
    get sendMethods() {
        console.error('重写此函数');
        return (() => { }) as Function
    }
    /**
     * 目标对象,父页面中的iframe,子页面中的window.parent,子类重写该对象,进行校验
     */
    get target() {
        const { target } = this.opts
        if (!!!target) throw new Error("target 不能为空")
        return target
    }
    set id(id) {
        this.opts.id = id
    }
    get id() {
        return this.opts.id
    }
    get source() {
        return this.opts.source
    }
    /**
     * 当前页面加载完成
     * @returns promise
     */
    load() {
        const { promise, resolve } = defer()
        this.target.addEventListener("load", resolve)
        return promise
    }
    /**
     * 挂载当前页面,监听对方消息
     * @returns IPC
     */
    mount(handler?: (e: MessageEvent) => void) {
        const { source } = this
        this.unMount(handler)
        source?.addEventListener('message', handler ?? this.handleMessage);
        return this
    }
    /**
     * 卸载当前页面,取消监听对方消息
     * @returns IPC
     */
    unMount(handler?: (e: MessageEvent) => void) {
        const { source } = this
        source?.removeEventListener('message', handler ?? this.handleMessage);
        return this
    }
    /**
     * 重置当前IPC
     */
    reset() {
        this.unMount()
        this.clear()
        this.opts.handlers = {}
    }
    /**
     * 触发target的钩子函数
     * @param params 
     */
    invokeHandler(params: IPCSpace.ISendParams) {
        const { handlersFixStr } = this.opts
        const { type, ...oths } = params
        this.send({ type: `${handlersFixStr}${type}`, ...oths })
    }
    /**
     * 钩子函数处理
     * @param params 
     * @returns 函数运行结果
     */
    watchHandler(params) {
        const { handlerType, data = [] } = params
        const { handlers } = this.opts
        const fn = handlers[handlerType]
        return fn?.(...data)
    }
    /**
     * 当前页面接收消息
     * @param e  message 事件对象
     * @returns void
     */
    handleMessage = (e: MessageEvent) => {
        const { id, type, data } = e.data
        const { handlersFixStr } = this.opts
        if (!!!this.checkID(id)) return
        const handlerType = this.isHandler(type, handlersFixStr)
        if (handlerType) {
            return this.watchHandler({ handlerType, data })
        }
        this.emit(type, data)
    }
    /**
     * 发送消息
     * @param params 
     * @returns IPC
     */
    send(params: IPCSpace.ISendParams) {
        const { origin, transfer } = this.opts
        const { type, data = {}, id = this.id } = params
        const { target, sendMethods } = this
        let fnParams = [{ type, data, id }, origin, transfer]
        if (type) {
            isWorker(target) && (fnParams = [{ type, data, id }, transfer]); sendMethods?.(...fnParams);
        }

        return this
    }
    /**
     * 校验id
     * @param id 
     * @returns {boolean}
     */
    private checkID(id: string) {
        return id === this.id
    }
    isWindow = isWindow
    formatToIframe = formatToIframe
    isHandler = isHandler
    isWorker = isWorker
}
/**
 * 格式化Iframe,取selector还是element对象
 * @param target 
 * @returns 
 */
export const formatToIframe = (target: IPCSpace.ITarget | string) => {
    return getType(target) === "string" ? document.querySelector(`${target}`) : target
}
/**
 * 当前环境是不是父窗口
 * @param source 需要判断的对象
 * @returns 
 */
export const isWindow = (source: any) => {
    return source && source === source.window
}
/**
 * 当前环境是不是子线程或线程对象
 * @param worker 需要判断的对象
 * @returns 
 */
export const isWorker = (worker: any) => {
    return worker instanceof Worker || typeof DedicatedWorkerGlobalScope !== "undefined"
}
/**
 * 当前的type是否能被截取,用来截取函数调用消息
 * @param type 消息类型
 * @param __fixStr 截取的字符
 * @returns 
 */
export const isHandler = (type, __fixStr = '') => {
    return type.split(__fixStr)?.[1]
}

Server类

Server类继承自上面的IPC,此外Server还实现了target存取器,以及sendMethods存取器,由于Client与Server的部分功能不相同,所以在二者中分别实现,target的作用是开放一个入口给P2P使其可以对子页面批量操作,sendMethods是对postMessage做兼容

import { IPC, IPCSpace } from "./ipc"

export class Server extends IPC {
    constructor(opts: Partial<IPCSpace.IOptions<HTMLIFrameElement | Worker>>) {
        super(opts)
    }
    get sendMethods() {
        return this.target instanceof Worker ? this.target?.postMessage.bind(this.target) : this.target?.contentWindow?.postMessage// Server发送消息的方式取子页面的contentWindow,如果是worker则直接使用postMessage
    }
    /**
     * 允许重新设置目标对象
     */
    set target(_target) {
        this.opts.target = _target
    }

    /**
     * 校验目标对象,若没传则说明当前server与client是一对多关系
     */
    get target() {
        const { target } = this.opts
        if (!!!target) return null
        const _target = this.formatToIframe(target)
        if (!!!(_target instanceof HTMLIFrameElement || _target instanceof Worker)) throw new Error("target必须是IFrame、Worker或标签选择器")
        return _target
    }
}

Client类

Client可以理解是Server的青春版,里面只有对target的单独处理与sendMethods的实现

import { IPC, IPCSpace } from "./ipc"
export class Client extends IPC {
    constructor(opts: Partial<IPCSpace.IOptions<Window>>) {
        super(opts)
    }
    get sendMethods() {
        return this.target.postMessage // Client发送消息的方式取父页面,一般是parent
    }
    /**
     * 校验父页面
     */
    get target() {
        const { target } = this.opts
        if (!!!(this.isWindow(target) || target === self)) throw new Error("target必须是Window或Worker的self对象")
        return target as Window
    }
}

PeerToPeer

是对原有功能的升级,实际上使用上述代码即可达到各类通信的要求,但是有些操作需要系统性的调度与分发,此时一个简单的分发器就比较重要了

PeerToPeer的主要实现有两大模块:一是多个子页面互发消息,二是父页面与多个子页面互发消息达到多对多的消息传递或函数调用

PeerToPeer类的核心代码是batchOperation和servers属性,此时的server可以当成是一个工具类,可以使用该函数对子页面进行批量操作

import { formatToIframe, IPCSpace } from "./ipc"
import { Server } from './server'
export type IClients = Iframe | string[]
export type Iframe = HTMLIFrameElement[]
export class PeerToPeer {
    /*关联的iframe列表,可以传element或选择器,
    如'#iframe','.iframe'等等*/
    clients: Iframe
    /**我们把每个client当成是一个观察者,新建一个server进行批量操作 */
    server: Server
    isWorker: boolean // 是否用于线程中
    constructor(clients: IClients, protected opts: Partial<IPCSpace.IOptions> = {}) {
        this.clients = this.formatClients(clients)
        this.isWorker = this.clients.every(it => it instanceof Worker)
        this.create(this.opts)
    }
    /**
     * 将iframe选择器转换成element对象
     * @param _clients 
     * @returns 
     */
    formatClients(_clients: IClients) {
        return _clients.map((it) => formatToIframe(it)) as Iframe
    }
    /**
     * 批量操作,核心操作
     * @param fn Server的函数
     * @param arr clients列表,默认全选
     * @param hook 数组操作函数
     * @returns 
     */
    protected batchOperation(fn, arr = this.clients, hook = "forEach") {
        const __clients = arr[hook]((it) => {
            this.server.target = it
            return fn(this.server)
        })
        this.server.target = null // 操作过后清空操作对象,函数内部产生闭包,可以正常运行
        return __clients
    }
    /**
     * 创建server,将批量操作功能放进了batchOperation中,与上一版相比节省资源,只需创建一个server即可
     */
    private create(opts) {
        this.server = new Server(opts)
    }
    /**
     * 子页面加载完毕
     * @returns 
     */
    load() {
        return Promise.all(this.batchOperation(it => it.load(), undefined, "map"))
    }
    /**
     * 挂载页面
     * @returns 
     */
    connect() {
        this.disconnect()
        if (this.isWorker) {
            // Worker的target是self而不是window,所以需要单独处理
            this.batchOperation(it => it.target.addEventListener('message', this.message))
        } else {
            this.server.mount()
            this.server.mount(this.message)
        }
        return this
    }
    /**
     * 卸载页面
     * @returns 
     */
    disconnect() {
        if (this.isWorker) {
            this.batchOperation(it => it.target.removeEventListener('message', this.message))
        } else {
            this.server.unMount()
            this.server.unMount(this.message)
        }
        return this
    }
    /**
     * 重置页面
     * @returns P2P
     */
    reset() {
        this.disconnect()
        this.server.reset()
        return this
    }
    /**
     * 消息接收钩子
     * @param e 
     */
    protected message = (e) => {
        const { data, source, target } = e
        // 线程与窗口取值不同
        let __target = source?.frameElement
        if (this.isWorker) {
            __target = target
            this.server.handleMessage(e)
        }
        this.broadcast(data, this.filterSelf(__target))
    }
    /**
     * 过滤当前页面,不发给自己
     * @param self 当前页面,即发送消息的子页面
     * @returns 
     */
    protected filterSelf(self) {
        return this.clients.filter(it => it !== self)
    }
    /**
     * 广播
     * @param param0 
     * @param clients // 发送给哪些列表
     */
    broadcast({ type, id, data }, clients?: Iframe) {
        this.batchOperation(it => it.send({ type, data, id }), clients)
    }
    /**
     * 批量执行函数
     * @param param0 
     * @param clients 
     */
    invoke({ type, id, data }, clients?: Iframe) {
        this.batchOperation(it => it.invokeHandler({ type, data, id }), clients)
    }
}

功能演示

基础功能

父子通信

父子双向通信,通过Server类与Client类通过关联直接发送消息

// 父页面 index.html
const server = new Server({
  target: "#son",
});
// 监听 "msg" 事件
server.on("msg", console.log.bind(null, "parent收到消息"));
// 挂载并加载服务
await server.mount().load();
// 发送 "msg" 消息
server.send({ type: "msg", data: { name: "parent" } });

// 子页面 son.html
const client = new Client({
  target: window.parent,
});
// 建立连接
client.mount();
// 监听 "msg" 事件
client.on("msg", console.log.bind(null, "son收到消息"));
// 发送 "msg" 消息
client.send({ type: "msg", data: { name: "son" } });

兄弟通信

同一个父页面下的两个子页面称为兄弟页面,我们可以使用PeerToPeer类建立新的连接

// 父页面 index.html
// 建立多点连接
const peer = new PeerToPeer(["#son2", "#son"]);
// 开启连接
peer.connect();
// 等待子页面加载
await peer.load();
// 加载完成,群发消息
peer.broadcast({ type: "load:finish" });

// 子页面1 son1.html
const client = new Client({
  target: window.parent,
});
client.mount();
client.on("msg", console.log.bind(null, "son收到消息"));
// 等待所有页面加载完成
client.on("load:finish", () => {
  client.send({ type: "msg", data: { name: "son" } });
});

// 子页面2 son2.html
const client = new Client({
  target: window.parent,
});
client.mount();
client.on("msg", console.log.bind(null, "son2收到消息"));
client.on("load:finish", () => {
  client.send({ type: "msg", data: { name: "son2" } });
});

父子兄弟通信

父子兄弟的通信是进阶的用法,在上面的兄弟通信页面基础上添加消息接收即可监听消息,发送消息可以使用peer.broadcast实现

// 父页面收发消息
peer.server.on("msg", console.log.bind(null, "parent收到消息"));
peer.broadcast({ type: "msg", data: { name: "parent" } });

 

线程通信

除此之外js-ipc还支持js的线程worker通信,下面是个例子,与iframe不同的是Client中传入的目标和当前源都是self,在主线程中peer传入的列表也是Worker对象

// index.js
const worker1 = new Worker("./worker1.js", { type: "module" });
const worker2 = new Worker("./worker2.js", { type: "module" });
const peer = new PeerToPeer([worker1, worker2]);
peer.connect();
peer.server.on("msg", console.log.bind(null, "parent收到消息"));

// worker1.js
const client = new Client({
  target: self,
  source: self,
});
// 建立连接
client.mount();
// 监听 "msg" 事件
client.on("msg", console.log.bind(null, "worker1收到消息"));

// worker2.js 同worker1

其他功能

函数调用

函数调用实际是一个发送消息的拓展,通过invokeHandler方法调用对方的函数

// 父页面
const server = new Server({
  target: "#son",
  handlers: {
    // 父页面的处理函数
    log: console.log,
  },
});


// 子页面
client.invokeHandler({ type: "log", data: ["log"] });

索引标识

当页面较多时可以通过id进行信息标识,避免消息发送错乱

// 父页面
const server = new Server({
  target: "#son",
  id: 12,// 通过id标识发送的消息
});


// 子页面
const client = new Client({
  target: window.parent,
  id: 12,// 子页面不加id就收不到消息
});

卸载页面

使用unMount取消当前页面的监听,取消挂载

client.unMount();

重置页面

使用reset函数对页面初始化,重置所有信息

client.reset()

批量执行

批量执行函数invoke是在peer.broadcast的基础上实现的,比如我们调用所有包含son4Info的子页面中的此函数

peer.invoke({ type: "son4info", data: ["parent"] });

父页面通过P2P注册函数

const peer = new PeerToPeer(["#son4", "#son5"], {
  handlers: {
    parentLog: console.log,
  },
});

批量操作

批量对子页面重置,卸载,挂载,监听加载

const peer = new PeerToPeer(["#son4", "#son5"]);
await peer.reset().disconnect().connect().load();
peer.broadcast({ type: "load:finish" });

总结

以上就是文章全部内容了,本文介绍了iframe和worker通过postMessage与onmessage进行通信,并基于此特性实现了进程通信的功能:IPC,在IPC类的基础上我们做了拓展,衍生出Server和Client分别对应着服务端和客户端的操作,此外我们还实现了端对端的批量操作P2P功能并对以上功能进行了演示。

感谢你看到最后,希望文章对你有帮助,如果觉得文章还不错的话,还请三连支持一下,感谢!

源码:js-ipc: JavaScript通信工具包,支持Iframe和Worker通信

或:myCode: 基于js的一些小案例或者项目的js-ipc子模块

NPM:js-ipc - npm

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/505951.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

虚拟机VMware Tools安装步骤

Vmware tools是虚拟机中一款超级增强工具&#xff0c;可以让我们更加方便使用虚拟机&#xff0c;能实现主机与虚拟机之间的文件共享&#xff0c;这篇文章主要介绍了虚拟机VMware Tools安装步骤,需要的朋友可以参考下 本人安装VMware Tools 的需求是 在Linux虚拟机和我的Windo…

Linux学习记录——이십삼 进程信号(2)

文章目录 1、可重入函数2、volatile关键字3、如何理解编译器的优化4、SIGCHLD信号 1、可重入函数 两个执行流都执行一个函数时&#xff0c;这个函数就被重入了。比如同一个函数insert&#xff0c;在main中执行时&#xff0c;这个进程时间片到了&#xff0c;嵌入了内核&#xf…

博客系统 —— Servlet 实现(前后端分离)(详细+整体代码+效果图)

目录 一、项目效果展示 二、创建 Servlet 项目 三、编写数据库的操作代码 1、创建数据库/表结构&#xff08;数据库设计&#xff09;&#xff08;Model&#xff09; 2、封装数据库操作&#xff08;Model&#xff09; &#xff08;1&#xff09;先创建 DBUtil 封装数据库连…

Etsy总是不出单怎么办?出单后怎么操作?

如果您在Etsy上总是无法出单&#xff0c;可以尝试以下几个步骤&#xff1a; 1、检查您的商品列表是否符合Etsy的要求&#xff0c;包括商品描述、价格、运费等信息是否准确无误。 2、确认您的账户信息是否完整&#xff0c;包括银行账户、信用卡信息等。 3、检查您的店铺设置是否…

socket API的使用+client/server代码演示+封装socket模块

前言&#xff1a;本章旨在讲解常见的socket API及其使用练习&#xff0c;尾部有封装好的&#xff08;socket相关的一些系统函数加上错误处理代码包装成新的函数&#xff09;模块wrap.c 目录 一、socket模型创建流程图 1&#xff09;socket函数 2&#xff09;bind函数 3&am…

Altium软件中相同模块布局布线的方法

文章目录 1、原理图设计1、绘制xxx.SchDoc&#xff0c;并设置port。具体方法&#xff1a;Place→Port。2、新建顶层原理图&#xff1a;可以命名为xxx_TOP3、repeat 原理图&#xff0c;将这里从XXX_SingleDut 改为 Repeat(S,1,12)4、以总线的方式出线&#xff0c;如下&#xff1…

网络安全CVE 漏洞分析及复现

漏洞详情 Shiro 在路径控制的时候&#xff0c;未能对传入的 url 编码进行 decode 解码&#xff0c;导致攻击者可以绕过过滤器&#xff0c;访问被过滤的路径。 漏洞影响版本 Shiro 1.0.0-incubating 对应 Maven Repo 里面也有 【一一帮助安全学习&#xff0c;所有资源获取一一…

NetBackup 10.2 新功能介绍:PostgreSQL 和 MySQL 自动化恢复达成

NetBackup 10.2 新功能介绍&#xff1a;PostgreSQL 和 MySQL 自动化恢复达成 原文来自&#xff1a;VERITAS 中文社区 2023-04-27 在执行恢复任务时&#xff0c;手动提取、更新数据库和实例并将其附加到 PostgreSQL 和 MySQL 是常规操作。而在最新的 NetBackup 10.2 版本中&am…

数据可视化工具 - ECharts以及柱状图的编写

1 快速上手 引入echarts 插件文件到html页面中 <head><meta charset"utf-8"/><title>ECharts</title><!-- step1 引入刚刚下载的 ECharts 文件 --><script src"./echarts.js"></script> </head>准备一个…

apc-service-bus项目Docker镜像发布

apc-service-bus项目Docker镜像发布 1. 提交代码到Gitee代码仓&#xff0c;通过建木将项目打包到服务器 1.1 可直接打开访问建木&#xff0c;无有不熟悉建木发布流程的请咨询其他同事或者自行研究 建木地址&#xff1a;http://10.11.148.21/ 1.2 找到bus的开发环境部署执行…

神经网络全连接层数学推导

全连接层分析 对于神经网络为什么都能产生很好的效果虽然其是一个黑盒&#xff0c;但是我们也可以对其中的一些数学推导有一定的了解。 数学背景 目标函数为 f ∣ ∣ m a x ( X W , 0 ) − Y ∣ ∣ F 2 &#xff0c;求 ∂ f ∂ W , ∂ f ∂ X , ∂ f ∂ Y 目标函数为f ||ma…

SpringBoot项目如何打包成exe应用程序

准备 准备工作&#xff1a; 一个jar包&#xff0c;没有bug能正常启动的jar包 exe4j&#xff0c;一个将jar转换成exe的工具 链接: https://pan.baidu.com/s/1m1qA31Z8MEcWWkp9qe8AiA 提取码: f1wt inno setup&#xff0c;一个将依赖和exe一起打成一个安装程序的工具 链接:…

设计模式——代理模式(静态代理、JDK动态代理、CGLIB动态代理)

是什么&#xff1f; 如果因为某些原因访问对象不适合&#xff0c;或者不能直接引用目标对象&#xff0c;这个时候就需要给该对象提供一个代理以控制对该对象的访问&#xff0c;代理对象作为访问对象和目标对象之间的中介&#xff1b; Java中的代理按照代理类生成时机不同又分…

婴儿摇篮语音播放芯片,高品质MP3音乐播放芯片,WT2003H

婴儿摇篮是一种用于帮助婴儿入睡的设备。传统的婴儿摇篮通常只是简单的摇晃&#xff0c;但是带有语音播报芯片的婴儿摇篮则可以更好地模拟妈妈的声音&#xff0c;从而更有效地帮助婴儿入睡。 如果您正在寻找高品质音乐摇篮方案&#xff0c;那么WT2003H语音播放芯片&#xff0c…

5月7日 2H55min|5月8日8H50min|时间轴复盘|14:00~14:30

5月8日 todo list list4 40min ✅ |实际上用了50+50 list6 40min ✅ |实际上用了30+60 阅读+听力连做 100min ✅ 口语 day01 ✅ 口语 day02 口语 day03

6、并发事务控制MVCC汇总

1.并发事务控制 单版本控制-锁 先来看锁&#xff0c;锁用独占的方式来保证在只有一个版本的情况下事务之间相互隔离&#xff0c;所以锁可以理解为单版本控制。 在 MySQL 事务中&#xff0c;锁的实现与隔离级别有关系&#xff0c;在 RR&#xff08;Repeatable Read&#xff0…

vCenter Server 8.0U1 OVF:在 Fusion 和 Workstation 中快速部署 vCSA

vCenter Server 8.0U1 OVF&#xff1a;在 Fusion 和 Workstation 中快速部署 vCSA vCenter Server 8.0U1 系列更新 请访问原文链接&#xff1a;https://sysin.org/blog/vmware-vcenter-8-ovf/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#x…

Win上通过Jconsole查看Java程序资源占用情况(教程总结,一篇就够了)

最近需要读取一个大文件&#xff0c;为了判断有没有读取到内存中&#xff0c;需要一个能查看jar包占用内存的工具&#xff0c;一顿面向百度后&#xff0c;发现了jdk自带的工具Jconsole&#xff0c;将教程分享给大家 一、介绍 JConsole 是一个内置 Java 性能分析器&#xff0c;…

手把手教你使用unisat 交易市场|BRC20|Unisat

开始前先熟悉下这张平台市场标注图&#xff0c;能让你跟得心应手&#xff01; 一、查看实时成交信息&#xff08;已 moon 为例子&#xff09; 搜索进入Token界面&#xff0c;点击 Orders 可以看到 1w 枚成交 87.86U&#xff08;单价 30 聪&#xff0c;大约 0.008786u&#xf…

牛客网剑指offer|中等题day2|JZ76删除链表中的重复节点、JZ23链表中环的入口节点、JZ24 反转链表(简单)

JZ76删除链表中的重复节点 链接&#xff1a;删除链表中重复的结点_牛客题霸_牛客网 参考代码&#xff1a; 自己好像还是偏向双指针这种想法&#xff0c;所以用了两个指针&#xff0c;这样感觉更好理解一些。 对了&#xff0c;去重有两种&#xff0c;我一开始写成了简单的那种&a…