深入探究js中的隐式变量声明

前两天遇到的问题,经过很多网友的深刻讨论,终于有一个相对可以解释的通的逻辑了,然后我仔细研究了一下相关的点,顺带研究了一下js中的隐式变量。

  • 以下文章中提到的隐式变量都是指没有用var,let,const等关键字定义的变量。
  • 以下文章中提到的var变量都是指用var声明定义的变量。

一遇到隐式变量,我们去百度一下,都会看见这样一句话,隐式变量是全局变量,在函数中用隐式变量也是全局变量,但是在函数中用var变量是局部变量,那我们来具体看下隐式变量到底与var变量有什么区别,下面我们来通过一些代码来探究隐式变量与var变量的区别。

1.隐式变量与var变量的区别

代码1:

var变量:

console.log(a);//undefined
var a = 100;

代码2:

隐式变量:

console.log(a);//Uncaught ReferenceError: a is not defined
a = 100;

看上面的代码我们发现var变量a是存在变量声明提升的,也就是代码1相当于下面的代码:

var a;
console.log(a);
a = 100;

这与我们以前了解的那个var变量是一样的,变量声明提升还是那个变量声明提升,并不会改变,这里如果想详细了解变量提升的,推荐这篇文章大家可以看一下。然后我们继续看代码2,隐式变量在前面打印变量a时会报错,那这里我们是不是可以猜测,隐式变量是不是不存在变量提升,当然以前好像也没有人说过隐式变量存在声明提升,可能我一厢情愿的认为隐式变量是全局变量就存在声明提升。然后我们继续看在函数中,代码块中声明的隐式变量是不是跟上面全局定义的隐式变量一样,不存在声明提升。

代码3:

函数中的var变量:

console.log(a);//Uncaught ReferenceError: a is not defined
function b() {
    console.log(a);//undefined
    var a = 100;
}
b(); 

代码4:

函数中的隐式变量:

console.log(a);//Uncaught ReferenceError: a is not defined
function b() {
	console.log(a);//Uncaught ReferenceError: a is not defined
	a = 100;
}
b();

我们来看上面的代码3和代码4,我们发现代码3,在第一行就报错,因为var在函数内部定义的变量属于局部变量,全局是访问不到的,然后把第一行注释掉再运行,发现第三行打印出的a是undefined,证明var变量在函数中进行了变量声明提升。我们来看代码4,在函数内定义了一个隐式变量,然后第一行就会报错,a是未定义,然后把第一行注释掉再运行,发现第三行也还是报错a未定义,这里是不是就说明隐式变量并不会有变量声明提升这种操作,换一句话说,就是隐式变量类似于函数一样,声明和定义是一起的,只有执行了这行代码,才能引用访问这个变量,这里推荐一篇关于变量生命周期的文章,可以细读一下,然后我们再看一下代码块中定义的var变量和隐式变量:

代码5:

代码块中的var变量:

console.log(a);//undefined
{
	console.log(a);//undefined
	var a = 100;
}

代码6:

代码块中的隐式变量:

console.log(a);//Uncaught ReferenceError: a is not defined
{
	console.log(a);//Uncaught ReferenceError: a is not defined
	a = 100;
}

然后我们看代码块中的var变量,是存在声明提升的,然后隐式变量是不存在声明提升的,所以上面访问都会报错。

  • 结论:根据上面的一系列的探究我们基本可以确定一个结论,那就是隐式变量是不存在变量声明提升的。

2.代码块中的函数声明提升

上面探究完隐式变量我们再稍微看下代码块中的函数声明提升:

代码7:

console.log(a);//undefined
{
	function a(){};
	console.log(a);//ƒ a(){}
}
console.log(a);//ƒ a(){}

我们看上面的代码,我们都知道函数是js中的"一等公民",优先级是最高的,那在上面这个代码中,函数是否提升到了块作用域的外面呢,如果我说提升了,那你从块作用域最外面调用你会发现报错,a不是一个方法:

a();//Uncaught TypeError: a is not a function
{
	function a(){};
}

