iis服务器助手广告广告
返回顶部
首页 > 资讯 > 前端开发 > JavaScript >JS如何实现一个单文件组件
  • 600
分享到

JS如何实现一个单文件组件

2024-04-02 19:04:59 600人浏览 安东尼
摘要

目录概述单文件组件基本概念简单的loader解析组件内容注册组件获取脚本内容DataURI和ObjectURI动态导入实现行为层兼容性问题及其他概述 前端开发人员只要了解过vue.j

概述

前端开发人员只要了解过vue.js框架可能都知道单文件组件。Vue.js中的单文件组件允许在一个文件中定义一个组件的所有内容。这是一个非常有用的解决方案,在浏览器网页中已经开始提倡这种机制。但是不幸的是,这个概念自从2017年8月被提出以来,到现在没有任何进展,像是已经要消亡了一样。然而,深入研究这个主题并试着使用现有的技术来实现单文件组件是很有趣的,值得尝试。

单文件组件

知道“渐进增强”这个概念的前端开发人员想必也听说过“分层”这个概念。在组件中,同样有这样的概念。事实上,每个组件至少有3层,甚至多余3层:内容/模板,表现和行为。又或者保守的说,每个组件会被分成至少3个文件,比如:一个按钮组件的文件结构可能是下面这样的:

Button/
| -- Button.html
| -- Button.CSS
| -- Button.js

采用这种方式分层相当于技术的分离(内容/模板:使用html,表现:使用css,行为:使用javascript)。如果没有采用任何构建工具打包,这意味着浏览器需要获取这3个文件。因此,一个想法是:迫切需要一种分离组件代码而不分离技术(文件)的技术来解决这个问题。这就是这篇文章要讨论的主题—单文件组件。

总的来说,我对“技术分层”持怀疑态度。它来自一个事实,就是组件分层常常因为绕不开“技术分层”而被放弃,而这两者是完全分离的。

回到主题,用单文件组件实现按钮可能是这样的:


<template>
  <!-- Button.html contents Go here. -->
</template>

<style>
  
</style>

<script>
  // Button.js contents go here.
</script>

可以看到这个单文件组件很像最初前端开发中的html文档,它有自己的style标签和script标签,只是表现层使用一个template标签。由于使用了简单的方式,得到一个强大的分层组件(内容/模板:<template>,表现:<style>,行为:<script>),而不需要使用3个分离的文件。

基本概念

首先,我们创建一个全局函数loadComponent()来加载组件。


window.loadComponent = (function() {
  function loadComponent( URL ) {}
  return loadComponent;
}());

这里使用了JavaScript模块模式。它允许定义所有必要的辅助函数,但是只向外公开loadComponent()函数。当然,现在这个函数还是空的。

后面,我们要创建一个<hello-world>组件,显式下面的内容。

Hello, world! My name is<given name>.

另外,点击这个组件,弹出一个信息:

Don't touch me!

组件代码保存为一个文件HelloWorld.wc(这里.wc代表WEBComponent)。初始代码如下:


<template>
  <div class="hello">
    <p>Hello, world! My name is <slot></slot>.</p>
  </div>
</template>
<style>
  div {
    background: red;
    border-radius: 30px;
    padding: 20px;
    font-size: 20px;
    text-align: center;
    width: 300px;
    margin: 0 auto;
  }
</style>
<script></script>

目前,没有给组件添加任何行为,只是定义了模板和样式。模板中,可以使用常见的html标签,比如<div>,另外,template中出现了<slot>元素表明组件将实现影子DOM。并且默认情况下这个DOM自身所有样式和模板都隐藏在这个DOM中。

组件在网页中使用的方式非常简单。


<hello-world>Comandeer</hello-world>
<script src="loader.js"></script>
<script>
  loadComponent( 'HelloWorld.wc' );
</script>

可以像标准的自定义元素一样使用组件。唯一的区别是需要在使用loadComponent()方法前先加载它(这个方法放在loader.js中)。loadComponent()方法完成所有繁重的工作,比如获取组件,并通过customElements.define()注册它。

