HTML5游戏引擎深度测评

最近看到网上一篇文章,标题叫做《[2016年 最火的 15 款 HTML5 游戏引擎
__](https://link.zhihu.com/?target=http%3A//www.oschina.net/news/72092/2016-top-15-html5
-game-
engines)》。目前针对HTML5游戏的解决方案已经非常多,但谁好谁差却没有对比性资料。特意花了几天时间,针对文章中出现的12款免费开源引擎做了一次相对完整的对比分析,希望能对大家有所帮助。

针对技术类产品对比,通常有多个维度进行对比,不仅仅是技术层面,还有许多非技术层面的内容会影响我们的使用结果。本文从如下几个维度进行多重对比。

  1. 2D与3D
  2. 编程语言
  3. 产品定位&功能
  4. 工作流
  5. 性能
  6. 学习资料
  7. 商业应用

2D与3D、编程语言对比

2D与3D

游戏领域中,最直白的一种分类方法便是2D与3D的区分。通常我们都会认为它们是游戏引擎领域两类不同的产品。

编程语言

基于HTML5技术的游戏引擎,所需要的脚本必定是JavaScript,只有JavaScript脚本语言才能运行于浏览器中。但目前市场上,出现了很多JavaScript代替品,例如TypeScript、CoffeeScript、LiveScript等等。不同语言直接的定位不同,语言哲学也不尽相同。一些游戏引擎在语言选择上也颇有意思。

结论

可以从表格中看出,下面三个引擎属于2D和3D通吃类型。

  • Egret
  • Turbulenz
  • PlayCanvas

在Web游戏领域胜出的编程语言是JavaScript和TypeScript。但绝大部分HTML5游戏引擎还是采用JavaScript语言。只有三款引擎选择支持TypeScript。

产品定位&功能

一个引擎的功能并非越多越好,功能应围绕引擎定位而定,这样的思路在一些引擎中体现尤为明显,下面我们针对每个引擎一一分析。

Three.js

定位

Three.js项目创建时间是在2010年的4月24日,到目前位置,应该算是比较老牌的开源项目了。事实上Three.js定义并非一个游戏引擎。在Github主页中,作者很明确的定义了Three.js的定位,叫做“JavaScript
3D library”。它仅仅是一个基于JavaScript语言的3D库而已。

功能

文本主要想对2D游戏引擎做深入分析,所有没有对Three.js的功能与那些流行的3D引擎加以对比。

Pixi.js

定位

Pixi.js的定义为“2D WebGL renderer with canvas
fallback”,翻译为中文是一款依赖于canvas的WebGL渲染器。所以当你看到Pixi.js提供了为数不多的功能时。

功能

游戏引擎中的功能,我们可以细分非常多分类,一篇文章无法讲解所有分类细节讲解明白。我将所有功能做了一个二级分类,方便分析。

Phaser

定位

Phaser的定位是 “Desktop and Mobile HTML5 game
framework”,中为称之为“桌面与移动端的HTML5游戏框架”。Phaser并不把自己定义为Engine,而是框架,同时渲染内核使用Pixi.js。

功能

Phaser功能众多,但绝大部分应用其他第三方作为实现。

Egret

定位

Egret算是HTML5游戏引擎中的新起之秀,其定位已不单纯为HTML5游戏引擎。官方将其定位为“游戏解决方案”,同时也并未过多提及HTML5。究其原因在于Egret不仅仅提供了一个基于HTML5技术的游戏引擎,更是提供了原生打包工具和众多周边产品,使其成为“解决方案”。

这里单独分析Egret
Engine这个产品,其语言使用TypeScript,有2D和3D版本。在架构设计上,同Pixi.js一样,参考了Flash成熟的2D架构体系。API方面,也参考了ActionScript。不仅如此,由于TypeScript的缘故,在事件系统中,也仿照ActionScript实现了addEventListener
这样的事件注册机制。

内核方面,Egret Engine采用了模块化的设计。这样可以将不同的功能进行解耦。更加有趣的是,Flash中引以为傲的自动脏矩形技术在Egret
Engine中也被实现。在canvas模式下,脏矩形会是渲染性能得到提升,比其他引擎更加有优势。

功能

Egret
Engine由于模块化设计的原因,将不同功能放到了不同模块中。这些模块以库的形式提供,下面表中是所有支持模块的总和,但不含平台API部分,例如微信API的封装。

