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

news2024/12/25 0:56:53

引言


由于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); // undefined
  var 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.coloradmin.cn/o/840905.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Air32 | 合宙Air001单片机内部FLASH读写示例

Air32 | 合宙Air001单片机内部FLASH读写示例 代码已经通过测试,开发环境KEIL-MDK 5.36。 测试代码 void FLASH_RdWrTest(void) {uint32_t Address;uint32_t PageReadBuffer[FLASH_PAGE_SIZE >> 2];uint32_t PageWriteBuffer[FLASH_PAGE_SIZE >> 2];mem…

仓储物流业如何实现精细化工时成本管理,提升出库效率?

面对日益复杂的物流需求,以及不断上涨的人力成本。仓储物流企业想要在市场竞争中脱颖而出,不仅需要提升出库效率,满足消费者次日达甚至当日达的高要求,更需要控制人力成本,保证企业持续盈利的能力。盖雅新推出的 「劳…

恒运资本:史上最强暑期档!总票房突破147亿,前三都是国产片!

暑期档电影又爆了! 就在刚刚曩昔的周末,在《封神第一部》《巨齿鲨2:深渊》《火热》等电影的大卖,以及《背注一掷》点映及预售的加持下,短短两天的大盘票房就到达10亿元。 其间,据猫眼专业版数据&#xff0…

Windows下安装Kafka(图文记录详细步骤)

Windows下安装Kafka Kafka简介一、Kafka安装前提安装Kafka之前,需要安装JDK、Zookeeper、Scala。1.1、JDK安装(version:1.8)1.1.1、JDK官网下载1.1.2、JDK网盘下载1.1.3、JDK安装 1.2、Zookeeper安装1.2.1、Zookeeper官网下载1.2.…

Java方法重写

目录 1.什么是方法重写 2.方法重写的规则 3.重写与重载的区别 1.什么是方法重写 重写(override,也称为覆盖):在子类中对父类中允许访问的方法的实现过程进行重新编写,子类中方法的名称、返回值类型、参数列表与父类…

Redis安装以及配置隧道连接(centOs)

目录 1.centOs安装Redis 2. Redis 启动和停⽌ 3. 操作Redis 2.Xshell配置隧道 1.centOs安装Redis #使⽤yum安装Redis yum -y install redis 2. Redis 启动和停⽌ #查看是否启动 ps -ef|grep redis#启动redis: redis-server /etc/redis.conf &#停⽌Redis redis-cli sh…

【web逆向】全报文加密流量的去加密测试方案

aHR0cHM6Ly90ZGx6LmNjYi5jb20vIy9sb2dpbg 国密混合 WEB JS逆向篇 先看报文:请求和响应都是全加密,这种情况就不像参数加密可以方便全文搜索定位加密代码,但因为前端必须解密响应的密文,因此万能的方法就是搜索拦截器&#xff0c…

B站电商分析,如何发现近期热门商品及优质视频带货达人?

哔哩哔哩是我们熟知的以二次元为基调的视频内容生产平台,具有浓厚而专注的二次元社区氛围,随着b站的迅猛发展,b站由原先的二次元市场逐渐扩张到电子竞技、美妆、生活、纪录片等多个领域。而b站的营收主要来自平台广告变现,内容付费…

python爬虫2:requests库-原理

python爬虫2:requests库-原理 前言 ​ python实现网络爬虫非常简单,只需要掌握一定的基础知识和一定的库使用技巧即可。本系列目标旨在梳理相关知识点,方便以后复习。 目录结构 文章目录 python爬虫2:requests库-原理1. 概述2. re…

JSP实训项目设计报告—MVC简易购物商城

JSP实训项目设计报告—MVC简易购物商城 文章目录 JSP实训项目设计报告—MVC简易购物商城设计目的设计要求设计思路系统要求单点登录模块商品展示模块购物车展示模块 概要设计Model层View层Controller层 详细设计Model层View层登录界面系统主界面 Controller层 系统运行效果项目…

基于Orangepi 3 lts 的云台相机

利用orangepi 3 lts 和arduino nano 制作了一个云台相机,可用于室内监控。 硬件: orangepi 3 ,arduino nano ,usb相机,180度舵机两个 WeChat_20230806213004 软件: 整体采用mqtt进行消息的中转。 相机采用python 利用opencv…

PAT(Advanced Level)刷题指南 —— 第四弹

一、1104 Sum of Number Segments 1. 问题描述 2. Sample Input 4 0.1 0.2 0.3 0.43. Sample Output 5.004. 题解 思路:打表,比如4个数,第一个数出现1*(4) = 4次,第二个数出现2*(4 - 1) 

以太网协议学习笔记

以太网接口电路主要由MAC(Media Access Control)控制器和物理层接口PHY两大部分构成。 PHY在发送数据时,接收MAC发过来的数据,把并行的数据转化为串行流数据,按照物理层的编码规则把数据编码转化为模拟信号发送出去&am…

【Opencv入门到项目实战】(一):Opencv安装及图像基本操作

文章目录 0.Opencv介绍及环境配置1.图像读取1.1 彩色图像读取1.2 灰色图像读取 2.视频读取3.ROI读取3.1 图形切片处理3.2 提取颜色通道 4.图像填充5.数值运算与图像融合5.1 加法运算5.2 图像融合 6. 总结 0.Opencv介绍及环境配置 OpenCV是一个强大的计算机视觉库,它…

Dockerfile部署golang

使用go镜像打包,运行在容器内 redis和mysql用外部的 项目目录结构 w1go项目: Dockerfile # 这种方式是docker项目加上 本地的mysql和redis环境 # go打包的容器 FROM golang:alpine AS builder# 为我们镜像设置一些必要的环境变量 ENV GO111MODULEon …

Maven-生命周期及命令

关于本文 ✍写作原因 之前在学校学习的时候,编写代码使用的项目都是单体架构,导入开源框架依赖时只需要在pom.xml里面添加依赖,点一下reload按钮即可解决大部分需求;但是在公司使用了dubbo微服务架构之后发现只知道使用reload不足…

编织代码的魔法:掌握Python字符串常用函数的奥秘!

在Python的编程世界里,字符串是你与计算机对话的语言,掌握字符串常用函数就像拥有了一把强大的魔杖,可以编织出令人惊叹的代码魔法。无论你是初学者还是有经验的开发者,本篇博客将带你深入探索Python字符串常用函数,揭…

(学习笔记-进程管理)线程

在早期的操作系统都是以进程为独立运行的基本单位,直到后面,计算机科学家们提出了更小的能独立运行的基本单位:线程 为什么使用线程? 举个例子,假设要编写一个视频播放软件,那么软件功能的核心模块有三个&#xff1a…

机器学习笔记:李宏毅ChatGPT课程1:刨析ChatGPT

ChatGPT——Chat Generative Pre-trained Transformer 1 文字接龙 每次输出一个概率分布,根据概率sample一个答案 ——>因为是根据概率采样,所以ChatGPT每次的答案是不一样的(把生成式学习拆分成多个分类问题)将生成的答案加到…

【Linux】总结1-命令工具

文章目录 基础指令shell命令以及运行原理Linux权限粘滞位工具 基础指令 ls、pwd、touch、mkdir、netstat、cp、mv、cd、tar、zip、unzip、grep、pstack、ps、rm、cat、more、less、head、tail、find、ulimit -a、clear、whoami、man touch:创建文件,也包…