㈠ Hermes源码分析(二)——解析字节码
前面一节 讲到字节码序列化为二进制是有固定的格式的,这里我们分析一下源码里面是怎么处理的
这里可以看到首先写入的是魔数,他的值为
对应的二进制见下图,注意是小端字节序
第二项是字节码的版本,笔者的版本是74,也即 上图中的4a00 0000
第三项是源码的hash,这里采用的是SHA1算法,生成的哈希值是160位,因此占用了20个字节
第四项是文件长度,这个字段是32位的,也就是下图中的为0aa030,转换成十进制就是696368,实际文件大小也是这么多
后面的字段类似,就不一一分析了,头部所有字段的类型都可以在 BytecodeFileHeader.h 中看到,Hermes按照既定的内存布局把字段写入后再序列化,就得到了我们看到的字节码文件。
这里写入的数据很多,以函数头的写入为例,我们调用了visitFunctionHeader方法,并通过byteCodeMole拿到函数的签名,将其写入函数表(存疑,在实际的文件中并没有看到这一部分)。注意这些数据必须按顺序写入,因为读出的时候也是按对应顺序来的。
我们知道react-native 在加载字节码的时候需要调用hermes的prepareJavaScript方法, 那这个方法做了些什么事呢?
这里做了两件事情:
1. 判断是否是字节码,如果是则调用createBCProviderFromBuffer,否则调用createBCProviderFromSrc,我们这里只关注createBCProviderFromBuffer
2.通过BCProviderFromBuffer的构造方法得到文件头和函数头的信息(populateFromBuffer方法),下面是这个方法的实现。
BytecodeFileFields的populateFromBuffer方法也是一个模版方法,注意这里调用populateFromBuffer方法的是一个 ConstBytecodeFileFields对象,他代表的是不可变的字节码字段。
细心的读者会发现这里也有visitFunctionHeaders方法, 这里主要为了复用visitBytecodeSegmentsInOrder的逻辑,把populator当作一个visitor来按顺序读取buffer的内容,并提前加载到BytecodeFileFields里面,以减少后面执行字节码时解析的时间。
Hermes引擎在读取了字节码之后会通过解析BytecodeFileHeader这个结构体中的字段来获取一些关键信息,例如bundle是否是字节码格式,是否包含了函数,字节码的版本是否匹配等。注意这里我们只是解析了头部,没有解析整个字节码,后面执行字节码时才会解析剩余的部分。
evaluatePreparedJavaScript这个方法,主要是调用了HermesRuntime的 runBytecode方法,这里hermesPrep时上一步解析头部时获取的BCProviderFromBuffer实例。
runBytecode这个方法比较长,主要做了几件事情:
这里说明一下,Domain是用于垃圾回收的运行时模块的代理, Domain被创建时是空的,并跟随着运行时模块进行传播, 在运行时模块的整个生命周期内都一直存在。在某个Domain下创建的所有函数都会保持着对这个Domain的强引用。当Domain被回收的时候,这个Domain下的所有函数都不能使用。
未完待续。。。
㈡ 深入理解React16之:(一).Fiber架构
React16虽然出了一阵子了。刚出来的时候,粗略看了一遍更新文档。以为没什么大的改动,也听说项目从react15-16的升级过度可以很平滑,再加上项目改版上线一直比较频繁,所以一直还用的15.6的版本。
偶然在知乎看到@程墨Morgan大神的live,便抱着好奇心和学习的心态报名了,受益良多。
我理解的Fiber架构:
在我之前的一篇文章有简单介绍, 阅读react源码--记录:1.1 问题记录
下面从一个具体实例理解一下,再加上我画了图,应该很好理解啦~(图画的有点渣)
假如有A,B,C,D组件,层级结构为:
我们知道组件的生命周期为:
挂载阶段:
那么在挂载阶段,A,B,C,D的生命周期渲染顺序是如何的呢?
以render()函数为分界线。从顶层组件开始,一直往下,直至最底层子组件。然后再往上。
组件update阶段同理。
————————
前面是react16以前的组建渲染方式。这就存在一个问题,
好似一个潜水员,当它一头扎进水里,就要往最底层一直游,直到找到最底层的组件,然后他再上岸。在这期间, 岸上发生的任何事,都不能对他进行干扰,如果有更重要的事情需要他去做(如用户操作),也必须得等他上岸
看一下fiber架构 组建的渲染顺序
潜水员会每隔一段时间就上岸,看是否有更重要的事情要做。
加入fiber的react将组件更新分为两个时期
这两个时期以render为分界,
phase1的生命周期是可以被打断的,每隔一段时间它会跳出当前渲染进程,去确定是否有其他更重要的任务。此过程,React 在 workingProgressTree (并不是真实的virtualDomTree)上复用 current 上的 Fiber 数据结构来一步地(通过requestIdleCallback)来构建新的 tree,标记处需要更新的节点,放入队列中。
phase2的生命周期是不可被打断的,React 将其所有的变更一次性更新到DOM上。
这里最重要的是phase1这是时期所做的事。因此我们需要具体了解phase1的机制。
这样的话,就和react16版本之前有很大区别了,因为可能会被执行多次,那么我们最好就得保证phase1的生命周期 每一次执行的结果都是一样的 ,否则就会有问题,因此, 最好都是纯函数。
对了,程墨大神还提到一个问题,饥饿问题,即如果高优先级的任务一直存在,那么低优先级的任务则永远无法进行,组件永远无法继续渲染。这个问题facebook目前好像还没解决,但以后会解决~
所以,facebook在react16增加fiber结构,其实并不是为了减少组件的渲染时间,事实上也并不会减少,最重要的是现在可以使得一些更高优先级的任务,如用户的操作能够优先执行,提高用户的体验,至少用户不会感觉到卡顿~
㈢ create-react-app build 打包隐藏源码
在使用 create-react-app 时,打包生产环境 npm run build ,浏览器打开后仍然是可以看到源码的。
在这里以新建一个默认项目为例:
项目根目录新建 .env.proction 文件,内容如下:
然后重新打包,浏览器打开后就看不到源码啦。
为了探究原理,执行 eject 后,可以看到webpack配置中有这么一段
这里的 process.env.GENERATE_SOURCEMAP 控制着是否捎带源码。所以我们可以配置环境变量 GENERATE_SOURCEMAP=false 即可。
当执行 build 时,将按顺序优先寻找 .env.proction.local , .env.proction , .env.local , .env 文件来配置环境变量,所以就有了上面的操作。
更多关于环境变量的信息可查看 Adding Custom Environment Variables 。
㈣ 如何用reactjs构建一个完整的前端页面
用reactjs构建一个完整的前端页面的步骤:
准备:React 的安装包,建议去官网下载安装
1、使用 React 的网页源码,结构大致如下:
<!DOCTYPE html><html><head> <script src="../build/react.js"></script> <script src="../build/react-dom.js"></script> <script src="../build/browser.min.js"></script></head><body> <div id="example"></div> <script type="text/babel"> // **用户代码 ** </script></body></html>
上面代码有两个地方需要注意。
首先,最后一个<script>标签的type属性为text/babel。这是因为 React 独有的 JSX 语法,跟 JavaScript 不兼容。凡是使用 JSX 的地方,都要加上type="text/babel"。
其次,上面代码一共用了三个库:react.js、react-dom.js和Browser.js,它们必须首先加载。其中,react.js是 React 的核心库,react-dom.js是提供与 DOM 相关的功能,Browser.js的作用是将 JSX 语法转为 JavaScript 语法,这一步很消耗时间,实际上线的时候,应该将它放到服务器完成。
2、将src子目录的js文件进行语法转换,转码后的文件全部放在build子目录。
$ babel src --out-dir build
3、渲染转换成html节点,以方便操作dom:
ReactDOM.render 是 React 的最基本方法,用于将模板转为 HTML 语言,并插入指定的 DOM 节点。
这里以插入hello world为例来说明
ReactDOM.render(<h1>Hello, world!</h1>,document.getElementById('example'));
4、运行结果如下: