一、浏览器的组成

浏览器的组成

上文中,中间部分是 WebKit,其他部分由浏览器实现,而 WebKit 中主要分为四大部分:

1. WebKit Embedding Api

提供和浏览器进行交互的一些接口,比如前进后腿、新开窗口、新开页面、关闭页面等;

2. WebKit Ports(Platform Api)

跨平台 Api,感觉应该是 WebKit 暴露一些没有实现接口,供不同的平台去实现,最后完成统一的功能,如:渲染、SSL 认证、音视频等;

最典型的比如在 iOS 的 WKWebView 上,使用 CoreGraphic 来处理相关的渲染逻辑,而在 Android 上则使用 Skia 来处理渲染逻辑;

3. WebCore

该部分的功能是解析 HTML 生成 DOM 和 CSSOM ,合并之后生成渲染树最终生成纹理(Graphic Context):

渲染流程

在以 WebKit 为基础的浏览器中,几乎所有的浏览器都没有重写 WebCore,而是直接使用的 WebKit 源码中的 WebCore 来完成 HTML 的渲染。不同浏览器之间的主要区别则在于 JSCore 的实现;

4. JSCore

早期因为只有 HTML 和浏览器两个角色。浏览器向后台发送表单时,即使是一个必选框没有填写,浏览器也无法获取 HTML/DOM,所以只能直接提交给服务端。因为当时网速慢,耗时很长之后让用户重新填写表单的体验很差,所以亟需一个角色来操作 HTML 进行验证;

浏览器 Javascript 语言的诞生的目的是为浏览器和 HTML 进行交互提供一个桥梁。而 Javascript 的环境提供、解析、运行、runtime 管理都由 JSCore 负责;

JSCore 的角色类似于编译器和虚拟机的结合;

JVM 虚拟机不承担编译的工作,编译器将 .java 文件编译生成字节码文件 .class,而 JVM 则直接解析字节码文件,根据不同的设备特性生成对应的二进制码,并且在整个 runtime 过程中管理内存和 GC 操作,JVM 的工作内容:

  1. 将字节码转化成二进制码(机器码);
  2. 执行并管理 Runtime;

而 OC 中通过编译器生成 Mach-O 文件之后,当程序被执行时,二进制代码直接被加载到虚拟内存并印射到物理内存中进行执行;

JSCore 则承担了编译器和虚拟机的角色。Javascript 首先被 JSCore 转化成字节码最终转化成二进制代码并执行,同时在 runtime 中管理内存和 GC,即 JSCore 的工作内容:

  1. 将 JS 代码编译成字节码;
  2. 将字节码转化成二进制码(机器码);
  3. 执行并管理 Runtime;
JSCore解析流程

二、JSCore 的特性

1. 基于寄存器的指令集结构

代码执行效率更高,但是占用内存更大;

2. 单线程

一个 JS 虚拟机只能单线程执行代码,如果要开启多线程,则只能开启多个 JS 虚拟机,比如开启两个浏览器窗口;

3. 事件响应机制

JS 虽然只支持单线程,但却可以异步执行。 JS 通过事件响应机制,将耗时或者异步操作丢到 WorkThread 中执行,执行完毕获取回调事件之后继续在 JS 线程执行相关代码:

事件响应机制

JS 的事件响应机制机制和主流的事件响应机制(RunLoop)并无太大差别,不再赘述;

三、JSCore 在 iOS 上的使用

1. JSVirtualMachine

表示一个 JS 虚拟机。

其实并没有 JS 虚拟机的概念,这个概念更像是一个封装之后的概念,封装的内容包括:JS 运行环境、JS 的解析功能、JS 的 Runtime 管理等;

一个 JSVirtualMachine 对象内部只能单线程执行,如果需要多线程执行 JS 代码,则需要开启多个 JSVirtualMachine,但是两个 JSVirtualMachine 之间的资源不共享;

2. JSContext

JSContext 可以理解成窗口,或者可以等加成 window 对象,而 JSContext 中的 globalObject 就是这个 window 对象转化之后的 Native 端表示:

context1[@"xklog"] = testBlock;
NSLog(@"%@",[context1.globalObject toObject]);

上述代码打印结果:

2021-12-10 17:02:21.336441+0800 UPHybridDemo[1982:82899] {xklog = "<__NSGlobalBlock__: 0x10f3edbe8>";
}

因为使用的是 JSCore 框架,而不是在浏览器中使用,所以 window 对象并没有宿主所提供的接口和功能,所以当前的 context 只有一个自定义的属性;

