javascript 变量与常量本质
Posted 岁月静好2570
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了javascript 变量与常量本质相关的知识,希望对你有一定的参考价值。
变量与常量的本质
-
变量是什么:用直白的语言描述就是,有一个数据保存起来了,当接下来需要使用到这个数据时,需要在保存这个数据的位置把它拿出来用,一般的解决方式就是用一个名称与这个数据对应起来,下次要用数据直接使用这个名称就行,这个名称就是变量。
-
变量的本质是:当一段数据保存在计算机的内存中,在程序运行的某一时刻需要读取这段数据时应该如何找到这个内存地址呢,解决方案就是变量----变量保存的就是这个内存地址的编号,读取变量的值即是使用变量保存的地址编号去查看该地址段当前保存的值是什么。
-
常量的本质也是这样,常量保存内存地址,内存地址保存数据,分别只是常量保存的数据只能是初始化时保存,之后不能改变,而变量则是随时可变。
-
至于变量与内存地址的关系,内存地址也是一种数据,变量名与内存地址之间的联系则由编程语言实现,猜测应该是程序在运行准备的时候将变量与内存地址保存的一个表中,当程序在执行阶段读写变量时,通过查表获取变量对应的地址,然后从地址中读写数据。
与变量有关的概念
-
作用域相关: 全局作用域、局部作用域、变量提升、暂时性死区。
-
值引用与堆引用
-
声明、读写与删除规则
-
垃圾回收机制
js变量
声明方式
-
使用
var || let
关键字,之后跟随变量名声明,变量声明时可以设置初始值,也可以不设置,不设置初始值时的默认值为undefined
。使用未定义的变量会抛出xxx is not undefined
,基于这个默认行为,推荐的编程范式是始终给声明的变量赋初始值,如值不确定则显示的赋值为null
。 -
一条声明变量语句可以定义多个变量,语法是关键字后定义表达式用逗号
,
分隔。 -
变量名必需是英文字母、数字、下划线;且数字不能用为首字母。虽然还有其他合法的字符可以做为变量名,但都不是好的选择。
// javascript
let name = \'chen\';
var age = 123;
let sex = \'男\',
height = 175;
const age = 30;
读写与删除操作
-
使用变量名即是读操作,变量名做为左值即是写操作
-
JS做为动态弱类型语言,写操作可以给变量赋任何类型的值,读操作时也不能确定值是何种类型
-
无法使用
delete
运算符删除声明的变量,var
删除不成功但不报错,let
会报错。
垃圾回收机制
- 垃圾回收需要掌握的几个点是:
- 宿主环境会实现好自动垃圾回收机制,一般不需要手动回收。
- 函数作用域内的变量会在函数运行结束后释放,但闭包函数中的变量不会,所以使用闭包后要检查闭包函数执行的返回值引用情况。
// JavaScript
let name = \'chen\';
console.log(name); // 读操作
name = 123; // 写操作
name = name > 345 ? name : 345; // 先读操作再写操作
变量作用域
- 变量作用域指的是程序在何种区域内可以读写变量,从字面意思来理解:变量产生作用的区域,有以下三种作用域
- 全局作用域: 整个程序从开始运行到结束运行算是一个生命周期,在这个生命周期内有一个顶层的全局变量,在程序运行的任何时间、任何地点都可以产生作用,换句话说,全局变量在任何地方都可以读写。
- 函数作用域:从函数声明语句的大括号内开始算,到大括号结束为止,在这个区域内声明的变量从外部无法读写,但函数内可以读写函数外的变量;当然这不是好的编程习惯,好的方式是,如果函数依赖外部数据,永远都要使用参数传递的方式来实现。当然,如果函数作为对象的方法时,可以自由的读写对象的属性。
- 块作用域:ES6中引入的概念:只在是大括号限定的代码块,都会是一个单独的作用域,循环、判断都形成自己的作用区域。
作用域链:以上三种作用域都是可以从内向外一层层查找变量,直到全局变量;如果有找到则做读写操作,如果未找到则抛出异常 XXX not is undefined
。这种象链条一样从内到外的查找方式就叫作用域链。局部作用域可以访问全局作用域中的变量,全局无法访问局部作用中的变量。当局部作用域与全局作用域存在同名变量时,优先使用局部作用域。
函数中使用函数外的变量,其行为是函数定义时确定变量位置,跟函数在哪个位置运行无关。
var
与 let
的区别
- 作用域范围不同:
var
只有全局作用域与函数作用域,let
除有全局与函数作用域外,还有块作用域,块作用域指的是只要在{}
大括号内就会形成局部作用域,大括号外无法访问。
// 变量作用域
// 全局作用域
var name = \'abc\'
function fn() {
// 局部作用域
console.log(name);
var name = 123;
}
// 全局作用域
let age = 36;
function fnName() {
// 函数作用域
let age = 40;
// 块作用域
if (true) {
let age = 50;
}
for (let i = 0; i < 10; i++) {
let age = 60;
console.log(age);
}
}
- 变量提升与暂时性死区:
var
声明的变量会在代码运行之前先有一个变量提升的机制,变量提升就是当程序运行到相应作用域内时,会先将var
关键字声明的变量名保存为一张与内存地址对应的表,内存地址中的值默认为undefined
,这就是需要注意的地方,变量提升的只是声明部分,赋的值不会被提升。这种行为会导致在var
关键字声明语句之前就可以读写操作变量了,而var
关键字又没有块作用域的概念,极易产生直觉之外的效果,是BUG的高发地。但是let
没有变量提升的机制,变量必需在声明语句之后才可以使用。但这又存在一个暂时性死区的问题,什么是暂时性死区:当前作用域开始执行之后到变量声明之前的区域都是死区;如果使用的变量名在当前作用域中不存在,程序会自动向外查找更高一层的作用域,直到全局作用域;但如果这个变量名在当前作用域是有声明的,只是运行到这一刻的时候还没到声明语句,此时就不会向外查找,而当前也还没到声明语句处,使用该变量名是非法的,是在暂时性死区中。
“暂时性死区”(TDZ)。意味着
typeof
不再是一个百分之百安全的操作,有些死区很隐蔽,比如函数参数设置默认值时前面的参数值是由后面参数定义的情况function(x = y , y = 2){}
,由于此时参数还未声明会报错。
-
var
声明的变量可以在同一作用域内重复声明,但重复声明变量是无效的,只是不报错,如果后面重复声明且初始化赋值了前面已经声明过的变量,则后声明变量覆盖前面的值。而let
声明的变量不可以重复声明 -
var
声明的变量是顶层全局对象(window || global
)的属性,无法通过delete
删除,而let
声明的变量不再是顶层全局对象的属性。 -
var
使用未声明变量与为未声明变量赋值产生的行为是不一样的,使用会报错,而赋值会隐式的生成全局变量,此点要极力避免,在严格模式下赋值也会报错。let
则统一了这两种操作的结果为抛出异常。
// JavaScript
var arrEs5 = [];
for (var i = 0; i < 10; i++) {
arrEs5[i] = function() {
console.log(i)
}
}
arrEs5[0](); // 10
let arrEs6 = [];
for (let i = 0; i < 10; i++; i++) {
arrEs6[i] = function() {
console.log(i)
}
}
arrEs6[0](); // 0
//for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。
for (let i = 0; i < 3; i++) {
let i = \'abc\';
console.log(i)
}
// abc
// abc
// abc
js常量
- ES6语言标准才引入常量的语法支持,使用
const
来声明常量。 - 常量必需在声明时初始化赋值,声明语句之后不可以写常量,只可以读取常量的值。
- 常量的作用域规则与
let
相同。 - 常量的值为复合类型时,常量实际指向的是复合类型数据的堆内存,此时复合类型中的值可以更改,只是不能更改该常量对复合数据的引用关系
本质上并不是变量的值不能改动,而是变量指向的那个内存地址不能改动。所以简单类型的数据,值就保存在那个内存地址,而复合类型的数据,内存地址保存的只是一个指针,因此将一个对象声明为常量是可以更改对象属性的。
以上是关于javascript 变量与常量本质的主要内容,如果未能解决你的问题,请参考以下文章