JS学习笔记(三)变量、作用域与内存

梦想不抛弃苦心追求的人,只要不停止追求,你们会沐浴在梦想的光辉之中。再美好的梦想与目标,再完美的计划和方案,如果不能尽快在行动中落实,最终只能是纸上谈兵,空想一番。只要瞄准了大方向,坚持不懈地做下去,才能够扫除挡在梦想前面的障碍,实现美好的人生蓝图。JS学习笔记(三)变量、作用域与内存,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

JS学习笔记(三)变量、作用域与内存

一、原始值和引用值

ES变量包含两种不同类型的数据:原始值和引用值。

  • 原始值:最简单的数据,保存原始值的变量按值访问
  • 引用值:多个值构成的对象,保存在内存中,JS不允许直接访问内存,所以操作对象时,实际操作的时对象的引用而非对象本身**(JS中字符串不是引用类型)**

 

1.1 复制值

  • 在通过变量把一个原始变量赋值到另一个变量时,原始值会被复制到新变量的位置。而两个变量可以独立使用,互不干扰。

  • 在把引用值从一个变量赋给另一个变量时,存储在变量中的值也会被复制到新变量的位置。操作完成后。两个便是实际指向同一个对象,会相互影响

1.2 传递参数

ES中所有函数的参数都是按值传递

 

1.3 确定类型

  • typeof :针对原始值

  • instanceof :判断一个值什么类型的对象。所有引用值都是Object的实例

 

二、执行上下文

执行上下文分为:全局上下文,函数上下文和块级上下文

变量或函数的上下文决定了他们可以访问哪些数据,以及行为。每一个上下文都有一个关联的变量对象,而这个上下文中定义的所有变量和函数都存在于这个对象上。

全局上下文是最外层的上下文,在浏览器中,全局上下文就是我们常说的window对象。所有通过var定义的全局变量和函数都会成为window对象的属性和方法。使用let和const的顶级声明不会在全局上下文中,但在作用域链解析上效果是一样的。

上下文在其代码都执行完毕后会被销毁,包括定义在它上面的所有变量和函数(全局上下文在应用程序退出时才会被销毁,如关闭网页或退出浏览器)

每个函数调用都有自己的上下文。当代码执行流进入函数时,函数的上下文被推倒一个上下文栈上。在函数执行完后,上下文栈会弹出该函数上下文,将控制权返还给之前的执行上下文。

上下文中的代码在执行的时候,会创建变量对象的一个作用域链。该作用域链决定了各级上下文中的代码在访问变量和函数时的顺序。正在执行的上下文的变量对象始终位于作用域链的最前端。若上下文是函数,则其活动对象用作变量对象。活动对象起初只有一个定义变量:arguments(全局上下文中没有)。全局上下文的变量对象始终是作用域链的最后一个变量对象。

内部上下文可以通过作用域链访问外部上下文中的一切,但外部上下文无法访问内部上下文中的任何东西。每个上下文都可以到上一级上下文中去搜索变量和函数,但任何上下文都不能到下一级上下文中去搜索。

 

2.1 作用域链增强

执行上下文主要有:全局上下文和函数上下文,但某些语句会导致**在作用域链前端临时添加一个上下文,这个上下文在代码执行后会被删除。**出现这个现象的两种情况:

  • try/catch语句的catch块:会创建一个新的变量对象,这个对象包含要抛出的错误对象的声明
  • with语句:会向作用域链前端添加指定的对象

 

2.2 变量声明

1. 使用var的函数作用域声明

  • 在使用var声明变量时,变量会被自动添加到最接近的上下文。在函数中,最接近的上下文就是函数的局部上下文,若函数未经声明就被初始化,则它会被自动添加到全局上下文。

  • var声明会被提升到函数或全局作用域的顶部

 

2. 使用let 的块级作用域声明

块级作用域是由最近的一对包含花括号{}界定的。即if块,while块,function块,甚至单独的块也是let声明变量的作用域。

  • let在同一作用域里不能声明两次,否则会报错。

 

3. 标识符查找

