第4章 变量、作用域与内存

引言


由于js是一门只有在声明变量后才能明确类型的语言,并且在任意时刻都可以改变数据类型。这也引起了一些问题


原始值与引用值

原始值就是基本数据类型,引言值就是复杂数据类型
变量在赋值的时候。js会判断如果是原始值,访问时就是按值访问。如果是引用值,访问就是按引用访问

在很多语言中,字符串是使用对象表示的,因此被认为是引用类型。ECMAScript打破了这个惯例。

动态属性

原始值不能动态的crud属性和方法

let name = "Nicholas";
name.age = 27;
console.log(name.age);   // undefined

引用值可以动态的crud属性和方法

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

原始类型如果通过new关键字赋值,本质上还是Object类型

let name = new String("Matt");
name.age = 26;console.log(name.age);     // 26
console.log(typeof name); // object

复制值

原始值之间的赋值是单纯的复制,俩个互不干扰

int num1=5;
int num2=num1;

在这里插入图片描述

引用值之间的赋值是地址的复制,地址指向同一片内存空间。操作的也是同一片内存空间

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

在这里插入图片描述

传递参数

ECMAScript中所有函数的参数都是按值传递的。js的函数参数当接到传递过来的值后,会开辟一个空间将值赋值给函数参数

改变外部的person可能会错误的认为这是按引用传值,其实这是错误的理解

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

如果是引用传值,那么person.name应该是Greg。

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

确定类型

typdeof对原始类型非常有用,但是我们想知道一个引用类型的具体类型。typdeof就无能无力了

所有变量和Object匹配都会返回true,因为所有引用类型都是用Object实现的。
如果用instanceof检测原始值,则始终会返回false,因为原始值不是对象。

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

执行上下文与作用域

变量或函数的上下文决定了它们可以访问哪些数据,以及它们的行为

全局上下文是最外层的上下文,在浏览器中,全局上下文就是我们常说的window对象,因此所有通过var定义的全局变量和函数都会成为window对象的属性和方法。全局上下文在应用程序退出前才会被销毁

局部上下文在其所有代码都执行完毕后会被销毁,包括定义在它上面的所有变量和函数

搜索过程始终从作用域链的最前端开始,然后逐级往后,直到找到标识符。(如果没有找到标识符,那么通常会报错。)

每个上下文都可以到上一级上下文中去搜索变量和函数,但任何上下文都不能到下一级上下文中去搜索。

变量声明

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

在使用var声明变量时,变量会被自动添加到最接近的上下文

如果变量未经声明就被初始化了,那么它就会自动被添加到全局上下文(在调用函数后会被创建)

function add(num1, num2) {var sum = num1 + num2;return sum;
}
let result = add(10, 20); // 30
console.log(sum);           // 报错:sum在这里不是有效变量

var声明会被拿到函数或全局作用域的顶部,位于作用域中所有代码之前

