JavaScriptCore全面解析 (上篇)

来源:http://www.cnblogs.com/liuliliuli2017/archive/2017/04/28/6782764.html
-Advertisement-
Play Games

收錄待用,修改轉載已取得 "騰訊雲" 授權 作者 | 殷源 編輯 | 迷鹿 殷源,專註移動客戶端開發,微軟Imagine Cup中國區特等獎獲得者,現就職於騰訊。 JavaScript越來越多地出現在我們客戶端開發的視野中,從ReactNative到JSpatch,JavaScript與客戶端相結合 ...


收錄待用,修改轉載已取得騰訊雲授權


作者 | 殷源
編輯 | 迷鹿

殷源,專註移動客戶端開發,微軟Imagine Cup中國區特等獎獲得者,現就職於騰訊。

JavaScript越來越多地出現在我們客戶端開發的視野中,從ReactNative到JSpatch,JavaScript與客戶端相結合的技術開始變得魅力無窮。本文主要講解iOS中的JavaScriptCore框架,正是它為iOS提供了執行JavaScript代碼的能力。未來的技術日新月異,JavaScript與iOS正在碰撞出新的激情。

JavaScriptCore是JavaScript的虛擬機,為JavaScript的執行提供底層資源。

一、JavaScript

在討論JavaScriptCore之前,我們首先必須對JavaScript有所瞭解。

1. JavaScript幹啥的?

  • 說的高大上一點:一門基於原型、函數先行的高級編程語言,通過解釋執行,是動態類型的直譯語言。是一門多範式的語言,它支持面向對象編程,命令式編程,以及函數式編程。

  • 說的通俗一點:主要用於網頁,為其提供動態交互的能力。可嵌入動態文本於HTML頁面,對瀏覽器事件作出響應,讀寫HTML元素,控制cookies等。

  • 再通俗一點:搶月餅,button.click()。(PS:請謹慎使用while迴圈)

2. JavaScript起源與歷史

  • 1990年底,歐洲核能研究組織(CERN)科學家Tim Berners-Lee,在互聯網的基礎上,發明瞭萬維網(World Wide Web),從此可以在網上瀏覽網頁文件。

  • 1994年12月,Netscape 發佈了一款面向普通用戶的新一代的瀏覽器Navigator 1.0版,市場份額一舉超過90%。

  • 1995年,Netscape公司雇佣了程式員Brendan Eich開發這種嵌入網頁的腳本語言。最初名字叫做Mocha,1995年9月改為LiveScript。

  • 1995年12月,Netscape公司與Sun公司達成協議,後者允許將這種語言叫做JavaScript。

3. JavaScript與ECMAScript

  • “JavaScript”是Sun公司的註冊商標,用來特製網景(現在的Mozilla)對於這門語言的實現。網景將這門語言作為標準提交給了ECMA——歐洲電腦製造協會。由於商標上的衝突,這門語言的標準版本改了一個醜陋的名字“ECMAScript”。同樣由於商標的衝突,微軟對這門語言的實現版本取了一個廣為人知的名字“Jscript”。

  • ECMAScript作為JavaScript的標準,一般認為後者是前者的實現。

4. Java和JavaScript

《雷鋒和雷峰塔》

Java 和 JavaScript 是兩門不同的編程語言
一般認為,當時 Netscape 之所以將 LiveScript 命名為 JavaScript,是因為 Java 是當時最流行的編程語言,帶有 “Java” 的名字有助於這門新生語言的傳播。

二、 JavaScriptCore

1. 瀏覽器演進

  • 演進完整圖

https://upload.wikimedia.org/wikipedia/commons/7/74/Timeline_of_web_browsers.svg

  • WebKit分支

現在使用WebKit的主要兩個瀏覽器Sfari和Chromium(Chorme的開源項目)。WebKit起源於KDE的開源項目Konqueror的分支,由蘋果公司用於Sfari瀏覽器。其一條分支發展成為Chorme的內核,2013年Google在此基礎上開發了新的Blink內核。

2. WebKit排版引擎

webkit是sfari、chrome等瀏覽器的排版引擎,各部分架構圖如下

  • webkit Embedding API是browser UI與webpage進行交互的api介面;

  • platformAPI提供與底層驅動的交互, 如網路, 字體渲染, 影音文件解碼, 渲染引擎等;

  • WebCore它實現了對文檔的模型化,包括了CSS, DOM, Render等的實現;

  • JSCore是專門處理JavaScript腳本的引擎;

