Shinkai005

V1

2022/01/20阅读:51主题:红绯

# JavaScript中变量名与函数名重名的问题

JavaScript中变量名与函数名重名的问题

转载一位大神关于变量名与函数名重名问题:

var a = 1;
function b(){
 a = 10;
 return;
 function a(){
  console.log(a);
 }
}
b();
console.log(a);//1

这题打印出a的值为多少呢?可能会有很多的同学认为打印出的值为10,但其实并不是,为什么呢?

误区1:变量提升

我想大家都明白,在执行函数b的时候,由于其内部有一句a=10,前面并没有var,所以在执行完函数b之后,认为变量a提升为全局变量,并且10这个值覆盖了之前的1。所以打印出的值为10。我想的话这是多数新手的思路。

这里有个知识点:

显示声明:var a = 1;

隐式声明:a = 1;

隐式声明会变量提升为全局变量

误区2:不知道函数声明也是有提升的

多数新手认为在执行函数b的时候,js执行代码自上往下,执行到return的时候退出函数,不再执行函数a,所以认为这个函数a根本不起作用。

js知识盲区:js预解析与解析变量声明的顺序

js预解析:在执行代码之前,js会预解析代码,代码中如果有变量声明和函数声明的话,那么便将变量声明和函数声明置顶,网上多数人认为函数声明置顶比变量声明置顶更优先。(事实上是如此吗?这里先埋一个坑,稍后再来讨论)

js解析声明变量的顺序:js在解析变量声明的时候,是分为两个步骤的。

比如这句代码:var a = 1;其实是分为(1)var a;(2)a=1;两个解析步骤来进行的。

好,讲完两个知识点,那么我将原代码改写成如下形式:

function b(){
 function a(){  //函数声明置顶
  console.log(a);
 }
 a = 10;
 return;
}
var a;
a = 1;
b();
console.log(a); //1

那么大家再来看看这段代码,觉得你们心目中的答案是多少呢?若是还是觉得等于10的话,那么就说明你们还没有考虑到本文要讲的重点:重名问题!

重名问题

在讲解这段代码前,我还要给大家要普及一个知识点:作用域链(若是有不懂的同学,可以查阅链接:js闭包)。

好,懂作用域链的同学应该明白,在调用变量或者一个对象属性等的时候,查找的顺序是由里向外的,js执行代码是自上往下的。

那么我们再来分析刚才修改过后的代码:

1)先声明变量a,系统给变量a分配一个内存,注意,这里变量a暂时还不知道它的变量类型,因为还没有赋值;

2)a=1,给变量赋值为1,变量类型为number;

3)开始执行函数b,函数b内有一个函数名为a的函数声明,且函数a下面有一个变量名为a的变量,且赋值为10,这里便开始了难点分析。首先看函数a,由于在执行函数b的时候,并没有调用函数a,因此函数a并没有起作用;但是执行到a=10的时候,js是这样做的:1.首先查找变量a的地址,从系统内存中开始按照作用域链查找;2.由于作用域链的查找顺序是由里向外的,故要先从函数b里面开始查找;3.在查找的过程中,发现函数b中已经声明了一个函数名为a的函数(重名问题!),所以查找到函数名为a的函数后,这里便不再往外查找;4.所以这里的a=10其实是将10赋值给了函数名为a的这个函数对象!

认真读完的人会有一个疑问,我把函数声明改成函数表达式呢?

函数表达式就类似变量声明,是没有特殊性的.因此答案是10.只有当在使用函数表达式的时候才存在 命名冲突的特殊情况;否则按照标准模式解析;

有的同学会问为什么了。这样说吧,在函数b里面的时候,函数a提升置顶,跟变量类似(都在js中声明了),Js给这个函数a分配了内存,而恰巧的是在执行a=10的时候,==优先查找的是与变量a同名的函数a==,因为它所在的作用域最近!讲到这里,我想绝大多数的同学已经明白了这题为什么会打印1了吧?

其实还是有疑惑, 因为a是存的地址,那么找到a的时候不是改地址即可么?

因为既然a=10这个值10赋值给了函数对象a,那么在全局环境下运行console.log(a),访问的是全局变量a,而全局变量a在系统内存中查找的值为1,所以打印的值为1.

那么同学们可以自行将函数b中的函数a给注释掉,看看打印的结果是什么?看代码:

function b(){
 a = 10;
 return;
}
var a;
a = 1;
b();
console.log(a);//10

答案很明显是10,为什么?大多数同学都知道的吧,是的,这里在函数b里的作用域里没有查找到名为a的对象或者变量,那么继续向外查找,发现在全局作用域里发现了有变量a的内存地址,于是将10这个值赋值给了全局变量a。

懂了?好,我们再将代码再修改一次,如下:

function b(){
a = 10;
return;
}
b();
console.log(a);

这个很简单的吧?打印的值为多少呢?依旧是10对吧,为什么呢?是这样的,在执行函数b的时候,发现有一个隐式声明a=10,它在函数b里的作用域中查找不到有关于名为a的地址,于是向外查找,发现全局作用域下也没有,那么变量提升,系统默认给变量a提供一个内存,并将值赋值给变量a,所以变量a变量提升为全局变量了。所以打印出的值为10.

好,讲完这个,我们继续看看我刚才埋下的坑,现在大家来运行这两段代码:

代码1:

var a;
function a()
 console.log(10); 

console.log(a);

代码2:

function a()
 console.log(10); 

var a;
console.log(a);

运行之后我们会发现,两段代码运行的结果是一样的,均为:function a(){console.log(10)}。

这就应证了网上的说法:函数声明置顶比变量声明置顶更优先。如果不是的话,那么第二段代码应该打印undefined,可是为什么打印出的却是函数a呢?这个例子是否告诉我们变量声明置顶比函数声明置顶更优先?

但很明确的告知大家,==函数声明置顶比变量声明置顶更优先==。

但上述的结果不是按照声明提升的思维来看,是因为在预解析阶段变量只声明了,但未赋值,而函数不一样,==它在预解析阶段就已经声明和赋值了==,它的值就是函数这个对象,而在打印的时候,打印是需要出具体的值的,而变量还未赋值,函数却已赋值了,所以两次运行结果均是function a(){console.log(10)}。如果去掉函数a,运行的结果是undefined,是因为a被初始化并赋值为undefined了。

变量名与函数名重名的总结:

1.要知道js解析变量声明的顺序

2.函数声明和变量声明会置顶且函数声明更优先!

3.作用域链的查找顺序是由里向外,js执行代码顺序是自上往下

{
    function a ({
      a=1000;
    }
    a=10;
}
console.log(a) // f a(){}

分类:

前端

标签:

Node.js

作者介绍

Shinkai005
V1

公众号:深海笔记Shinkai