最近看了一個(gè)對(duì)Github上面編程語(yǔ)言使用統(tǒng)計(jì)的排行榜,JavaScript真可以說(shuō)是一枝獨(dú)秀,很難想象20年前,這個(gè)語(yǔ)言只是瀏覽器中的裝飾性語(yǔ)言,能做的事情也就是一點(diǎn)特效或者檢查一下要提交給服務(wù)器的表單是否滿足要求。今天的JavaScript已經(jīng)是一個(gè)全棧語(yǔ)言,從客戶端到服務(wù)器無(wú)所不在。很多編程語(yǔ)言都提供了跟JavaScript進(jìn)行交互的接口,這一點(diǎn)在iOS開發(fā)中也不例外。
??iOS7以前,在App中調(diào)用JavaScript的方式只有一種,就是通過(guò)UIWebView對(duì)象的stringByEvaluatingJavaScriptFromString:方法。由于UIWebView中包含了CSS渲染引擎和JavaScript執(zhí)行引擎(說(shuō)白了就是微型一個(gè)瀏覽器),因此這個(gè)方法可以讓UIWebView通過(guò)它的JavaScript運(yùn)行時(shí)環(huán)境執(zhí)行JavaScript代碼,但是能做的事情非常有限,我們可以先看看下面的例子。
- (void)webViewDidFinishLoad:(UIWebView *)webView {
// 獲得UIWebView中加載頁(yè)面的標(biāo)題
NSString *title = [webView stringByEvaluatingJavaScriptFromString:
@"document.title"];
NSLog(@"%@", title);
// 獲得UIWebView中加載頁(yè)面的鏈接地址
NSString *urlStr = [webView stringByEvaluatingJavaScriptFromString:
@"location.href"];
NSLog(@"%@", urlStr);
}
從iOS7開始,我們可以使用JavaScriptCore框架來(lái)讓我們的Objective-C代碼和JavaScript進(jìn)行深度交互,簡(jiǎn)單的說(shuō)我們可以在Objective-C代碼中訪問(wèn)JavaScript中的變量或調(diào)用JavaScript的函數(shù),也可以JavaScript中使用Objective-C的對(duì)象和方法。我們可以先看一個(gè)簡(jiǎn)單的例子。
先加入JavaScriptCore的頭文件。
#import <JavaScriptCore/JavaScriptCore.h>
在Objective-C中使用JavaScript的正則表達(dá)式驗(yàn)證字符串。
// 創(chuàng)建JavaScript執(zhí)行環(huán)境(上下文)
JSContext *context = [[JSContext alloc] init];
NSString *funCode =
@"var isValidNumber = function(phone) {"
" var phonePattern = /^1[34578]\\\\d{9}$/;"
" return phone.match(phonePattern);"
"};";
// 執(zhí)行上面的JavaScript代碼
[context evaluateScript:funCode];
// 獲得isValidNumber函數(shù)并傳參調(diào)用
JSValue *jsFunction = context[@"isValidNumber"];
JSValue *value1 = [jsFunction callWithArguments:@[ @"13012345678" ]];
NSLog(@"%@", [value1 toBool]? @"有效": @"無(wú)效"); // 有效
JSValue *value2 = [jsFunction callWithArguments:@[ @"12345678899" ]];
NSLog(@"%@", [value2 toBool]? @"有效": @"無(wú)效"); // 無(wú)效
在Objective-C中調(diào)用JavaScript函數(shù)求階乘。
// 創(chuàng)建JavaScript執(zhí)行環(huán)境(上下文)
JSContext *context = [[JSContext alloc] init];
// 可以將一個(gè)block傳給JavaScript上下文
// 它會(huì)被轉(zhuǎn)換成一個(gè)JavaScript中的函數(shù)
context[@"factorial"] = ^(int x) {
double result = 1.0;
for (; x > 1; x--) {
result *= x;
}
return result;
};
// 執(zhí)行求階乘的函數(shù)
[context evaluateScript:@"var num = factorial(5);"];
JSValue *num = context[@"num"];
NSLog(@"5! = %@", num); // 5! = 120
JavaScript和Objective-C中類型的對(duì)應(yīng)關(guān)系如下表所示:
| Objective-C類型 | JavaScript類型 |
|---|---|
| nil | undefined |
| NSNull | null |
| NSString | string |
| NSNumber | number, boolean |
| NSDictionary | Object object |
| NSArray | Array object |
| NSDate | Date object |
| NSBlock | Function object |
| id | Wrapper object |
| Class | Constructor object |
再來(lái)看一個(gè)例子。我們?cè)诟晥D控制器中放置一個(gè)按鈕,點(diǎn)擊后會(huì)導(dǎo)航到下一個(gè)視圖控制器,其中有一個(gè)UIWebView加載了一個(gè)網(wǎng)頁(yè),頁(yè)面中有一顆按鈕,我們希望點(diǎn)擊按鈕后導(dǎo)航到上一個(gè)視圖控制器,要做到這一點(diǎn),就需要在JavaScript中訪問(wèn)Objective-C對(duì)象和方法。

頁(yè)面的代碼:
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>測(cè)試頁(yè)面</title>
<style type="text/css">
#backButton {
display: inline-block;
width:50px; height:30px;
}
</style>
</head>
<body>
<button id="backButton">返回</button>
<script type="text/javascript">
var btn = document.getElementById("backButton");
var cb = function() {
window.alert('Hello');
};
btn.addEventListener('click', cb, false);
</script>
</body>
</html>
第二個(gè)視圖控制器的代碼:
#import "ViewController.h"
#import <JavaScriptCore/JavaScriptCore.h>
@protocol MyProtocol <JSExport>
- (void) letsGoBack;
@end
@interface SecondViewController : ViewController <MyProtocol>
@end
@interface SecondViewController () <UIWebViewDelegate>
@property (weak, nonatomic) IBOutlet UIWebView *myWebViw;
@end
@implementation SecondViewController
- (void)viewDidLoad {
[super viewDidLoad];
[_myWebViw loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:
@"http://localhost:8080/myweb/test.html"]]];
_myWebViw.delegate = self;
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
// 通過(guò)UIWebView獲得網(wǎng)頁(yè)中的JavaScript執(zhí)行環(huán)境
JSContext *context = [webView valueForKeyPath:
@"documentView.webView.mainFrame.javaScriptContext"];
// 設(shè)置處理異常的block回調(diào)
[context setExceptionHandler:^(JSContext *ctx, JSValue *value) {
NSLog(@"error: %@", value);
}];
context[@"callBackObj"] = self;
// 下面的代碼移除了按鈕原先綁定的事件回調(diào)重新綁定返回上一個(gè)視圖控制器的代碼
NSString *code =
@"var btn = document.getElementById('backButton');"
"btn.removeEventListener('click', cb);"
"btn.addEventListener('click', function() {"
" callBackObj.letsGoBack();"
"});";
[context evaluateScript:code];
}
// 實(shí)現(xiàn)協(xié)議中的方法
- (void) letsGoBack {
// 必須回到主線程刷新用戶界面
dispatch_async(dispatch_get_main_queue(), ^{
[self.navigationController popViewControllerAnimated:YES];
});
}
@end
可以在Github上下載到上面例子的完整代碼。