從前端到全端:JavaScript逆襲之路

背景
html

近年來,前端技術突飛猛進,前端已經不只僅是網頁,更多的開始由狹義向廣義發展。 前後涌現出了具有後端能力的node,具有移動開發能力的react native,具有遊戲渲染能力的cocos2d-js,以及iOS上的熱修復技術JSPatch等等新技術。 咋一看,幾乎各個端都被JavaScript攻陷,大有一統江湖之勢。 究竟,JavaScript如何作到上天入地無所不能?JavaScript真的能一統江湖嗎?前端

領域vue

表明技術html5

web前端node

各種MVVM框架,recat,angular,vue...react

故事要從JavaScript的由來講起。android

亂世出英雄:git

JavaScript的誕生github

背景:web

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其實沒有半毛錢關係。

JavaScript推出後在瀏覽器上大獲成功,微軟在不久後就爲Internet Explorer 3.0瀏覽器推出了JScript,以與處於市場領導地位的Netscape產品同臺競爭。JScript也是一種JavaScript實現,這兩個

JavaScript語言版本在瀏覽器端共存意味着語言標準化的缺失,對這門語言進行標準化被提上了日程,在1997年,由Netscape、SUN、微軟、寶藍等公司組織及我的組成的技術委員會在ECMA(歐洲計算機制造商協會)肯定定義了一種名叫ECMAScript的新腳本語言標準,規範名爲ECMA-262。JavaScript成爲了ECMAScript的實現之一。ECMA-262 第五版,便是ES5。

ECMA-262,包括ES5, ES6等是一個標準,JavaScript是ECMAScript的一個實現。

完整的JavaScript實現應包含三個部分:

名稱

描述

ECMAScript

(語言核心)

便是基本語法,代碼塊,做用域,數據類型等。

在網景導航2.0和IE 3.0出現以後的幾年間,網景和微軟公司不停的發佈新版本的瀏覽器,支持更多的新功能。自此拉開了瀏覽器之戰的序幕。這場瀏覽器之戰到如今還在繼續,如下一張圖看清楚過程。

從瀏覽器之戰能夠看出,各家瀏覽器比拼的大體兩個方面視覺體驗(渲染排版)和速度(腳本運行)。

因此一個完整的瀏覽器組成,至少包含兩個部分:

瀏覽器

組成部分

描述

排版引擎

(內核)

全稱是Layout engine,也稱爲瀏覽器內核(web browser engine)、頁面渲染引擎(rendering engine)或樣版引擎)是一種軟件組件,負責獲取標記式內容(如HTML、XML及圖像文件等等)、整理信息(如CSS及XSL等),並將排版後的內容輸出至顯示器或打印機。全部網頁瀏覽器、電子郵件客戶端以及其它須要根據表示性的標記語言(Presentational markup)來顯示內容的應用程序都須要排版引擎。

JavaScript引擎

JavaScript引擎是一個專門處理JavaScript腳本的虛擬機,通常會附帶在網頁瀏覽器之中。常見的有SpiderMonkey,V8等。

補充一個市面常見瀏覽器的內核和JavaScript引擎搭配:

瀏覽器名稱

排版引擎

(內核)

JavaScript引擎

Google Chrome

使用webkit排版,借鑑Safari和Firefox部分紅果

V8

其餘JavaScript引擎,Rhino,由Mozilla基金會管理,開放源代碼,徹底以Java編寫,能夠看作SpiderMonkey的Java版。 注意:webkit不僅僅只是一個排版引擎,webkit = 排版引擎 + JavaScript引擎。 > 因此,JavaScript是動態語言,它的運行都是基於JavaScript引擎,引擎大都是由靜態語言實現C++、Java、and so on。JavaScript的能力也是由引擎賦予。無論是瀏覽器環境中是window,亦或是node環境中的process,均是由引擎提供。 (番外:Mozilla的人不知道爲啥特別喜歡猴子,常常以猴子命名技術,因此看到帶Monkey的,十有八九估計是他們搞的。)

諾曼底登錄:

JAVASCRIPT BINDING/BRIDGE 橋接技術

在瀏覽器環境中,DOM、BOM、window對象、setTimeout/setInterval,alert,console等方法均不是JavaScript自身具有的能力,而是瀏覽器native實現,而後經過JavaScript引擎注入到JS運行的全局上下文中,供JS使用。 鑑別方式,在調試器console中打出來,帶有[native code]的便是:

講道理:

一、JavaScript運行 → 依賴於JavaScript引擎 ← 瀏覽器集成了JavaScript引擎,同時經過JavaScript引擎注入native代碼工JS腳本使用

二、發散一下思惟,只要有JavaScript引擎,就能運行JS腳本,無論有沒有瀏覽器!只是缺乏瀏覽器提供的alert,window等方法。

