热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

学习笔记6—作用域与内存

目录1原始值与引用值2复制值3传递参数4确定类型5执行上下文与作用域6垃圾回收1原始值与引用值原始值就是最简单的数据,引用值则是由多个值构成的对象。在把一个值赋给变

目录

  • 1 原始值与引用值
  • 2 复制值
  • 3 传递参数
  • 4 确定类型
  • 5 执行上下文与作用域
  • 6 垃圾回收


1 原始值与引用值

原始值就是最简单的数据,引用值则是由多个值构成的对象。
在把一个值赋给变量时,Javascript引擎必须确定这个值是原始值还是引用值。保存原始值的变量是按值访问的,因为我们操作的就是存储在变量中的实际值。引用值是保存在内存中的对象。与其他语言不同,Javascript不允许直接访问内存位置,因此也就不能直接操作对象所在的内存空间。在操作对象时,实际上操作的是该对象的引用,而非实际对象本身。为此,保存引用值的变量是按引用访问的。

2 复制值

除了存储方式不同,原始值和引用值在通过变量复制时也有所不同。在通过变量把一个原始值赋值到另一个变量时,原始值会被复制到新变量的位置。

let num1 = 5;
let num2 = num1;

num1包含数值5,当把num2初始化为num1时,num2也会得到数值5,这个值跟存储在num1中的5是完全独立的。这两个变量可以独立使用,互不干扰。
在把引用值从一个变量赋值给另一个变量时,存储在变量中的值也会被复制到新变量所在的位置。区别在于,这里复制的实际上是一个指针,指向存储在堆内存中的对象。操作完成后,两个变量实际上指向同一个对象,因此在一个对象上面的变化会在另一个对象上反映出来:

let obj1 = new Object();
let obj2 = obj1;
obj1.name = "Nicholas";
console.log(obj2.name); //"Nicholas"

变量obj保存了一个新对象的实例,然后这个值被复制到obj2,此时两个变量都指向了同一个对象。

3 传递参数

ECMAScript中所有函数的参数都是按值传递的,意味着函数外的值会被复制到函数内部的参数中,就像从一个变量复制到另一个变量一样。如果是原始值,那么就跟原始值变量的复制一样,如果是引用值,那么就跟引用值的变量复制一样。

function setName(obj){obj.name = "Nicholas";
}
let person = new Object();
setName(person);
console.log(person.name); //"Nicholas"

4 确定类型

为了解决是什么类型对象的问题,ECMAScript提供了instanceof操作符:

console.log(person instanceof Object);//变量person是Object么
console,log(colors instanceof Array); //变量colors是Array么
console.log(pattern instanceof RegExp); //变量pattern是RegExp么

如果变量给定引用类型的实例,则instanceof操作符返回true

5 执行上下文与作用域

关于执行上下文的理解:编程时,我们一般也是先给程序定义一些前提(环境变量、描述环境变化的全局变量等),这些“前提”就是上文,然后再编写各功能模块的代码,这是下文。
变量或函数的上下文决定了它们可以访问哪些数据,以及它们的行为。每个上下文都有一个关联的变量对象,而这个上下文中定义的所有变量和函数都存在于这个对象上。虽然无法通过代码访问变量对象,但后台处理数据会用到它。
执行上下文有且只有三类,全局执行上下文,函数上下文,与eval上下文;由于eval一般不会使用,这里不做讨论。
全局上下文是最外层的上下文。在浏览器中,全局上下文就是我们常说的window对象,因此所有通过var定义的全局变量和函数都会成为window对象的属性和方法。使用let和const的顶级声明不会定义在全局上下文中,但在作用域链解析上效果是一样的。上下文在其所有代码都执行完毕后会被销毁,包括定义在它上面的所有变量和函数(全局上下文在应用程序退出前才会被销毁,比如关闭网页或退出浏览器)。
每个函数调用都有自己的上下文。当代码执行流进入函数时,函数的上下文被推到一个上下文栈上,在函数执行完后,上下文栈会弹出该函数上下文,将控制权返还给之前的执行上下文。ECMAScript程序的执行流就是通过这个上下文栈进行控制的。
作用域:指的是您有权访问的变量集合。
上下文中的代码在执行过程中,会创建变量对象的一个作用域链。这个作用域链决定了各级上下文中的代码在访问变量和函数时的顺序。代码正在执行的上下文的变量对象始终位于作用域链的最前端。如果上下文是函数,则其活动对象用作变量对象。活动对象最初只有一个定义变量:arguments。作用域链中的下一个变量对象来自包含上下文,再下一个对象来自再下一个包含上下文。以此类推直至全局上下文,全局上下文的变量对象始终是作用域链的最后一个变量对象。
代码执行时的标识符解析是通过沿作用域链逐级搜索标识符名称完成的。搜索过程始终从作用域链的最前端开始,然后逐级往后,直到找到标识符。