enchant.js

定位

enchant.js并非一个引擎,而是一个框架。同时,enchant.js也不仅仅用于游戏,还可以用于app。

功能

enchant.js框架自身提供的功能非常有限,如果需要其他功能,必须自己扩展或者寻找响应的插件。

craftyJS

定位

craftyJS将自己定义为针对JavaScript游戏的框架。

功能

Turbulenz

定位

Turbulenz引擎实际上是为自己的游戏渠道中的游戏提供的游戏引擎。因为和自身渠道绑定,所以在引擎中提供了很多low level
API。借助这些底层API,可以呼叫Turbulenz游戏渠道中的一些系统级别功能。

功能

由于Turbulenz对很多功能做了扩展,同时推出Low Level API和 High Level
API。这里不再对其中庞杂的系统进行功能分析,大家如果有兴趣可以到其官网查看。

cocos2d-js

定位

cocos2d-js是喊着Cocos2D-X的金钥匙出身的,它仅仅是Cocos2D-X的一个HTML5实现的分支。

功能

cocos2d-js的功能提供的相当完整,你在游戏中需要的功能几乎都能够找到。

PlayCanvas

定位

PlayCanvas主要用于3D渲染,本文还是以2D讨论为主,对PlayCanvas的分析就不做过多分析。

melonJS

定位

melonJS是一个轻量级的HTML5游戏框架,并且通过插件机制扩展其功能。

功能

Quintus

定位

Quintus将自己定位为简单好用的JavaScript游戏引擎,同时支持移动和PC端。

功能

Quintus自身并不支持WebGL,同时提供的功能也较少,在Github中排名也很靠后。

Hilo

定位

Hilo这个引擎来源于阿里前端团队,从官网的主页上看,这个引擎的定位比较模糊。Hilo作为一个跨终端的互动小游戏解决方案,同时有称综合解决方案。从它的演变来看,Hilo属于阿里前端在实践总总结出来的一套工具库。整体引擎并非最初有计划设计构想。

功能

Hilo功能相对比较简单,对于游戏开发来说,缺失功能较多。

工作流

对团队开发来讲,工作流搭建是非常重要的,我个人比较看重这点。如果是小型团队或者个人开发者可能对此需求并不大。当项目规模变大时,一个好的工作流会事半功倍。

Three.js

3D并不在本篇文章的讨论范围之内,同时Three.js也并非游戏引擎,不存在游戏开发工作流一说。这里简单介绍一下Three.js所提供的在线编辑器。

Three.js提供的在线编辑器应该是基于Three.js开发的,功能不多,但相当小巧。

Pixi.js

Pixi.js作为一个渲染器,其工具支持也是相当清爽,除了一个程序库之外,没有提供任何工具。

Phaser

Phaser和Pixi.js一样,没有提供任何工具支持,在其官网上只是推荐了两个代码编辑器。还提供了一个简单的在线代码编辑器。

Egret

Egret提供的工具非常多,也复合其解决方案的定位。在Egret整个体系下你可以看到如下工具支撑。

Egret Wing:Egret出品的一个IDE编辑器。在提供代码编辑功能的同时,还内置可视化的UI编辑器。与Egret
Engine中的GUI、EUI框架配合使用。

ResDepot:这是个小工具,用来配置游戏资源加载表。如果游戏资源多的话,用这个小工具拖拽一下就完成了。

TextureMerger:一个纹理合并的小工具,功能有点像TexturePacker。

DragonBones Pro:针对Egret中骨骼动画解决方案提供的DragonBones动画编辑器。

Egret Inspector:一个基于Chrome浏览器的插件,可以针对Egret游戏进行调试。

Egret iOS & Android Support:这两个东西可以将你的HTML5游戏打包成原生APP。

还有一些其他的工具,但定位与游戏开发不同,有兴趣可以去它的官网看。

从上幂啊你的分析看出,Egret在工作流的支持上做的还是非常完成的,从Wing的代码编写,到ResDepot和TextureMerger的资源整合,再到Inspector调试,和原生打包。游戏开发过程中的每个环节基本都有工具支撑。

enchant.js

enchant.js 没有提供任何工具支撑,在官网中也没有任何相关支持工具的介绍。

craftyJS