三、既然瀏覽器能夠往JavaScript引擎中注入代碼,賦予JS腳本在網頁中特殊的能力,同理咱們能夠本身集成JavaScript引擎,本身定義本身的方法往JavaScript引擎中注入,賦予JS更多更強的自定義能力!

注入的關鍵是:值類型相互對應,Obj映射class的一個實例,function映射一個句柄或者引用

JavaScript

C++

Java

number

int/long/float/double

int/long/float/double

JAVASCRIPT數值型中的坑

JavaScript內部,全部數字都是以64位浮點數形式儲存,即便整數也是如此。因此,1與1.0是相同的,是同一個數。

這就是說,在JavaScript語言的底層,根本沒有整數,全部數字都是小數(64位浮點數)。容易形成混淆的是,某些運算只有整數才能完成,此時JavaScript會自動把64位浮點數,轉成32位整數,而後再進行運算。因爲浮點數不是精確的值,因此涉及小數的比較和運算要特別當心。儘可能避免使用JavaScript作精準計算和密集計算。

根據國際標準IEEE 754,JavaScript浮點數的64個二進制位,從最左邊開始,是這樣組成的。

*第1位:符號位,0表示正數,1表示負數

*第2位到第12位:儲存指數部分

*第13位到第64位:儲存小數部分(即有效數字) 符號位決定了一個數的正負,指數部分決定了數值的大小,小數部分決定了數值的精度。 IEEE 754規定,有效數字第一位默認老是1,不保存在64位浮點數之中。也就是說,有效數字老是1.xx...xx的形式,其中xx..xx的部分保存在64位浮點數之中,最長可能爲52位。所以,JavaScript提供的有效數字最長爲53個二進制位(64位浮點的後52位+有效數字第一位的1)。

內部表現公式:(-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,可是nodeJS的運行依賴於Google的V8 引擎,V8是C++實現,底層使用C++實現底層功能,好比網絡,數據庫IO,對外暴露一個構造器接口注入到上下文中,注意此處暴露的只是一個構造器接口而不是一個建立完的實例。而後實現了一個require的hook函數。當使用require加載一個JS模塊時,跟網頁中使用AMD 的require並沒有異樣,當使用require加載系統庫,既是C++的模塊時,會調用暴露出來的構造器接口,獲得一個實例對象。無論是裝載JS模塊仍是裝載C++模塊,獲得的均可以看作是一個Module Object,node會將裝載完的模塊緩存到binding_cache中,下次在別處的代碼中使用require裝載模塊時,就會先去binding_cache中查找,若是找到了則返回該module object,若是沒找到再執行上面的裝載流程。 這就是node的基本原理:C++封裝底層操做,經過V8注入,使得JS腳本有網絡和IO能力。

基於Spring的橋接

以上說到的幾個都是C++層面的應用,那麼經典的Java怎麼玩?是否是Java就必須是靜態語言的玩法,沒有辦法像C++之類的,可使用JS的動態特性? 固然不是。這個時候,咱們須要提及前面介紹過的一個JS引擎 Rhino,Rhino是徹底由Java編寫,可想而知,Rhino幾乎就是爲Java應用而生的。 用法是這樣:

一、首先在咱們的Java應用中集成Rhino;

二、全部的IO操做,網絡操做等,都封裝成service,並提供增刪改查,setter && getter等多種方法

三、經過spring,把這些service bean注入到Rhino中;

四、把業務邏輯寫到JS代碼中,JS代碼調用多個已注入的Java service處理業務邏輯,拼裝數據返回!

好處:修改業務邏輯不須要修改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

categorynode.get.js

categorynode.get.html.ftl

categorynode.get.json.ftl

移動端集成思路&實踐:

React Native中的橋接

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等。

Cocos2d-JS中的橋接

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技術中的橋接

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熱更新,熱修復:

一、集成JavaScriptCore引擎;

二、經過引擎,橋接JS和OC;

三、經過JS修改OC反射。

詳細的JSPatch技術介紹請移步:https://github.com/bang590/JSPatch/wiki

關於JavaScript引擎:

在iOS 或 android 上可以運行的JavaScript 引擎有4個:JavaScriptCore,SpiderMonkey,V8,Rhino。下面這個表格展現各個引擎在iOS 和 Android 的兼容性。

JavaScript引擎

iOS

Android

JavaScriptCore

Interpreter only(僅解釋器模式)

Interpreter and JIT(解釋器模式和即時編譯模式)

由於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從立足於前端,到征戰全端的逆襲之路,能夠總結爲「攜引擎以令天下」。 不足之處,還請各位看官輕拍~

閱讀原文

相關文章
相關標籤/搜索