var color = "blue";
function changeColor(){if(color === "blue"){color = "red";}else{color = "blue";}
}
changeColor();

对于这个例子,changeColor()的作用域链包含两个对象:一个是他自己的变量对象(就是定义arguments对象的那个),另一个是全局上下文的变量对象。这个函数内部之所以能够访问变量color,就是因为可以在作用域链中找到它。
此外,局部作用域中定义的变量可用于在局部上下文中替换全局变量:

var color = "blue";function changeColor() {let anotherColor = "red";function swapColors() {let tempColor = anotherColor;anotherColor = color;color = tempColor;}swapColors();}changeColor();console.log(color);

以上代码涉及3个上下文:全局上下文、changeColor()的局部上下文和swapColors()的局部上下文。全局上下文中有一个变量color和一个函数changeColor()。changeColor()的局部上下文中有一个变量anotherColor和一个函数swapColors(),但在这里可以访问全局上下文中的变量color。swapColors()的局部上下文中有一个变量tempColor,只能在这个上下文中访问到。全局上下文和changeColor()的局部上下文都无法访问到tempColor。而在swapColors()中则可以访问另外两个上下文中的变量,因为它们都是父上下文。

6 垃圾回收

Javascript是使用垃圾回收的语言,也就是说执行环境负责在代码执行时管理内存。基本思路很简单:确定哪个变量不会再使用,然后释放它占用的内存。这个过程是周期性的,即垃圾回收程序每隔一段时间就会自动运行。垃圾回收程序必须跟踪记录哪个变量还会使用,以及哪个变量不会再使用,以便回收内存。如何标记未使用的变量也许有不同的实现方式。但在浏览器的发展史上,用到过两种主要的标记策略:标记清理和引用计数。
(1)标记清理
Javascript最常用的垃圾回收策略是标记清理,当变量进入上下文时,比如在函数内部声明一个变量时,这个变量会被加上存在于上下文中的标记。而在上下文中的变量,逻辑上讲,永远不应该释放它们的内存,因为只要上下文中的代码在运行,就有可能用到它们。当变量离开上下文时,也会被加上离开上下文的标记。
给变量加标记的方式有很多种,比如当变量进入上下文时,反转某一位,或者可以维护“在上下文中”和“不在上下文中”两个变量列表,可以把变量从一个列表转移到另一个列表。标记过程的实现并不重要,关键是策略。
垃圾回收程序运行的时候,会标记内存中存储的所有变量。然后它会将所有在上下文中的变量,以及被在上下文中的变量引用的变量标记去掉。在此之后再被加上标记的变量就是待删除的了,因为任何在上下文中的变量都访问不到它们了。随后垃圾回收程序做一次内存清理,销毁带标记的所有值并回收它们的内存。
(2)引用计数
另一种没那么常用的垃圾回收策略是引用计数,其思路是对每个值都记录它被引用的次数。声明变量并给他赋一个引用值是,这个值的引用次数为1.如果同一个值又被赋给另一个变量,那么引用次数加1.类似地,如果保存对该引用的变量被其他值给覆盖了,那么引用次数减一。当一个值的引用次数为0时,就说明没办法再访问到这个值了,因此可以安全地收回其内存了。垃圾回收程序下次运行时就会释放引用次数为0值的内存。
引用计数最早由Netscape Navigator 3.0采用,但很快就遇到了严重的问题:循环引用。所谓循环引用就是对象A有一个指针指向对象B,而对象B也引用对象A,如:

function problem(){let objectA = new Object();let objectB = new Object();objectA.someOtherObject = objectB;objectB.anotherObject = objectA;
}

在这个例子中,object和objectB通过各自的属性相互引用,意味着它们的引用计数都为2,在标记清理策略下,这不是问题,因为在函数结束后,这两个对象都不在作用域中。而在引用计数策略下objectA和objectB在函数结束后还会存在,因为它们的引用数永远不会变成0。
(3)静态分配与矢量池
为了提高Javascript的性能,最后要考虑的一点往往就是压榨浏览器了。此时,一个关键问题就是如何减少浏览器执行垃圾回收的次数。开发者无法直接控制什么时候开始收集垃圾,但可以间接控制触发垃圾回收的条件。理论上,如果能合理使用分配的内存,同时避免多余的垃圾回收,那就可以保住因释放内存而损失的性能。
浏览器决定何时运行垃圾回收程序的一个标准就是对象更替的速度。如果有很多对象被初始化。然后一下子又都超出了作用域,那么浏览器就会采用更激进的方式调度垃圾回收程序运行,这样当然会影响性能:

function addVector(a,b){let resultant = new Vector();resultant.x = a.x + b.x;resultant.y = a.y + b.y;return resultant;
}

