浏览器工作原理与实践
李兵
前盛大创新院高级研究员
立即订阅
6167 人已学习
课程目录
已完结 42 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 参透了浏览器的工作原理,你就能解决80%的前端难题
免费
宏观视角下的浏览器 (6讲)
01 | Chrome架构:仅仅打开了1个页面,为什么有4个进程?
02 | TCP协议:如何保证页面文件能被完整送达浏览器?
03 | HTTP请求流程:为什么很多站点第二次打开速度会很快?
04 | 导航流程:从输入URL到页面展示,这中间发生了什么?
05 | 渲染流程(上):HTML、CSS和JavaScript,是如何变成页面的?
06 | 渲染流程(下):HTML、CSS和JavaScript,是如何变成页面的?
浏览器中的JavaScript执行机制 (5讲)
07 | 变量提升:JavaScript代码是按顺序执行的吗?
08 | 调用栈:为什么JavaScript代码会出现栈溢出?
09 | 块级作用域:var缺陷以及为什么要引入let和const?
10 | 作用域链和闭包 :代码中出现相同的变量,JavaScript引擎是如何选择的?
11 | this:从JavaScript执行上下文的视角讲清楚this
V8工作原理 (3讲)
12 | 栈空间和堆空间:数据是如何存储的?
13 | 垃圾回收:垃圾数据是如何自动回收的?
14 | 编译器和解释器:V8是如何执行一段JavaScript代码的?
浏览器中的页面循环系统 (6讲)
15 | 消息队列和事件循环:页面是怎么“活”起来的?
16 | WebAPI:setTimeout是如何实现的?
17 | WebAPI:XMLHttpRequest是怎么实现的?
18 | 宏任务和微任务:不是所有任务都是一个待遇
19 | Promise:使用Promise,告别回调函数
20 | async/await:使用同步的方式去写异步代码
浏览器中的页面 (8讲)
21 | Chrome开发者工具:利用网络面板做性能分析
22 | DOM树:JavaScript是如何影响DOM树构建的?
23 | 渲染流水线:CSS如何影响首次加载时的白屏时间?
24 | 分层和合成机制:为什么CSS动画比JavaScript高效?
25 | 页面性能:如何系统地优化页面?
26 | 虚拟DOM:虚拟DOM和实际的DOM有何不同?
27 | 渐进式网页应用(PWA):它究竟解决了Web应用的哪些问题?
28 | WebComponent:像搭积木一样构建Web应用
浏览器中的网络 (3讲)
29 | HTTP/1:HTTP性能优化
30|HTTP/2:如何提升网络速度?
31|HTTP/3:甩掉TCP、TLS 的包袱,构建高效网络
浏览器安全 (5讲)
32 | 同源策略:为什么XMLHttpRequest不能跨域请求资源?
33 | 跨站脚本攻击(XSS):为什么Cookie中有HttpOnly属性?
34 | CSRF攻击:陌生链接不要随便点
35 | 安全沙箱:页面和系统之间的隔离墙
36 | HTTPS:让数据传输更安全
结束语 (1讲)
结束语 | 大道至简
课外加餐 (4讲)
加餐一|浏览上下文组:如何计算Chrome中渲染进程的个数?
加餐二|任务调度:有了setTimeOut,为什么还要使用rAF?
加餐三|加载阶段性能:使用Audits来优化Web性能
加餐四|页面性能工具:如何使用Performance?
浏览器工作原理与实践
登录|注册

12 | 栈空间和堆空间:数据是如何存储的?

李兵 2019-08-31
对于前端开发者来说,JavaScript 的内存机制是一个不被经常提及的概念 ,因此很容易被忽视。特别是一些非计算机专业的同学,对内存机制可能没有非常清晰的认识,甚至有些同学根本就不知道 JavaScript 的内存机制是什么。
但是如果你想成为行业专家,并打造高性能前端应用,那么你就必须要搞清楚 JavaScript 的内存机制了。
其实,要搞清楚 JavaScript 的内存机制并不是一件很困难的事,在接下来的三篇文章(数据在内存中的存放、JavaScript 处理垃圾回收以及 V8 执行代码)中,我们将通过内存机制的介绍,循序渐进带你走进 JavaScript 内存的世界。
今天我们讲述第一部分的内容——JavaScript 中的数据是如何存储在内存中的。虽然 JavaScript 并不需要直接去管理内存,但是在实际项目中为了能避开一些不必要的坑,你还是需要了解数据在内存中的存储方式的。