craftyJS也没有提供任何工具支撑,仅仅是一个开源代码库。

Turbulenz

Turbulenz在你下载的目录中包含了很多工具,大部分与格式转换相关。所有工具均为命令含小工具,没有提供任何可视化操作软件支持。

cocos2d-js

Cocos2d-js近年来变化很大,但对于JS这个分支的支持却少之又少。前一段时间新出了一个工具叫做Cocos
Creator。我没有具体使用过,但看截图仿佛有Unity3D的影子。从介绍中看,应该对游戏支持还是不错的,编辑方面目前还欠缺。

PlayCanvas

PlayCanvas也提供了一个在线编辑器,不过是针对它的3D功能。编辑器看上去和Three.js提供的在线编辑器份很相似。这里直接借用官方文档中的截图给大家看一下。

melonJS

melonJS除了源码库以外,也没有提供任何工具支持。但在其官方主页中,包含几个其他编辑器的连接。比如著名的Tiled地图编辑器等。

Quintus

Quintus没有提供任何工具支撑。

Hilo

Hilo没有提供任何工具支撑。

总结

结果并不出乎意料,对于开源游戏引擎来讲,维护库就是耗费作者很大一部分精力,更何况去制作编辑器之类的软件产品。很多引擎都会依赖一些比较流行的第三方工具,例如Tiled、TexturePacker等等。虽然可以实现功能,但整个工作流搭配起来还是多多少少会有一些问题
。只有Egret和Cocos2D-js提供了相关可视化编辑工具。而这两对于工作流的理解则完全不同。从产品中不难看出,Cocos2D-
JS更像Unity3D,提供一个大而全的软件给开发者用。Egret则是什么角色用什么工具,将产品按照角色划分,针对不同角色和开发流程中的各个环节进行产品设计。

相对来说,Egret的这种方式使得每个工具更加垂直,能够做的功能也更加深入,不会让工具显得臃肿。而Cocos
Creator则力求完整,一个软件解决所有事情。

性能

性能测试上,我只针对2D游戏引擎做了一个渲染压力测试。

测试内容为同屏渲染对象数量相同的情况下进行帧频数据对比,为了保证测试的公平性,我使用同一台电脑,相同版本的Chrome浏览器进行测试,游戏场景尺寸均为800*600,显示的图片也为同一张。每个引擎进行同屏5000、10000、20000个显示对象渲染。

其中craftyjs引擎渲染出现问题,这里不作数据对比。 Quintus引擎不支持WebGL渲染模式,因此这里页不作数据对比。
Phaser渲染内核使用Pixi.js,因此Phaser渲染数据参考Pixi.js结果。

所有引擎编写的代码大致相同,开始做for循环,创建定量显示对象,然后在循环中对每个显示对象做旋转操作。

核心算法代码如下:

我的电脑配置如下:

最终测试结果

结论

按照上述测试方法,我们可以对引擎性能排名做一个大致排列:

第一名:Pixi.js 和 Turbulenz

第二名:Egret

第三名:Cocos2d-js

第四名:Hilo

第五名:enchant.js

第六名:melonJS

最后放出一张测试时效果图

学习资料

通常情况下,我们都会选择一个资料较全的产品进行学习使用,毕竟使用过程中会遇到各种各样的问题。现在游戏引擎的文档,讨论组等都已经成为了产品标配。下面这个表格就对各个引擎的这些“标配”做一个对比。

结论

从上面对比表格可以看出,绝大部分引擎在文档教程方面做的还是比较深入的,但完成程度不同。大部分都为英文文档,对于国内的开发者来说可能学习起来成本略高。其中两个支持中文的引擎Egret、Hilo均为国人产品,这两款引擎在文档方面,Egret做的相当优秀,开发者可以从它的http://edn.egret.com
__
中查阅大量中文资料。

在学习难度上,Egret算是最为简单的,无论从完整度还是中文普及度上。

商业应用

这部分对比是在商业产品应用中的占比情况。一个引擎被商业产品应用广泛的情况下,足以证明此引擎具备商业产品使用价值。通俗的讲,别人能用这玩意做出游戏,你也能。所以针对这两方面进行一下粗略的分析。

我对国外的HTML5游戏市场完全不了解,这个市场分析的东西太大,不好做评价。就分析一下国内的,简单看一下到底哪个引擎用的多。

