内存图与 JS 世界
操作系统常识
一切都运行在内存里
开机
- 操作系统在C盘里(macOS的在根目录下多个目录里)
- 当你按下开机键,主板通电,开始读取固件
- 固件就是固定在主板上的存储设备,里面有开机程序
- 开机程序会将文件里的操作系统加载到内存中运行
操作系统(以Linux为例)
- 首先加载操作系统内核
- 如何启动初始化进程,编号为1,每个进程都有编号
- 启动系统服务:文件、安全、联网
- 等待用户登录:输入密码登录/ ssh登录
- 登录后,运行shell,用户就可以和操作系统对话了
- bash 是一种shell,图形化界面可认为是一种shell
打开浏览器
Chrome.exe
- 你双击Chrome图标,就会运行chrome.exe文件
- 开启Chrome进程,作为主进程
- 主进程会开启一些辅助进程,如网络服务、GPU加速
- 每次新建一个网页,就有可能开启一个子进程
浏览器的功能
- 发起请求,下载HTML,解析HTML,下载CSS,解析CSS,渲染页面,下载JS,执行JS等
- 功能模块:用户界面、渲染引擎、JS引擎、存储等
- 上面功能模块一般各自处于不同的线程(比进程更小的单位)
- 如果进程是车间,那么线程就是车间里面的流水线
JS是单线程的,DOM是跨线程的操作会慢一点。
JS引擎
JS引擎举例
- Chrome用的是V8引擎,C++编写
- 网景用的是SpiderMonkery,后被Firefox使用,C++
- Safari用的是JavaScriptCore
- IE用的是Chakra(JScript9)
- Edge用的是 Chakra(JavaScript)
- Node.js 用的是 V8引擎
主要功能
- 编译:把JS代码翻译为机器能执行的字节码或机器码
- 优化:改写代码,使其更高效
- 执行:执行上面的字节码或机器码
- 垃圾回收:把JS用完的内存回收,方别之后再次使用
执行JS代码
准备工作
- 提供API:window、document、setTimeout
- 上面这些东西都不是JS自身具备的动能
- 我们将这些功能称为运行环境runtime env
- 一旦把JS放进页面,就开始执行JS
总结
- JS代码在哪里运行?
- 答:内存
内存图
瓜分内存
红色区域
作用
- 红色专门用来存放数据,目前只研究该区域
- 红色区域并不存变量名
- 每种浏览器的分配规则并不一样
- 上图的区域并不完整,还有[调用栈]、[任务队列]等区域
Stack 和 Heap
- 红色区域分为Stack栈和Heap 堆
- Stack区特点:每个数据顺序存放
- Heap区特点:每个数据随机存放
Stack和Heap举例
代码
var a = 1
var b = a
var person = {name:'frank',child:{name:'jack'}}
var person2 = person
规律
- 数据分两种 非对象 和 对象
- 非对象都存在 Stack
- 对象都存在 Heap
- = 号总是会把右边的东西复制到左边(不存在什么传值和传址)
区分值和地址:不会画内存图的人才需要做这件事
对象被篡改了
代码
var person = {name:'frank',child:{name:'jack'}}
var person2 = person
person2.name = 'ryan'
console.log(person.name) // 'ryan'
当你执行JS的时候浏览器准备了些什么?
提供了window
还有什么
要有console
- 于是有了console,并挂到window上
要有document
- 于是有了document,并且挂到window上
要有对象
- 于是有了Object,并挂到window上
- var person = {} 等价于 var person = new Object()
要有数组(一种特殊对象)
- 于是就有了Array,并且挂到window上
- var a = [1,2,3] 等价于 var a = new Array(1,2,3)
要有函数(一种特殊的对象)
- 于是就有了Function,并且挂到window上
- function f{} 等价于 var f = new Function()
提问
- 为什么有 var a = [], 还要提供 var a = new Array()呢
- 答:因为后者是正规写法,但是没人用。前者不正规,但是好用
- 为什么有function f() {},还要提供var f = new Function 写法呢
- 答:同上
挂在window上的东西可以在任何地方直接用。
把window用内存图画出来
简单画法
细节
关于window
- window 变量和 window对象是两个东西
- window 变量是一个容器。存放window对象的地址
- window对象是Heap里的一坨数据
- 不信的话,可以让var x = window, 那么这个x就指向了window对象,window变量就没用了
- 不要这样写,会把别人绕晕
同理
- console 和 console对象不是同一个东西
- Object 和 Object函数对象不是同一个东西
- 前者是内存地址,后者是一坨内存
JS三座大山之原型链
图里的prototype是干什么用的
打印出来看看:console.dir(window.Object.prototype) ,window可以省略
代码
- var obj = {}
- obj.toString()
为什么不报错?为什么可以运行,空对象为什么有toString()?
答
- obj有一个隐藏属性
- 隐藏属性存储了Object.prototype对象的地址
- obj.toString() 发现 obj 上没有toString
- 就去隐藏属性对应的对象里面找
- 于是就找到了Object.prototype.toString
还有一个问题
代码
var obj2 = {}
obj2.toString()
obj 和 obj2 有什么联系
相同点:都可以调用.toString()
不同点:地址不同 obj != obj2,可以拥有不同的属性,反过来说是什么意思呢?
XXX.prototype存储了 XXX 对象的共同属性,这就是原型
原型有什么好处
如果没有原型
声明一个对象
var obj = {
toString: window.Object.prototype.toString,
hasOwnPropertyOf: window.Object....
}
obj.toString()
var obj2 = {
toString: window.Object.prototype.toString,
hasOwnPropertyOf: window.Object....
}
这样就会很累~
原型让我们无需重复声明共有属性,省代码,省内存。
每个对象都有一个隐藏属性,指向原型(对象)
如果没有这个隐藏属性,那么obj就不知道自己共有属性在哪。
这个隐藏属性的名字叫做:__proto__
prototype 和__proto__
区别是什么?
- 都存着原型的地址
- 只不过prototype挂在函数上
__proto__
挂在每个新生成的对象上
问
以下代码为什么不报错?为什么可以运行?
var arr = [1,2,3]
arr.join('-')
答
- arr 有一个隐藏属性
- 隐藏属性存储了Array.prototype对象的地址
JS原型链世界