原始值&引用值

  • 原始值(primitive value):Undefined, Null, Boolean, Number, String, Symbol

    • 按值访问,直接操作存储在变量中的实际值
  • 引用值(reference value):Object

    • 按引用访问,操作的是对该对象的引用,而非直接操作对象本身

动态属性

  • 对引用值可以随时添加、修改和删除属性与方法;原始值不能添加属性,尽管这样做不会报错(只有引用值可以动态添加后面可以使用的属性)

  • 如果使用new关键字对原始类型进行初始化,则会创建一个Object类型实例,但行为与原始值是类似的

    1. let name1 = 'Leo';
    2. let name2 = new String('David');
    3. name1.age = 19;
    4. name2.age = 21;
    5. console.log(name1.age); // undefined
    6. console.log(name2.age); // 21
    7. console.log(typeof name1); // string
    8. console.log(typeof name2); // object

复制值

  • 原始值的浅拷贝(值的副本)和引用值的深拷贝(引用的拷贝)

参数传递

  • ECMAScript中所有函数的参数都是值传递

    • 对于引用值,传递的是堆内存上的对象的引用(即将引用拷贝给函数的参数),函数内部的改变也会影响到外部的对象

      1. function setName(obj){
      2. obj.name = 'Leo';
      3. }
      4. let person = {};
      5. setName(person);
      6. console.log(person.name); // Leo
      7. -------------------------------------
      8. function setName(obj){
      9. obj.name = 'Leo';
      10. obj = {}; // obj由指向堆内存上person的指针,变成了指向新建的本地对象的指针,函数退出后即被销毁
      11. obj.name = 'David'; // 对函数外的person对象没有影响
      12. }
      13. let person = {};
      14. setName(person);
      15. console.log(person.name); // Leo

