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

网上有很多的博客有谈到这个重名的问题,但是我觉得将不算是很全面,至少我看了还是不懂下面这道题到底是怎么回事。那在讲解这个问题的时候,我们先来看看下面这道题,一道面试题(南山西丽xx研究院):

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

这题打印出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预解析与解析变量声明的顺序

扫描二维码关注公众号,回复: 915746 查看本文章

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);

那么大家再来看看这段代码,觉得你们心目中的答案是多少呢?若是还是觉得等于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的这个函数对象!

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

因为既然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,为什么?大多数同学都知道的吧,是的,这里在函数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(a)}。

按之前网上的说法:函数声明置顶比变量声明置顶更优先。那么第二段代码应该打印undefined,可是为什么打印出的却是函数a呢?这个例子是否告诉我们变量声明置顶比函数声明置顶更优先(这个是我同学研究出来的)?这个不得而知,我也不太确定,但是总归一句话,变量赋值肯定是在两个声明的下面的,所以这并不妨碍我们编写代码,或者做面试题。当然如果刚好出了这么一道题,我希望大家把它当特殊例子对待吧。

或者可以直接这么理解,虽然在下面又声明了一次变量a(与函数名重名),但是原本js内存地址中本来已经声明了一个名为a的函数对象(相当于已经将a这个地址指向了函数对象),再次声明一次a,但并没赋值,所以打印出来的依旧是函数a。

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

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

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

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

猜你喜欢

转载自blog.csdn.net/charles_tian/article/details/79775909