热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

WebAssembly过期:关于大型项目JavaScript的思考

在Auth0处,我们的大多数软件都是使用JavaScript开发的。我们在前端和后端都大量使用该语言。在本文中,我们将探讨JavaScript作为通用语

在Auth0处 ,我们的大多数软件都是使用Javascript开发的。 我们在前端和后端都大量使用该语言。

在本文中,我们将探讨Javascript作为通用语言的有用性,并简要介绍从概念到今天的Javascript开发。 我还将采访一些高级Auth0开发人员在大规模使用Javascript方面的兴衰,并最终了解WebAssembly如何具有完成图片并将语言转换为成熟的开发平台的潜力。

Javascript作为通用语言

对于如今的年轻开发人员而言,过去似乎还不那么清楚:Javascript是否可以被视为通用语言? 我认为今天我们可以肯定地回答这个问题,答案是“是”。 但是Javascript并不是完全年轻:它诞生于1995年,距今已有20多年了!

在超过15年的时间里,Javascript在网络之​​外的吸引力不大,它主要用于前端开发。 许多开发人员认为Javascript只不过是实现梦想的交互式和响应式网站的必要工具。 毫无疑问,即使到今天,Javascript在所有常见浏览器中都没有可移植模块系统(尽管import / export语句是最新规范的一部分)。 因此,从某种意义上说,随着越来越多的开发人员找到扩大其使用范围的方法,Javascript的开发缓慢地发展。

有人会说,能够做某事并不意味着应该做。 当涉及到编程语言时,我觉得这有点苛刻。 作为开发人员,我们倾向于获得某些品味和风格。 一些开发人员偏爱经典的程序语言,而一些开发人员爱上了功能范式,而另一些开发人员则喜欢像手套一样适合中层或厨房使用的语言。 谁能说Javascript,即使是过去的形式,都不适合他们吗?

纵观这些年来的Javascript进展

Javascript作为Web的粘合语言开始了它的生活。 Netscape Navigator(90年代主要的网络浏览器)的创建者认为,设计师和兼职程序员可以使用的语言将使网络更加动态。 因此在1995年,他们将Brendan Eich任命为董事会成员。 Eich的任务是为浏览器创建类似于Scheme的语言。 如果您不熟悉Scheme,那是Lisp家族的一种非常简单的语言。 与所有Lisps一样,Scheme的语法非常少,因此很容易上手。

但是,事情并不是那么顺利。 同时,Sun Microsystems一直在推动Java集成到Web浏览器中。 微软及其自身技术的竞争也无济于事。 因此,必须匆忙开发Javascript。 而且,Java的兴起使Netscape希望他们的新语言能够对其进行补充。

Eich被迫尽快提出原型。 一些人声称它是在几周内完成的。 结果是一种动态语言,其语法与Java相似,但哲学却截然不同。 首先,这种新语言的对象模型与Simula派生的Java对象模型完全不同。 该语言的最初原型称为Mocha,后来称为LiveScript。

由于市场营销的原因,LiveScript在发布时迅速被重命名为Javascript。 Java正在兴起,以“ Java”为名可能会激发对该语言的更多兴趣。

该初始版本是Javascript的第一个版本,并且其中提供了数量惊人的今天称为Javascript的版本。 特别是,对象模型(基于原型)以及语言的许多功能方面(闭包的语义,API的异步特性)都是一成不变的。 不幸的是,它的匆忙发展导致了许多怪癖。

这个版本尽管在许多方面都很强大,但是却缺少显着的功能,这些功能在开发更大的系统时很有用。 例外就是一个例子。

Javascript的下几个版本将关注使其广泛可用。 为实现这一目标而采取的第一步就是使其成为标准。 因此,标准化工作始于ECMA,后来又通过ISO。 ECMAScript是标准化之后采用的名称,与Netscape Navigator中包含的Javascript的第一个版本非常相似。 直到1999年ECMAScript 3或Javascript 1.5为止,当今我们所了解和使用的大多数Javascript都已完成。 此版本包括异常处理,instanceof,所有常用控制机制(执行/执行,切换),评估和大多数内置函数和对象(数组,对象等)。