我用了国内比较火的HTML5游戏平台新浪微博作为数据采样基础,一个人实在精力有限,不可能做的完整。由于客户端对游戏地址进行了加密,无法直接获取。所以用了一些调试工具来看游戏网页的标记,以此判断游戏到底使用什么引擎制作。

最终统计结果如下:

一共找了50款游戏,如上面表格。50款引擎,使用纯HTML5开发的6款,使用Egret开发的30款,Cocos2d-
js的14款,laya的1款,createjs的1款。

百分比统计如下:

从网上又搜刮了一些数据,下面是国内HTML5游戏四大典型大渠道,即新浪微博(社交型APP类代表),爱微游(微信大号类代表),QQ浏览器(各大浏览器类代表),WiFi万能钥匙(超级APP类代表)统计的数据,比我自己的全。

下面是主流渠道H5游戏引擎使用率。

HTML5付费游戏全渠道累计用户排名前30的产品

不难看出,Egret 和 Cocos2D-js联合瓜分了大部分市场。而Egret占比居然过半。看来Egret在国内HTML5游戏市场还是非常强悍的。

总结

  1. Three.js:作为老牌的3D库,它已经有众多案例,在PC多网页3D中是目前不错的选择。
  2. Phaser:文档教程,和案例方面都很不错,功能也算的上丰富。非常适合独立游戏开发和小团队使用。
  3. Pixi.js:作为渲染器,其渲染性能绝对是非常优秀的,游戏功能方面支持很差,适合极客程序员把玩。
  4. Egret:性能不错,在工作流方面支持非常优秀,适应中度和重度HTML5游戏开发,有较多商业项目验证,非常适合商业团队使用。
  5. enchant.js:性能偏差,不太推荐。
  6. craftyJS:文档教程等方面不太完善,很难找到对应技术支持,不推荐。
  7. Turbulenz:性能极佳,但捆绑其自身业务,不太适合国内市场。
  8. cocos2d-js:老牌引擎,其性能在排名中居中,工作流支持相对完整,推荐。
  9. PlayCanvas:重度3D游戏开发引擎,本文不对3D做推荐。
  10. melonJS:性能不理想,不推荐。
  11. Quintus:不支持WebGL模式,性能较差,不推荐。
  12. Hilo:阿里前端团队作品,偏向于前端开发工程师,与游戏专业开发距离较大,推荐做HTML5营销小交互的使用。

更多详情: https://zhuanlan.zhihu.com/p/20768495

深入理解Babel原理及其使用

#

中文网:https://www.babeljs.cn/

官网:https://babeljs.io/


前言

半年前也写过一篇babel的简单使用文章,当时看了下babel的文档,但是很多地方还不理解,所以文章里没有怎么说道babel的一些关键概念,只是机械的描述如何使用(配合webstorm)。

最近刚好遇到一个问题,发现是因为js代码中使用的es6的新api没有被转义,导致抛异常了。查找了下资料,是没有引入polyfill所致,之前一直对babel的这些概念没有很好的理解,把这个bug作为引子,认真了解了下babel,写出此文。

本文的babel使用场景局限于babel配合webpack来转译输出es5的js代码,babel的命令行、以代码形式调用或node环境下这些统统都不会涉及。

Babel的包构成

核心包

  • babel-core:babel转译器本身,提供了babel的转译API,如babel.transform等,用于对代码进行转译。像webpack的babel-loader就是调用这些API来完成转译过程的。

  • babylon:js的词法解析器

  • babel-traverse:用于对AST(抽象语法树,想了解的请自行查询编译原理)的遍历,主要给plugin用

  • babel-generator:根据AST生成代码

功能包

  • babel-types:用于检验、构建和改变AST树的节点

  • babel-template:辅助函数,用于从字符串形式的代码来构建AST树节点

  • babel-helpers:一系列预制的babel-template函数,用于提供给一些plugins使用

  • babel-code-frames:用于生成错误信息,打印出错误点源代码帧以及指出出错位置

  • babel-plugin-xxx:babel转译过程中使用到的插件,其中babel-plugin-transform-xxx是transform步骤使用的

  • babel-preset-xxx:transform阶段使用到的一系列的plugin

  • babel-polyfill:JS标准新增的原生对象和API的shim,实现上仅仅是core-js和regenerator-runtime两个包的封装

  • babel-runtime:功能类似babel-polyfill,一般用于library或plugin中,因为它不会污染全局作用域

