JS 原型链 Note-FrontEnd-10

219 阅读6分钟

主要是为了理解 JS 原型链,内容包括进程和线程、Chrome 浏览器的进程和线程、JS 引擎、JS 中的内存、JS 原型。

一、进程和线程

1. 进程和线程的类比

进程(process)和线程(thread)是操作系统的基本概念,可以简单做一个类比。

计算机的核心是 CPU,它承担了所有的计算任务,它就像一座工厂,时刻在运行。假定工厂的电力有限,一次只能供给一个车间使用,也就是说,一个车间开工的时候,其他车间都必须停工,背后的含义就是,单个 CPU 一次只能运行一个任务。

进程就好比工厂的车间,它代表 CPU 所能处理的单个任务,任一时刻,CPU 总是运行一个进程,其他进程处于非运行状态。

一个车间里,可以有很多工人,他们协同完成一个任务。

线程就好比车间里的工人,一个进程可以包括多个线程。

2. 进程和线程的区别

  • 进程是操作系统分配资源的最小单位,线程是程序执行的最小单位
  • 一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线
  • 进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)及一些进程级的资源(如打开文件和信号)
  • 调度和切换:线程上下文切换比进程上下文切换要快得多

二、Chrome 浏览器的进程和线程

1. 浏览器多进程架构

Chrome 浏览器使用多个进程来隔离不同的网页,因此在 Chrome 中打开一个网页相当于起了一个进程。

在浏览器刚被设计出来的时候,那时的网页非常的简单,每个网页的资源占有率是非常低的,因此一个进程处理多个网页时可行的。然后在今天,大量网页变得日益复杂。把所有网页都放进一个进程的浏览器面临在健壮性,响应速度,安全性方面的挑战。因为如果浏览器中的一个 Tab 网页崩溃的话,将会导致其他被打开的网页应用。另外相对于线程,进程之间是不共享资源和地址空间的,所以不会存在太多的安全问题,而由于多个线程共享着相同的地址空间和资源,所以会存在线程之间有可能会恶意修改或者获取非授权数据等复杂的安全问题。

2. 浏览器内核

简单来说浏览器内核是通过取得页面内容、整理信息(应用 CSS)、计算和组合最终输出可视化的图像结果,通常也被称为渲染引擎。从上面我们可以知道,Chrome 浏览器为每个 Tab 页面单独启用进程,因此每个 Tab 网页都有由其独立的渲染引擎实例。

3. 浏览器内核是多线程

浏览器内核是多线程,在内核控制下各线程相互配合以保持同步,一个浏览器通常由以下常驻线程组成:

  • GUI 渲染线程
  • JavaScript 引擎线程
  • 定时触发器线程
  • 事件触发线程
  • 异步 http 请求线程

三、JS 引擎

Javascript 引擎,也可以称为 JS 内核,主要负责处理 Javascript 脚本程序,例如 Chrome 使用的 V8 引擎,主要功能如下:

  • 编译:把 JS 代码翻译为机器能执行的字节或机器码
  • 优化:改写代码,使其更高效
  • 执行:执行上面的字节码或者机器人
  • 垃圾回收:把 JS 用完的内存回收,方便再次使用

Javascript 是单线程的。

这是因为 Javascript 这门脚本语言诞生的使命所致:JavaScript 为处理页面中用户的交互,以及操作 DOM 树、CSS 样式树来给用户呈现一份动态而丰富的交互体验和服务器逻辑的交互处理。如果 JavaScript 是多线程的方式来操作这些 UI DOM,则可能出现 UI 操作的冲突; 如果 Javascript 是多线程的话,在多线程的交互下,处于 UI 中的 DOM节点就可能成为一个临界资源,假设存在两个线程同时操作一个 DOM,一个负责修改一个负责删除,那么这个时候就需要浏览器来裁决如何生效哪个线程的执行结果。当然我们可以通过锁来解决上面的问题。但为了避免因为引入了锁而带来更大的复杂性,Javascript 在最初就选择了单线程执行。

四、JS 中的内存

简单给分配给 JS 引擎的内存分一下区域,代码区、环境区、Stack 区、Heap 区。

  • 代码区存放我们写的 JS 代码。

  • 环境区存放我们声明的变量,其中内置有一个 window 全局变量。

  • Stack 区存放变量的值或地址,当然这里存了一个指向 window 对象的地址。

  • Heap 区存放地址所指向的对象,当然这里存了一个 window 对象。

五、JS 原型

这个需要举例子理解,光讲原理很难解释。

1. 举例子

首先,window 对象下有很多属性,console、Object、Array 等等,window.Object 是一个函数对象,window.Object 这个函数对象有一个重要属性是 prototype,window.Object.prototype 里面有这些属性 toString(函数)、valueOf(函数)等等。

然后,我们创建一个对象,并调用一个函数。

var obj = {}
obj.toString()

它运行成功了,为什么呢?obj 对象里没有 toString 函数,怎么运行成功的呀?

因为新创建的 obj 里有一个隐藏属性 __proto__,它指向一个地址,这个地址和 window.Object.prototype 指向的地址是同一个地址,而 window.Object.prototype 下有 toString 属性,所以在运行 obj.toSring() 的时候,JS 会机智地从隐藏属性指向的地址里找到 toString(),于是运行成功了。

一个重要概念,每个对象都有__proto__这样的隐藏属性

2. 再解读

当我们「读取」 obj.toString 时,JS 引擎会做下面的事情:

  1. 看看 obj 对象本身有没有 toString 属性。没有就走到下一步。
  2. 看看obj.__proto__对象有没有 toString 属性,发现obj.__proto__有 toString 属性,于是找到了
  3. 如果obj.__proto__没有,那么浏览器会继续查看obj.__proto__.__proto__
  4. 如果obj.__proto__.__proto__ 也没有,那么浏览器会继续查看
  5. 直到找到toString或者__proto__为 null。

上面的过程,就是「读」属性的「搜索过程」, 而这个「搜索过程」,是连着由__proto__组成的链子一直走的。

这个链子,就叫做「原型链」,prototype 指向的内容就是原型啦。

同理,创建数组的时候,该对象下有一个隐藏属性__proto__,我们可以使用 window.Array.prototype 下的属性。

3. 图说天下

简单描绘一下原型链~

原型链

「资料来源:阮一峰的网络日志-2013年4月24日浏览器进程?线程?傻傻分不清!JS 中 __proto__和 prototype 存在的意义是什么?