在了解了所有概念之后,是时候动手实践了。

简单的loader

如果想从外部文件中加载文件,需要使用万能的ajax。但是现在已经是2020年了,在大部分浏览器中,你可以大胆的使用Fetchapi


function loadComponent( URL ) {
  return fetch( URL );
}

但是,这只是获取到文件,没有对文件做任何处理,接下来要做的是把ajax返回内容转换成text文本,如下:


function loadComponent( URL ) {
  return fetch( URL ).then( ( response ) => {
    return response.text();
  } );
}

由于loadComponent()函数返回的是fetch函数的执行结果,所以它是一个Promise对象。可以在then方法中检查文件(HelloWorld.wc)是不是真的被加载,还有,是不是被转成文本了:


loadComponent('HelloWorld.wc').then((component) => {
    console.log(component);
});

运行结果如下:

chrome浏览器下,使用console()方法,我们看到HelloWorld.wc的内容被转成了text并输出,所以貌似行得通!

解析组件内容

然而,仅仅把文本输出并没有达到我们的目的。最终要把它转换成DOM用于显示,并能和用户真正交互起来。

在浏览器环境中有一个非常实用的类DOMParser,可以实用它创建一个DOM解析器。实例化一个DOMParser类得到一个对象,实用这个对象可以将组件文本转换成DOM:


window.loadComponent = (function () {
    function loadComponent(URL) {
        return fetch(URL).then((response) => {
            return response.text();
        }).then((html) => {
            const parser = new DOMParser(); // 1
            return parser.parseFromString(html, 'text/html'); // 2
        });
    }
    return loadComponent;
}());

首先,创建一个DOMParser实例parser(1),然后用这个实例将组件内容转化成DOM(2)。值得注意的是,这里实用的是HTML模式(‘text/html')。如果希望代码更好的符合JSX标准或者原始的Vue.js组件,可以实用XML模式(‘text/XML')。但是,在这种情况下,需要更改组件本身的结构(例如,添加可以容纳其他元素的主元素)。

这是再输出loadComponent()函数的返回结果,就是一个DOM树了。

在chrome浏览器下,console.log()输出解析后的HelloWorld.wc文件,是一个DOM树。

注意,parser.parseFromString方法自动给组件添加上<html>,<head>,<body>标签元素。这是由HTML解析器的工作原理造成的。在HTML LS规范中详细描述了构建DOM树的算法。这篇文章很长,要花点时间阅读,可以简单地理解为解析器会默认把所有内容放在<head>元素中,直至遇到一个只能放在<body>标签内的DOM元素。所以,组件代码中所有的元素(<element>,<style>,<script>)都允许放在<head>中。如果在<template>外层包一个<p>元素,那么解析器将把它放在<body>中。

还有一个问题,组件被解析之后并没有<!DOCTYPE html>声明,所以这得到的是一个非正常的html文档,因此浏览器会使用一种被成为怪异模式的方式来渲染这种html文档。所幸的是,在这里它不会带来任何负面作用,因为这里只使用DOM解析器将组件分割成适当的部分。

有了DOM树之后,可以只截取我们需要的部分。


return fetch(URL).then((response) => {
    return response.text();
}).then((html) => {
    const parser = new DOMParser();
    const document = parser.parseFromString(html, 'text/html');
    const head = document.head;
    const template = head.querySelector('template');
    const style = head.querySelector('style');
    const script = head.querySelector('script');

    return {
        template,
        style,
        script
    };
}); 

最后整理一下代码,loadComponent方法如下。


window.loadComponent = (function () {
    function fetchAndParse(URL) {
        return fetch(URL).then((response) => {
            return response.text();
        }).then((html) => {
            const parser = new DOMParser();
            const document = parser.parseFromString(html, 'text/html');
            const head = document.head;
            const template = head.querySelector('template');
            const style = head.querySelector('style');
            const script = head.querySelector('script');

            return {
                template,
                style,
                script
            };
        });
    }

    function loadComponent(URL) {
        return fetchAndParse(URL);
    }

    return loadComponent;
}()); 