工具包

babel-cli:babel的命令行工具,通过命令行对js代码进行转译

babel-register:通过绑定node.js的require来自动转译require引用的js代码文件

babel的配置

使用形式

如果是以命令行方式使用babel,那么babel的设置就以命令行参数的形式带过去;

还可以在package.json里在babel字段添加设置;

但是建议还是使用一个单独的.babelrc文件,把babel的设置都放置在这里,所有babel
API的options(除了回调函数之外)都能够支持,具体的options见babel的API
options文档

常用options字段说明

  • env:指定在不同环境下使用的配置。比如production和development两个环境使用不同的配置,就可以通过这个字段来配置。env字段的从process.env.BABEL_ENV获取,如果BABEL_ENV不存在,则从process.env.NODE_ENV获取,如果NODE_ENV还是不存在,则取默认值”development”

  • plugins:要加载和使用的插件列表,插件名前的babel-plugin-可省略;plugin列表按从头到尾的顺序运行

  • presets:要加载和使用的preset列表,preset名前的babel-preset-可省略;presets列表的preset按从尾到头的逆序运行(为了兼容用户使用习惯)

  • 同时设置了presets和plugins,那么plugins的先运行;每个preset和plugin都可以再配置自己的option

配置文件的查找

babel会从当前转译的文件所在目录下查找配置文件,如果没有找到,就顺着文档目录树一层层往上查找,一直到.babelrc文件存在或者带babel字段的package.json文件存在为止。

babel的工作原理

babel是一个转译器,感觉相对于编译器compiler,叫转译器transpiler更准确,因为它只是把同种语言的高版本规则翻译成低版本规则,而不像编译器那样,输出的是另一种更低级的语言代码。

但是和编译器类似,babel的转译过程也分为三个阶段:parsing、transforming、generating,以ES6代码转译为ES5代码为例,babel转译的具体过程如下:

ES6代码输入 ==》 babylon进行解析 ==》 得到AST

==》 plugin用babel-traverse对AST树进行遍历转译 ==》 得到新的AST树

==》 用babel-generator通过AST树生成ES5代码

此外,还要注意很重要的一点就是,babel只是转译新标准引入的语法,比如ES6的箭头函数转译成ES5的函数;而新标准引入的新的原生对象,部分原生对象新增的原型方法,新增的API等(如Proxy、Set等),这些babel是不会转译的。需要用户自行引入polyfill来解决

plugins

插件应用于babel的转译过程,尤其是第二个阶段transforming,如果这个阶段不使用任何插件,那么babel会原样输出代码。

我们主要关注transforming阶段使用的插件,因为transform插件会自动使用对应的词法插件,所以parsing阶段的插件不需要配置。

presets

如果要自行配置转译过程中使用的各类插件,那太痛苦了,所以babel官方帮我们做了一些预设的插件集,称之为preset,这样我们只需要使用对应的preset就可以了。以JS标准为例,babel提供了如下的一些preset:

  • es2015

  • es2016

  • es2017

  • env

es20xx的preset只转译该年份批准的标准,而env则代指最新的标准,包括了latest和es20xx各年份

另外,还有 stage-0到stage-4的标准成形之前的各个阶段,这些都是实验版的preset,建议不要使用。

polyfill

polyfill是一个针对ES2015+环境的shim,实现上来说babel-polyfill包只是简单的把core-js和regenerator
runtime包装了下,这两个包才是真正的实现代码所在(后文会详细介绍core-js)。

使用babel-
polyfill会把ES2015+环境整体引入到你的代码环境中,让你的代码可以直接使用新标准所引入的新原生对象,新API等,一般来说单独的应用和页面都可以这样使用。

使用方法

  1. 先安装包: npm install –save babel-polyfill

  2. 要确保在入口处导入polyfill,因为polyfill代码需要在所有其他代码前先被调用

代码方式: import “babel-polyfill”

webpack配置: module.exports = { entry: [“babel-polyfill”, “./app/js”] };

如果只是需要引入部分新原生对象或API,那么可以按需引入,而不必导入全部的环境,具体见下文的core-js

runtime

polyfill和runtime的区别