然后这样之前的我就得出一个结论,函数声明提升并没有将函数提升到最外层,那就要问那上面我们打印的a为undefined,为什么不是报错a未定义呢,根据阮一峰老师的这篇文章的描述,我们可以得出一个结论:

  • 块作用域内定义的函数在块作用域内会进行函数提升,提升到最上面,然后在最外层类似于var声明一个同名变量,默认值为undefined。

3.代码块中存在同名的隐式变量与函数的情况

然后我们再回过头来看代码块中同时存在同名的隐式变量和函数时会怎样:

首先我们来看上面这个代码,此时如果我们再最外层的上面打印a和b你会发现会打印出来是undefined:

代码8:

console.log(a,b);//undefined undefined
{
	console.log(a);//ƒ a() {}
	function a() {};
	a = 50;
	console.log(a);//50
}
console.log(a);//ƒ a() {}
{
	console.log(b);//ƒ b() {}
	b = 50;
	function b() {};
	console.log(b);//50
}
console.log(b);//50

我们看上面的代码,你会发现最前面打印a和b都是undefined,通过前面对隐式变量和函数声明的探究我们可以知道此时外面的a和b都是函数声明定义的,另外我们从侧面也可以看出这一点,vscode有一个功能,是可以查看变量是谁定义的,那我们来看一下:

此时我们发现vscode分析出来最上面的a和b都是a和b函数定义的,根据我们上面的探究,既然隐式变量不存在变量声明提升,那只能函数定义的,然后通过对函数的探究可以证实这一点,同时配上vscode分析出来的结果,也证实了这一点,首先可以确定,最外层的全局的a和b都是函数定义的。然后我们再继续往下看,最开始的a和b打印undefined没问题,

然后我们来看下上面图片上第16行打印的结果为什么是a方法,通过我上篇文章一行一行的调试你会发现,此时代码块中的a其实是被限制在块作用域里面的,并不是全局的变量,此时其实a = 50这行代码不是隐式变量,因为a已经被函数定义过了,那a = 50也就是对之前定义过的变量赋值了,所以此时

a = 50不是隐式变量,然后对之前定义的a赋值,在代码块中打印出来为50,然后出了块作用域打印出来的结果为方法a,通过代码一步步的调试你会发现全局的a,只有在执行a方法的代码时才会把块作用域赋的值同步到全局。

第一步:此时全局的a为undefined,此时a是代码块中函数定义的:

第二步:此时我们发现出来了一个块作用域,因为代码块中的函数提前了,此时代码块中的a的值为a方法,而全局的a还是undefined,没什么问题

第三步:此时我们会发现当函数a这一行代码走完之后,块作用域里面的a跟全局的a都变成了a方法,也就是说想要让块作用域中的a的值同步到全局,必须让代码执行到定义a方法的下一行才可以,要不然代码块中的a的值是不会同步到代码块外面的

第四步:到这一步我们会发现块作用域中的a变成了50,而全局的a还是a方法,根据上一步得到的结论,此时只要在a = 50这行代码后面再执行一次方法a,a = 50就会被同步到全局,此时验证一下也确实是这样

然后出块作用域你会发现打印a为a方法,此时你就不会好奇为什么打印结果是a方法了,因为块作用域中的a并没有同步到全局,而b打印50,是因为b = 50后面执行了一行b方法,将b同步到了全局。到这里我们也就了解为什么两个代码只是换了一下函数的位置打印结果过就完全不同了,然后看文章的时候如果发现有什么错误或写的不好的地方,还请指正,我会立即做出修改。

原文地址:https://www.cnblogs.com/yukixing/p/11617354.html

时间: 2024-10-06 09:03:14

深入探究js中的隐式变量声明的相关文章

EL表达式中引用隐式变量

