1. 程式人生 > >JavaScriptCore 全面解析 (上篇)

JavaScriptCore 全面解析 (上篇)

作者:殷源,就職於騰訊,專注移動客戶端開發,微軟Imagine Cup中國區特等獎獲得者。
責編:屠敏,關注物聯網、移動開發領域,尋求報道或投稿請發郵件[email protected],或微信tm_forever_miss。
本文為騰雲閣投稿。

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. 瀏覽器演進

  • 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

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協議可選擇性地匯出屬性和方法。

後面會詳細講解物件型別的轉換。

相關推薦

JavaScriptCore 全面解析

作者:殷源,就職於騰訊,專注移動客戶端開發,微軟Imagine Cup中國區特等獎獲得者。 責編:屠敏,關注物聯網、移動開發領域,尋求報道或投稿請發郵件[email protected],或微信tm_forever_miss。 本文為騰雲

MyBatis詳細原始碼解析

# 前言 我會一步一步帶你剖析MyBatis這個經典的半ORM框架的原始碼! 我是使用Spring Boot + MyBatis的方式進行測試,但並未進行整合,還是使用最原始的方式。 # 專案結構 **匯入依賴:** 1. mybatis:[mybatis](https://mvnrepositor

Mybaits 原始碼解析 ----- 全網最詳細:Select 語句的執行過程分析Mapper方法是如何呼叫到XML中的SQL的?

上一篇我們分析了Mapper介面代理類的生成,本篇接著分析是如何呼叫到XML中的SQL 我們回顧一下MapperMethod 的execute方法 public Object execute(SqlSession sqlSession, Object[] args) { Object res

Class檔案結構全面解析

什麼是Class檔案? 在Java剛剛誕生的時候就提出了一個非常著名的口號:“一次編寫,到處執行。(Write Once,Run Anywhere)”。為了實現平臺無關性,各種不同平臺的虛擬機器都統一使用一種程式儲存格式,就是位元組碼(ByteCode)。它就以二進位制位元組流的方式被存放在Class檔案中,

【MyBatis源碼分析】insert方法、update方法、delete方法處理流程

times database connect 環境 enable clas 它的 java對象 ace 打開一個會話Session 前文分析了MyBatis將配置文件轉換為Java對象的流程,本文開始分析一下insert方法、update方法、delete方法處理的流程,至

編程經常使用設計模式具體解釋--工廠、單例、建造者、原型

-a 裝飾器模式 nds support art 類的繼承 兩個 開放 lose 參考來自:http://zz563143188.iteye.com/blog/1847029 一、設計模式的分類 整體來說設計模式分為三大類: 創建型模式。共五種:工廠方法模式、抽

2017最新PHP經典面試題目匯總

4.0 .net true 服務 一次 模板 混合 符號 組織 原文鏈接:http://www.cnblogs.com/zhyunfe/p/6209097.html 1、雙引號和單引號的區別 雙引號解釋變量,單引號不解釋變量 雙引號裏插入單引號,其中單引號裏如果有變量

Vue.js——組件快速入門

綁定 ram 字符串過濾 技術 dem ava 對象 src get Vue.js——60分鐘組件快速入門(上篇) 組件簡介 組件系統是Vue.js其中一個重要的概念,它提供了一種抽象,讓我們可以使用獨立可復用的小組件來構建大型應用,任意類型的應用界面都可以抽象為一個組件

iOS多線程開發之離不開的GCD

sop 先進先出 調度 事件 實現 說明 優先級 子線程 函數 一、GCD基本概念 GCD 全稱Grand Central Dispatch(大中樞隊列調度),是一套低層API,提供了?種新的方法來進?並發程序編寫。從基本功能上講,GCD有點像NSOperatio

支付網關 | 京東618、雙11用戶支付的核心承載系統

java 支付 雙11 支付網關 618 二零一七年六月二十一日,就是年中大促剛結束的那一天,我午飯時間獨在辦公室裏徘徊,遇見X君,前來問我道,“可曾為這次大促寫了一點什麽沒有?”我說“沒有”。他就正告我,“還是寫一點罷;小夥伴們很想了解支撐起這麽大的用戶支付流量所采用的技術。”「摘要

.net ef core 領域設計代碼轉換

解決 con mage keys $1 服務 結構 刪除 sql 一、前言 .net core 2.0正式版已經發布幾個月了,經過研究,決定把項目轉移過來,新手的話可以先看一些官方介紹 傳送門:https://docs.microsoft.com/zh-cn/do

CLR via C# 讀書筆記-27.計算限制的異步操作

top oid 輔助線 var 思考 read 運行 簡單例子 class 前言 學習這件事情是一個習慣,不能停。。。另外這篇已經看過兩個月過去,但覺得有些事情不總結跟沒做沒啥區別,遂記下此文 1.CLR線程池基礎 2.ThreadPool的簡單使用練習 3.執行上下文 4

【SqlServer系列】淺談SQL Server事務與鎖

架構 tab 要求 允許 ble 1.2 定義 由於 數據庫引擎 一 概述 在數據庫方面,對於非DBA的程序員來說,事務與鎖是一大難點,針對該難點,本篇文章試圖采用圖文的方式來與大家一起探討。 “淺談SQL Server 事務與鎖”這個專題共分

【ASP.NET Core】處理異常

關心 指向 然而 sub 相關 pri roo epon netcore 依照老周的良好作風,開始之前先說點題外話。 前面的博文中,老周介紹過自定義 MVC 視圖的搜索路徑,即向 ViewLocationFormats 列表添加相應的內容,其實,對 Razor Page

[ Python ] 基本數據類型及屬性

獲取 string ast 轉換 分割字符串 upper not found 不可 inf 1. 基本數據類型 (1) 數字 - int (2) 字符串 - str (3) 布爾值 - bool 2. int 類型中重要的方法

這一次,真正搞懂信用評分模型

工程師 集中 重要 sklearn app 目的 概率 單變量 是我 python風控評分卡建模和風控常識 https://study.163.com/course/introduction.htm?courseId=1005214003&utm_campaign

DoTwe幸運28平臺搭建下載en全解析入門

陌生 搭建 今天 入門 tween 幸運 開發人員 方式 開發 DoTween,Itw幸運28平臺搭建下載【征途源碼論壇zhengtuwl.com】聯系方式:QQ:2747044651幸運28平臺搭建下載een,這些名字作為一個Unity開發人員聽起來並不陌生,它們在動畫方

從誌願軍“斷刀”再論敏捷之道

慢慢 失敗 多個 之一 朝鮮 無法 一次 mark 學習 從誌願軍“斷刀”再論敏捷之道(上篇) 作者:歐德張(原創) ??在現在的IT項目中,以往常用的是瀑布模型套路,這些年敏捷模式大受歡迎,關於敏捷,現在諸人開口PMI-ACP,閉口則SCRUM,又有諸多實踐、案例遵行其

Win10深度學習環境配置:python3 + curl + pip + Jupyter notebook

好記性不如爛筆頭,純粹為自己的學習生活記錄點什麼! 本次記錄win10下安裝python3+curl+pip+jupyter,以及修改右鍵快捷開啟cmd 對於大多數的學習者,還是習慣選擇在ubuntu系統上學習深度學習,主要還是因為絕大多數演算法實現都是ubunt

資料結構------------線性表

線性表:由n(n>=0)個數據特性相同的元素構成的有限序列 線性表中的袁旭個數n(n>=0)定義為線性表的長度,n=0時為空表 非空的線性表或線性結構特點: 1)存在唯一的一個數被稱為“第一個”的資料元素; 2)存在唯一的一個數被稱為“最後一個”的資料元素; 3)除第