3. JavaScript引擎

  • JavaScript引擎是專門處理JavaScript腳本的虛擬機,一般會附帶在網頁瀏覽器之中。第一個JavaScript引擎由布蘭登·艾克在網景公司開發,用於Netscape Navigator網頁瀏覽器中。JavaScriptCore就是一個JavaScript引擎。

  • 下圖是當前主要的還在開發中的JavaScript引擎

4. JavaScriptCore組成

JavaScriptCore主要由以下模塊組成:

  • Lexer 詞法分析器,將腳本源碼分解成一系列的Token

  • Parser 語法分析器,處理Token並生成相應的語法樹

  • LLInt 低級解釋器,執行Parser生成的二進位代碼

  • Baseline JIT 基線JIT(just in time 實施編譯)

  • DFG 低延遲優化的JIT

  • FTL 高通量優化的JIT

關於更多JavaScriptCore的實現細節,參考 https://trac.webkit.org/wiki/JavaScriptCore

5. JavaScriptCore

JavaScriptCore是一個C++實現的開源項目。使用Apple提供的JavaScriptCore框架,你可以在Objective-C或者基於C的程式中執行Javascript代碼,也可以向JavaScript環境中插入一些自定義的對象。JavaScriptCore從iOS 7.0之後可以直接使用。

在JavaScriptCore.h中,我們可以看到這個


#ifndef JavaScriptCore_h
#define JavaScriptCore_h

#include <JavaScriptCore/JavaScript.h>
#include <JavaScriptCore/JSStringRefCF.h>

#if defined(__OBJC__) && JSC_OBJC_API_ENABLED

#import "JSContext.h"
#import "JSValue.h"
#import "JSManagedValue.h"
#import "JSVirtualMachine.h"
#import "JSExport.h"

#endif

#endif /* JavaScriptCore_h */

這裡已經很清晰地列出了JavaScriptCore的主要幾個類:

  • JSContext

  • JSValue

  • JSManagedValue

  • JSVirtualMachine

  • JSExport

接下來我們會依次講解這幾個類的用法。

6. Hello World!

這段代碼展示瞭如何在Objective-C中執行一段JavaScript代碼,並且獲取返回值並轉換成OC數據列印

//創建虛擬機
JSVirtualMachine *vm = [[JSVirtualMachine alloc] init];

//創建上下文
JSContext *context = [[JSContext alloc] initWithVirtualMachine:vm];

//執行JavaScript代碼並獲取返回值
JSValue *value = [context evaluateScript:@"1+2*3"];

//轉換成OC數據並列印
NSLog(@"value = %d", [value toInt32]);

Output

value = 7

三、 JSVirtualMachine

一個JSVirtualMachine的實例就是一個完整獨立的JavaScript的執行環境,為JavaScript的執行提供底層資源。

這個類主要用來做兩件事情:

  1. 實現併發的JavaScript執行

  2. JavaScript和Objective-C橋接對象的記憶體管理

看下頭文件SVirtualMachine.h里有什麼:

NS_CLASS_AVAILABLE(10_9, 7_0)
@interface JSVirtualMachine : NSObject

/* 創建一個新的完全獨立的虛擬機 */
(instancetype)init;

/* 對橋接對象進行記憶體管理 */
- (void)addManagedReference:(id)object withOwner:(id)owner;

/* 取消對橋接對象的記憶體管理 */
- (void)removeManagedReference:(id)object withOwner:(id)owner;

@end

每一個JavaScript上下文(JSContext對象)都歸屬於一個虛擬機(JSVirtualMachine)。每個虛擬機可以包含多個不同的上下文,並允許在這些不同的上下文之間傳值(JSValue對象)。

然而,每個虛擬機都是完整且獨立的,有其獨立的堆空間和垃圾回收器(garbage collector ),GC無法處理別的虛擬機堆中的對象,因此你不能把一個虛擬機中創建的值傳給另一個虛擬機。

線程和JavaScript的併發執行

JavaScriptCore API都是線程安全的。你可以在任意線程創建JSValue或者執行JS代碼,然而,所有其他想要使用該虛擬機的線程都要等待。

  • 如果想併發執行JS,需要使用多個不同的虛擬機來實現。

  • 可以在子線程中執行JS代碼。

通過下麵這個demo來理解一下這個併發機制

JSContext *context = [[CustomJSContext alloc] init];
JSContext *context1 = [[CustomJSContext alloc] init];
JSContext *context2 = [[CustomJSContext alloc] initWithVirtualMachine:[context virtualMachine]];
NSLog(@"start");
dispatch_async(queue, ^{
    while (true) {
        sleep(1);
        [context evaluateScript:@"log('tick')"];
    }
});
dispatch_async(queue1, ^{
    while (true) {
        sleep(1);
        [context1 evaluateScript:@"log('tick_1')"];
    }
});
dispatch_async(queue2, ^{
    while (true) {
        sleep(1);
        [context2 evaluateScript:@"log('tick_2')"];
    }
});
[context evaluateScript:@"sleep(5)"];
NSLog(@"end");

context和context2屬於同一個虛擬機。

context1屬於另一個虛擬機。

三個線程分別非同步執行每秒1次的js log,首先會休眠1秒。

在context上執行一個休眠5秒的JS函數。

首先執行的應該是休眠5秒的JS函數,在此期間,context所處的虛擬機上的其他調用都會處於等待狀態,因此tick和tick_2在前5秒都不會有執行。

而context1所處的虛擬機仍然可以正常執行tick_1

休眠5秒結束後,tick和tick_2才會開始執行(不保證先後順序)。

實際運行輸出的log是:

start
tick_1
tick_1
tick_1
tick_1
end
tick
tick_2

四、 JSContext

一個JSContext對象代表一個JavaScript執行環境。在native代碼中,使用JSContext去執行JS代碼,訪問JS中定義或者計算的值,並使JavaScript可以訪問native的對象、方法、函數。

1. JSContext執行JS代碼

  • 調用evaluateScript函數可以執行一段top-level 的JS代碼,並可向global對象添加函數和對象定義

  • 其返回值是JavaScript代碼中最後一個生成的值

API Reference

NS_CLASS_AVAILABLE(10_9, 7_0)
@interface JSContext : NSObject

/* 創建一個JSContext,同時會創建一個新的JSVirtualMachine */
(instancetype)init;

/* 在指定虛擬機上創建一個JSContext */
(instancetype)initWithVirtualMachine:
        (JSVirtualMachine*)virtualMachine;

/* 執行一段JS代碼,返回最後生成的一個值 */
(JSValue *)evaluateScript:(NSString *)script;

/* 執行一段JS代碼,並將sourceURL認作其源碼URL(僅作標記用) */
- (JSValue *)evaluateScript:(NSString *)script withSourceURL:(NSURL*)sourceURL     NS_AVAILABLE(10_10, 8_0);

/* 獲取當前執行的JavaScript代碼的context */
+ (JSContext *)currentContext;

/* 獲取當前執行的JavaScript function*/
+ (JSValue *)currentCallee NS_AVAILABLE(10_10, 8_0);

/* 獲取當前執行的JavaScript代碼的this */
+ (JSValue *)currentThis;

/* Returns the arguments to the current native callback from JavaScript code.*/
+ (NSArray *)currentArguments;

/* 獲取當前context的全局對象。WebKit中的context返回的便是WindowProxy對象*/
@property (readonly, strong) JSValue *globalObject;

@property (strong) JSValue *exception;
@property (copy) void(^exceptionHandler)(JSContext *context, JSValue
    *exception);

@property (readonly, strong) JSVirtualMachine *virtualMachine;

@property (copy) NSString *name NS_AVAILABLE(10_10, 8_0);

@end

2. JSContext訪問JS對象

一個JSContext對象對應了一個全局對象(global object)。例如web瀏覽器中中的JSContext,其全局對象就是window對象。在其他環境中,全局對象也承擔了類似的角色,用來區分不同的JavaScript context的作用域。全局變數是全局對象的屬性,可以通過JSValue對象或者context下標的方式來訪問。

一言不合上代碼:

JSValue *value = [context evaluateScript:@"var a = 1+2*3;"];

NSLog(@"a = %@", [context objectForKeyedSubscript:@"a"]);
NSLog(@"a = %@", [context.globalObject objectForKeyedSubscript:@"a"]);
NSLog(@"a = %@", context[@"a"]);