从外部文件中获取组件代码的方式不止FetchAPI这一种,XMLHttpRequest有一个专用的文档模式,允许您省略整个解析步骤。但是XMLHttpRequest返回的不是一个Promise,这个需要自己包装。

注册组件

现在有了组件的层,可以创建reGISterComponent()方法来注册新的自定义组件了。


window.loadComponent = (function () {
    function fetchAndParse(URL) {
        […]
    }
    function registerComponent() {
    }
    function loadComponent(URL) {
        return fetchAndParse(URL).then(registerComponent);
    }
    return loadComponent;
}()); 

要注意的是,自定义组件必须是一个继承自HTMLElement的类。此外,每个组件都将使用用于存储样式和模板内容的影子DOM。所以每个引用这个组件的场合下,这个组件都有相同的样式。方法如下:


function registerComponent({template, style, script}) {
    class UnityComponent extends HTMLElement {
        connectedCallback() {
            this._upcast();
        }

        _upcast() {
            const shadow = this.attachShadow({mode: 'open'});
            shadow.appendChild(style.clonenode(true));
            shadow.appendChild(document.importNode(template.content, true));
        }
    }
} 

应该在registerComponent()方法内创建UnityComponent类,因为这个类要使用传入registerComponent()的参数。这个类会使用一种稍加修改的机制来实现影子DOM,在这篇关于影子DOM的文章(波兰文)中我有详细介绍。

关于注册组件,现在只剩下一件事,给单文件组件一个名字,并把它加到当前页面的DOM中。


function registerComponent( { template, style, script } ) {
  class UnityComponent extends HTMLElement {
    [...]
  }

  return customElements.define( 'hello-world', UnityComponent );
} 

现在可以打开看一下了,如下:

在chrome中,这个按钮组件中,有一个红色矩形,文字内容:Hello, world! My name is Comandeer。

获取脚本内容

现在一个简单的按钮组件已经实现。现在要实现最困难的部分,添加行为层,并自定义按钮内内容。在上面的步骤中,我们应该使用到按钮的地方传入内容,而不是在组件代码中硬编码按钮内的文字内容。同理还要处理组件内绑定的事件监听,这里我们使用类似Vue.js的约定,如下:


<template>
  […]
</template>

<style>
  […]
</style>

<script>
  export default { // 1
    name: 'hello-world', // 2
    onClick() { // 3
      alert( `Don't touch me!` );
    }
  }
</script> 

可以假设组件内<script>标签中的内容是一个JavaScript模块,它导出内容(1)。模块导出的对象包含组件的名称(2)和一个已“on..”开头的事件监听方法(3)。

这看上去很工整,没有内容暴露在模块外部(因为JavaScript中modules并不是在全局作用域中)。这里有一个问题:没有一个标准可以处理从内部模块导出的对象(这些代码直接定义在HTML文档中)。import语句会假设获取到一个模块标识,根据这个标识导入。最常见的是从一个包含代码的文件的URL路径。组件不是一个js文件,没有这样一个标识,内部的模块是没有这样的标识的。

在缴械投降之前,可以使用跟一个超级脏的hack。最少有2中方式让浏览器像处理一个文件一样处理一段文本:DataURI和ObjectURI。也有一些建议是使用ServiceWorker。但是在这里显得有点大材小用。

DataURI和ObjectURI

DataURI是一个古老,原始的方法。它的基础是将文件内容转换成URL,去掉不必要的空格,然后使用Base64对所有内容进行编码。假设有一个JavaScript文件,内容如下:


export default true; 

转换成DataURI如下:


data:application/javascript;base64,ZXhwb3J0IGRlZmF1bHQgdHJ1ZTs= 

然后,可以像引入一个文件一样引入这个URI:


import test from 'data:application/javascript;base64,ZXhwb3J0IGRlZmF1bHQgdHJ1ZTs=';
console.log( test ); 

