JavaScript 运行原理

前言

正文从这开始~~

JavaScript V8 引擎是如何与内存管理,调用堆栈,线程和事件循环协同工作的。

内存

Orinoco logo: V8 的垃圾回收器

每当您在 JavaScript 中定义变量,常量,对象等时,都需要一些地方来存储它。这个地方就是内存

当遇到语句 var a = 10 时,内存将分配一个位置来存储 a 的值。

可用内存是有限的,复杂的程序可能包含许多变量和嵌套对象。因此合理地利用可用内存至关重要。

与像 C 这样需要显式分配和释放内存的语言不同,JavaScript 提供了自动垃圾收集的功能。一旦对象/变量脱离上下文并且不再使用,它占用的内存将被回收并返回空闲内存池。

在 V8 中,垃圾收集器名为 Orinoco,它能高效地完成上面介绍的过程。

标记扫描算法

标记扫描算法

为了确定可以从内存中安全删除的对象,使用了这种简单有效的算法。该算法的名称描述了其工作原理;将对象标记为可访问/不可访问,并清除不可访问的对象。

垃圾收集器会定期从根对象或全局对象开始,然后遍历它们所引用的对象,然后再遍历这些引用所引用的对象,依此类推。然后清除所有无法访问的对象。

内存泄漏

尽管垃圾回收是高效的,但这并不意味着开发人员可以对内存管理不管不顾。管理内存是一个复杂的过程,确定哪一块内存是不需要的不能完全依赖算法。

内存泄漏是指程序使用过的一部分内存,现在不再使用了,但这些内存并未返回到内存池。

以下是一些导致程序内存泄漏的常见错误。

全局变量:如果您持续地创建全局变量,即使您不使用它们,它们也会在程序执行过程中始终存在。如果这些变量是深层嵌套的对象,则会浪费大量内存。

var a = { ... }
var b = { ... }
function hello() {
  c = a;  // this is the global variable that you aren't aware of.
}

如果您访问未声明的变量,则将在全局范围内创建一个变量。在上面的示例中,c 是您没有使用 var 关键字隐式创建的全局变量/全局对象。

事件监听:假设您在网页中创建了大量监听事件来实现交互或动画,当用户跳转到单页应用程序中的其他页面时,而您忘记了移除它们,就可能会发生内存泄漏。因为当用户在这些页面之间来回跳转时,这些事件监听会不断累加。

var element  = document.getElementById('button');
element.addEventListener('click', onClick)

定时器:当引用这些闭包中的对象时,垃圾收集器将永远不会清除被引用的对象,直到闭包本身被清除。