/
Output:

a = 7
a = 7
a = 7

這裡列出了三種訪問JavaScript對象的方法

  • 通過context的實例方法objectForKeyedSubscript

  • 通過context.globalObject的objectForKeyedSubscript實例方法

  • 通過下標方式

設置屬性也是對應的。

API Reference

/* 為JSContext提供下標訪問元素的方式 */
@interface JSContext (SubscriptSupport)

/* 首先將key轉為JSValue對象,然後使用這個值在JavaScript context的全局對象中查找這個名字的屬性並返回 */
(JSValue *)objectForKeyedSubscript:(id)key;

/* 首先將key轉為JSValue對象,然後用這個值在JavaScript context的全局對象中設置這個屬性。
可使用這個方法將native中的對象或者方法橋接給JavaScript調用 */
(void)setObject:(id)object forKeyedSubscript:(NSObject <NSCopying>*)key;

@end

/* 例如:以下代碼在JavaScript中創建了一個實現是Objective-C block的function */
context[@"makeNSColor"] = ^(NSDictionary *rgb){
    float r = [rgb[@"red"] floatValue];
    float g = [rgb[@"green"] floatValue];
    float b = [rgb[@"blue"] floatValue];
    return [NSColor colorWithRed:(r / 255.f) green:(g / 255.f) blue:(b / 255.f)         alpha:1.0];
};
JSValue *value = [context evaluateScript:@"makeNSColor({red:12, green:23, blue:67})"];

五、 JSValue

一個JSValue實例就是一個JavaScript值的引用。使用JSValue類在JavaScript和native代碼之間轉換一些基本類型的數據(比如數值和字元串)。你也可以使用這個類去創建包裝了自定義類的native對象的JavaScript對象,或者創建由native方法或者block實現的JavaScript函數。

每個JSValue實例都來源於一個代表JavaScript執行環境的JSContext對象,這個執行環境就包含了這個JSValue對應的值。每個JSValue對象都持有其JSContext對象的強引用,只要有任何一個與特定JSContext關聯的JSValue被持有(retain),這個JSContext就會一直存活。通過調用JSValue的實例方法返回的其他的JSValue對象都屬於與最始的JSValue相同的JSContext。

每個JSValue都通過其JSContext間接關聯了一個特定的代表執行資源基礎的JSVirtualMachine對象。你只能將一個JSValue對象傳給由相同虛擬機管理(host)的JSValue或者JSContext的實例方法。如果嘗試把一個虛擬機的JSValue傳給另一個虛擬機,將會觸發一個Objective-C異常。

1. JSValue類型轉換

JSValue提供了一系列的方法將native與JavaScript的數據類型進行相互轉換:

2. NSDictionary與JS對象

NSDictionary對象以及其包含的keys與JavaScript中的對應名稱的屬性相互轉換。key所對應的值也會遞歸地進行拷貝和轉換。

[context evaluateScript:@"var color = {red:230, green:90, blue:100}"];

//js->native 給你看我的顏色
JSValue *colorValue = context[@"color"];
NSLog(@"r=%@, g=%@, b=%@", colorValue[@"red"], colorValue[@"green"], colorValue[@"blue"]);
NSDictionary *colorDic = [colorValue toDictionary];
NSLog(@"r=%@, g=%@, b=%@", colorDic[@"red"], colorDic[@"green"], colorDic[@"blue"]);

//native->js 給你點顏色看看
context[@"color"] = @{@"red":@(0), @"green":@(0), @"blue":@(0)};
[context evaluateScript:@"log('r:'+color.red+'g:'+color.green+' b:'+color.blue)"];
Output:

r=230, g=90, b=100
r=230, g=90, b=100
r:0 g:0 b:0

可見,JS中的對象可以直接轉換成Objective-C中的NSDictionary,NSDictionary傳入JavaScript也可以直接當作對象被使用。

3. NSArray與JS數組

NSArray對象與JavaScript中的array相互轉轉。其子元素也會遞歸地進行拷貝和轉換。