提升让同一作用域中的代码不必考虑变量是否已经声明就可以直接使用(不要使用

console.log(name); // undefined
var name = 'Jake';
function() {console.log(name); // undefinedvar name = 'Jake';
}

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

let和var最大的区别是作用域是块级的,换句话说,if块、while块、function块,甚至连单独的块也是let声明变量的作用域

if (true) {let a;
}
console.log(a); // ReferenceError: a没有定义
while (true) {let b;
}
console.log(b); // ReferenceError: b没有定义
function foo() {let c;
}
console.log(c); // ReferenceError: c没有定义// 这没什么可奇怪的// var声明也会导致报错
// 这不是对象字面量,而是一个独立的块
// JavaScript解释器会根据其中内容识别出它来
{let d;
}
console.log(d); // ReferenceError: d没有定义

let与var的另一个不同之处是在同一作用域内不能声明两次

let的行为非常适合在循环中声明迭代变量。使用var声明的迭代变量会泄漏到循环外部,这种情况应该避免

3. 使用const的常量声明

使用const声明的变量必须同时初始化为某个值。一经声明,在其生命周期的任何时候都不能再重新赋予新值。

const a; // SyntaxError:常量声明时没有初始化
const b = 3;
console.log(b); // 3
b = 4; // TypeError: 给常量赋值

const除了要遵循以上规则,其他方面与let声明是一样的:

赋值为对象的const变量不能再被重新赋值为其他引用值,但对象的键则不受限制。

const o1 = {};
o1 = {}; // TypeError:给常量赋值
const o2 = {};
o2.name = 'Jake';
console.log(o2.name); // 'Jake'

垃圾回收

JavaScript是通过自动内存管理实现内存分配和闲置资源的语言。确定哪个变量不会再使用,然后释放它占用的内存。这个过程是周期性的,即垃圾回收程序每隔一定时间就会自动运行

标记清理

从根上下文(通常是全局上下文或当前执行的函数)开始,遍历所有的变量和对象。将所有可达(被引用)的对象标记为“存活”。在标记阶段之后,遍历整个内存空间,将未标记为“存活”的对象判定为垃圾,进行清除。这些未标记的对象会被销毁,其占用的内存将被释放。

引用计数

对每个值都记录它被引用的次数。声明变量并给它赋一个引用值时,这个值的引用数为1。如果同一个值又被赋给另一个变量,那么引用数加1,如果保存对该值引用的变量被其他值给覆盖了,那么引用数减1。当一个值的引用数为0时,就说明没办法再访问到这个值了,因此可以安全地收回其内存了。垃圾回收程序下次运行的时候就会释放引用数为0的值的内存。

这种方法没有办法解决循环引用的问题,即俩个对象互相引用

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

内存管理

将内存占用量保持在一个较小的值可以让页面性能更好。优化内存占用的最佳手段就是保证在执行代码时只保存必要的数据。

解除引用。这个建议最适合全局变量和全局对象的属性。局部变量在超出作用域后会被自动解除引用

function createPerson(name){let localPerson = new Object();localPerson.name = name;return localPerson;
}
let globalPerson = createPerson("Nicholas");
// 解除globalPerson对值的引用,
globalPerson = null;

解除引用的关键在于确保相关的值已经不在上下文里了,因此它在下次垃圾回收时会被回收。

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

由于const和let的块级作用域,让他们可能更早被垃圾回收

2. 隐藏类和删除操作

js引擎V8会在后台配置,让这两个类实例共享相同的隐藏类,因为这两个实例共享同一个构造函数和原型

function Article() {this.title = 'Inauguration Ceremony Features Kazoo Band';
}
let a1 = new Article();
let a2 = new Article();

如果在后面添加如下代码后,此时两个Article实例就会对应两个不同的隐藏类。根据这种操作的频率和隐藏类的大小,这有可能对性能产生明显影响。

a2.author = 'Jake';

为了避免性能的消耗,最好的法子就在构造函数中一次性声明完毕

function Article(opt_author) {this.title = 'Inauguration Ceremony Features Kazoo Band';this.author = opt_author;
}
let a1 = new Article();
let a2 = new Article('Jake');

在代码结束后,即使两个实例使用了同一个构造函数,它们也不再共享一个隐藏类。动态删除属性与动态添加属性导致的后果一样

function Article() {this.title = 'Inauguration Ceremony Features Kazoo Band';this.author = 'Jake';
}
let a1 = new Article();
let a2 = new Article();
delete a1.author;

最佳实践是把不想要的属性设置为null。这样可以保持隐藏类不变和继续共享,同时也能达到删除引用值供垃圾回收程序回收的效果。

function Article() {this.title = 'Inauguration Ceremony Features Kazoo Band';this.author = 'Jake';
}
let a1 = new Article();
let a2 = new Article();
a1.author = null;

3. 内存泄漏

在内存有限的设备上,或者在函数会被调用很多次的情况下,内存泄漏可能是个大问题。

最常见的内存泄露,方法被调用后会在window上挂载一个name属性。只要window不被清除,那么这个属性就一直存储。使用let,const可以避免这种情况的发生

function setName() {name = 'Jake';
}

由于定时器的回调函数一直调用name,所以name永远不会被清除

let name = 'Jake';
setInterval(() => {console.log(name);
}, 100);

js中的闭包很容易造成内存泄露的问题,调用outer()会导致分配给name的内存被泄漏。以上代码执行后创建了一个内部闭包,只要返回的函数存在就不能清理name,因为闭包一直在引用着它

let outer = function() {let name = 'Jake';return function() {return name;};
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/78892.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

1、sparkStreaming概述

1、sparkStreaming概述 1.1 SparkStreaming是什么 它是一个可扩展,高吞吐具有容错性的流式计算框架 吞吐量:单位时间内成功传输数据的数量 之前我们接触的spark-core和spark-sql都是处理属于离线批处理任务,数据一般都是在固定位置上&…

python简单知识点大全

python简单知识点大全 一、变量二、字符串三、比较运算符四、随机数4.1、随机整数4.2、随机浮点数4.3、随机数重现 五、数字类型5.1、整数5.2、浮点数5.3、复数 六、数字运算七、内置函数八、布尔类型八、逻辑运算符九、短路逻辑和运算符优先级十、分支和循环10.1、if语句10.2、…

相机传感器格式与镜头光圈参数

相机靶面大小 CCD/CMOS图像传感器尺寸(sensor format)1/2’‘、1/3’‘、1/4’实际是多大 1英寸——靶面尺寸为宽12.7mm*高9.6mm,对角线16mm。 2/3英寸——靶面尺寸为宽8.8mm*高6.6mm,对角线11mm。 1/2英寸——靶面尺寸为宽6.…

maven安装(windows)

环境 maven:Apache Maven 3.5.2 jdk环境:jdk 1.8.0_192 系统版本:win10 一、安装 apache官网下载需要的版本,然后解压缩,解压路径尽量不要有空格和中文 官网下载地址 https://maven.apache.org/download.cgihttps:…

微信小程序animation动画,微信小程序animation动画无限循环播放

需求是酱紫的: 页面顶部的喇叭通知,内容不固定,宽度不固定,就是做走马灯(轮播)效果,从左到右的走马灯(轮播),每播放一遍暂停 1500ms ~ 2000ms 刚…

ARCGIS地理配准出现的问题

第一种。已有省级行政区矢量数据,在网上随便找一个相同省级行政区图片,利用地理配准工具给图片添加坐标信息。 依次添加省级行政区选择矢量数据、浙江省图片。 此时,图层默认的坐标系与第一个加载进来的省级行政区选择矢量数据的坐标系一致…

【TypeScript】TS接口interface类型(三)

【TypeScript】TS接口interface类型(三) 【TypeScript】TS接口interface类型(三)一、接口类型二、实践使用2.1 常规类型2.2 设置属性只读 readonly2.3 设置索引签名2.4 设置可选属性2.5 函数类型接口 一、接口类型 TypeScript中的…

【Linux操作系统】Vim:提升你的编辑效率

Vim是一款功能强大的文本编辑器,它具有高度可定制性和灵活性,可以帮助程序员和文本编辑者提高编辑效率。本文将介绍Vim的基本使用方法、常用功能和一些实用技巧。 文章目录 1. Vim的基本使用方法:2. 常用功能:2.1 文件操作&#…

linux的目录结构和文件类型

p:同一台主机之间不同程序/进程间通信要用的文件 s:不同主机之间不同程序/进程间通信要用的文件

Vue-组件二次封装

本次对el-input进行简单封装进行演示 封装很简单,就给激活样式的边框(主要是功能) 本次封装主要使用到vue自带的几个对象 $attrs:获取绑定在组件上的所有属性$listeners: 获取绑定在组件上的所有函数方法$slots: 获取应用在组件内的所有插槽 …

微服务——DSL查询文档+搜索结果处理

DSL Query分类 DSL Query的基本语法 全文检索查询 常用场景 match查询 要填一个存在的字段,已经要检索的内容 匹配度越高排名越前,这里all字段包含三个字段在里面。 multi_match查询 精确查询 不分词的查询 查询语法 term查询 range查询 gte是大于等…

Python web实战之 Django 的 MVC 设计模式详解

技术栈:Python、Django、HTML、CSS、JavaScript。 概要 在 Web 开发中,MVC(Model-View-Controller)模式是一种非常常见的设计模式,它可以帮助我们更好地管理代码,提高代码的可维护性。今天就介绍如何使用 …

【Linux】【预】配置网络IP,挂载网络目录

【Linux】【预】配置网络IP,挂载网络目录 1. 配置查看IP2.配置Linux中的IP3. 串口连接开发板,配置 1. 配置查看IP a . 查看ipifconfig如下操作,其中的:192.168.252.140就是它的IP b . 使用xmodem 连接到虚拟机,最后点…

小研究 - MySQL 数据库下存储过程的综合运用研究

信息系统工程领域对数据安全的要求比较高,MySQL 数据库管理系统普遍应用于各种信息系统应用软件的开发之中,而角色与权限设计不仅关乎数据库中数据保密性的性能高低,也关系到用户使用数据库的最低要求。在对数据库的安全性进行设计时&#xf…

C#与C/C++交互(1)——需要了解的基础知识

【前言】 C#中用于实现调用C/C的方案是P/Invoke(Platform Invoke),让托管代码可以调用库中的函数。类似的功能,JAVA中叫JNI,Python中叫Ctypes。 常见的代码用法如下: [DllImport("Test.dll", E…

【笔记】湖仓一体架构演进与发展

https://www.bilibili.com/video/BV1oF411F7rQ/?spm_id_from333.788.recommend_more_video.0&vd_sourcefa36a95b3c3fa4f32dd400f8cabddeaf

小程序的 weiui的使用以及引入

https://wechat-miniprogram.github.io/weui/docs/quickstart.html 网址 1.点进去,在app.json里面配置 在你需要的 页面的 json里面配置,按需引入 然后看文档,再在你的 wxml里面使用就好了

C#,数值计算——堆选择(Heap Select)的计算方法与源程序

1 简述 HeapSelect 是一种用于选择数组中第 K 个最大元素的算法。它是选择问题的变体,涉及在无序或偏序集合中查找特定元素。 算法概要:数组被转换为最大堆,然后反复删除根节点并替换为下一个最大的元素,直到找到第 K 个最大的元…

TCP Socket 基础知识点(实例是以Java进行演示)

本篇根据TCP & Socket 相关知识点和学习所得进行整理所得。 文章目录 前言1. TCP相关知识点1.1 双工/单工1.2 TCP协议的主要特点1.3 TCP的可靠性原理1.4 报文段1.4.1 端口1.4.2 seq序号1.4.3 ack确认号1.4.4 数据偏移1.4.5 保留1.4.6 控制位1.4.7 窗口1.4.8 校验和1.4.9 紧…

Rocky(centos) jar 注册成服务,能开机自启动

概述 涉及:1)sh 无法直接运行java命令,可以软连,此处是直接路径 2)sh脚本报一堆空格换行错误:需将转成unix标准格式; #切换到上传的脚本路径 dos2unix 脚本文件名.sh 2)SELINUX …