DataURI这种方式的一个明显的缺点是随着JavaScript文件内容增多,这个URL的长度会随之变得很长。还有把二进制数据放在DataURI非常困难。

所以,现在有一种新的ObjectURI。它是从几种标准中衍生出来的,包括FileAPI和HTML5中的<video>和<audio>标签。ObjectURI的目的很简单,从给定的二进制数据创建一个“伪文件”,在当前上下文中给出一个唯一URI。简单点说,就是在内存中创建一个有唯一名称的文件。ObjectURI有DataURI所有的优点(一种创建"文件"的方法),而没它的缺点(即使文件有100M也没关系)。

对象uri通常是从多媒体流(例如在<video>或<audio>上下文中)或通过输入[type=file]和拖放机制发送的文件创建的。还可以使用File,Blob这两个类手动创建。在本例中,我们使用Bolb,先把内容放在模块中,然后转换成ObjectURI:


const myJSFile = new Blob( [ 'export default true;' ], { type: 'application/javascript' } );
const myJSURL = URL.createObjectURL( myJSFile );

console.log( myJSURL ); // blob:https://blog.comandeer.pl/8e8fbd73-5505-470d-a797-dfb06ca71333 

动态导入

不过,还有一个问题:import语句不接受变量作为模块标识符。这意味着除了使用该方法将模块转换成“文件”之外,还是无法导入它。还是无解的吗?

也不尽然。这个问题在很久之前就被提出了,使用动态导入机制可以解决。它是ES2020标准的一部分,并且在Firefox,Safari和node.js13.x中已经被实现。使用一个变量作为要动态导入的模块的标示符已经不再是一个难题了:


const myJSFile = new Blob( [ 'export default true;' ], { type: 'application/javascript' } );
const myJSURL = URL.createObjectURL( myJSFile );

import( myJSURL ).then( ( module ) => {
  console.log( module.default ); // true
}); 

从上面代码中可以看到,import()命令可以像方法一样使用,它返回一个Promise对象,then方法中得到模块对象。在它的default属性中包含了模块中定义的所有导出对象。

实现

现在我们已经知道思路了,现在可以着手实现它。在添加一个工具方法,getSetting()。在registerComponents()方法之前调用它,进而从脚本代码中获取所有信息。


function getSettings( { template, style, script } ) {
  return {
    template,
    style,
    script
  };
}
[...]
function loadComponent( URL ) {
  return fetchAndParse( URL ).then( getSettings ).then( registerComponent );
} 

现在,这个方法返回了所有传入的参数。按照上面介绍的逻辑,将脚本代码转换成ObjectURI:


const jsFile = new Blob( [ script.textContent ], { type: 'application/javascript' } );
const jsURL = URL.createObjectURL( jsFile ); 

下一步,使用import加载模块,返回模板,样式和组件的名称:


return import( jsURL ).then( ( module ) => {
  return {
    name: module.default.name,
    template,
    style
  }
} ); 

由于这个原因,registerComponent()仍然获得3个参数,但是现在它获取的是name,而不是脚本。正确的代码如下:


function registerComponent( { template, style, name } ) {
  class UnityComponent extends HTMLElement {
    [...]
  }

  return customElements.define( name, UnityComponent );
} 

行为层

组件还剩下最后一层:行为层,用来处理事件。现在我们只是在getSettings()方法中获取到了组件的名字,还要获取事件监听。可以使用Object.entrie()方法获取。在getSettings()方法中添加合适的代码:


function getSettings( { template, style, script } ) {
  [...]

  function getListeners( settings ) { // 1
    const listeners = {};

    Object.entries( settings ).forEach( ( [ setting, value ] ) => { // 3
      if ( setting.startsWith( 'on' ) ) { // 4
        listeners[ setting[ 2 ].toLowerCase() + setting.substr( 3 ) ] = value; // 5
      }
    } );

    return listeners;
  }

  return import( jsURL ).then( ( module ) => {
    const listeners = getListeners( module.default ); // 2

    return {
      name: module.default.name,
      listeners, // 6
      template,
      style
    }
  } );
} 

