Golang embed 库全面解析:从基础到高级应用

Golang embed 库全面解析:从基础到高级应用

    • 引言
      • Golang的 `embed`:简化资源管理
      • 提升可移植性与便利性
      • 适用场景的拓展
    • embed 库的基本概念
      • embed 库的工作原理
      • 使用 embed 的基本语法
      • 访问嵌入资源的方法
      • embed 的限制
    • 如何使用 embed
      • 嵌入单个文件
      • 嵌入整个目录
      • 结合 http.FileSystem 使用
      • 注意事项和最佳实践
    • embed 在不同场景下的应用
      • 在 Web 开发中使用 embed
      • 在桌面应用中使用 embed
      • 在命令行工具中的应用
      • 结合云平台和容器化
    • embed 与其他资源嵌入技术的比较
      • packr 和 statik:之前的流行选择
      • embed 的优势
      • 选择合适的工具
    • 高级技巧和最佳实践
      • 动态选择嵌入的资源
      • 利用 embed 提高应用安全性
      • 使用 embed 管理前端资源
      • embed 与模块化代码
      • 性能考虑
    • 常见问题解答
      • Q1: 如何处理嵌入资源时的路径问题?
      • Q2: 嵌入的资源可以在运行时修改吗?
      • Q3: 如何处理嵌入大量或大型文件时的性能问题?
      • Q4: 是否所有类型的文件都可以使用 `embed` 嵌入?
      • Q5: 如何确保嵌入的资源在不同的环境下都能正常工作?
    • 结语
      • 重要性与价值
      • 实战开发的强大工具
      • 持续学习与实践

在这里插入图片描述

引言

在现代软件开发中,资源管理始终是一个核心议题。随着 Golang 的日益普及,开发者们对于便捷且高效的资源嵌入方式有着迫切需求。Golang 1.16 版本引入的 embed 标准库,正是在这样的背景下应运而生,它不仅改变了传统的资源管理方式,还极大地提升了代码的可维护性和应用的可移植性。

Golang的 embed:简化资源管理

embed 出现之前,Golang 开发者通常需要通过各种手段管理静态文件和资源,如使用第三方库或编写额外的加载代码。这不仅增加了开发的复杂度,也降低了程序的运行效率。embed 的出现,使得将文件和资源直接嵌入到 Go 程序的二进制文件中成为可能,极大地简化了整个流程。

提升可移植性与便利性

随着 embed 的应用,一个显著的优势是程序的可移植性大大提升。嵌入的资源与程序本身合二为一,不再需要额外的文件依赖,这使得部署和分发变得更为简单和高效。无论是在容器化部署还是云平台上,embed 都展现出了其独特的优势。

适用场景的拓展

不仅仅是在基本的文件嵌入上,embed 还能在多种复杂的开发场景中发挥重要作用。无论是在 Web 开发中的静态文件处理,还是在桌面应用程序中的资源管理,embed 都提供了一种高效且简洁的解决方案。

本文将深入探索 embed 标准库的各种用法和实践,从基本概念出发,逐步深入到高级应用技巧,旨在帮助开发者全面理解并有效利用这一强大的工具。我们将通过实例演示 embed 的使用,探讨在不同类型的项目中如何灵活应用,以及与其他资源嵌入技术的比较分析,为读者提供一条清晰、全面的学习路径。

随着文章的深入,读者将能够清晰地了解到 embed 的真正价值所在,并将这一工具应用到实际的项目开发中,从而提升开发效率,优化项目结构。接下来,让我们一起步入 embed 的世界,探索它的无限可能。

embed 库的基本概念

在深入探讨 Golang 的 embed 库之前,理解其核心概念和工作原理是至关重要的。这一节将为你揭开 embed 的神秘面纱,帮助你建立起对它的基本认识。

embed 库的工作原理

embed 库的核心功能是将指定的文件或目录嵌入到 Go 程序的可执行文件中。在编译时,embed 会将标记为嵌入的文件或目录的内容作为字节数组(byte slice)直接嵌入到编译后的二进制文件中。这意味着,在程序运行时,这些资源可以直接从二进制文件中提取和使用,而无需额外的文件读取操作。

