JavaScript如何作到上天入地無所不能?JavaScript真的能一統江湖嗎?javascript
近年來,前端技術突飛猛進,前端已經不只僅是網頁,更多的開始由狹義向廣義發展。 前後涌現出了具有後端能力的node,具有移動開發能力的react native,具有遊戲渲染能力的cocos2d-js,以及iOS上的熱修復技術JSPatch等等新技術。 咋一看,幾乎各個端都被JavaScript攻陷,大有一統江湖之勢。 究竟,JavaScript如何作到上天入地無所不能?JavaScript真的能一統江湖嗎?html
故事要從JavaScript的由來講起。前端
高能瞎扯淡版,正經臉的同窗能夠忽略 有人的地方就有江湖,有江湖的地方就有紛爭。 故事要從當年的瀏覽器之戰提及。 時間回到1994年, (→ 那時候我仍是個寶寶~ #天真臉#) 景兄弟橫空出世,並自帶神器網景導航,戰鬥力爆表,勢如劈竹,瞬時間威震天下。 一出世就武裝到牙齒,武力值這麼高還自帶兵器,這個科學嗎? 港真,我也以爲不科學,也許跟熊孩子哪吒、女漢子雅典娜是一個品種吧? 這一切北方的老前輩微軟大溼,都看在眼裏,不甘天下盡歸景兄弟這個初出茅廬的毛孩子,大溼積澱多年,潛心修煉一年,終於帶着大殺器IE 1.0出關了,誓於景兄弟爭個高低。 自此景兄弟的網景導航 VS 微軟大溼的IE 的軍備戰爭開始。 景兄弟仔細掂量,微軟大溼財大氣粗,內功深厚,臣妾實在是辦不到啊啊啊啊啊啊。 景兄弟緊急召集門人商議對策,有一門人曰:」以咱們微薄之力硬磕,是萬萬使不得的。現在咱們,一是宜施行合縱之策,抱大腿,組成聯盟!二是避其鋒芒,出奇招致勝。「 因而景兄弟依照此策略,一方面找到了當時德高爲重的另外一位前輩SUN,組成了開發者聯盟。 (微軟大溼:握草,聯盟都粗來了,那我是否是得搞個部落?) 另外一方面,景兄弟找到了鍛造大師布蘭登,請布大師幫忙升級兵器網景導航,大師就是大師,不費吹灰之力就完成了強化升級,然而布大師突發奇想,原本這是近距離攻擊兵器,要是有多一個遠距離攻擊的能力那豈不是更好?Just do it. 想罷大師就加了一個遠距離攻擊的feature。因而有了自帶遠距離攻擊能力的網景導航2.0。景兄弟一看這麼流弊內心甚是歡喜,不過遠距離攻擊的技能叫作LiveScript,感受不是特別Fashion。特然想到這不是跟SUN前輩聯盟嘛,SUN家的Java正是獨霸武林之時。不如把名字改爲跟Java有關,蹭一把東風,蹭點光環。一拍腦殼,JavaScript!!!衆門人一聽:」好好好,JavaScript 流弊炫酷吊炸天!「 果真第一節下半場,景兄弟攜強化過的網景導航2.0 戰個痛快,那是槓槓的!人家一問,你咋還能遠程攻擊,你這個遠程攻擊用的是啥?答曰:JavaScript。「JavaScript,必定是跟SUN家Java是一個系列產品,必定很流弊!」#光環加成,各類膜拜臉# 微軟大溼虧了一場,痛定思痛,也要搞遠程攻擊功能,果真不久,就祭出了一樣帶有遠程攻擊能力的IE 3.0,鑑於景兄弟的遠程攻擊叫作JavaScript,J開頭的感受應該比較流弊,因此微軟大溼的叫作JScript。 而後戰爭就從地面貼身肉搏戰,開始逐步升級到了遠距離核戰爭。 正所謂,城門失火,殃及池魚。這麼打下去苦逼的是搬磚的頁面仔,就是我這種,處處都是雷區,無處下腳。 最後到了1997年,「聯合國安理會祕書長」艾瑪(ECMA)出來調停,多方簽署了「核不擴散條約」,約束各類遠程攻擊武器的使用,這才走上了正軌。
1995年SUN開發了Java技術,這是第一個通用軟件平臺。Java擁有跨平臺、面向對象、泛型編程的特性,普遍應用於企業級Web應用開發和移動應用開發。Java也伴隨着互聯網的迅猛發展而發展,逐漸成爲重要的網絡編程語言。名聞遐邇。
1994年Netscape公司成立,並推出了本身的瀏覽器的免費版本 Netscape Navigator,很快就佔有了瀏覽器市場。到了 1995 年,微軟公司開始加入,並很快發佈了本身的 Internet Explorer 1.0。
1995年,當時在Netscape就任的Brendan Eich(布蘭登·艾克),正爲Netscape Navigator 2.0瀏覽器開發的一門名爲LiveScript的腳本語言,後來Netscape與Sun Microsystems組成的開發聯盟,爲了讓這門語言搭上Java這個編程語言「熱詞」,將其臨時更名爲「JavaScript」,往後這成爲大衆對這門語言有諸多誤解的緣由之一。
JavaScript最初受Java啓發而開始設計的,目的之一就是「看上去像Java」,所以語法上有相似之處,一些名稱和命名規範也借自Java。但JavaScript的主要設計原則源自Self和Scheme。JavaScript與Java名稱上的近似,是當時Netscape爲了營銷考慮與SUN達成協議的結果。
因此,JavaScript和Java其實沒有半毛錢關係。html5
JavaScript推出後在瀏覽器上大獲成功,微軟在不久後就爲Internet Explorer 3.0瀏覽器推出了JScript,以與處於市場領導地位的Netscape產品同臺競爭。JScript也是一種JavaScript實現,這兩個
JavaScript語言版本在瀏覽器端共存意味着語言標準化的缺失,對這門語言進行標準化被提上了日程,在1997年,由Netscape、SUN、微軟、寶藍等公司組織及我的組成的技術委員會在ECMA(歐洲計算機制造商協會)肯定定義了一種名叫ECMAScript的新腳本語言標準,規範名爲ECMA-262。JavaScript成爲了ECMAScript的實現之一。ECMA-262 第五版,便是ES5。java
> ECMA-262,包括ES5, ES6等是一個標準,JavaScript是ECMAScript的一個實現。node
完整的JavaScript實現應該包含三個部分:react
在網景導航2.0和IE 3.0出現以後的幾年間,網景和微軟公司不停的發佈新版本的瀏覽器,支持更多的新功能。自此拉開了瀏覽器之戰的序幕。這場瀏覽器之戰到如今還在繼續,如下一張圖看清楚過程。android
從瀏覽器之戰能夠看出,各家瀏覽器比拼的大體兩個方面視覺體驗(渲染排版)和速度(腳本運行)。ios
> 因此一個完整的瀏覽器組成,至少包含兩個部分:git
補充一個市面常見瀏覽器的內核和JavaScript引擎搭配:
其餘JavaScript引擎,Rhino,由Mozilla基金會管理,開放源代碼,徹底以Java編寫,能夠看作SpiderMonkey的Java版。 注意:webkit不僅僅只是一個排版引擎,webkit = 排版引擎 + JavaScript引擎。 > 因此,JavaScript是動態語言,它的運行都是基於JavaScript引擎,引擎大都是由靜態語言實現C++、Java、and so on。JavaScript的能力也是由引擎賦予。無論是瀏覽器環境中是window,亦或是node環境中的process,均是由引擎提供。 (番外:Mozilla的人不知道爲啥特別喜歡猴子,常常以猴子命名技術,因此看到帶Monkey的,十有八九估計是他們搞的。)
在瀏覽器環境中,DOM、BOM、window對象、setTimeout/setInterval,alert,console等方法均不是JavaScript自身具有的能力,而是瀏覽器native實現,而後經過JavaScript引擎注入到JS運行的全局上下文中,供JS使用。 鑑別方式,在調試器console中打出來,帶有[native code]的便是:
講道理:
JavaScript內部,全部數字都是以64位浮點數形式儲存,即便整數也是如此。因此,1與1.0是相同的,是同一個數。
這就是說,在JavaScript語言的底層,根本沒有整數,全部數字都是小數(64位浮點數)。容易形成混淆的是,某些運算只有整數才能完成,此時JavaScript會自動把64位浮點數,轉成32位整數,而後再進行運算。因爲浮點數不是精確的值,因此涉及小數的比較和運算要特別當心。儘可能避免使用JavaScript作精準計算和密集計算。
根據國際標準IEEE 754,JavaScript浮點數的64個二進制位,從最左邊開始,是這樣組成的。
內部表現公式:(-1)^符號位 * 1.xx...xx * 2^指數位
精度最多隻能到53個二進制位,這意味着,絕對值小於2的53次方的整數,即-(253-1)到253-1,均可以精確表示。 而大部分的後端語言,C++、Java、Python等的long型都是能夠支持到64位,所以long型數據從後端語言傳給JavaScript會發生低位截斷。遇到這種狀況通常使用String處理,如須要在JavaScript中作long型計算,須要自行實現計算器。
有了自行往JavaScript引擎中注入的想法,接下來就是分析可行性。 大部分是JavaScript引擎是使用C++編寫,若是本身的程序使用的是C++能夠很方便的進行注入,若是是OC,可使用OC和C++混編的形式。 其餘語言怎麼破? 要在一門靜態語言上與動態語言JavaScript相互調用,最便捷的方式是找到一個這門語言實現的JavaScript引擎(開源),直接進行集成,注入。若是沒有,則須要使用多一層橋接,把這門語言的接口暴露給C++,再由C++實現的JavaScript引擎將接口注入供JavaScript使用。
服務端集成思路&實踐:
咱們都知道nodeJS,可是nodeJS的運行依賴於Google的V8 引擎,V8是C++實現,底層使用C++實現底層功能,好比網絡,數據庫IO,對外暴露一個構造器接口注入到上下文中,注意此處暴露的只是一個構造器接口而不是一個建立完的實例。而後實現了一個require的hook函數。當使用require加載一個JS模塊時,跟網頁中使用AMD 的require並沒有異樣,當使用require加載系統庫,既是C++的模塊時,會調用暴露出來的構造器接口,獲得一個實例對象。無論是裝載JS模塊仍是裝載C++模塊,獲得的均可以看作是一個Module Object,node會將裝載完的模塊緩存到bindingcache中,下次在別處的代碼中使用require裝載模塊時,就會先去bindingcache中查找,若是找到了則返回該module object,若是沒找到再執行上面的裝載流程。 這就是node的基本原理:C++封裝底層操做,經過V8注入,使得JS腳本有網絡和IO能力
以上說到的幾個都是C++層面的應用,那麼經典的Java怎麼玩?是否是Java就必須是靜態語言的玩法,沒有辦法像C++之類的,可使用JS的動態特性? 固然不是。這個時候,咱們須要提及前面介紹過的一個JS引擎 Rhino,Rhino是徹底由Java編寫,可想而知,Rhino幾乎就是爲Java應用而生的。 用法是這樣:
好處:修改業務邏輯不須要修改Java代碼,也就是不須要從新編譯和部署,只須要刷新下跑在Rhino中的JS代碼便可。以往Java應用的一個痛點是部署,須要從新編譯,打包,部署重啓服務器,如今以這種形式開發,能夠達到服務端的熱更新和熱部署。既能夠享有Java服務的穩定性和可靠性,又能夠享有JS的靈活性。 這種技術和用法在差很少十年前就有過,前EMC的工程師基於EMC著名的商業產品Documentum,設計了一套Java開源的中小企業CMS系統Alfresco,在該系統中實現了這種技術,這種技術基於spring,叫作spring-surf,作了一個膠水層。能夠看作小十年前的node吧。
Demo,使用spring-surf框架的系統中一個webscript模塊
categorynode.get.xml定義URL攔截器和權限控制;
.get指明是處理GET請求,RESTful;
在categorynode.get.js中調用已注入的Java Bean處理業務邏輯;
若爲網頁請求返回.html.ftl,若爲Ajax,返回.json.ftl;
(此處配套使用的是FreeMarker模板引擎)
> categorynode.get.desc.xml
<webscript> <shortname>category node</shortname> <description>Document List Component - category node data webscript</description> <url>/slingshot/doclib/categorynode/node/{store_type}/{store_id}/{id}/{path}</url> <url>/slingshot/doclib/categorynode/node/{store_type}/{store_id}/{id}</url> <format default="json">argument</format> <authentication>user</authentication> <transaction allow="readonly">required</transaction> <lifecycle>internal</lifecycle> </webscript>
> categorynode.get.js
model.categorynode = getCategoryNode(); function getCategoryNode(){ try{ var items = new Array(), hasSubfolders = true, evalChildFolders = args["children"] !== "false"; var catAspect = (args["aspect"] != null) ? args["aspect"] : "cm:generalclassifiable", nodeRef = url.templateArgs.store_type + "://" + url.templateArgs.store_id + "/" + url.templateArgs.id, path = url.templateArgs.path, rootCategories = classification.getRootCategories(catAspect), rootNode, parent, categoryResults; if (rootCategories != null && rootCategories.length > 0) { rootNode = rootCategories[0].parent; if (path == null) { categoryResults = classification.getRootCategories(catAspect); } else {//...} for each (item in categoryResults) { if (evalChildFolders) { hasSubfolders = item.children.length > 0; } items.push( { node: item, hasSubfolders: hasSubfolders, aspect: catAspect }); } } return ({ items: items }); } catch(e) { return; } }
> categorynode.get.html.ftl
<#include "categorynode.get.json.ftl">
==> categorynode.get.json.ftl
<#escape x as jsonUtils.encodeJSONString(x)> { "totalResults": ${categorynode.items?size?c}, "items": [ <#list categorynode.items as item> <#assign c = item.node> { "nodeRef": "${c.nodeRef}", "name": "${c.name}", "index": "${c.properties["lem:index"]!"0"}", "description": "${(c.properties.description!"")}", "hasChildren": ${item.hasSubfolders?string}, "aspect": "${item.aspect!""}", "userAccess": { "create": ${c.hasPermission("CreateChildren")?string}, "edit": ${c.hasPermission("Write")?string}, "delete": ${c.hasPermission("Delete")?string} } }<#if item_has_next>,</#if> </#list> ] } </#escape>
移動端集成思路&實踐:
React Native目前也是異常火爆,RN程序的運行依賴於Facebook的RN框架。在iOS、Android的模擬器或是真機上,React Native使用的是JavaScriptCore引擎,也就是Safari所使用的JavaScript引擎。可是在iOS上JavaScriptCore並無使用即時編譯技術(JIT),由於在iOS中應用無權擁有可寫可執行的內存頁(於是沒法動態生成代碼),在安卓上,理論上是可使用的。JavaScriptCore引擎也是使用C++編寫,在iOS和安卓中,JavaScriptCore都作了一層封裝,能夠無須關心引擎和系統橋接的那一層。iOS/Android系統經過JavaScriptCore引擎將定製好的各類原生組件注入,如:listview,text等。
cocos2dx是遊戲開發中很是經常使用的遊戲渲染引擎,有一系列的產品,如:cocos2dx(C++),cocos2d-lua(lua), cocos2d-js(JavaScript)等多個產品。其中最新退出的是cocos2dx的JS版本的cocos2d-js,編寫遊戲渲染特效代碼相比於C++和lua很是方便。對於作須要常常更新的渲染場景,C++是靜態語言,每次修改都須要從新編譯才能運行,顯然是不合適的。天然也就想到了腳本語言,lua和js,二者有些相似,都是動態語言,只須要集成一個運行引擎,提供一個運行的容器便可運行,同時經過引擎注入底層方法供腳本調用便可。lua好處是精簡,語法精簡,引擎頁很小很精簡,因此不可避免的代碼量會比js多,同時學習成本比較高。js的好處是有ECMAScrtpt的核心,語法比較豐富,同時有支持一些高級屬性。在cocos2d-js中,cocos2dx(C++)集成了SpiderMonkey(C++)做爲JS運行引擎,中間作了一個膠水層既是JS Binding,經過引擎注入了一個cc的全局對象,映射的是底層C++的一個單例C++實例。表面上寫的是JS代碼,實際上操做的是底層的C++。cocos2d-js是代碼能夠運行在多種環境中,當運行的網頁環境中時,使用的是cocos2d-html5引擎,底層操做的是canvas;當運行在客戶端上時,使用的是cocos2dx引擎,底層操做的是C++,再由C++去操控openGL作繪製和渲染。提供相同的API,對開發者幾乎是透明無差別的,開發者只須要關注實現效果便可。達到一套代碼,多端運行(網頁端,客戶端)。
JSPatch是目前比較流行的iOS上的熱修復技術,JSPatch 能作到經過 JS 調用和改寫 OC 方法最根本的緣由是 Objective-C 是動態語言,OC 上全部方法的調用/類的生成都經過 Objective-C Runtime 在運行時進行,咱們能夠經過類名/方法名反射獲得相應的類和方法。JSPatch 的基本原理就是:JS 傳遞字符串給 OC,OC 經過 Runtime 接口調用和替換 OC 方法。
關鍵技術之一是 JS 和 OC 之間的消息互傳。JSPatch裏包含了,一個JS引擎JavaScriptCore(Safari,React Native用的同款)。用到了 JavaScriptCore 的接口,OC 端在啓動 JSPatch 引擎時會建立一個 JSContext 實例,JSContext 是 JS 代碼的執行環境,能夠給 JSContext 添加方法,JS 就能夠直接調用這個方法。本質上就是經過JavaScriptCore引擎注入,暴露OC的方法供JS調用來實現動態修改OC的反射。
Demo,iOS熱更新,熱修復:
require('UIView'); //1,在全局查找 UIView變量,查到則返回, // 查不到則生成 UIView = { __clsName: "UIView" } //2,將類名經過橋接層,傳給OC //3,OC經過runtime方法找出這個類全部方法,返回給橋接層 //4,橋接層爲JS生成對應的JS 屬性/方法 var demoView = UIView.alloc().init(); demoView.setBackgroundColor(require('UIColor').grayColor()); demoView.setAlpha(0.2)
詳細的JSPatch技術介紹請移步:https://github.com/bang590/JSPatch/wiki
關於JavaScript引擎:
在iOS 或 android 上可以運行的JavaScript 引擎有4個:JavaScriptCore,SpiderMonkey,V8,Rhino。下面這個表格展現各個引擎在iOS 和 Android 的兼容性。
由於iOS平臺不支持JIT即時編譯,而V8只有JIT模式,因此V8沒法在iOS平臺使用(越獄設備除外,想體驗iOS JIT的同窗能夠自行越獄)。 因此,目前能夠作到橫跨iOS和Android雙平臺的JS引擎,只有兩款,便是SpiderMonkey和JavaScriptCore。 JavaScript引擎會受不少東西影響,好比交叉編譯器的版本、引擎的版本和操做系統的種類等。
至於如何選擇,能夠參考:《Part I: How to Choose a JavaScript Engine for iOS and Android Development》
至此,JavaScript從立足於前端,到征戰全端的逆襲之路,能夠總結爲「攜引擎以令天下」。 不足之處,還請各位看官輕拍~
bang590/JSPatch中問參考文檔
Cocos2d-JS | Cocos2d-x官方參考文檔
Alfresco官方參考文檔
《Browser Wars: The End or Just the Beginning?》
《Part I: How to Choose a JavaScript Engine for iOS and Android Development》
《React Native 從入門到源碼》