现在方法变得有点复杂了。添加了一个新的函数getListeners()(1) ,将模块的输出传入这个参数中。

然后使用Object.entries()(3)方法遍历导出的模块。如果当前属性以“on”(4)开头,说明是一个监听函数,将这个节点的值(监听函数)添加到listeners对象中去,使用setting[2].toLowerCase()+setting.substr(3)(5)得到键值。

键值是通过去掉开头的“on”,并将后面的“Click”首字母转换成小写组成的(就是从onClick得到click作为建值)。然后传入isteners对象(6)。

可以使用[].reduce()方法代替[].forEach()方法,这样可以省略掉listeners这个变量,如下:


function getListeners( settings ) {
  return Object.entries( settings ).reduce( ( listeners, [ setting, value ] ) => {
    if ( setting.startsWith( 'on' ) ) {
      listeners[ setting[ 2 ].toLowerCase() + setting.substr( 3 ) ] = value;
    }

    return listeners;
  }, {} );
} 

现在,可以将监听绑定在组件内部的类中:


function registerComponent( { template, style, name, listeners } ) { // 1
  class UnityComponent extends HTMLElement {
    connectedCallback() {
      this._upcast();
      this._attachListeners(); // 2
    }
    [...]
    _attachListeners() {
      Object.entries( listeners ).forEach( ( [ event, listener ] ) => { // 3
        this.addEventListener( event, listener, false ); // 4
      } );
    }
  }
  return customElements.define( name, UnityComponent );
} 

在listeners方法(1)上增加了一个参数,并且在class中添加了一个新方法_attachListeners()(2)。在这里可以再次使用Object.entries()来遍历listeners(3),并把他们绑定到element(4)。

最后,点击组件可以弹出“Don't touch me!”,如下

兼容性问题及其他

可以看到,为了实现这个单文件组件,大部分工作围绕如何支持基本的FORM。很多部分使用了脏hacks(使用ObjectURI来加载ES中的模块,没有浏览器的支持,这种技术没有什么意义)。还好,所有的技术在主流浏览器下运行正常,包括:Chrome,Firefox和Safari。

尽管如此,创建一个这样的项目会接触到很多浏览器技术和最新的web标准,也是一件很有趣的事情。

以上就是JS如何实现一个单文件组件的详细内容,更多关于JS实现一个单文件组件的资料请关注编程网其它相关文章!

--结束END--

本文标题: JS如何实现一个单文件组件