使用 embed 的基本语法

在 Go 代码中使用 embed 非常简单。首先,需要导入 embed 包:

import "embed"

接着,你可以使用 //go:embed 指令来指示编译器嵌入特定的文件或目录。例如:

//go:embed image.png
var myImage embed.FS

在这个例子中,image.png 文件将被嵌入到程序中,并且可以通过 embed.FS 类型的 myImage 变量来访问。

访问嵌入资源的方法

一旦资源被嵌入,就可以通过 embed 包提供的接口来访问它们。例如,要读取上面嵌入的 image.png 文件,可以使用以下代码:

data, err := myImage.ReadFile("image.png")
if err != nil {// 处理错误
}
// 使用 data 变量

这里 ReadFile 方法返回了一个字节数组,包含了嵌入文件的内容。

embed 的限制

虽然 embed 非常强大,但它也有一些限制。最重要的是,只能嵌入项目目录下的文件或目录。此外,embed 指令必须紧跟在变量声明之前,不能有空行或其他注释。

通过了解这些基本概念和语法,你已经迈出了学习 embed 的第一步。接下来,我们将进入更加实战化的篇章,详细探讨如何在实际项目中运用 embed,包括具体的代码示例和应用场景分析。

如何使用 embed

掌握了 embed 的基本概念之后,我们来看看如何在实际项目中应用这个强大的工具。这一节将提供详细的指导和代码示例,帮助你将理论知识转化为实际操作。

嵌入单个文件

开始之前,让我们从最简单的场景出发:嵌入单个文件。假设你有一个配置文件 config.json,你想将其嵌入到你的程序中。首先,使用 //go:embed 指令和 embed.FS 类型的变量声明:

//go:embed config.json
var configFS embed.FS

接着,你可以使用 ReadFile 方法来读取文件内容:

configData, err := configFS.ReadFile("config.json")
if err != nil {// 错误处理
}
// 这里可以使用 configData

嵌入整个目录

如果你想嵌入一个完整的目录,而不仅仅是单个文件,embed 也能轻松做到。比如,你有一个包含多个 HTML 模板的目录 templates,你可以这样嵌入整个目录:

//go:embed templates/*
var templatesFS embed.FS

然后,你可以使用 ReadDir 方法来遍历目录中的文件:

entries, err := templatesFS.ReadDir("templates")
if err != nil {// 错误处理
}
for _, entry := range entries {fileData, err := templatesFS.ReadFile("templates/" + entry.Name())if err != nil {// 错误处理}// 使用 fileData
}

结合 http.FileSystem 使用

embed 也可以与 Go 的 HTTP 包结合使用,直接在 Web 服务器中提供嵌入的文件。例如,你可以将静态资源(如 JavaScript、CSS 文件)嵌入到你的 Web 应用中,并使用 http.FileSystem 接口直接服务这些文件:

//go:embed static/*
var staticFS embed.FSfunc main() {http.Handle("/", http.FileServer(http.FS(staticFS)))log.Fatal(http.ListenAndServe(":8080", nil))
}

在这个示例中,所有在 static 目录下的文件都将通过 HTTP 服务器对外提供。

注意事项和最佳实践

在使用 embed 时,有几点需要特别注意:

  1. 确保嵌入的文件或目录位于你的 Go 工程目录中。
  2. //go:embed 指令必须紧跟在变量声明之前,不能有空行。
  3. 考虑到安全性,谨慎选择哪些文件嵌入,避免暴露敏感信息。

通过这些实用的示例和提示,你现在应该对如何在项目中使用 embed 有了深刻的理解。接下来,我们将进入更广阔的应用领域,探索 embed 在不同场景下的实践方法。

embed 在不同场景下的应用

了解了 embed 的基本使用方法后,我们将探讨它在不同开发场景中的具体应用。从 Web 服务到桌面应用,embed 提供了灵活而高效的资源管理方式。

在 Web 开发中使用 embed

在 Web 开发中,embed 可以用来嵌入静态资源,如 HTML、CSS、JavaScript 文件等。这为构建单页应用(SPA)和提供高效的静态文件服务提供了极大的便利。

假设你正在开发一个 Web 应用,你可以将前端的所有静态资源嵌入到 Go 程序中。这样,无论将应用部署到哪里,这些静态资源都会随应用一起被部署,无需担心资源文件的分发和访问问题。

//go:embed static
var staticFiles embed.FSfunc main() {// 设置静态文件路由http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(staticFiles))))log.Fatal(http.ListenAndServe(":8080", nil))
}

在这个例子中,static 目录下的所有文件都被嵌入,并通过 HTTP 服务对外提供。

在桌面应用中使用 embed

桌面应用开发中,embed 的优势同样明显。它允许开发者将必要的资源文件,如图像、配置文件、模板等,直接嵌入到应用程序中。这样一来,应用程序的分发和安装变得更加简单,用户无需担心资源文件的丢失或损坏。

//go:embed images
var imagesFS embed.FSfunc loadImage(name string) (image.Image, error) {file, err := imagesFS.Open("images/" + name)if err != nil {return nil, err}defer file.Close()return image.Decode(file)
}

在这个场景中,images 目录下的所有图像文件都被嵌入到程序中,可以通过 loadImage 函数轻松加载。

在命令行工具中的应用

embed 同样适用于命令行工具的开发。例如,如果你的工具需要一些预定义的配置模板或数据文件,可以使用 embed 将这些资源嵌入到可执行文件中,从而使工具的分发和使用更加方便。

//go:embed templates
var templatesFS embed.FSfunc loadTemplate(name string) (string, error) {data, err := templatesFS.ReadFile("templates/" + name)if err != nil {return "", err}return string(data), nil
}

通过嵌入模板文件,你的命令行工具可以在任何环境下无需额外的文件依赖即可运行。

结合云平台和容器化

在云平台和容器化的环境中,embed 的优势更加凸显。它允许开发者创建自包含的应用,这些应用内置了所有必要的资源,极大地简化了部署和扩展过程。

使用 embed,你可以构建一个不依赖于外部文件系统的 Docker 镜像,这使得你的应用在任何支持 Docker 的平台上都可以一致运行,大大降低了部署复杂性。

embed 与其他资源嵌入技术的比较

Golang 的 embed 库虽然是资源嵌入领域的重要工具,但并非唯一选择。了解它与其他资源嵌入技术(如 packr、statik 等)的比较,可以帮助我们更好地选择适合自己项目的工具。

packr 和 statik:之前的流行选择

embed 出现之前,packr 和 statik 是 Golang 社区中流行的资源嵌入工具。它们通过生成额外的 Go 代码或使用特殊的构建工具来实现资源嵌入。这些工具各有优势,例如 packr 在处理大量小文件时表现出色,而 statik 适用于需要将整个目录嵌入的场景。

然而,这些工具也有一些局限性,比如它们可能需要额外的构建步骤,或与 Go 工程的结构和依赖管理不够协调。

embed 的优势

与 packr 和 statik 等工具相比,embed 作为 Golang 的官方标准库,具有以下几个显著优势:

  1. 无缝集成:作为标准库的一部分,embed 与 Go 的生态系统和工具链无缝集成,不需要额外的依赖或构建步骤。

  2. 简化的工作流embed 的使用更简单直接,仅需几行代码即可实现资源嵌入,无需生成额外的 Go 代码或使用特殊的构建工具。

  3. 更好的性能:作为标准库的一部分,embed 在性能上进行了优化,特别是在处理大型文件或大量文件时。

  4. 官方支持和文档:作为 Golang 的一部分,embed 享有官方的支持和完善的文档,这对于长期维护和社区支持来说至关重要。

选择合适的工具

尽管 embed 在许多方面都有优势,但在某些特定场景下,其他工具仍然有其应用价值。因此,选择哪种资源嵌入技术应根据项目的具体需求和环境来决定。如果你的项目已经在使用 packr 或 statik,并且它们能很好地满足你的需求,那么没有必要强行迁移到 embed。但对于新项目或正在寻找更简化、高效资源管理方法的项目来说,embed 无疑是一个值得考虑的选择。

高级技巧和最佳实践

当你开始熟悉 embed 的基本用法后,掌握一些高级技巧和最佳实践可以帮助你更高效地使用这个工具,并提升你的项目质量。这一节将分享一些进阶的使用方法和建议。

动态选择嵌入的资源

虽然 embed 通常用于静态嵌入资源,但你可以结合 Go 的编译时标志(build tags)来动态选择要嵌入的文件。例如,你可能希望在开发环境和生产环境中使用不同的资源文件:

// +build dev//go:embed config/dev.json
var configFS embed.FS
// +build !dev//go:embed config/prod.json
var configFS embed.FS

通过在编译时指定不同的标志,你可以切换使用不同的配置文件。

利用 embed 提高应用安全性

embed 可以用来提高应用程序的安全性。通过嵌入关键的资源文件(例如 TLS 证书、配置文件等),你可以减少这些文件在部署过程中被篡改的风险。同时,这也简化了证书或配置的分发过程,因为它们直接包含在你的可执行文件中。

使用 embed 管理前端资源

对于包含前端组件的 Go 应用(如使用 React 或 Vue 构建的 UI),embed 可以用来管理构建后的前端资源。你可以将构建的 HTML、CSS、JavaScript 文件嵌入到 Go 应用中,然后通过 Go 的 HTTP 服务器提供这些文件,从而简化部署和托管。

embed 与模块化代码

在使用 embed 时,考虑代码的模块化和可维护性也很重要。例如,你可以创建专门的包来管理嵌入的资源,使资源管理更加集中和模块化。这样,资源的更改和更新将不会影响到主应用逻辑,从而提高代码的清晰度和可维护性。

性能考虑

虽然 embed 在性能上做了优化,但大量或大型文件的嵌入可能会影响应用程序的启动时间和内存使用。在考虑使用 embed 时,评估应用程序的性能需求和资源大小是必要的。对于大型资源,可能需要考虑其他的文件分发或加载策略。

通过这些高级技巧和最佳实践,你可以更加深入地理解和利用 embed,使你的 Go 应用更加强大和高效。接下来,我们将解答一些在使用 embed 过程中可能遇到的常见问题。

常见问题解答

在使用 Golang 的 embed 库时,开发者可能会遇到一些常见的问题。这一节将解答这些问题,帮助你更顺利地使用 embed

Q1: 如何处理嵌入资源时的路径问题?

当使用 embed 嵌入文件或目录时,路径是基于源文件的位置来确定的。如果你的代码结构较为复杂,确保正确引用资源的相对路径是非常重要的。一个常见的错误是使用错误的路径或文件名,导致资源无法正确嵌入。建议在代码中使用常量来定义这些路径,以减少错误。

Q2: 嵌入的资源可以在运行时修改吗?

嵌入到程序中的资源是只读的。这意味着一旦资源被嵌入到二进制文件中,它们就不能在运行时被修改。如果你需要修改资源,你应该考虑将其作为外部文件处理,或者在程序中生成新的文件。

Q3: 如何处理嵌入大量或大型文件时的性能问题?

虽然 embed 对性能做了优化,但嵌入大量或大型文件可能会影响程序的启动时间和内存占用。对于这种情况,一个解决方案是仅嵌入必要的文件,或者将大型文件分割成更小的部分。在设计应用程序时,应该考虑到这些性能方面的影响。

Q4: 是否所有类型的文件都可以使用 embed 嵌入?

理论上,embed 可以嵌入任何类型的文件,但实际上,它更适合用于静态资源,如配置文件、HTML、CSS、图片等。对于需要频繁更新或动态生成的文件,使用 embed 可能不是最佳选择。

Q5: 如何确保嵌入的资源在不同的环境下都能正常工作?

确保嵌入的资源在不同环境下能正常工作的关键是在开发过程中进行充分的测试。这包括在不同的操作系统和平台上测试你的程序,以及确保所有的路径和文件引用都是正确的。使用持续集成(CI)和自动化测试可以帮助确保应用程序的稳定性和可靠性。

通过解答这些常见问题,希望你能在使用 embed 时避免一些常见的陷阱,从而更加高效地开发你的 Golang 应用。

结语

在本文中,我们深入探讨了 Golang 的 embed 标准库,从其基本概念和用法到在不同场景下的应用,再到与其他资源嵌入技术的比较,以及高级技巧和最佳实践。我们也解答了一些在使用 embed 过程中可能遇到的常见问题。

重要性与价值

embed 的引入,为 Golang 带来了革命性的改变,尤其是在资源管理方面。它不仅简化了资源的嵌入和管理,还提高了程序的可移植性和部署的便利性。对于希望构建高效、可靠且易于维护的 Golang 应用的开发者来说,理解和掌握 embed 是非常重要的。

实战开发的强大工具

无论是在 Web 开发、桌面应用还是命令行工具中,embed 都展现出了其强大的实战能力。通过将资源直接嵌入到二进制文件中,embed 使得应用程序更加自包含,降低了对外部文件依赖,同时也简化了部署和分发过程。

持续学习与实践

正如任何其他的技术或工具,掌握 embed 需要时间和实践。鼓励每位开发者在日常工作中尝试使用 embed,并结合本文介绍的最佳实践和技巧来优化你的项目。通过持续学习和实践,你将能够更加深入地理解 embed 的强大功能,并在你的项目中发挥其最大价值。

希望本文能够帮助你更好地理解 Golang 的 embed 库,并在你的开发旅程中发挥重要作用。随着 Golang 的不断发展和完善,embed 无疑将继续在开发者社区中扮演重要角色。

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

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

相关文章

zephyr学习笔记

zephyr内核对象学习 定时器 类似linux的定时器, 可以分别设置第一次到期时间和后续的周期触发时间, 可以注册到期回调和停止回调 还有一个计数状态,用于标记timer到期了多少次 duration:设定timer第一次到期的时间。 period: …

keycloak-鉴权springboot

一、环境描述 keycloak鉴权springboot的方式,此处简单介绍,springboot官方也提供了demo https://github.com/keycloak/keycloak-quickstarts/tree/latest/spring/rest-authz-resource-server 以及文档说明 Securing Applications and Services Guide…

华为OD机试真题-提取字符串中的最长数学表达式并计算-2023年OD统一考试(C卷)---Python3--开源

题目: 考察内容: 滑动窗口 eval() 思路:先把合法字符提取出来;再从合法字符提取出合法表达式;再获取最长字符串,并运算最后结果。 代码: """ analyze: 如果没有,返…

数字逻辑与计算机组成

冯诺依曼计算机 计算机结构 计算机特点 1.采用二进制 2.程序存储 2.由五大部件组成计算机系统:运算器、存储器、控制器、输入设备和输出设备 计算机硬件系统的层次 中央处理器(CPU):运算器 控制器 计算机主机:…

CAN总线位时序的介绍

CAN控制器根据两根线上的电位差来判断总线电平。总线电平分为显性电平和隐性电平,二者必居其一。发送方通过使总线电平发生变化,将消息发送给接收方。 显性电平对应逻辑 0,CAN_H 和 CAN_L 之差为 2.5V 左右。而隐性电平对应逻辑 1&#xff0c…

阿里云搭建私有docker仓库(学习)

搭建私有云仓库 首先登录后直接在页面搜索栏中搜索“容器镜像服务” 进入后直接选择个人版(可以免费使用) 选择镜像仓库后创建一个镜像仓库 在创建仓库之前我们先创建一个命名空间 然后可以再创建我们的仓库,可以与我们的github账号进行关联…

开发知识点-Python-爬虫

爬虫 scrapybeautifulsoupfind_all find祖先/父节点兄弟节点nextpreviousCSS选择器属性值 attrsselect 后 class 正则使用字符串来描述、匹配一系列符合某个规则的字符串组成元字符使用grep匹配正则组与捕获断言与标记条件匹配正则表达式的标志 特定中文 匹配 scrapy scrapy内…

【C语言】指针超级无敌金刚霹雳进阶(但不难,还是基础)

点击这里访问我的博客主页~~ 对指针概念还不太清楚的点击这里访问上一篇指针初阶2.0 上上篇指针初阶1.0 谢谢各位大佬的支持咯 今天我们一起来学习指针进阶内容 指针进阶 一、指针变量1、字符指针变量2、数组指针变量①数组指针变量的定义②数组指针变量的初始化 3、函数指…

C++面试干货---带你梳理常考的面试题(二)

顾得泉:个人主页 个人专栏:《Linux操作系统》 《C从入门到精通》 《LeedCode刷题》 键盘敲烂,年薪百万! 1.struct 和 class 区别 1.默认访问权限:struct中的成员默认为public,而class中的成员默认为priv…

网上搞钱的方法你知道几个?盘点3个普通人都可操作的赚钱项目

项目一,微头条 我们可以借助精彩的文章,分享知识、心得和见解,吸引更多的读者关注并获得更多的点赞与评论。关键字的巧妙运用将使你的文章更具吸引力和影响力,同时也会为你带来更多的关注度和阅读量。我们写微头条文章的时候&…

2024.3.5每日一题

LeetCode 到达目的地的方案数 题目链接:1976. 到达目的地的方案数 - 力扣(LeetCode) 题目描述 你在一个城市里,城市由 n 个路口组成,路口编号为 0 到 n - 1 ,某些路口之间有 双向 道路。输入保证你可以…

LeetCode 2673. 使二叉树所有路径值相等的最小代价【贪心】1917

本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章…

python三剑客之一——Numpy

温故而知新,借着工作需要用到Numpy的机会重新学习一遍Numpy。 Numpy是一个运行速度非常快的数学库,主要用于数组计算,包含如下: 一个强大的N维数组对象ndarray【Nd(Dimension维度)array】 广播功能函数 整…

2024.3.5

作业1、使用select实现tcp服务器端&#xff0c;poll实现tcp客户端 服务器端&#xff1a; #include <myhead.h> #define SER_IP "192.168.199.131" //服务端IP #define SER_PORT 8888 //服务端端口号int main(int argc, const char *argv[])…

plc远程锁机网关,远程锁机与防拆功能双重保障

在设备租赁和分期购买领域&#xff0c;如何确保设备的安全与资金的回收一直是各大厂家和卖家关注的重点。传统的锁机方式往往需要人工介入&#xff0c;不仅效率低下&#xff0c;而且成本高昂。如今&#xff0c;借助HiWoo Box的远程锁机功能&#xff0c;这些问题将迎刃而解。 什…

Linux运维工具-ywtool默认功能介绍

提示:工具下载链接在文章最后 目录 一.资源检查二.日志刷新三.工具升级四.linux运维工具ywtool介绍五.ywtool工具下载链接 一.资源检查 只要系统安装了ywtool工具,默认就会配置上"资源检查"的脚本资源检查脚本的执行时间:每天凌晨3点进行检查资源检查脚本的检查内容…

基于springboot+vue的球队训练信息管理系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

日本极致产品力|与日本所有食物百搭,年销量2亿箱的啤酒品牌

摘要&#xff1a;《极致产品力》日本深度研学,可以帮助企业找产品、找方向、找方法,在日本终端市场考察中洞悉热销产品背后的成功逻辑,了解最新最前沿的产品趋势和机会。结合日本消费趋势中国转化的众多经验,从品牌、包装、卖点、技术和生产工艺等多方面寻找中口市场的解决方案…

微服务:Nacos注册中心

国内公司一般都推崇阿里巴巴的技术&#xff0c;比如注册中心&#xff0c;SpringCloudAlibaba也推出了一个名为Nacos的注册中心。 1.认识Nacos Nacos是阿里巴巴的产品&#xff0c;现在是SpringCloud中的一个组件。相比Eureka功能更加丰富&#xff0c;在国内受欢迎程度较高。 …

express+mysql+vue,从零搭建一个商城管理系统10--添加商品

提示&#xff1a;学习express&#xff0c;搭建管理系统 文章目录 前言一、新建models/goods.js二、新建routes/goods.js三、添加goods表四、添加商品总结 前言 需求&#xff1a;主要学习express&#xff0c;所以先写service部分 一、新建models/goods.js models/goods.js con…