在那之后,Javascript进入了黑暗时期。 竞争小组对于Javascript的开发有不同的想法。 一些人主张使用高级功能,例如模块,一种静态类型和基于类的面向对象的编程。 其他人则认为这太多了。 提出了ECMAScript 4的建议,实施者开始在其引擎中集成一些功能。 不幸的是,社区从未决定要包括哪些功能。 微软还在开发JScript,这是带有扩展功能的Javascript的实现。 结果,ECMAScript 4被放弃了。

直到2005年Javascript开发才开始兴起。 对ECMAScript 3进行了改进。 该标准之外还开发了其他几个功能(let,生成器,迭代器)。 由失败的ECMAScript 4规范引起的混乱得以解决,并在2009年同意将对ECMAScript 3的改进重新命名为ECMAScript5。为未来的发展定义了路径,并重新评估了针对版本4提出的许多功能。

该标准的当前版本ECMAScript 7(又名2016)包括为版本4计划的某些功能,例如类和导入/导出语句。 这些功能旨在使Javascript更适合中型和大型系统开发。 毕竟,这就是ECMAScript 4背后的原理。 但是Javascript是否能兑现这一诺言?

让我们看一下Javascript功能的客观描述。

语言特点:不错

句法熟悉

C语言族享有广泛的思维。 C,C ++,Java,C#和Javascript的总和可能超过所有使用的其他语言。 尽管这可能是许多Javascript怪癖的原因,但使Javascript成为一种类似于C的语言的语法使现有开发人员更容易掌握。 即使在今天,这仍是有帮助的,因为类似C的语言仍在开发领域占主导地位。

在没有经验的开发人员看一两个常见示例后,他们可以轻松地开始编写Javascript代码:

function test(a, b, c) {a.doStuff(b.property, c);return a.property;
}

异步性质

对于新开发人员来说,使用Javascript的最大冲击可能就是一切本质上都是异步的。 这需要一些时间来习惯,但是如果您考虑一下Javascript的概念,这是完全有意义的:这是一种将可编程逻辑集成到网页中的简单方法。 谈到这一点,需要考虑两件事:非阻塞行为是必不可少的,而共享内存则太复杂了。

解决方案:回调和闭包。

const consumer = new Consumer();$.ajax({method: "GET",url: "http://test.com/resource"
}).done(function(data) {consumer.push(data);
});

可以说,由于这种方法的好处,Javascript看到了服务器端开发的需求。 诸如async / await之类的功能将使异步开发更加容易。

功能特点和封闭

Javascript的多范式方法已见成效。 根深蒂固于一种范例中的许多语言,例如Java,已经开始实现其他范例。 Javascript从一开始就具有这种功能。 原型继承足够强大,可以实现所有OOP语义。 闭包允许将函数视为一流对象,并按原样传递。 具有便捷符号(JSON)的对象和数组结合了这些功能,使Javascript本质上具有强大的功能。

以下是来自RxJS文档的示例:

const source = getAsyncStockData();const subscription = source.filter(quote => quote.price > 30).map(quote => quote.price).forEach(price => console.log(`Prices higher than $30: ${price}`);

语言功能:不好

怪癖

Javascript是匆忙开发的,它表明了这一点。 例如,自动分号插入(该功能旨在简化非开发人员的开发工作)可能会产生意外的结果:

function test() {functionCall();obj.operation();// Other codereturn //<-- semicolon inserted here, returns undefined{key: "This object should be returned instead"}
}

这些怪癖使Javascript变得不直观&#xff0c;并可能导致生产力下降。 有经验的开发人员知道如何避免这些陷阱&#xff0c;因此中型和大型系统确实需要有经验的开发人员&#xff0c;使用Javascript而非其他语言时可能需要更多经验。 Javascript Garden列出了其中一些怪癖。

打字和自动转换功能弱

尽管测试是Javascript开发的重要组成部分&#xff0c;但并非所有测试总是会遇到简单的转换错误。 而且&#xff0c;Javascript执行许多隐式强制转换。 专家开发人员非常了解这些强制转换的语义&#xff0c;并在可能的情况下尽量避免使用它们。

以下是Javascript转换工作方式的一个极端示例&#xff1a;

console.log((![]&#43;[])[&#43;!![]]);
//This prints the character “a”

由于能够将任何值转换为布尔值&#xff0c;所以这是可能的。 第一个隐式强制转换导致值“ false”被强制转换为整数&#xff0c;然后索引为其第二个值。 狡猾而疯狂。

模块系统

ECMAScript 6&#xff08;2015&#xff09;最终定义了潜在模块系统的语法。 但是&#xff0c;当前没有浏览器以可用的方式实现此功能。 换句话说&#xff0c;即使在今天&#xff0c;也需要外部模块装载器。

模块对于正确的软件开发至关重要。 拆分和重用代码的标准方法可能是其最基本的方面之一。 关于Javascript模块 &#xff0c;我们仍在使用竞争解决方案&#xff1a;需求&#xff08;Node.js模块&#xff09;&#xff0c;导入/导出以及模块加载器或编译器&#xff08;Babel&#xff0c; System.js &#xff0c;Webpack&#xff09;&#xff0c;甚至是普通的立即调用的函数或UMD 。

全球和吊装

Javascript变量始终在函数范围内定义&#xff08;除非使用let来声明它们&#xff0c;这是最近添加的&#xff09;。 这可能会导致变量意外更改。 不难想象&#xff0c;变量的意外更改对于大规模开发可能会造成问题。

function test() {if (true) {var a &#61; 1;console.log(a);}var a;if (a) {// This code runs, a &#61;&#61;&#61; 1console.log("Here");}
}

由于非开发人员希望使用Javascript&#xff0c;因此通过一些基本检查是不严格的。 所有变量&#xff0c;即使未定义时&#xff0c;都在某些上下文中创建。 如果未指定上下文&#xff0c;则在全局上下文中创建它们。 换句话说&#xff0c;如果由于某种原因而忘记为变量指定正确的上下文&#xff0c;则它将在错误的位置静默地创建和更新。

function test() {variable &#61; "test";
}
test();
console.log(window.variable);

幸运的是&#xff0c;可以在Javascript严格模式下对全局变量进行更严格的检查。

缺少适当的整数类型

Javascript中的所有数字变量均为浮点类型&#xff08;在特殊情况下除外&#xff09;。 通常这足够了。 不幸的是&#xff0c;许多算法期望定义明确的整数语义可用。 通常可以在Javascript数值类型的顶部实现这些语义&#xff0c;但这会导致代码不理想。

对于32位整数类型&#xff0c;可以使用按位运算符获得最佳语义&#xff08;这是Javascript中32位整数可用的唯一情况&#xff09;。 不幸的是&#xff0c;没有64位整数的本机替代方法&#xff08;当今许多平台都可以使用本机&#xff09;。

当前版本的Javascript包括部分类型化的数组。 但是&#xff0c;这些还不够。

关于使用Javascript开发大型系统的意见

在Auth0处 &#xff0c;我们的大多数软件都是使用Javascript开发的。 我们很早就对Node.js进行了大量投资。 到目前为止&#xff0c;它已经取得了回报。 但是我们一些最高级的开发人员有很多故事。

我们已经请工程主管Damian Schenkelman和工程主管Jose Romaniello分享他们对此事的想法。

开发人员被麦克风包围着&#xff0c;采访了他关于在大型项目中使用Javascript的情况

问&#xff1a;您对Javascript作为通用语言有何看法&#xff1f;

D. Schenkelman &#xff1a;我喜欢这种语言&#xff0c;因为它的核心概念非常少&#xff0c;闭包是可以基于其构建的非常强大的功能。

显然有缺点&#xff1a;隐式类型转换和弱类型系统。 我发现&#xff0c;如果坚持使用好部分 &#xff0c;Javascript可能是一门不错的语言。 当然&#xff0c;测试也是开发的重要组成部分。

J. Romaniello &#xff1a;我认为很好。 您可以将其用于当今的几乎所有事物&#xff0c;但对于许多情况而言并不理想。
在某些时候看来可以实现某个目标的好处的事情很容易对您不利。

该语言本身以及运行它的平台&#xff08;即浏览器或Node.js&#xff09;都非常容易理解。 JavaSacript的真正力量来自工具&#xff0c;库及其庞大社区的生态系统。

我认为Node.js的哲学非常正确&#xff08;也许来自Unix&#xff1f;&#xff09;是小的内核和广阔的用户群。

Node.js的大多数模块仅公开一个功能非常具体的功能&#xff0c;并且所有功能都有模块。 这些只是文档齐全的小型构建块&#xff0c;开发人员可以理解并使用它们来解决问题。

我认为它不能归因于包管理器或语言&#xff0c;但这更像是一种事实上的完成事情的方式。 其他技术也具有类似的工具&#xff0c;但是它们没有库&#xff0c;而是具有Spring&#xff0c;WCF等的“成败”的“框架”。

问&#xff1a;在您使用Javascript开发性能最高的服务的那些年中&#xff0c;您能想到来自Javascript完全使事情变得乏味或完全节省了一天的故事吗&#xff1f;

D. Schenkelman &#xff1a;实际上&#xff0c;我发现我们犯的大多数大错误都与缺少属性或类型错误的对象有关。 这些错误很容易通过隐式检查类型而避免&#xff0c;并且需要更多的纪律才能用Javascript编写全面的测试。 我认为在这些情况下&#xff0c;渐进式打字可以提供很大帮助。 不幸的是&#xff0c;我们尚未起草一套新的准则以立即开始执行此操作&#xff0c;但这是我们强烈考虑的问题。 我认为TypeScript是朝着正确方向迈出的一步&#xff0c;尤其是涉及模块间合同时。 当然&#xff0c;这并不是说TypeScript应该代替测试&#xff1a;根本不应该&#xff0c;但是它可以帮助捕获愚蠢的错误。 短绒棉也有很大帮助。

J. Romaniello &#xff1a;Matias&#xff0c;Eugenio&#xff0c;Iaco和我来自Auth0之前的另一个世界。 我们是.NET开发人员很多年了。 与其他语言相比&#xff0c;以Node.js开头Auth0使我们得以以惊人的速度发展&#xff0c;因为我们在数据库&#xff08;Mongo&#xff09;中具有Javascript&#xff0c;在后端&#xff08;Node&#xff09;中具有Javascript&#xff0c;而在浏览器中具有Javascript。 在基于模式的数据库中使用强类型语言通常需要编写适配器并从一种模型映射到另一种模型。 在Javascript中&#xff0c;您一直使用“哈希映射”。

我不能将任何特定的失败归因于语言本身。 我们犯了很多错误&#xff0c;例如&#xff0c;我们了解了循环10万个对象以渲染某些东西以阻止事件循环的困难方法。
特别是关于Node.js&#xff0c;有时我们希望有更详细的错误。 在某些情况下&#xff0c;您只会得到“ ECONNRESET”异常而没有其他任何细节。 幸运的是&#xff0c;Node.js代码库易于理解&#xff0c;它使我可以修复这些问题。

问&#xff1a;如果您现在可以选择任何语言或框架来开发后端&#xff08;例如Auth0&#xff09;&#xff0c;那么它将是哪种语言或框架&#xff1f; 您会再次选择Node.js和Javascript吗&#xff1f;

D. Schenkelman &#xff1a;我认为这并不像看起来那么重要。 我的意思是&#xff0c;开发平台&#xff0c;尤其是在涉及初创公司时&#xff0c;不仅限于编码。 代码只是实现您的产品的手段。 只要这套工具可以合理地应用于所讨论的问题领域&#xff0c;编码就只是难题的一部分。 无论您选择Java&#xff0c;Javascript&#xff0c;C&#xff03;还是其他许多久经考验的平台&#xff0c;您都将获得结果。

工程学还必须考虑事物的业务方面。 只要您可以在一个团队中保持合理的生产力&#xff0c;这种语言就不会像运输&#xff0c;满足客户需求或获利那样重要。

通常&#xff0c;对于我们团队中的大多数开发人员而言&#xff0c;Javascript很容易上手。 当您快速成长时&#xff0c;这非常重要。 以我的经验&#xff0c;大型平台都擅长于此。 因此&#xff0c;很难说如果我们选择其他东西会发生什么&#xff0c;但是我也认为这也不太重要。

J. Romaniello &#xff1a;Auth0后端正在演变为小型服务。 这使我们能够在不同类型的负载上自动扩展&#xff0c;从而提高了容错能力&#xff0c;更好的监视等。我们使用Node.js的方式与开始时的方式不同。 我想我会再次选择Node.js或类似Erlang / Elixir的东西。


总的来说&#xff0c;我们经验最丰富的开发人员认为Javascript具有出色的生态系统&#xff0c;即使这种语言有时不能很好地解决问题&#xff0c;它也会有所作为。 但是&#xff0c;如果我们可以向该生态系统开放更多工具呢&#xff1f;

输入WebAssembly

在后端&#xff0c;您有很多选择。 为工作找到合适的工具不是问题。 但是&#xff0c;当涉及到前端开发或客户端应用程序时&#xff0c;您就不得不使用Javascript。 而且&#xff0c;如上所述&#xff0c;Javascript对于许多应用程序来说都是完美有效的工具。 它在大型系统上的越来越广泛的使用证明了这一点&#xff0c;但是认为它是适用于所有情况的正确工具是不明智的。

WebAssembly有潜力改变所有这一切。 想象一下有可能在公司内部为新项目选择经过验证的库。 您是否有使用C实现的内部算法库&#xff1f; 没问题&#xff0c;将其编译为WASM并将其加载到您的应用程序中。 然后开发Javascript中合理的任何部分。 这是网络多年来已经缺失的那种力量&#xff0c;并且最终指日可待。 不仅限于前端。 预计Node.js也将允许加载WASM模块。 从某种意义上讲&#xff0c; WebAssembly是Javascript虚拟机从以语言为中心到通用VM的变体。

Web装配过程流程图

自.NET平台于2002年发布以来&#xff0c;通用虚拟机已经飙升。 例如&#xff0c;Java已成为新语言和现有语言的平台。 Scala和Clojure也许是这一趋势的最大代表。 完全新平台的开发是在拥有一套经过实践检验的工具以及与问题的正确语言相结合的好处的前提下进行的。 而且Javascript已成为一个丰富的平台。

WebAssembly中的最近几个月令人兴奋&#xff1a;Binaryen&#xff0c;一种新的生成WASM文件的编译器基础结构已经开始工作&#xff1b; Firefox&#xff0c;Chrome和Edge在实验性标志的后面都有有效的WebAssembly实现&#xff1b; 规格和设计文件的大小已经增加。 您甚至可以尝试使用带有Unity示例的ASM.js后备功能的功能完善的可运行演示 。 WebAssembly指日可待&#xff0c;但仍未准备好。

同时&#xff0c;出于对Javascript的需求或缺乏灵活性&#xff0c;正在开发大型应用程序。 应用程序越大&#xff0c;达到极限的机会就越大&#xff1a;大型整数数学&#xff0c;SIMD&#xff0c;线程化等。WebAssembly是Javascript生态系统多年来缺少的补充。

结论

Javascript是许多应用程序的正确工具。 功能特性&#xff0c;语法熟悉性&#xff0c;异步特性&#xff0c;大量的库以及强大的社区使其成为目前最好的开发平台之一。

但是&#xff0c;由于与其他解决方案集成时缺乏灵活性&#xff0c;迫使Javascript进入了不合适的位置。 如果您只有锤子&#xff0c;那么一切看起来都像钉子。

WebAssembly将完全改变这种情况&#xff0c;将Javascript变成一个成熟的开发平台。 WebAssembly是Javascript的最终需求&#xff0c;而且还不能很快推出。

From: https://www.sitepoint.com/webassembly-is-overdue-Javascript-for-large-projects/



推荐阅读
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • PHP图片截取方法及应用实例
    本文介绍了使用PHP动态切割JPEG图片的方法,并提供了应用实例,包括截取视频图、提取文章内容中的图片地址、裁切图片等问题。详细介绍了相关的PHP函数和参数的使用,以及图片切割的具体步骤。同时,还提供了一些注意事项和优化建议。通过本文的学习,读者可以掌握PHP图片截取的技巧,实现自己的需求。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • flowable工作流 流程变量_信也科技工作流平台的技术实践
    1背景随着公司业务发展及内部业务流程诉求的增长,目前信息化系统不能够很好满足期望,主要体现如下:目前OA流程引擎无法满足企业特定业务流程需求,且移动端体 ... [详细]
  • Android自定义控件绘图篇之Paint函数大汇总
    本文介绍了Android自定义控件绘图篇中的Paint函数大汇总,包括重置画笔、设置颜色、设置透明度、设置样式、设置宽度、设置抗锯齿等功能。通过学习这些函数,可以更好地掌握Paint的用法。 ... [详细]
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • 本文介绍了一个在线急等问题解决方法,即如何统计数据库中某个字段下的所有数据,并将结果显示在文本框里。作者提到了自己是一个菜鸟,希望能够得到帮助。作者使用的是ACCESS数据库,并且给出了一个例子,希望得到的结果是560。作者还提到自己已经尝试了使用"select sum(字段2) from 表名"的语句,得到的结果是650,但不知道如何得到560。希望能够得到解决方案。 ... [详细]
  • 本文详细介绍了Spring的JdbcTemplate的使用方法,包括执行存储过程、存储函数的call()方法,执行任何SQL语句的execute()方法,单个更新和批量更新的update()和batchUpdate()方法,以及单查和列表查询的query()和queryForXXX()方法。提供了经过测试的API供使用。 ... [详细]
  • 本文介绍了在使用Python中的aiohttp模块模拟服务器时出现的连接失败问题,并提供了相应的解决方法。文章中详细说明了出错的代码以及相关的软件版本和环境信息,同时也提到了相关的警告信息和函数的替代方案。通过阅读本文,读者可以了解到如何解决Python连接服务器失败的问题,并对aiohttp模块有更深入的了解。 ... [详细]
  • Html5-Canvas实现简易的抽奖转盘效果
    本文介绍了如何使用Html5和Canvas标签来实现简易的抽奖转盘效果,同时使用了jQueryRotate.js旋转插件。文章中给出了主要的html和css代码,并展示了实现的基本效果。 ... [详细]
  • WebSocket与Socket.io的理解
    WebSocketprotocol是HTML5一种新的协议。它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送 ... [详细]
  • 本文讨论了Kotlin中扩展函数的一些惯用用法以及其合理性。作者认为在某些情况下,定义扩展函数没有意义,但官方的编码约定支持这种方式。文章还介绍了在类之外定义扩展函数的具体用法,并讨论了避免使用扩展函数的边缘情况。作者提出了对于扩展函数的合理性的质疑,并给出了自己的反驳。最后,文章强调了在编写Kotlin代码时可以自由地使用扩展函数的重要性。 ... [详细]
  • 通过手机浏览器调用客户端QQ
    php教程|php手册thinkphp代码,代码示例,代码参考,php短信,数据库备份代码,令牌验证,去除代码中的空白和注释调用QQ客户端php教程-php手册可调用iosandr ... [详细]
author-avatar
手机用户2502916423
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有