这里可以在 JS 端也打印看下:

image.png

需要注意的是,在WKWebView 中只能通过 userContentController 来添加原生方法,且不是直接添加到 window 上的;如果想要进一步验证,感觉可以使用 UIWebView 获取 Context 来试一试;

3. JSValue

略;

4. JSExport

遵循 JSExport 协议的 OBJC 对象可以印射到 JS 中。

JS 继承的本质是在构造函数中重写原型链,并将子类的属性添加到这个原型链中:

function mammal (){}mammal.prototype.commonness = function(){alert('哺乳动物都用肺呼吸');}; function Person() {}Person.prototype = new mammal();//原型链的生成,Person的实例也可以访问commonness属性了Person.prototype.name = 'tony stark';Person.prototype.age  = 48;Person.prototype.job  = 'Iron Man';Person.prototype.sayName = function() {alert(this.name);}var person1 = new Person();person1.commonness(); // 弹出'哺乳动物都用肺呼吸'person1.sayName(); // 'tony stark'

所以,遵循 JSExport 协议的对象会根据继承关系生成对应的原型链;

四、Hybrid交互

1. Native 调用 JS 时的作用域问题

虽然对象或者类可以在 Native 和 JS 之间进行转换,但是向 JS 中插入原生调用并不是将原生代码转化成了 JS 代码,而只是做了一个类似于打上一个标记之类的操作:

image.png

从上文第 2 点的 JSContext 全局对象打印结果也可以看出,Native 的回调在 JS 端保留的只是一个 Block 地址,本质上是一个函数地址,指向代码所在的位置。

这里的 [native code] 并不是指原生代码,而是函数中的 JS 代码过多或者不容易展示,直接使用 [native code] 表示,native 表示 JS;

所以,JS 里面虽然在 window 中添加了全局对象,但是 Native 的代码不会被转化成 JS 代码,只是一个指向。真正执行时,通过这个指向去寻找 Natie Code 的地址并执行。如果是 WKWebView,则还涉及到进程间的通信:

image.png

虽然 Native 代码不能被转成 JS 代码,但是 Native 可以通过 JS 注入将字符串转成对应的 JS 代码:

[self.webview evaluateJavaScript:@"(function (){var a = 10;var b = 20; return a+b;})();" completionHandler:^(NSNumber _Nullable result, NSError * _Nullable error) {NSLog(@"%@",result);
}];

即:

  • Native 可以为 JS 环境添加代码,但是 JS 只能引用(指向) Native 环境中的代码;

所以,在设计 JS 调用原生方法之后的回调时,可能会考虑到直接将 JS 中的回调转化成字符串,JS 调用原生完成后,原生直接使用 evaluateJavaScript 来调用对应的 callBack 方法;

第一种,直接使用 webkit 自带的 postMessage 来完成转换:

function func01(){const person = {firstName: "John",lastName: "Doe",age: 50,eyeColor: "blue",callBack: function () {console.log("native call");},};window.webkit.messageHandlers.nativeMethod.postMessage(person);
}

这种方式会报错:

克隆算法

其原因是因为 postMessage 传递的参数会被自动转换成 Native 类型,但是这个转换不支持 function 和 Error :

结构化了龙算法

所以只能使用字符串:

image.png

此时使用 Native 进行调用时,就会出现作用域改变的问题:

image.png

总结:

  • Native 注入 JS 并调用,其作用域永远是在全局,即和 Window 统一作用域。

可以添加 javascript: 前缀,其意义是以一个 script 来执行 JS,但是其作用域仍然是 window;

所以如果是为了实现 JS 回调,不能这么做。因为如果 function 内部访问了 function 外部变量时,Native 注入 JS 并调用会改变 function 的作用域链,导致变量无法访问;

6. JS 中的作用域链

作用域链寻找变量的本质是:

  • 如果没有声明变量,则优先到创建 fn 函数的那个作用域中取,无论 fn 函数将在哪里调用;

例子:

var x = 10;function fn() {console.log(x);
}function show(f) {var x = 20;(function() {f(); //10,而不是20})();
}
show(fn);

再看一个例子:

var a = 10;
function fn() {var b = 20;function bar() {console.log(a + b); //30}return bar;
}var x = fn(),
b = 200;
x(); //bar()

2. callback 的设计原则

如上文所讲,如果将 function 字符串化之后传给 Native ,Native 通过注入 JS 调用,最终相当于在全局作用域下声明了一个 function 并执行,function 及其内部变量的作用域发生变化。如果 function 只是访问入参那还好,如果访问 function 外的上级作用域,那么此时该变量就会找不到,因为回去全局搜索变量;

所以,回调设计的原则:

  • Native 负责透传 callback 信息给 H5,H5 负责调用 callback 函数;

针对这个原则,有两种方案:

  1. 函数名;
  2. callbackId;

第一种方案比较脑残:
Native 中直接拼出这样的字符串:'func(param1,param2)' 丢到 H5; 感觉这就是个脑残代码,而且参数你怎么传?

第二种方案:

  1. H5 端 Map 方式存储 callbackId : callBackFunc ;
  2. JS 端需要为原生提供一个回调相关的函数;
  3. H5 调用原生时将 CallBackId 一起给到 Native ,Native 调用完毕调用 JS 中的回调函数,并将 CallBackId 传回给 H5;
  4. H5 通过 callbackId 获取 CallBackFunc 进行调用;

这样不会改变函数的作用域和作用域链;

3. WebViewJavaScriptBridge 原理

WebViewJavaScriptBridge 的本质是使用 iframe 标签触发 WKWebView 的拦截请求,所以也适用于单页面(SAP):

image.png

image.png

4. WKWebView 的封装的思考

WKWebView 中的 UserContentController 本质上是 Apple 对 JSCore 的封装:

image.png

只不过,uerContentController 通过 [userCC addScriptMessageHandler:self name:@"nativeMehtod"]; 被添加到了 window.webkit.messageHandlers 这个对象中:

image.png

早期的 UIWebView 可以通过注入 JS 的方式获取到 window 对象,而 window 对象几乎就等于 JSCore 框架中的 JSContext 对象。

WKWebView 封装的目的是为了限制客户端在 WebView 上对 JSCore 的使用,所以在 WKWebView 上并不提供获取 JSContext 的接口。无法获取到 JSContext 对象也就无法获取到 JSVituralMachine 对象,也就无法无法改变 WKWebView 内部的 JS 环境。

另外一个原因可能是 WKWebView 涉及到多进程问题。直接使用 JSCore 框架时,JSVirtualMachine 和 JSContext 和 WorkThread 都是出于同一进程。而 WKWebView 采用多进程的方式实现,所以提供另外一个进程的 JSContext 并支持修改,这可能会导致代码过度复杂虎或不可控;

http://www.taodudu.cc/news/show-1912741.html

相关文章:

  • 编程语言的动态性(Dart和OC对比)
  • iOS:Universal Link
  • AFN中的鉴权
  • openGL ES 教程(二):渲染管线
  • MySQL(2)----DDL语句之增、删、改、查操作
  • MySQL(3)-----DML数据库操作(上)
  • 线性表的基本运算
  • MySQL(4)-----DML数据库操作(下)
  • MySQL(1)----帮助使用
  • MySQL(6)-----数据类型
  • (1)封装JSON数据的三种方式
  • (2)从文件中解析JSON数据
  • (1)I/O流对象-----FileInputStream与FileOutputStream
  • MyBatis(一)------目录
  • MyBatis(二)------使用JDBC编程问题总结
  • MySQL(5)-----DQL语句的基本查询与高级查询
  • MySQL(7)-----常用约束
  • MySQL(8)-----truncate清空表和字段自增
  • MySQL(9)-----多表创建及描述表关系(需求)
  • (1)Spring框架----通俗易懂的IoC原理
  • MySQL(10)-----多表创建及描述表关系(一对多的分析和实现)
  • MySQL(11)-----多表创建及描述表关系(多对多的分析和实现)
  • MySQL(12)-----多表查询(内连接和外连接)
  • MySQL(13)-----多表查询(子查询)
  • MySQL(14)-----运算符和优先级
  • (1)关于File类你知道多少
  • (2)I/O流对象-----FilterInputStream与FilterOutputStream
  • (3)I/O流对象-----复制图片/文件/视频的几种I/O流方式
  • Java集合源码分析(一):数组与链表
  • Java集合源码分析(二):哈希表

JSCore浅析及其在iOS上的使用相关推荐

  1. iOS 上常用的两个功能:点击屏幕和return退出隐藏键盘和解决虚拟键盘

    原文地址:http://blog.csdn.net/xiaotanyu13/article/details/7711954 iOS上面对键盘的处理很不人性化,所以这些功能都需要自己来实现, 首先是点击 ...

  2. ios snapkit m_如何使用自动布局和SnapKit在iOS上创建漂亮的拉伸布局

    ios snapkit m by Enabled Solutions 由Enabled Solutions 如何使用自动布局和SnapKit在iOS上创建漂亮的拉伸布局 (How to create ...

  3. document.onclick在ios上不触发的解决方法与touchstart点击穿透处理

    document.onclick = function (e) {var e = e ? e : window.event;var tar = e.srcElement || e.target;if ...

  4. 解决 iframe 在 ios 上不能滚动的问题

    解决 iframe 在 ios 上不能滚动的问题 参考文章: (1)解决 iframe 在 ios 上不能滚动的问题 (2)https://www.cnblogs.com/xieze/p/670211 ...

  5. 解决页面使用overflow: scroll在iOS上滑动卡顿的问题

    解决页面使用overflow: scroll在iOS上滑动卡顿的问题 参考文章: (1)解决页面使用overflow: scroll在iOS上滑动卡顿的问题 (2)https://www.cnblog ...

  6. vue中解决时间在ios上显示NAN的问题

    vue中解决时间在ios上显示NAN的问题 参考文章: (1)vue中解决时间在ios上显示NAN的问题 (2)https://www.cnblogs.com/wzs5800/p/9580785.ht ...

  7. ios html双击下移,H5页面在ios上双击div,导致屏幕上移的js解决办法

    页面中的汉堡道学数里屏.中近,期据面蔽最,近,期据面键有动画效果,在安卓手机上双击没问题,在ios上双击就导致页面上移,再点击按钮就恢复了,但这样很不友好,在网上查找资料,发现很多人都用这段代码,于是 ...

  8. Pluto - iOS 上一个高性能的排版渲染引擎

    Pluto 是 iOS 上的一个排版渲染引擎,通过 JSON/JS 文件可以很方便地描述界面元素,开发效率很高,并且在流畅度,内存等方便有保证.pluto.oa.com 上有更多详细资料. Qzone ...

  9. utm虚拟机安装linux,UTM: 在 iOS 上安装 Windows 或 Linux 等系统及虚机安装过程

    Apps & Tweaks|Jailbreak Guide|iDevices UTM Version: 2.0.20 Repo: https://getutm.app/ Support: 11 ...

  10. IOS上 关于状态栏的相关设置(UIStatusBar)和preferredStatusBarStyle不执行问题

    转:http://m.blog.csdn.net/blog/zqx654033799/23597509 一.在老版本的iOS中,状态栏永远都是白色风格.而在iOS 7中,我们可以修改每个view co ...

最新文章

  1. js组合模式和寄生组合模式的区别研究
  2. SVN删除服务器端项目文件
  3. php指定异常状态码,php怎么设置状态码
  4. 互联网基础知识_数字化工业网络—工业互联网的网络技术.pptx
  5. 一张图理解buffer与cache
  6. Mac eclipse找不到source的解决办法
  7. Swift -- 7.3 类和结构体
  8. 黑客利用智能灯泡窃取用户数据!
  9. 玉林师范学院计算机宿舍专业,玉林师范学院宿舍怎么样 住宿条件好不好
  10. 层级分类(续)-使用B-CNN(Branch CNN)实现
  11. Eclipse 9.x 10.0 之破解详细步骤
  12. 中国单体酒店联盟沃家商务酒店(长沙)简介
  13. python平行四边形代码_python 已知平行四边形三个点,求第四个点的案例
  14. 肖邦 《第一钢琴协奏曲》E小调,OP.11 个人赏析
  15. 关于NGS中“depth”和“coverage”的理解
  16. SlidesJS基本使用方法和官方文档解释 【Jquery幻灯片插件 Jquery相册插件】
  17. 借记来帐,借记往账,贷记来帐,贷记往账
  18. 漂亮的表格样式(使用CSS样式表控制表格样式)
  19. 做为一名大数据新手,如何成为大数据工程师?附上学习路线
  20. Python销售管理系统

热门文章

  1. com.sun:tools
  2. java8 stream流操作的flatMap(流的扁平化)
  3. Canal中间件学习总结
  4. 并发编程学习之线程池工作原理
  5. 构建自定义的同步工具
  6. 现代浏览器探秘(part 1):架构
  7. three 实现绕物体旋转,卫星绕星球旋转
  8. Revit二次开发: 文件损坏
  9. Java程序发生异常就挂了吗?
  10. Unity3D游戏开发从零单排(三) - 极速创建狂拽酷炫的游戏地形