直接使用babel-
polyfill对于应用或页面等环境在你控制之中的情况来说,并没有什么问题。但是对于在library中使用polyfill,就变得不可行了。因为library是供外部使用的,但外部的环境并不在library的可控范围,而polyfill是会污染原来的全局环境的(因为新的原生对象、API这些都直接由polyfill引入到全局环境)。这样就很容易会发生冲突,所以这个时候
,babel-runtime就可以派上用场了。

transform-runtime和babel-runtime

babel-plugin-transform-runtime插件依赖babel-runtime,babel-runtime是真正提供runtime环境的包
;也就是说transform-runtime插件是把js代码中使用到的新原生对象和静态方法转换成对runtime实现包的引用,举个例子如下:

1
2
3
4
5
6
7
8
9
// 输入的ES6代码`

var sym = Symbol();

`// 通过transform-runtime转换后的ES5+runtime代码 `

var _symbol = require("babel-runtime/core-js/symbol");

var sym = (0, _symbol.default)();

从上面这个例子可见,原本代码中使用的ES6新原生对象Symbol被transform-runtimec插件转换成了babel-
runtime的实现,既保持了Symbol的功能,同时又没有像polyfill那样污染全局环境(因为最终生成的代码中,并没有对Symbol的引用)

另外,这里我们也可以隐约发现,babel-runtime其实也不是真正的实现代码所在,真正的代码实现是在core-js中,后面我们再说

transform-runtime插件的功能

  1. 把代码中的使用到的ES6引入的新原生对象和静态方法用babel-runtime/core-js导出的对象和方法替代

  2. 当使用generators或async函数时,用babel-runtime/regenerator导出的函数取代(类似polyfill分成regenerator和core-js两个部分)

  3. 把Babel生成的辅助函数改为用babel-runtime/helpers导出的函数来替代(babel默认会在每个文件顶部放置所需要的辅助函数,如果文件多的话,这些辅助函数就在每个文件中都重复了,通过引用babel-runtime/helpers就可以统一起来,减少代码体积)

上述三点就是transform-runtime插件所做的事情,由此也可见,babel-runtime就是一个提供了regenerator、core-
js和helpers的运行时库。

建议不要直接使用babel-runtime,因为transform-runtime依赖babel-runtime,大部分情况下都可以用transform-
runtime达成目的。

此外,transform-
runtime在.babelrc里配置的时候,还可以设置helpers、polyfill、regenerator这三个开关,以自行决定runtime是否要引入对应的功能。

最后补充一点:由于runtime不会污染全局空间,所以实例方法是无法工作的(因为这必须在原型链上添加这个方法,这是和polyfill最大的不同) ,比如:

1
2
3
4
5
var arr = ['a', 'b', 'c'];

arr.fill(7); // 实例方法不行

Array.prototype.fill.apply(arr, 7); // 用原型链来调用也是不行

通过core-js实现按需引入polyfill或runtime

core-js包才上述的polyfill、runtime的核心,因为polyfill和runtime其实都只是对core-
js和regenerator的再封装,方便使用而已。

但是polyfill和runtime都是整体引入的,不能做细粒度的调整,如果我们的代码只是用到了小部分ES6而导致需要使用polyfill和runtime的话,会造成代码体积不必要的增大(runtime的影响较小)。所以,按需引入的需求就自然而然产生了
,这个时候就得依靠core-js来实现了。

core-js的组织结构

首先,core-js有三种使用方式:

  • 默认方式:require(‘core-js’)

这种方式包括全部特性,标准的和非标准的

  • 库的形式: var core = require(‘core-js/library’)

这种方式也包括全部特性,只是它不会污染全局名字空间

  • 只是shim: require(‘core-js/shim’)或var shim = require(‘core-js/library/shim’)

这种方式只包括标准特性(就是只有polyfill功能,没有扩展的特性)

core-js的结构是高度模块化的,它把每个特性都组织到一个小模块里,然后再把这些小模块组合成一个大特性,层层组织。比如:

core-js/es6(core-js/library/es6)就包含了全部的ES6特性,而core-js/es6/array(core-
js/library/es6/array)则只包含ES6的Array特性,而core-js/fn/array/from(core-
js/library/fn/array/from)则只有Array.from这个实现。