调用这个函数时,会在堆上创建一个新对象,然后修改它,最后再把它返回给调用者。如果这个矢量对象的生命周期很短,那么它会很快失去所有对它的引用,成为可以被回收的值。加入这个矢量加法函数频繁被调用,那么垃圾回收调度程序会发现这里对象更替速度很快,从而会更频繁地安排垃圾回收。该问题的解决方案是不要动态创建矢量对象,比如可以修改上面的函数,让它使用一个已有的矢量对象:

function addVector(a,b,resultant){resultant.x = a.x + b.x;resultant.y = a.y + b.y;
}

当然,这需要在其他地方实例化矢量参数resultant,但这个函数的行为没有变。那么在哪里创建矢量可以不让垃圾回收调度程序盯上呢?
一个策略是使用对象池,在初始化的某一时刻,可以创建一个对象池,用来管理一组可回收的对象。应用程序可以向这个对象池请求一个对象、设置其属性,使用它,然后在操作完成后再把它还给对象池,由于没发生对象初始化,垃圾回收探测就不会发现有对象更替,因此垃圾回收程序就不会那么频繁地运行。P100


推荐阅读
  • 本文介绍了RxJava在Android开发中的广泛应用以及其在事件总线(Event Bus)实现中的使用方法。RxJava是一种基于观察者模式的异步java库,可以提高开发效率、降低维护成本。通过RxJava,开发者可以实现事件的异步处理和链式操作。对于已经具备RxJava基础的开发者来说,本文将详细介绍如何利用RxJava实现事件总线,并提供了使用建议。 ... [详细]
  • 本文讨论了微软的STL容器类是否线程安全。根据MSDN的回答,STL容器类包括vector、deque、list、queue、stack、priority_queue、valarray、map、hash_map、multimap、hash_multimap、set、hash_set、multiset、hash_multiset、basic_string和bitset。对于单个对象来说,多个线程同时读取是安全的。但如果一个线程正在写入一个对象,那么所有的读写操作都需要进行同步。 ... [详细]
  • 巧用arguments在Javascript的函数中有个名为arguments的类数组对象。它看起来是那么的诡异而且名不经传,但众多的Javascript库都使用着它强大的功能。所 ... [详细]
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 目录实现效果:实现环境实现方法一:基本思路主要代码JavaScript代码总结方法二主要代码总结方法三基本思路主要代码JavaScriptHTML总结实 ... [详细]
  • 如何使用Java获取服务器硬件信息和磁盘负载率
    本文介绍了使用Java编程语言获取服务器硬件信息和磁盘负载率的方法。首先在远程服务器上搭建一个支持服务端语言的HTTP服务,并获取服务器的磁盘信息,并将结果输出。然后在本地使用JS编写一个AJAX脚本,远程请求服务端的程序,得到结果并展示给用户。其中还介绍了如何提取硬盘序列号的方法。 ... [详细]
  • javascript  – 概述在Firefox上无法正常工作
    我试图提出一些自定义大纲,以达到一些Web可访问性建议.但我不能用Firefox制作.这就是它在Chrome上的外观:而那个图标实际上是一个锚点.在Firefox上,它只概述了整个 ... [详细]
  • 不同优化算法的比较分析及实验验证
    本文介绍了神经网络优化中常用的优化方法,包括学习率调整和梯度估计修正,并通过实验验证了不同优化算法的效果。实验结果表明,Adam算法在综合考虑学习率调整和梯度估计修正方面表现较好。该研究对于优化神经网络的训练过程具有指导意义。 ... [详细]
  • 利用Visual Basic开发SAP接口程序初探的方法与原理
    本文介绍了利用Visual Basic开发SAP接口程序的方法与原理,以及SAP R/3系统的特点和二次开发平台ABAP的使用。通过程序接口自动读取SAP R/3的数据表或视图,在外部进行处理和利用水晶报表等工具生成符合中国人习惯的报表样式。具体介绍了RFC调用的原理和模型,并强调本文主要不讨论SAP R/3函数的开发,而是针对使用SAP的公司的非ABAP开发人员提供了初步的接口程序开发指导。 ... [详细]
  • Iamtryingtocreateanarrayofstructinstanceslikethis:我试图创建一个这样的struct实例数组:letinstallers: ... [详细]
  • 本文介绍了在满足特定条件时如何在输入字段中使用默认值的方法和相应的代码。当输入字段填充100或更多的金额时,使用50作为默认值;当输入字段填充有-20或更多(负数)时,使用-10作为默认值。文章还提供了相关的JavaScript和Jquery代码,用于动态地根据条件使用默认值。 ... [详细]
  • 引号快捷键_首选项和设置——自定义快捷键
    3.3自定义快捷键(CustomizingHotkeys)ChemDraw快捷键由一个XML文件定义,我们可以根据自己的需要, ... [详细]
  • 浅解XXE与Portswigger Web Sec
    XXE与PortswiggerWebSec​相关链接:​博客园​安全脉搏​FreeBuf​XML的全称为XML外部实体注入,在学习的过程中发现有回显的XXE并不多,而 ... [详细]
author-avatar
手机用户2502863297
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有