javascriptcorejavascript是做什么的用的,客户端使用它可实现什么功能

JavaScriptCore的基本用法(二) - 简书
JavaScriptCore的基本用法(二)
代理设置(JS调用OC的第二种方法)
//首先写一个协议
遵守JSExport协议
@protocol JSExportTest &JSExport&
//宏转换下,将JS函数名称指定为Add;
JSExportAs(add, - (NSInteger)add:(NSInteger)a b:(NSInteger)b);
@property (nonatomic, assign) NSI
//建一个对象实现这个协议
@interface JSTest : NSObject&JSExportTest&
@implementation JSTest
@synthesize sum = _
//实现协议方法
- (NSInteger)add:(NSInteger)a b:(NSInteger)b{
return a +
-(void)setSum:(NSInteger)sum{
NSLog(@"%ld",(long)sum);
在viewcontroller里面
JSContext *context = [[JSContext alloc] init];
//设置异常处理
self.context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
[JSContext currentContext].exception =
NSLog(@"exception:%@",exception);
//将obj添加到context中
scontext[@"obj"] = [][JSTest alloc]init];
//JS里面调用obj方法,并将结果赋值给obj的sum属性
[context evaluateScript:@"obj.sum = obj.add(2,3)"];
在JS中进行调用这个对象的方法,并将结果赋值sum。唯一要注意的是OC的函数命名和JS函数命名规则问题。协议中定义的是add: b:,但是JS里面方法名字是add(a,b)。可以通过JSExportAs这个宏转换成JS的函数名字。
Objective-C的异常会在运行时被Xcode捕获,而在JSContext中执行的JavaScript如果出现异常,只会被JSContext捕获并存储在exception属性上,而不会向外抛出。时时刻刻检查JSContext对象的exception是否不为nil显然是不合适,更合理的方式是给JSContext对象设置exceptionHandler,它接受的是^(JSContext context, JSValue exceptionValue)形式的Block。其默认值就是将传入的exceptionValue赋给传入的context的exception属性:
JSContext *context = [[JSContext alloc] init];
context.exceptionHandler = ^(JSContext *con, JSValue *exception) {
NSLog(@"%@", exception);
con.exception =
[context evaluateScript:@"fengzhen = 66"];
ReferenceError: Can't find variable: fengzhen
无论是把Block传给JSContext对象让其变成JavaScript方法,还是把它赋给exceptionHandler属性,在Block内都不要直接使用其外部定义的JSContext对象或者JSValue,应该将其当做参数传入到Block中,或者通过JSContext的类方法+ (JSContext *)currentC来获得。否则会造成循环引用使得内存无法被正确释放。
OC使用的是ARC,JS使用的是垃圾回收机制,js的引用全都是强引用,垃圾回收机制会帮他们打破这种强引用,所以JS不存在循环引用的问题。一般情况下,OC和JS对象之间内存管理都无需我们去关心。不过还是有几个注意点需要我们去留意下。
1、不要在block里面直接使用context,或者使用外部的JSValue对象。
JSContext *context = [[JSContext alloc] init];
//设置异常处理
self.context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
//直接这么使用是错误的
//context.exception =
[JSContext currentContext].exception =
NSLog(@"exception:%@",exception);
2.OC对象不要用属性直接保存JSValue对象,因为这样太容易造成循环引用。
下面的例子:
#import &Foundation/Foundation.h&
#import &JavaScriptCore/JavaScriptCore.h&
//首先写一个协议
遵守JSExport协议
@protocol JSExportTest &JSExport&
//宏转换下,将JS函数名称指定为Add;
JSExportAs(add, - (NSInteger)add:(NSInteger)a b:(NSInteger)b);
@property (nonatomic, strong) JSValue *
//建一个对象实现这个协议
@interface JSTest : NSObject&JSExportTest&
#import "JSTest.h"
@implementation JSTest
@synthesize value = _
//实现协议方法
-(void)setValue:(JSValue *)value{
viewController里面
JSContext *context = [[JSContext alloc]init];
context.exceptionHandler = ^(JSContext *j, JSValue *v){
NSLog(@"%@",j.exception);
[context evaluateScript:@"function callback(){return 'hello world'};function setObj(obj){this.obj =obj.value = callback}"];
[context[@"setObj"] callWithArguments:@[self.testObj]];
调用JS方法,进行赋值,JS对象保留了传进来的obj,最后,JS将自己的回调callback赋值给了obj,方便obj下次回调给JS;由于JS那边保存了obj,而且obj这边也保留了JS的回调。这样就形成了循环引用。为了打破这种强引用,apple有一个JSManagedValue 的类,官方的原话:
The JSManagedValue's JavaScript value is reachable from JavaScript
The owner of the managed reference is reachable in Objective-C. Manually adding or removing the managed reference in the JSVirtualMachine determines reachability.
JSManagedValue 帮助我们保存JSValue,里面保存的JS对象必须在JS中存在,同时 JSManagedValue 的owner在OC中也存在.因此我们把代理的m文件修改如下:
-(void)setValue:(JSValue *)value{
由于是回掉的关系
obj保存了JS的回掉, js也保存了obj,这样就形成了循环引用
JSManageValue帮助我们保存了JSValue,哪里保存的js对象在js中存在。 JSMangerValue的owner在OC中也存在。
JSManagedValue *mavalue = [JSManagedValue managedValueWithValue:value];
//建立弱引用关系
[[[JSContext
currentContext] virtualMachine] addManagedReference:mavalue withOwner:self];
3.不要在不同的 JSVirtualMachine 之间进行传递JS对象。
一个JSVirtualMachine可以运行多个context,由于都是在同一个堆内存和同一个垃圾回收下,所以相互之间传值是没问题的。但是如果在不同的 JSVirtualMachine传值,垃圾回收就不知道他们之间的关系了,可能会引起异常。
4.JavaScriptCore线程是安全的。
每个context运行的时候通过lock关联的JSVirtualMachine。如果要进行并发操作,可以创建多个JSVirtualMachine实例进行操作。
你脖子扬起来| 导语Java越来越多地出现在我们客户端开发的视野中,从ReactNative到JSpatch,Java与客户端相结合的技术开始变得魅力无穷。本文主要讲解iOS中的JavaCore框架,正是它为iOS提供了执行Java代码的能力。未来的技术日新月异,Java与iOS正在碰撞出新的激情。
作者:殷源--腾讯移动端工程师
@IMWeb前端社区
Java越来越多地出现在我们客户端开发的视野中,从ReactNative到JSpatch,Java与客户端相结合的技术开始变得魅力无穷。本文主要讲解iOS中的JavaCore框架,正是它为iOS提供了执行Java代码的能力。未来的技术日新月异,Java与iOS正在碰撞出新的激情。
JavaCore是Java的虚拟机,为Java的执行提供底层资源。
  一、Java
在讨论JavaCore之前,我们首先必须对Java有所了解。
  1. Java干啥的?
说的高大上一点:一门基于原型、函数先行的高级编程语言,通过解释执行,是动态类型的直译语言。是一门多范式的语言,它支持面向对象编程,命令式编程,以及函数式编程。
说的通俗一点:主要用于网页,为其提供动态交互的能力。可嵌入动态文本于HTML页面,对浏览器事件作出响应,读写HTML元素,控制cookies等。
再通俗一点:抢月饼,button.click()。(PS:请谨慎使用while循环)
2. Java起源与历史
1990年底,欧洲核能研究组织(CERN)科学家Tim Berners-Lee,在互联网的基础上,发明了万维网(World Wide Web),从此可以在网上浏览网页文件。
1994年12月,Netscape 发布了一款面向普通用户的新一代的浏览器Navigator 1.0版,市场份额一举超过90%。
1995年,Netscape公司雇佣了程序员Brendan Eich开发这种嵌入网页的脚本语言。最初名字叫做Mocha,1995年9月改为Live。
1995年12月,Netscape公司与Sun公司达成协议,后者允许将这种语言叫做Java。
  3. Java与ECMA
“Java”是Sun公司的注册商标,用来特制网景(现在的Mozilla)对于这门语言的实现。网景将这门语言作为标准提交给了ECMA——欧洲计算机制造协会。由于商标上的冲突,这门语言的标准版本改了一个丑陋的名字“ECMA”。同样由于商标的冲突,微软对这门语言的实现版本取了一个广为人知的名字“J”。
ECMA作为Java的标准,一般认为后者是前者的实现。
  4. Java和Java
Java 和 Java 是两门不同的编程语言
  一般认为,当时 Netscape 之所以将 Live 命名为 Java,是因为 Java 是当时最流行的编程语言,带有 “Java” 的名字有助于这门新生语言的传播。
  二、 JavaCore 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是专门处理Java脚本的引擎;
  3. Java引擎
Java引擎是专门处理Java脚本的虚拟机,一般会附带在网页浏览器之中。
第一个Java引擎由布兰登·艾克在网景公司开发,用于Netscape Navigator网页浏览器中。
JavaCore就是一个Java引擎。
下图是当前主要的还在开发中的Java引擎
  4. JavaCore组成
JavaCore主要由以下模块组成:
Lexer 词法分析器,将脚本源码分解成一系列的Token
Parser 语法分析器,处理Token并生成相应的语法树
LLInt 低级解释器,执行Parser生成的二进制代码
Baseline JIT 基线JIT(just in time 实施编译)
DFG 低延迟优化的JIT
FTL 高通量优化的JIT
  5. JavaCore
JavaCore是一个C++实现的开源项目。使用Apple提供的JavaCore框架,你可以在Objective-C或者基于C的程序中执行Java代码,也可以向Java环境中插入一些自定义的对象。JavaCore从iOS 7.0之后可以直接使用。
在JavaCore.h中,我们可以看到这个
这里已经很清晰地列出了JavaCore的主要几个类:
  JSValue
  JSManagedValue
  JSVirtualMachine
  JSExport
接下来我们会依次讲解这几个类的用法。
  6. Hello World!
这段代码展示了如何在Objective-C中执行一段Java代码,并且获取返回值并转换成OC数据打印
  三、 JSVirtualMachine
一个JSVirtualMachine的实例就是一个完整独立的Java的执行环境,为Java的执行提供底层资源。
这个类主要用来做两件事情:
1、实现并发的Java执行
2、Java和Objective-C桥接对象的内存管理
看下头文件SVirtualMachine.h里有什么:
每一个Java上下文(JSContext对象)都归属于一个虚拟机(JSVirtualMachine)。每个虚拟机可以包含多个不同的上下文,并允许在这些不同的上下文之间传值(JSValue对象)。
然而,每个虚拟机都是完整且独立的,有其独立的堆空间和垃圾回收器(garbage collector ),GC无法处理别的虚拟机堆中的对象,因此你不能把一个虚拟机中创建的值传给另一个虚拟机。
  线程和Java的并发执行
JavaCore API都是线程安全的。你可以在任意线程创建JSValue或者执行JS代码,然而,所有其他想要使用该虚拟机的线程都要等待。
如果想并发执行JS,需要使用多个不同的虚拟机来实现。
可以在子线程中执行JS代码。
通过下面这个demo来理解一下这个并发机制
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是:
  四、 JSContext
一个JSContext对象代表一个Java执行环境。在native代码中,使用JSContext去执行JS代码,访问JS中定义或者计算的值,并使Java可以访问native的对象、方法、函数。
  1. JSContext执行JS代码
调用evaluate函数可以执行一段top-level 的JS代码,并可向global对象添加函数和对象定义
其返回值是Java代码中最后一个生成的值
API Reference
  2. JSContext访问JS对象
一个JSContext对象对应了一个全局对象(global object)。例如web浏览器中中的JSContext,其全局对象就是window对象。在其他环境中,全局对象也承担了类似的角色,用来区分不同的Java context的作用域。全局变量是全局对象的属性,可以通过JSValue对象或者context下标的方式来访问。
一言不合上代码:
这里列出了三种访问Java对象的方法
通过context的实例方法objectForKeyedSub
通过context.globalObject的objectForKeyedSub实例方法
通过下标方式
设置属性也是对应的。
API Reference
  五、 JSValue
一个JSValue实例就是一个Java值的引用。使用JSValue类在Java和native代码之间转换一些基本类型的数据(比如数值和字符串)。你也可以使用这个类去创建包装了自定义类的native对象的Java对象,或者创建由native方法或者block实现的Java函数。
每个JSValue实例都来源于一个代表Java执行环境的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与Java的数据类型进行相互转换:
  2. NSDictionary与JS对象
NSDictionary对象以及其包含的keys与Java中的对应名称的属性相互转换。key所对应的值也会递归地进行拷贝和转换。
  Output:
可见,JS中的对象可以直接转换成Objective-C中的NSDictionary,NSDictionary传入Java也可以直接当作对象被使用。
  3. NSArray与JS数组
NSArray对象与Java中的array相互转转。其子元素也会递归地进行拷贝和转换。
  4. Block/函数和JS function
Objective-C中的block转换成Java中的function对象。参数以及返回类型使用相同的规则转换。
将一个代表native的block或者方法的Java function进行转换将会得到那个block或方法。
  其他的Java函数将会被转换为一个空的dictionary。因为Java函数也是一个对象。
  5. OC对象和JS对象
对于所有其他native的对象类型,JavaCore都会创建一个拥有constructor原型链的wrapper对象,用来反映native类型的继承关系。默认情况下,native对象的属性和方法并不会导出给其对应的Java wrapper对象。通过JSExport协议可选择性地导出属性和方法。
后面会详细讲解对象类型的转换。
  六、 JSExport
JSExport协议提供了一种声明式的方法去向Java代码导出Objective-C的实例类及其实例方法,类方法和属性。
  1. 在Java中调用native代码
两种方式:
2、JSExport
Block的方式很简单,如下:
JSExport的方式需要通过继承JSExport协议的方式来导出指定的方法和属性:
继承于JSExport协议的MyPointExports协议中的实例变量,实例方法和类方法都会被导出,而MyPoint类的- (void)myPrivateMethod方法却不会被导出。
  在OC代码中我们这样导出:
在JS代码中可以这样调用:
  2. 导出OC方法和属性给JS
默认情况下,一个Objective-C类的方法和属性是不会导出给Java的。你必须选择指定的方法和属性来导出。对于一个class实现的每个协议,如果这个协议继承了JSExport协议,JavaCore就将这个协议的方法和属性列表导出给Java。
对于每一个导出的实例方法,JavaCore都会在prototype中创建一个存取器属性。对于每一个导出的类方法,JavaCore会在constructor对象中创建一个对应的Java function。
在Objective-C中通过@property声明的属性决定了Java中的对应属性的特征:
  Objective-C类中的属性,成员变量以及返回值都将根据JSValue指定的拷贝协议进行转换。
3. 函数名转换
转换成驼峰形式:
去掉所有的冒号
所有冒号后的第一个小写字母都会被转为大写
  4. 自定义导出函数名
如果不喜欢默认的转换规则,也可以使用JSExportAs来自定义转换
  5. 导出OC对象给JS
如何导出自定义的对象?
自定义对象有复杂的继承关系是如何导出的?
在讨论这个话题之前,我们首先需要对Java中的对象与继承关系有所了解。
  七、 Java对象继承
如果你已经了解Java的对象继承,可以跳过本节。
这里会快速介绍Java对象继承的一些知识:
  1. Java的数据类型
最新的 ECMA 标准定义了 7 种数据类型:
6 种 原始类型:
Symbol (ECMA 6 新定义)
  2. Java原始值
除 Object 以外的所有类型都是不可变的(值本身无法被改变)。我们称这些类型的值为“原始值”。
布尔类型:两个值:true 和 false
Null 类型:只有一个值: null
Undefined 类型:一个没有被赋值的变量会有个默认值 undefined
字符串类型:不同于类 C 语言,Java 字符串是不可更改的。这意味着字符串一旦被创建,就不能被修改
  3. Java对象
在 Java 里,对象可以被看作是一组属性的集合。这些属性还可以被增减。属性的值可以是任意类型,包括具有复杂数据结构的对象。
以下代码构造了一个point对象:
  4. Java属性
ECMA定义的对象中有两种属性:数据属性和访问器属性。
  数据属性是键值对,并且每个数据属性拥有下列特性:
访问器属性
  访问器属性有一个或两个访问器函数 (get 和 set) 来存取数值,并且有以下特性:
  5. Java属性设置与检测
设置一个对象的属性会只会修改或新增其自有属性,不会改变其继承的同名属性
调用一个对象的属性会依次检索本身及其继承的属性,直到检测到
在chrome的控制台中,我们分别打印设置x属性前后point对象的内部结构:
可见,设置一个对象的属性并不会修改其继承的属性,只会修改或增加其自有属性。
这里我们谈到了proto和继承属性,下面我们详细讲解。
  八、 Prototype
Java对于有基于类的语言经验的开发人员来说有点令人困惑 (如Java或C ++) ,因为它是动态的,并且本身不提供类实现。(在ES2015/ES6中引入了class关键字,但是只是语法糖,Java 仍然是基于原型的)。
当谈到继承时,Java 只有一种结构:对象。每个对象都有一个内部链接到另一个对象,称为它的原型 prototype。该原型对象有自己的原型,等等,直到达到一个以null为原型的对象。根据定义,null没有原型,并且作为这个原型链 prototype chain中的最终链接。
任何一个对象都有一个__proto__属性,用来表示其继承了什么原型。
以下代码定一个具有继承关系的对象,point对象继承了一个具有x,y属性的原型对象。
在Chrome的控制台中,我们打印对象结构:
可见继承关系,point继承的原型又继承了Object.prototype,而Object.prototype的__proto__指向null,因而它是继承关系的终点。
  这里我们首先要知道prototype和__proto__是两种属性,前者只有function才有,后者所有的对象都有。后面会详细讲到。
  1. Java类?
Java 只有一种结构:对象。类的概念又从何而来?
在Java中我们可以通过function来模拟类,例如我们定义一个MyPoint的函数,并把他认作MyPoint类,就可以通过new来创建具有x,y属性的对象
打印point对象结构:
这里出现一个constructor的概念
  2. Java constructor
每个Java函数都自动拥有一个prototype的属性,这个prototype属性是一个对象,这个对象包含唯一一个不可枚举属性constructor。constructor属性值是一个函数对象
执行以下代码我们会发现对于任意函数 F.prototype.constructor == F
这里即存在一个反向引用的关系:
  3. new发生了什么?
当调用new MyPoint(99, 66)时,虚拟机生成了一个point对象,并调用了MyPoint的prototype的constructor对象对point进行初始化,并且自动将MyPoint.prototype作为新对象point的原型。
  相当于下面的伪代码
  4. __proto__与prototype
简单地说:
__proto__是所有对象的属性,表示对象自己继承了什么对象
prototype是Function的属性,决定了new出来的新对象的__proto__
如图详细解释了两者的区别
  5. 打印Java对象结构
在浏览器提供的Java调试工具中,我们可以很方便地打印出Java对象的内部结构
在Mac/iOS客户端JavaCore中并没有这样的打印函数,这里我自定义了一个打印函数
鉴于对象的内部结构容易出现循环引用导致迭代打印陷入死循环,我们在这里简单地处理,对属性不进行迭代打印。
为了描述对象的原型链,这里手动在对象末尾对其原型进行打印。
  6. log
我们为所有的context都添加一个log函数,方便我们在JS中向控制台输出日志
  九、 导出OC对象给JS
现在我们继续回到Objective-C中,看下OC对象是如何导出的
  1. 简单对象的导出
当你从一个未指定拷贝协议的Objective-C实例创建一个Java对象时,JavaCore会创建一个Java的wrapper对象。对于具体类型,JavaCore会自动拷贝值到合适的Java类型。
以下代码定义了一个继承自NSObject的简单类
然后我们打印Java中的d_point对象结构如下:
可见,其type属性并没有被导出。
  JS中的对象原型是就是Object.prototype。
  2. 继承关系的导出
在Java中,继承关系是通过原型链(prototype chain)来支持的。对于每一个导出的Objective-C类,JavaCore会在context中创建一个prototype。对于NSObject类,其prototype对象就是Java context的Object.prototype。对于所有其他的Objective-C类,JavaCore会创建一个prototype属性指向其父类的原型属性的原型对象。如此,Java中的wrapper对象的原型链就反映了Objective-C中类型的继承关系。
我们让DPoint继承子MyPoint
在OC中,它的继承关系是这样的
在JS中,它的继承关系是这样的
打印对象结构来验证:
可见,DPoint自身的未导出的属性type没有在JS对象中反应出来,其继承的MyPoint的导出的属性和函数都在JS对象的原型中。
  十、 内存管理 1. 循环引用
之前已经讲到, 每个JSValue对象都持有其JSContext对象的强引用,只要有任何一个与特定JSContext关联的JSValue被持有(retain),这个JSContext就会一直存活。如果我们将一个native对象导出给Java,即将这个对象交由Java的全局对象持有,引用关系是这样的:
这时如果我们在native对象中强引用持有JSContext或者JSValue,便会造成循环引用:
因此在使用时要注意以下几点:
  2. 避免直接使用外部context
避免在导出的block/native函数中直接使用JSContext
使用 [JSContext currentContext] 来获取当前context能够避免循环引用
  3. 避免直接使用外部JSValue
避免在导出的block/native函数中直接使用JSValue
这里我们使用了JSManagedValue来解决这个问题
  十一、 JSManagedValue
一个JSManagedValue对象包含了一个JSValue对象,“有条件地持有(conditional retain)”的特性使其可以自动管理内存。
最基本的用法就是用来在导入到Java的native对象中存储JSValue。
不要在在一个导出到Java的native对象中持有JSValue对象。因为每个JSValue对象都包含了一个JSContext对象,这种关系将会导致循环引用,因而可能造成内存泄漏。
  1. 有条件地持有
所谓“有条件地持有(conditional retain)”,是指在以下两种情况任何一个满足的情况下保证其管理的JSValue被持有:可以通过Java的对象图找到该JSValue
可以通过native对象图找到该JSManagedValue。使用addManagedReference:withOwner:方法可向虚拟机记录该关系反之,如果以上条件都不满足,JSManagedValue对象就会将其value置为nil并释放该JSValue。
JSManagedValue对其包含的JSValue的持有关系与ARC下的虚引用(weak reference)类似。
  2. 为什么不直接用虚引用?
通常我们使用weak来修饰block内需要使用的外部引用以避免循环引用,由于JSValue对应的JS对象内存由虚拟机进行管理并负责回收,这种方法不能准确地控制block内的引用JSValue的生命周期,可能在block内需要使用JSValue的时候,其已经被虚拟机回收。
API Reference
  十二、 异常处理
JSContext的exceptionHandler属性可用来接收Java中抛出的异常
默认的exceptionHandler会将exception设置给context的exception属性
因此,默认的表现就是从Java中抛给native的未处理的异常又被抛回到Java中,异常并未被捕获处理。
将context.exception设置为nil将会导致Java认为异常已经被捕获处理。
随时关注更多前端干货文章!
  微信:IMWebTech
声明:本文由入驻搜狐公众平台的作者撰写,除搜狐官方账号外,观点仅代表作者本人,不代表搜狐立场。}

我要回帖

更多关于 javascript 富客户端 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信