本文链接: https://www.lsjlt.com/news/125438.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • JS如何实现一个单文件组件
    目录概述单文件组件基本概念简单的loader解析组件内容注册组件获取脚本内容DataURI和ObjectURI动态导入实现行为层兼容性问题及其他概述 前端开发人员只要了解过vue.j...
    99+
    2024-04-02
  • Vue如何实现一个单文件组件
    这篇文章主要介绍了Vue如何实现一个单文件组件的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Vue如何实现一个单文件组件文章都会有所收获,下面我们一起来看看吧。初识单文件组件还是利用工欲善其事必先利其器 中的源...
    99+
    2023-07-04
  • JS实现一个文件选择组件详解
    目录前言插件安装插件使用参数说明前言 花了点时间利用广度与深度优先搜索算法实现了一个文件选择插件,支持无限层次的文件夹嵌套,已开源并打包上传到了npm。 本文将跟大家分享一下这个插件...
    99+
    2024-04-02
  • Vuejs中怎么实现一个单文件组件
    Vuejs中怎么实现一个单文件组件,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。初识单文件组件还是利用工欲善其事必先利其器 中的源码,在 ...
    99+
    2024-04-02
  • Vue中怎么实现一个单文件组件
    这期内容当中小编将会给大家带来有关Vue中怎么实现一个单文件组件,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。背景相信大家在使用Vue开发项目时,基本都是以单文件组件的形...
    99+
    2024-04-02
  • 使用JavaScript怎么实现一个单文件组件
    使用JavaScript怎么实现一个单文件组件?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。JavaScript是什么JavaScript是一种直译式的脚本语言...
    99+
    2023-06-14
  • JS如何实现单个或多个文件批量下载
    这篇文章主要介绍了JS如何实现单个或多个文件批量下载的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇JS如何实现单个或多个文件批量下载文章都会有所收获,下面我们一起来看看吧。单个文件Download方案一:loc...
    99+
    2023-07-05
  • 如何实现webpack打包时排除其中一个css、js文件或单独打包一个css、js文件的方法
    这篇文章主要介绍了如何实现webpack打包时排除其中一个css、js文件或单独打包一个css、js文件的方法,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大...
    99+
    2024-04-02
  • vue实现一个单文件组件的完整过程记录
    目录前言单文件组件 基本概念 简单的loader 解析组件内容 注册组件 获取脚本内容 Data URI和Object URI 动态导入 实现 行为层 兼容性问题及其他 总结前言 前...
    99+
    2024-04-02
  • vue单文件组件的实现
    最近翻阅了一下vue。发觉有一个单文件组件之前基本忽视掉了。vue.js中的单文件组件允许在一个文件中定义一个组件的所有内容。也就是说,一个页面或者是一个组件,我们想将他们捆绑在一起...
    99+
    2024-04-02
  • Ant Design_Form表单上传文件组件如何实现
    本文小编为大家详细介绍“Ant Design_Form表单上传文件组件如何实现”,内容详细,步骤清晰,细节处理妥当,希望这篇“Ant Design_Form表单上传文件组件如何实现”文章能帮助大家解决疑惑,下面跟着小编的...
    99+
    2023-07-05
  • Js文件函数中如何调用另一个Js文件函数
    小编给大家分享一下Js文件函数中如何调用另一个Js文件函数,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!我们知道,在html中,...
    99+
    2024-04-02
  • vue实现一个单独的组件注释
    目录一个单独的组件注释效果图如下vue代码注释规范,代码规范注释规范1.TEMPLATE结构内容注释2.STYLUS注释3.SCRIPT注释一个单独的组件注释 写了一个组件 加了一些...
    99+
    2024-04-02
  • VB.NET中怎么实现一个菜单组件
    VB.NET中怎么实现一个菜单组件,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。Windows应用程序中往往离不开菜单,菜单的应用是十分普遍了,并且菜单的设计是十分简单的,这主...
    99+
    2023-06-17
  • AjaxFileUpload如何实现单个文件的Ajax文件上传库
    这篇文章将为大家详细讲解有关AjaxFileUpload如何实现单个文件的Ajax文件上传库,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。jQuery.AjaxFileU...
    99+
    2024-04-02
  • js如何实现一个Canvas统计图插件
    小编给大家分享一下js如何实现一个Canvas统计图插件,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!先说下实现的功能吧:  1...
    99+
    2024-04-02
  • Vue如何实现一个可复用组件
    本篇内容主要讲解“Vue如何实现一个可复用组件”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Vue如何实现一个可复用组件”吧!构成组件组件,是一个具有一定功能,且不同组件间功能相对独立的模块。组...
    99+
    2023-07-04
  • Vue下如何实现一个树形组件
    这篇文章主要介绍“Vue下如何实现一个树形组件”,在日常操作中,相信很多人在Vue下如何实现一个树形组件问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Vue下如何实现一个树形组件”的疑惑有所帮助!接下来,请跟...
    99+
    2023-07-04
  • js如何实现文件流式下载文件
    在JavaScript中,可以使用Blob对象和URL.createObjectURL()方法来实现文件的流式下载。具体步骤如下:1...
    99+
    2023-08-09
    js
  • 如何使用react实现一个tab组件
    这篇“如何使用react实现一个tab组件”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“如何使用react实现一个tab组件...
    99+
    2023-07-04
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作