搜索标识符从作用域链前端开始,以给定的名称搜索对应的标识符,局部上下文 -> 全局上下文搜索

 

三、垃圾回收(重点)

JS使用垃圾回收机制,即执行环境负责在代码执行时管理内存,通过自动内存管理实现内存分配和闲置资源回收。

思路:确定哪个变量不会再使用,然后释放它占用的内存。过程时周期性的,即垃圾回收程序每隔一定时间就会自动运行。标记策略:标记清理和引用计数

3.1 标记清理(最常用)

思路

当变量进入上下文,如再函数内部声明一个变量时,这个变量会被加上存在于上下文中的标记。而当变量离开上下文时,会被加上离开上下文的标记。

垃圾程序运行时,会标记在内存中存储的所有变量。然后,他会将所有在上下文中的变量,以及被在上下文中的变量引用的变量的标记去掉。之后再有被加上标记的变量就是待删除的,因为任何在上下文中的变量都访问不到他们了

 

3.2 引用计数

思路

对每个值都记录它被引用的次数

策略

  • 声明变量时赋予的引用值为1
  • 同一个值被赋给另一个变量,引用数加1
  • 若保存对该值引用的变量被其他值覆盖,引用数减1
  • 引用数为0时,回收内存

缺陷

在代码中存在循环引用时会出现问题

 

3.3 内存管理

若内存占用量保持在较小的值可以让页面的性能更好。若数据不再必要,则可把它设置为null,释放其引用。叫做解除引用。最适合全局变量和全局对象的属性

function foo(name) {
    let localPerson = new Object();
    localPerson.name = name;
    return localPerson;
}
let globalPerson = foo('Niki');
// 接触globalPerson对值的引用
globalPerson = null;

注意:解除一个值的引用并不会自动导致相关内存被回收。解除引用的关键在于确保相关的值已经不在上下文中了,因此它在下次垃圾回收时会被回收。

1. 通过const和let声明提升性能

const和let都以块为作用域,相比于使用var,使用这两个新关键字可能会更早的让垃圾回收接入,回收内存。

 

2. 隐藏类和删除操作

V8引擎在将解释后的JavaScript代码编译为实际的机器码时会利用”隐藏类“。运行期间,V8会将创建的对象与隐藏类关联,以跟踪他们的属性特征。

使用delete关键字会导致生成相同的隐藏片段。

function foo() {
	this.title = 'hhhhh';
    this.author = 'niki';
}

let a1 = new foo();
let a2 = new foo();

delete a1.author;

代码结束后,即使两个实例使用了同一个构造函数,它们也不再共享一个隐藏类。动态删除属性与动态添加属性导致的后果一样。最好是把不想要的属性设置为null;这样可以保持隐藏类不变和继续共享,同时也能达到删除引用值供垃圾回收程序回收的效果。

 

3. 内存泄漏

可能造成内存泄露的情况:

  • 意外声明全局变量
function setName(){
	name = 'niki';
}

解释器会把变量name当作window属性创建,只要window本身不被清理就不会消失。

解决方案:在变量声明前加上var,let或const

  • 定时器
let name = 'niki';
setInterval(() => {
    console.log(name);
},100);

这个例子定时器的回调通过闭包调用了外部变量,只要定时器一直运行,回调函数引用的name就会一直占用内存

  • 使用JavaScript闭包
let outer = function(){
    let name = 'Jake';
    return function() {
        return name;
    };
}

调用outer()导致分配给name的内存被泄露。代码执行后创建了一个内部闭包,只要返回的函数存在就不能清理name。因为闭包一直在引用他。

 

4. 静态分配与对象池

浏览器决定何时运行垃圾回收程序的一个标准就是对象更替速度

策略:

使用对象池

**在初始化时,创建一个对象池,用来管理一组可回收对象。应用程序可以向这个对象池请求一个对象、设置其属性使用它,然后在操作完成后再把它返回给对象池,用来管理一组可回收的对象池。**由于没发生对象初始化,垃圾回收探测就不会发现有对象更替。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/150439.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!