[context evaluateScript:@“var friends = ['Alice','Jenny','XiaoMing']"];

//js->native 你說哪個是真愛?
JSValue *friendsValue = context[@"friends"];
NSLog(@"%@, %@, %@", friendsValue[0], friendsValue[1], friendsValue[2]);
NSArray *friendsArray = [friendsValue toArray];
NSLog(@"%@, %@, %@", friendsArray[0], friendsArray[1], friendsArray[2]);

//native->js 我覺XiaoMing和不不錯,給你再推薦個Jimmy
context[@"girlFriends"] = @[friendsArray[2], @"Jimmy"];
[context evaluateScript:@"log('girlFriends :'+girlFriends[0]+' '+girlFriends[1])"];

Output:

Alice, Jenny, XiaoMing
Alice, Jenny, XiaoMing
girlFriends : XiaoMing Jimmy

4. Block/函數和JS function

Objective-C中的block轉換成JavaScript中的function對象。參數以及返回類型使用相同的規則轉換。

將一個代表native的block或者方法的JavaScript function進行轉換將會得到那個block或方法。

其他的JavaScript函數將會被轉換為一個空的dictionary。因為JavaScript函數也是一個對象。

5. OC對象和JS對象

對於所有其他native的對象類型,JavaScriptCore都會創建一個擁有constructor原型鏈的wrapper對象,用來反映native類型的繼承關係。預設情況下,native對象的屬性和方法並不會導出給其對應的JavaScript wrapper對象。通過JSExport協議可選擇性地導出屬性和方法。

後面會詳細講解對象類型的轉換。


原文鏈接:https://www.qcloud.com/community/article/873202


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 每天一個設計模式 -9 裝飾者模式 一、現實 使用繼承不總能夠實現最有彈性和最好維護的設計。 利用組合和委托可以在運行時具有繼承行為的效果。 利用繼承設計子類的行為,是在編譯時靜態決定的,而且所有的子類都會繼承到相同的行為。 利用組合的做法擴展對象的行為,就可以在運行時動態地進行擴展。 二、認識裝飾 ...
  • 一、 基本概述 問題:假設有一個控制器,該控制器上有7個可編程的插槽,每個都可以指定到一個不同的家電裝置,每個插槽都有對應的開關按鈕。這個遙控器還具備一個整體的撤銷按鈕。廠家已經提供了控制家電基本處理類。希望你能夠創建一組控制遙控器的API,讓每個插槽都能夠控制一個或一組裝置。(如下圖,廠商類) 分 ...
  • java 企業網站源碼 前後臺都有 靜態模版引擎, 代碼生成器大大提高開發效率 前臺: 支持兩套模版, 可以在後臺切換 系統介紹: 1.網站後臺採用主流的 SSM 框架 jsp JSTL,網站後臺採用freemaker靜態化模版引擎生成html 2.因為是生成的html,所以訪問速度快,輕便,對服務 ...
  • 1..net ajax顯示後臺返回值 <script> $(document).ready(function () { $("#btn").click(function () { //var data = new string(); $.ajax({ type: "POST", //要用post方式 ...
  • 註意 轉載須保留原文鏈接(http://www.cnblogs.com/wzhiq896/p/6783296.html ) 作者:wangwen896 整理 1、分類 2、註釋方式 3、簡單指令 4、變數命名 5、NaN和isNaN 6、轉義字元 7、邏輯短路、邏輯中斷 8、優先順序 9、類型轉換(t ...
  • 最近接觸了Bootstrap,涉及到了LESS,CSS的預處理器使用最廣泛的就是LESS和Sass,都是努力把CSS武裝成為開發語言,讓它從簡單的描述性語言過渡到具有程式式特性的語言,主要的特性就是:變數、Mixins、嵌套、繼承等。就像教程里說的:CSS的預處理器就是讓CSS從設計師的工具,變為開 ...
  • 收錄待用,修改轉載已取得 "騰訊雲" 授權 作者 | 殷源 編輯 | 迷鹿 殷源,專註移動客戶端開發,微軟Imagine Cup中國區特等獎獲得者,現就職於騰訊。 接 "JavaScriptCore全面解析 (上篇)" 六、 JSExport JSExport協議提供了一種聲明式的方法去向 "Jav ...
  • 1、什麼是HTML語義化? “語義化”指的是在需要更少的人類干預的情況下,能夠研究和手機信息,讓網頁能夠被機器理解,最終讓人類受益。 語義化的目的就是讓大家直觀的認識標簽(markup)和屬性(attribute)的用途和作用。很明顯Hx系列看起來很像標題,因為擁有粗體和較大的字型大小。<strong> ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...