实现按需使用,就是自己选择使用到的特性,然后导入即可。具体的每个特性和对应的路径可以直接查看[core-
js的github](https://link.jianshu.com/?t=https://github.com/zloirock/core-
js#ecmascript-6)

core-js的按需使用

1、类似polyfill,直接把特性添加到全局环境,这种方式体验最完整

1
2
3
4
5
6
7
8
9
10
11
require('core-js/fn/set');

require('core-js/fn/array/from');

require('core-js/fn/array/find-index');



Array.from(newSet([1, 2, 3, 2, 1])); // => [1, 2, 3]

[1, 2, NaN, 3, 4].findIndex(isNaN); // => 2

2、类似runtime一样,以库的形式来使用特性,这种方式不会污染全局名字空间,但是不能使用实例方法

1
2
3
4
5
6
7
8
9
10
11
12

varSet = require('core-js/library/fn/set');

varfrom = require('core-js/library/fn/array/from');

var findIndex = require('core-js/library/fn/array/find-index');



from(newSet([1, 2, 3, 2, 1])); // => [1, 2, 3]

findIndex([1, 2, NaN, 3, 4], isNaN); // => 2

3、因为第二种库的形式不能使用prototype方法,所以第三种方式使用了一个小技巧,通过::这个符号而不是.来调用实例方式,从而达到曲线救国的目的。这种方式的使用,路径中都会带有/virtual/

1
2
3
4
5
6
7
8
9
10
11
12
13

import {fill, findIndex} from'core-js/library/fn/array/virtual';



Array(10)::fill(0).map((a, b) => b * b)::findIndex(it => it && !(it % 8)); //
=> 4



`// 对比下polyfill的实现 `

`// Array(10).fill(0).map((a, b) => b * b).findIndex(it => it && !(it % 8));`

总结

Babel使用的难点主要在于理解polyfill、runtime和core-js,通过本文,把这三者的概念和关系理清楚了,对babel的使用就不存在问题!


javascript在不断的发展,各种新的标准和提案层出不穷,但是由于浏览器的多样性,导致可能几年之内都无法广泛普及,babel可以让你提前使用这些语言特性,他是一种用途很多的javascript编译器,他把最新版的javascript编译成当下可以执行的版本,简言之,利用babel就可以让我们在当前的项目中随意的使用这些新最新的es6,甚至es7的语法。说白了就是把各种javascript千奇百怪的语言统统专为浏览器可以认识的语言。

新建项目:
npm init

安装babel-cli:
npm i babel-cli --save-dev

新建一个文件index.js

1
2
3
4
5
6

let numbers = [1,2,3];

let dou = numbers.map((number)=>number*2);

console.log(dou);

这是es6最新的语法,map遍历数组并输出

然后用babel来编译这段代码,不编译,直接运行,可能会报错,编译成标准的js语言compiled.js

babel index.js -o compiled.js

项目中自动生成compiled.js

打开compiled.js 文件,发现并没有起作用,相当于复制过来了,其实我们在在用babel的时候是需要配置文件泪完成编译的,

新建配置.babelrc文件

1
2
3
4
5
6
7
8

{

    "plugins":[ ],

    "presets":[ ]

}

下面来一个预设,它可以把es6的代码编译为es5

npm i babel-preset-es2015 --save-dev

安装完后把这插件配置到.babelrc文件

1
2
3
4
5
6
7
{

    "plugins":[ ],

    "presets":["es2015"]

}

再次运行编译

打开compiled.js文件

变了

1
2
3
4
5
6
7
8
9
var numbers = [1, 2, 3];

var dou = numbers.map(function (number) {

  return number * 2;

});

console.log(dou);

接下来在编译一段es7的代码,es7编译为es5

我们需要一个插件来完成

npm i babel-plugin-transform-object-rest-spread --save-dev

然后把这个插件配置到.babelrc文件中去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{

    "plugins":["transform-object-rest-spread"],

    "presets":["es2015"]

}



let  mike = {name:'mike',age:40};

mike={...mike,sex:'男'};

console.log(mike);

然后运行编译命令

打开compiled.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
'use strict';





var _extends = Object.assign || function (target) { for (var i = 1; i <
arguments.length; i++) { var source = arguments[i]; for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] =
source[key]; } } } return target; };





var mike = { name: 'mike', age: 40 };

mike = _extends({}, mike, { sex: '男' });

console.log(mike);