setInterval(() => {
// reference objects
}
// now forget to clear the interval.
// you just created a memory leak!

被删除的 DOM 节点:有点类似于全局变量内存泄漏,并且非常常见。DOM 节点存储在 Object Graph memory 和 DOM tree 中。通过一个示例可以更好地说明这种情况。

var terminator = document.getElementById('terminate');
var badElem = document.getElementById('toDelete');
terminator.addEventListener('click', function()  {memory
  badElem.remove();
});

在点击 id ='terminate' 的按钮后,toDelete 节点将从 DOM tree 中删除。但是,由于事件监听中引用了该对象 badElem,因此会认为该对象分配的内存仍在被使用中。

var terminator = document.getElementById('terminate');
terminator.addEventListener('click', function()  {
var badElem = document.getElementById('toDelete');
  badElem.remove();
});

现在,badElem 变量被定义为一个局部变量,当删除操作完成时,垃圾回收器可以回收它的内存。

调用堆栈

堆栈是遵循 LIFO(后进先出)方法来存储和访问数据的数据结构。对 JavaScript 引擎来说,堆栈用于记住函数中最后执行的命令的位置。

function multiplyByTwo(x) {
return x*2;
}
function calculate() {
const sum = 4+ 2;
return multiplyByTwo(sum);
}
calculate()
var hello = "some more code follows"
  • 首先,引擎知道程序中有两个函数。

  • 运行calculate()函数。

  • 在调用堆栈上弹出calculate函数并计算总和。

  • 运行multiplyByTwo()函数。

  • 调用栈上弹出multiplyByTwo函数,并执行算术运算x * 2。

  • 返回该值时,从堆栈中弹出multiplyByTwo(),然后返回calculate()函数。

  • 在calculate()函数返回时,从堆栈中弹出calculate,然后继续执行代码。

堆栈溢出

在不弹出堆栈的情况下,连续压栈量取决于堆栈的大小。如果您继续压栈达到堆栈容量的极限,将导致堆栈溢出,此时 chrome 浏览器 会报错,同时生成堆栈快照,也称为堆栈帧。

递归:当函数调用自身时,称为递归。在您想减少算法执行的时间(时间复杂度),但是其它方法理解和实现起来很复杂时,递归就显得非常有用。

在下面这个示例中,return 1 语句永远不会被执行,并且 lonely 函数会不断调用自身而不会返回,最终导致堆栈溢出。

function lonely() {
if(false) {
return1;  // the base case
}
 lonely();   // the recursive call
}
JavaScript 为什么是单线程的?

多个线程表示您可以同时独立执行程序的多个部分。确定一种语言是单线程还是多线程的最简单方法是看它拥有有多少个调用堆栈。JS 只有一个,所以它是单线程语言。

您可能会想这不是瓶颈吗?如果我运行多个耗时的操作,也称为阻塞操作(如HTTP请求),那么该程序将必须等待每个操作的响应完成后,再执行下一个操作。

为了解决这个问题,我们需要一种异步执行任务的方法来解决单线程的弊端,事件循环为此而生。

事件循环

目前,上面提到的大部分内容都被 V8 囊括,但是如果您在 V8 代码库中搜索诸如 setTimeout 或 DOM 之类的内容的实现,那你可能什么都找不到。因为除了运行时引擎外,JS 还包含 Web API 模块,这些 API 是浏览器提供来扩展 JS 功能的。

结语

编写一门编程语言还有很多工作要做,而且每年它的实现方式可能也在不断变化。我希望这两篇文章可以帮助您成为更好的 JS 程序员,并接纳 JS 怪异的部分。现在,您应该习惯使用V8,事件循环,调用堆栈等术语。

大多数和我一样人学习 JS 都是从学习一个新的框架开始。我觉得我们现在应该对引擎内部执行的东西有一些了解,这将有助于我们编写出更好的代码。

关于本文译者:@梁天培译文:https://juejin.im/post/5da17d7cf265da5b7b3ee8f5作者:@Priyesh Patel原文:https://blog.bitsrc.io/how-does-javascript-work-part-2-40cc15360bc

 好文 推 荐 

☞ CSS第四级选择器

☞ 浅谈:前端如何赋能业务

☞ 前端工程师的产品思维

好文章,我 在看 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: UniApp 是一个使用 Vue.js 开发跨平台应用的框架。它的运行原理是基于 HBuilderX 编辑器将开发者编写的代码转换成原生平台的代码,然后运行在各个平台上。 首先,开发者使用 HBuilderX 编辑器创建一个 UniApp 项目,并选择所要开发的平台,如微信小程序、App、H5 等。然后,开发者使用 Vue.js 组件和语法编写应用的各个页面和功能。 UniApp 在编译的过程中,通过将开发者编写的代码转换为各个平台的运行代码,使得应用在不同平台上都能够正常运行。例如,对于微信小程序,UniApp 会将代码转换为符合微信小程序标准的 wxml、wxss 和 js 文件;对于 App,UniApp 会将代码转换为原生的 Android 或 iOS 代码。 UniApp 还提供了一套跨平台的 API,使得开发者能够方便地调用原生平台的功能和接口。比如,开发者可以使用 uni.request 方法来发起网络请求,无论是在微信小程序还是 App 中,都能够正常使用。 最后,UniApp 使用各个平台的打包工具将应用打包成可执行文件或可发布的代码文件。对于微信小程序,UniApp 会生成一个可以导入到微信开发者工具中预览和发布的项目文件夹;对于 App,UniApp 会生成原生的 Android 或 iOS 项目,开发者可以进行进一步的编译和发布。 综上所述,UniApp 的运行原理是将开发者编写的代码转换为各个平台的原生代码,并提供跨平台的 API,使得应用能够在不同平台上运行和发布。这种方式减少了开发者的工作量,提高了开发效率,使得开发跨平台应用变得更加简单和便捷。 ### 回答2: UniApp是一种基于Vue.js框架的跨平台开发解决方案,可以同时开发iOS、Android和Web应用。UniApp的运行原理主要是基于两个关键技术点:编译和渲染。 首先,UniApp应用在开发阶段使用Vue.js框架进行编写,开发者使用Vue的语法和相关技术进行业务和界面的构建。UniApp将Vue.js框架进行了扩展和适配,使其能够同时运行在不同的平台上。 在编译阶段,UniApp会将开发者编写的Vue代码转换为原生的JavaScript代码。它会根据应用的配置文件和平台的特性,进行相应的编译优化和转换。通过这个过程,UniApp将统一的Vue代码转换为特定平台的原生代码。 在渲染阶段,UniApp使用了各个平台自带的渲染引擎进行UI的渲染。对于iOS和Android,UniApp使用了各自的原生渲染引擎进行渲染;对于Web平台,UniApp使用了WebView进行渲染。UniApp将编译生成的原生代码交给相应的渲染引擎进行解析和渲染,最终呈现出用户界面。 除了编译和渲染外,UniApp还提供了一套统一的API和组件库。这些API和组件库可以在不同平台上实现一致的功能和样式,开发者可以直接调用这些API和组件进行业务的开发和界面的构建。 综上所述,UniApp的运行原理是通过编译和渲染来实现跨平台的应用开发。通过编译将统一的Vue代码转换为特定平台的原生代码,并使用各个平台的渲染引擎进行UI的渲染,最终实现在不同平台上运行的应用。这种原理使得开发者可以用一套代码同时开发多个平台的应用,提高了开发效率和代码复用性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值