让人疑惑的代码

首先,我们先看下面这两段代码:
function foo(){
var a = 1
var b = a
a = 2
console.log(a)
console.log(b)
}
foo()
function foo(){
var a = {name:"极客时间"}
var b = a
a.name = "极客邦"
console.log(a)
console.log(b)
}
foo()
若执行上述这两段代码,你知道它们输出的结果是什么吗?下面我们就来一个一个分析下。
执行第一段代码,打印出来 a 的值是 2,b 的值是 1,这没什么难以理解的。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《浏览器工作原理与实践》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(41)

  • 蓝配鸡
    希望老师再开一个专栏专门讲JS,同学们顶上去
    2019-09-04
    52
  • 仰5卧0起坐
    1、从内存模型角度分析执行代码的执行流程第二步看,在堆空间创建closure(foo)对象,它是存储在foo函数的执行上下文中的。
    那么closure(foo)创建开始时是空对象,执行第三步的时候,才会逐渐把变量添加到其中。
    2、当foo函数执行结束后,foo的执行上下文是不是销毁了?如果销毁了,产生一下两个疑问:
        a、如果foo函数执行上下文销毁了,closure(foo)并没有销毁,那foo函数执行上下文是怎么销毁的呢?就比如销毁一个盒子,盒子毁里,里面的东西应该也是毁掉的
        b、既然closure(foo)既然没有销毁,那它存储在堆中的什么地方呢?必定它所依赖的foo执行上下文已经不存在了

    作者回复: 关于foo函数执行上下文销毁过程:foo函数执行结束之后,当前执行状态的指针下移到栈中的全局执行上下文的位置,foo函数的执行上下文的那块数据就挪出来,这也就是foo函数执行上下文的销毁过程,这个文中有提到,你可以参考“调用栈中切换执行上下文状态“图。

    第二个问题:innerBar返回后,含有setName和getName对象,这两个对象里面包含了堆中的closure(foo)的引用。虽然foo执行上下文销毁了,foo函数中的对closure(foo)的引用也断开了,但是setName和getName里面又重新建立起来了对closure(foo)引用。

    你可以:
    1:打开“开发者工具”
    2:在控制台执行上述代码
    3:然后选择“Memory”标签,点击"take snapshot" 获取V8的堆内存快照。
    4:然后“command+f"(mac) 或者 "ctrl+f"(win),搜索“setName”,然后你就会发现setName对象下面包含了 raw_outer_scope_info_or_feedback_metadata,对闭包的引用数据就在这里面。

    2019-08-31
    5
    34
  • 李懂
    这里讲解的真好,闭包果然不是在栈中存储的,消除了我一直以来的疑惑,上下文销毁后闭包去哪了!
    现在还有个问题,setname方法是通过什么引用闭包的,是存在内部变量么?
    2019-08-31
    1
    8
  • 柒月
    最近面试老问这个问题,什么是深拷贝和浅拷贝以及如何实现一个深拷贝?
    1、JSON.parse(JSON.stringify(obj))
    2、递归遍历对象
    3、Object.assigin() 这种方法只能拷贝一层,有嵌套的情况就不适用了。
    2019-08-31
    3
    7
  • Marvin
    function copy(dest){
      if(typeof dest === 'object'){
        if(!dest) return dest; // null
        var obj = dest.constructor(); // Object/Array
        for(var key in dest){
          obj[key] = copy(dest[key])
        }
        return obj;
      } else {
        return dest;
      }
    }

    作者回复: 挺好 继续

    2019-08-31
    3
    7
  • XWL
    老师,我有几个疑问:
    1、Function 函数类型也是继承于Object,声明函数后是不是也是存在堆空间中的,那么浏览器编译函数时是不是会同时创建执行上下文和向堆空间中压入一个值
    2、function a(){
                var b = 1;
                var c = {
                        d: 2
                };
         }
         当 a 的执行上下文销毁后,c 对象在堆空间中的引用会跟着销毁么,将 c 返回出去或不返回,会不会是不一样的情况

    作者回复: 函数就是一种特别的对象,所以会保存在堆上,编译函数时,这个函数的已经存在于堆中了!

    第二个问题返回了c对象的话,那么说明全局环境对c对象有引用,既然有引用那么就不会被垃圾回收器标记出来,所以c对象也就不会回收!

    2019-09-18
    5
  • ChaoZzz
    1. JSON.parse(JSON.stringify(...))

    function copy(src){
    let dest;
    dest = JSON.parse(JSON.stringify(src));
    return dest;
    }
    缺点:无法拷贝函数

    2. Object.assign({}, source)

    function copy(src){
    let dest;
    dest = Object.assign({}, src);
    return dest;
    }
    缺点:只能拷贝一层

    3. function copy(src) {
    let dest;
            if (typeof src !== 'object') return ;
            dest = src instanceof Array ? [] : {};
            for (let key of Object.keys(src)) {
                dest[key] = typeof src[key] === 'object' ? deepCopy(src[key]) : src[key];
            }
            return dest;
    }
    2019-08-31
    1
    4
  • Lx
    老师,后续是否会谅解下devtools做内存分析?基本用法会,但具体分析问题不知道怎么弄……
    2019-08-31
    4
  • 浪里行舟
    老师,专栏的配图怎么画出来的

    作者回复: macos的keynote

    2019-09-19
    1
    2
  • mfist
    1. dest = JSON.parse(JSON.stringify(src))

    2. function copy(src){
      let type = typeof src
      if('object' === type){
        let result = Object.create(null)
        for(let i in src){
            if(typeof src[i] === 'object'){
              result[i] = copy(src[i])
            }else{
              result[i] = src[i]
            }
        }
        return result
      }

      return src
    }

    3. 今日总结
    javascript的内存机制主要包括数据的存放、垃圾回收、代码的执行三个部分 这一节主要介绍数据的存放,javascript作为一种动态弱类型语言(动态静态是指使用之前是否确定数据类型,强弱类型是指是否支持隐式类型转换)其在内存空间存储有三个部分:代码空间(存储可执行代码)、栈空间(存储基本数据类型)、堆空间(对象)。闭包(Closure)中的变量存放在哪里呢?内部函数引用外部函数变量时候引擎会在堆空间中创建一个closure对象存放闭包变量。
    2019-08-31
    2
  • 烈日烹雪
    老师,第二遍看时有了个疑问 字符串存在栈里,但是字符串与其他原始类型不同,不一定只占几个字节的长度。如果字符串特别长,长到好几M,也会存在栈里吗?
    2019-10-31
    1
  • Elmer
    请问从内存角度如何分析闭包是如何进行链式查找变量的。
    比如:
    function test () {
     var a = 1;
    return function () {var b=2; return function(){console.log(a,b)}}
    }
    test()()()会答应出1 2
    从内存的角度如何分析1是怎么找到的,才想闭包也有链的机制?
    2019-09-08
    1
  • coder
    在闭包那段代码中,执行setName的时候,会不会也创建一个执行上下文?那setName执行完毕,是不是闭包就消失了(不考虑getName)?因为setName执行完毕,它的执行上下文就出栈了,那么闭包没有被引用,内存回收就清理了?
    2019-12-10
  • 大树
    老师能分析一下 箭头函数的内存模型吗 因为你在文章里提到过 箭头函数不创建执行上下文

    作者回复: 这块我会在下个v8专栏来深度分析

    2019-12-05
  • 大树
    箭头函数没有自己的执行上下文,所以箭头函数的 this 就是它外层函数的 this。
    那么箭头函数 如何存储 存储变量
    2019-12-05
  • -_-_aaa
    'JavaScript 中的数据类型一种有 8 种',怎么没把 ‘function’ 这种类型算进去

    作者回复: function是对象 object

    2019-12-04
  • 趁你还年轻233
    “JavaScript 中的数据类型一共有 8 种。”

    TypedArray,Blob,FIle,Promise这些呢?

    作者回复: 这些都属于object类型

    2019-11-11
  • William
    老师,闭包的内部对象有词法环境和变量环境吗?
    2019-11-08
  • William
    老师,for循环里的用var定义变量并实现闭包时,此时for循环的var变量是全局的;再看到myName是var关键字声明的,但是根据【闭包的产生过程】这张图片来看,似乎myName是闭包单独持有,并不是全局的,这和for循环中使用var的情况有差别吗,不知道是不是哪里理解错了。
    2019-11-08
  • Geek_b724f1
    我也提交我的,第一次留言,好害怕
    function copy(src) {
    let dest = {};
    for (let key in src) {
    if (typeof src[key] == "object") {
    dest[key] = copy(src[key]);
    } else {
    dest[key] = src[key];
    }
    }
    return dest;
    }
    2019-10-21
收起评论
41
返回
顶部