这个插件启示就是添加了一个_extends方法来完成这个功能

总结:

babel的核心概念就是利用一系列的plugin来管理编译案列,通过不同的plugin,他不仅可以编译es6的代码,还可以编译react
JSX语法或者别的语法,甚至可以使用还在提案阶段的es7的一些特性,这就足以看出她的可扩展性。在以后的博客,会介绍他和webpack,react如何共同创建一个完美的开发环境。

Javascript优化总结

Javascript中声明变量速度的研究

声明变量到底要不要var

使用var语句多次声明一个变量不仅是合法的,而且也不会造成任何错误。

如果重复使用的一个声明有一个初始值,那么它担当的不过是一个赋值语句的角色。

如果重复使用的一个声明没有一个初始值,那么它不会对原来存在的变量有任何的影响。

没有var声明的变量,是作为全局变量存在的;有var声明的变量,属于局部变量,尤其是在函数内部。并且,经过测试,带var声明比不带var速度要快,比如:”var
a”比”a”具有更快的执行速度。我们有理由推测,局部变量的声明比全局变量的声明效率更高。

直接量与new

变量有好几种类型,但是同一类型的变量创建的方式可能不一样,比如string、Object、Array等这些类型的变量声明或者创建。

经过测试,通过直接量声明的速度明显快于通过new声明的速度,比如”var a={} “比”var a=new Object()”具有更快的执行速度。

局部变量与全局变量

现在笔者来告诉你,函数内尽量多设局部变量,这样即安全又快速,变量操作也更加合理,不会因为函数内胡乱操作全局变量而导致逻辑错误。

写个例子说明下,先建立一个全局变量:var m=1; ,接着我读取这个全局变量1000000次

全局变量:

1
2
3
for(var i=1000000;i--;)

{a=m;}

局部变量:

1
2
3
4
5
6
7
8
9
(function (){

    var n=m;

    for(var i=1000000;i--;)

    {a=n;}

})();

执行速度2>1

通过上面测试表明,先将m这个全局变量的值存给局部变量n,在将n赋值给a具有更快的速度。

变量名长度

变量名是自己取的,为了照顾语义和规范,变量名可能稍长,但是注意了,变量名的长度也会影响代码的执行速度。

经过测试,长的变量名声明的执行速度没有短的快,比如”var a=1”比”var
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=1”具有更快的执行速度。

所以,我们在声明变量的时候,尽量缩写一些单词,并记忆一些常见的缩写,以下是缩写示例:

单词 缩写
array(数组) arr
string(字符串) str
number(数量/数字) num
function(函数) fun
message(消息/信息) mes
userNumber(会员数量) userNum/uNum
parentElement(父级节点/父级元素) parentEle
returnArray(返回的数组) returnArr

优化JS中的条件分支语句

一、将条件分支,按可能性顺序从高到低排列

说明:可以减少解释器对条件的探测次数。

二、在同一条件子的多(>2)条件分支时,使用switch优于if

说明:switch分支选择的效率高于if,在IE下尤为明显。4分支的测试,IE下switch的执行时间约为if的一半。

三、使用三目运算符替代条件分支

使用前:

1
2
3
4
5
6
7
if (a > b)

{ num = a; }

else

{ num = b; }

使用后:

1
num = a > b ? a : b;

字符串拼接

如果要连接多个字符串,应该少使用+=,如

1
2
3
4
5
s+=a;

s+=b;

s+=c;

应该写成s+=a + b + c;

而如果是收集字符串,比如多次对同一个字符串进行+=操作的话,最好使用一个缓存,使用JavaScript数组来收集,最后使用join方法连接起来

1
2
3
4
5
6
7
8
9
var buf = [];

        for (var i = 0; i < 100; i++) {

            buf.push(i.toString());

        }

        var all = buf.join("");

数字转成字符串

一般最好用”” + 1来将数字转换成字符串,虽然看起来比较丑一点,但事实上这个效率是最高的,性能上来说:

(“” +) > String() > .toString() > new String()


  • for()语句性能优于for(…in…)语句

  • 避免重复创建函数,避免使用闭包。推荐使用prototype追加方法

  • 使用{}创建对象

  • 使用[]创建数组,如果你不知道数组长度,使用Array#push。当你需要复制数组的时候,请使用Array#slice

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×