执行上下文&作用域

  • 上下文中的代码在执行的时候,会创建变量对象的一个作用域链(scope chain),这个作用域链决定了各级上下文中的代码在访问变量和函数时的顺序。

    • 代码正在执行的上下文的变量对象始终位于作用域链的最前端。如果上下文是函数,则其活动对象( activation object)用作变量对象。活动对象最初只有一个定义变量: arguments。(全局上下文中没有这个变量。) 作用域链中的下一个变量对象来自包含上下文,再下一个对象来自再下一个包含上下文。以此类推直至全局上下文;全局上下文的变量对象始终是作用域链的最后一个变量对象。

    • 代码执行时的标识符解析是通过沿作用域链逐级搜索标识符名称完成的。搜索过程始终从作用城链的最前端开始,然后逐级往后,直到找到标识符。(如果没有找到标识符,那么通常会报错

    • 上下文之间线性、有序连接,每个上下文都可以到上级上下文中搜索变量与函数,但不能到下一级上下文中搜索

变量声明

  • var:变量会被自动添加到最近的上下文。若未声明就被初始化,则会被自动添加到全局上下文

    • 由于hoisting的存在,var声明的变量可以在声明前被使用(不推荐这样做)
  • let:块级作用域
    • 块内用let声明的变量,在块外不能被访问
    • 严格讲let也存在hoisting,但是由于“暂时性死区”,在声明前不能使用let变量
  • const: 块级作用域
    • const变量声明的同时必须初始化,且在生命周期内不能被再赋值
    • 对于声明为const的对象。不能被赋以其他的引用值,但是对象内部的键是可以改变的
      • 使用 Object.freeze({...}) 声明一个对象,则整个对象都不可以改变

垃圾回收

  • 通过自动内存管理实现内存分配和闲置资源回收:释放不会再被使用的变量占用的内存

标记清理

  • 变量进入上下文中时会被加上“存在于上下文中”的标记;变量离开上下文时,会被加上“离开上下文的标记”

    • 垃圾回收程序运行时,为内存中所有变量加标记,再将位于上下文中的变量和被上下文中的变量引用的变量的标记去掉,此时做内存清理:销毁带标记的所有值并收回他们占据的内存资源

引用计数

  • 对每个值都记录它被引用的次数,每被赋给一个变量就对其引用数+1,保存对该值引用的变量被其他值覆盖时就对其引用数-1;一个值引用数为0时说明该值已无法访问,即可安全的回收其内存资源

    • 问题:循环引用(对象A、B通过各自的属性相互引用时,引用数永远不会变成0,导致内存资源无法回收)

内存管理

  • 将内存占用量保持在较小水平可以让页面性能更好,应当保证在执行代码时只保存必要的数据

    • 如果数据不再必要,就将其置为null,从而释放引用(解除引用)(适用于全局变量与全局对象的属性),在下次垃圾回收时这些被解引用的资源将被回收

      1. function createPerson(name){
      2. let localPerson = new Object();
      3. localPerson.name = name;
      4. return localPerson;
      5. }
      6. let globalPerson = createPerson('Leo');
      7. globalPerson = null; // 解除全局变量globalPerson对值的引用,这份资源将被回收
  • 使用let与const声明提升性能:以块为作用域,可以让垃圾回收程序更早介入,更早的收回内存资源

  • 隐藏类与删除操作

    • JavaScript引擎会将创建的对象与隐藏类关联起来,跟踪它们的属性特征;能共享相同隐藏类的对象会带来潜在的性能提升

    • 如果两个类实例共享同一个构造函数与原型,这两个类实例就会共享相同的隐藏类

      1. function Article() {
      2. this.title = "Hello world";
      3. }
      4. let a1 = new Article();
      5. let a2 = new Article(); // 此时两个Article实例共享一个相同的隐藏类
      6. a2.author = "Leo"; // 此时两个Article实例对应两个不同的隐藏类
      • 应当尽量避免JavaScript “先创建再补充”的动态属性赋值,在构造函数中一次性声明所有属性
    • 使用delete关键字,会导致生成相同的隐藏类片段

      1. function Article() {
      2. this.title = "Hello world";
      3. this.author = "Leo";
      4. }
      5. let a1 = new Article();
      6. let a2 = new Article();
      7. // delete a1.author; // 此时两个Article实例对应两个不同的隐藏类
      8. a1.author = null; // 最佳实践
      • 使用delete动态删除属性与动态添加属性导致的后果相同
      • 应当将不想要的属性设置为null,既保持隐藏类不变与继续共享,同时也达到删除引用值供垃圾回收程序回收的效果

内存泄漏

  • 意外声明全局变量

    1. function setName() {
    2. name = "Leo";
    3. }
    • 此时的name属性被当做window的属性创建,window本身不被清理name就不会消失
    • 解决:在name前加上var,let,const即可
  • 定时器

    1. let name = "Leo";
    2. setInterval(() => {
    3. console.log(name);
    4. }, 100);
    • 定时器的回调引用了外部变量,只要定时器一直运行,回调引用的name就会一直占用内存,垃圾回收程序就不会清理此外部变量
  • 闭包

    1. let outer = function() {
    2. let name = "Leo";
    3. return function() {
    4. return name;
    5. };
    6. };
    • 代码执行后创建了一个内部闭包,只要返回的函数一直存在就不能清理name,因为闭包一直在引用它

静态分配&对象池

理论上,如果能合理使用分配的内存,同时避免多余的垃圾回收,那就可以保住因释放内存而损失的性能

  • 当一些对象更替速度非常快时,垃圾回收机制会被更加频繁的调用,造成不必要的开销,降低性能

  • 使用对象池:创建对象池管理一组可回收的对象,在需要时应用程序可以向对象池请求一个对象而不是再创建一个对象,使用完后再把这个对象归还给对象池,以供后续需要时拿出来重复使用

    • 此时,由于没有发生对象的初始化,垃圾回收探测就不会发现有对象的更替,垃圾回收机制就不会那么频繁的被调用
    • 并非所有对象都适合拿来“池化”――因为维护对象池也要造成一定开销。对生成时开销不大的对象进行池化,反而可能会出现“维护对象池的开销”大于“生成新对象的开销”,从而使性能降低的情况。但是对于生成时开销可观的对象,池化技术就是提高性能的有效策略了。
  • 如果对象池按需分配(对象不存在则创建新的,对象存在时复用存在的),则对象池有单调增长但为静态的内存——可选择数组进行维护

    • JavaScript数组大小可变,一旦申请空间已满且继续添加,就会删除已有数组而申请一个更大的数组,这个动态分配操作会引起垃圾回收机制的调用:应当在初始化时就创建好一个大小足够的数组,从而避免先删除再创建的操作

《JavaScript高级程序设计》Chapter04 变量,作用域,内存的更多相关文章

  1. 《JavaScript高级程序设计》 -- 变量、作用域和内存问题(二)

    1.基本类型与引用类型 基本类型:值保存在变量中 (Number.String.Boolean.Undefined.Null).在内存中占据固定大小空间,被保存在栈内存中 引用类型:值是保存在内存中的 ...

  2. JavaScript高级程序设计-(3) 变量、作用域和内存问题

    传递参数 ECMAScript所有函数参数都是按值传递的,即使对象在函数内部修改了参数的值,原始的引用任然不变,局部对象在函数执行完毕后被销毁

  3. 读javascript高级程序设计02-变量作用域

    一. 延长作用域链 有些语句可以在作用域前端临时增加一个变量对象,该变量对象在代码执行完成后会被移除. ①with语句延长作用域. function buildUrl(){ var qs=" ...

  4. 读javascript高级程序设计00-目录

    javascript高级编程读书笔记系列,也是本砖头书.感觉js是一种很好上手的语言,不过本书细细读来发现了很多之前不了解的细节,受益良多.<br/>本笔记是为了方便日后查阅,仅作学习交流 ...

  5. 读javascript高级程序设计-目录

    javascript高级编程读书笔记系列,也是本砖头书.感觉js是一种很好上手的语言,不过本书细细读来发现了很多之前不了解的细节,受益良多.<br/>本笔记是为了方便日后查阅,仅作学习交流 ...

  6. 《JavaScript高级程序设计》学习笔记(3)——变量、作用域和内存问题

    欢迎关注本人的微信公众号"前端小填填",专注前端技术的基础和项目开发的学习. 本节内容对应<JavaScript高级程序设计>的第四章内容. 1.函数:通过函数可以封装 ...

  7. JavaScript高级程序设计学习(三)之变量、作用域和内存问题

    这次讲的主要是变量,作用域和内存问题. 任何一门编程语言,都涉及这三个. 变量,比如全局变量,局部变量等,作用域,也分全局作用域和方法作用域,内存问题,在java中就涉及到一个垃圾回收的问题,由于ja ...

  8. 《JavaScript高级程序设计(第3版)》阅读总结记录第一章之JavaScript简介

    前言: 为什么会想到把<JavaScript 高级程序设计(第 3 版)>总结记录呢,之前写过一篇博客,研究的轮播效果,后来又去看了<JavaScript 高级程序设计(第3版)&g ...

  9. 阅读摘录《javascript 高级程序设计》01

    前言: 因为工作需要,所以开始主攻前台JS方面的技术.在以前的工作中,使用过这门脚本语言.但是都是比较凌乱的,用到什么学什么,只是为了实现业务,而去使用. 不会考虑到代码优化,封装对象等.今次特意借了 ...

  10. 读书时间《JavaScript高级程序设计》一:基础篇

    第一次看了<JavaScript高级程序设计>第二版,那时见到手上的书,第一感觉真是好厚的一本书啊.现在再次回顾一下,看的是<JavaScript高级程序设计>第三版,并记录一 ...

随机推荐

  1. mybatis-关联查询1-一对多关联查询

    或者多表单独查询方式

  2. 页面布局 Wrap 组件

    一.Flutter RaisedButton 定义一个按钮 Flutter 中通过 RaisedButton 定义一个按钮.RaisedButton 里面有很多的参数,这一讲我们只是简单的进行使用. ...

  3. 安装TortoiseSVN. msi 报错 2503 2502错误

    tortoisegit下载地址 https://tortoisegit.org/download/ [错误现象] 安装TortoiseSVN. msi 报错 2503 2502错误 [错误原因] 没有 ...

  4. 维纳攻击 wiener attack

    维纳攻击 wiener attack 目录 维纳攻击 wiener attack 攻击条件 使用原理 十三届全国大学生网络安全竞赛 bd 分析 解答 [羊城杯 2020]rrrrrsa (wiener ...

  5. 项目中pom.xml的某些坐标无法加载

    项目中pom.xml的某些坐标无法加载 maven官方查找对应的坐标文件下载 https://mvnrepository.com/artifact/com.fasterxml.jackson.core ...

  6. Rsync已过时?替代文件同步软件了解一下

    随着企业结构分散化的不断扩大,企业内部和企业间的信息互动更加频繁.越来越多的企业要求内部各种业务数据在服务器.数据中心甚至云上能够有实时的同步留存.所以,企业需要文件同步软件,通过在两个或更多设备之间 ...

  7. 光纤加速计算 383-高速信号处理板 XCKU060的双路QSFP+光纤PCIe 卡 XCKU060板卡

    基于kintex UltraScale XCKU060的双路QSFP+光纤PCIe 卡 一.板卡概述 本板卡系北京太速科技自主研发,基于Xilinx UltraScale Kintex系列FPGA   ...

  8. JavaScript 字符串的操作

    1. 在指定位置,插入字符串(此需求来源于,img中src没值的字符串标签中,插入图片http地址) // 使用slice写出的 自定义方法,绑定在String的原型链上 String.prototy ...

  9. 【BOOK】【实例】【requests库+正则表达式】猫眼TOP100电影排名爬取

    猫眼电影TOP100页面爬取 https://maoyan.com/board/4 ##猫眼电影TOP100爬取 import requests import re import json impor ...

  10. ajax异步参数详解及alax错误信息error分析

    一.$.ajax()的参数列表 ↑ 下面是Jquery中AJAX参数详细列表: 参数名 类型 描述 url String (默认: 当前页地址) 发送请求的地址. type String (默认: & ...