除了在jsp中9大隐式变量(在前面文章也叫预定义变量)在转化成为servlet后_jspService中可以看到: public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response) throws java.io.IOException, javax.servlet.ServletException {

万恶之源:C语言中的隐式函数声明

1 什么是C语言的隐式函数声明 在C语言中,函数在调用前不一定非要声明.如果没有声明,那么编译器会自己主动依照一种隐式声明的规则,为调用函数的C代码产生汇编代码.以下是一个样例: int main(int argc, char** argv) { double x = any_name_function(); return 0; } 单纯的编译上述源代码.并没有不论什么报错,仅仅是在链接阶段由于找不到名为any_name_function的函数体而报错. [[email protected] t

js中的隐式类型转化

//1.数学运算的时候 var num = +'123'; var num1 = -'123'; var num2 = '123'/1; console.log(typeof num1) console.log(typeof num) //2.new 运算符 var num = new Number('你好'); console.log(num) //3 字符串拼接 无论是任何其他数据类型的值跟字符串进行拼接,都会变成字符串 var ret = '123'+456;//123456 var re

javascript中的隐式类型转化

javascript中的隐式类型转化 #隐式转换 ## "+" 字符串和数字 如果某个操作数是字符串或者能够通过以下步骤转换为字符串的话,+将进行拼接操作. 如果其中一个操作数是对象(包括数组),则首先对其调用`ToPrimitive`抽象操作,该抽象操作再调用`[[DefaultValue]]`,以数字作为上下文. `[1,2]+[3,4]=='1,23,4'` 原因,因为数组的valueOf操作无法得到简单的基本类型,于是它转而调用toString.因此上栗得到的是'1,23,4'

Maven内置隐式变量(转)

Maven提供了三个隐式的变量可以用来访问环境变量,POM信息,和Maven Settings env env变量,暴露了你操作系统或者shell的环境变量.便 如在Maven POM中一个对${env.PATH}的引用将会被${PATH}环境变量替换,在Windows中为%PATH%. projetc project变量暴露了POM.可以使用点标记(.)的路径来引用POM元素的值.例如 <project><modelVersion>4.0.0</modelVersion&g

Shell、Awk 中自动隐式类型转换的“坑”

1.问题: 在林林总总的编程语言里,弱类型的语言着实不少,一方面这种"动态类型"用起来很方便,而另一方面则"坑"你没商量~ 常见的 SQL.Shell.Awk 都会遇到各种暗藏的"隐式类型转换",下面就列举一些 shell.awk 里的自动隐式类型转换 case,防止掉坑. 注意 shell.awk 的变量为空 字符串.变量为空 未定义.初始值的隐式转换问题: # shell 下的字典排序比较 [email protected] 10:59:23

Makefile中的隐式规则

Makefile中的隐式规则 1.隐式规则中的变量 隐式规则中使用的变量分成两种:一种是命令相关的,如"CC":一种是参数相关的,如"CFLAGS". 与命令相关的变量 变量 含义 AR 函数库打开包程序.默认命令是"ar" AS 汇编语言编译程序.默认命令是"as" CC C语言编译程序.默认命令是"cc" CXX C++语言编译程序.默认命令是"g++" CO 从RCS文件中扩展文件

Scala 中的隐式转换 implicit

Scala语言中的隐式转换是一个十分强大的语言特性,主要可以起到两个作用: 一.自动进行某些数据类型的隐式转换 String类型是不能自动转换为Int类型的,所以当给一个Int类型的变量或常量赋予String类型的值时编译器将报错.所以,一下语句是错误的. val x: Int = "100" 如果需要将一个字符串类型的整形数值赋给Int,比如使用String.toInt方法,例如: v al x: Int = "100".toInt 如果想让字符串自动转换为整形,

JavaScript中关于隐式转换的一些总结

JavaScript运算符中的隐式转换规律:一.递增递减运算符(前置.后置)1.如果包含的是有效数字字符串或者是有效浮点数字符串,则会将字符串转换(Number())为数值,再进行加减操作,返回值的类型是:number类型.2.如果不包含有效数字字符串,则会将字符串的值转换为NaN,返回值的类型是:number类型.3.如果是boolean类型,则先会把true或者false转换为1或者0,再进行加减操作,返回值的类型是:number类型.4.如果是null类型,则先会把null转换为0,在进行