关于JS函数声明前置的笔记

之前学习JS的变量声明前置和函数声明的相关内容时,忘记讨论一个互相影响的问题了,这里特地补充一下。

即JavaScript函数声明和变量声明同名情况下的优先级讨论。


首先看一个栗子

var fn = 10;
function fn() {}
console.log(fn);    // 10

function fn2() {}
var fn2 = 10;
console.log(fn);    // 10

无论是哪种书写方式,最后的console值总为10,即,最后一个生效的是和变量声明的代码相关。

我们改写一下:

// 栗子1
console.log(fn);    // funciton fn() {...}
var fn = 10;
console.log(fn);    // 10
function fn() {}
console.log(fn);    // 10

// 栗子2
console.log(fn2);    // funciton fn2() {...}
function fn2() {}
console.log(fn2);    // function fn2() {...}
var fn2 = 20;
console.log(fn2);    // 20

// 栗子3
console.log(fn3);    // funciton fn3() {...}
var fn3;
console.log(fn3);    // funciton fn3() {...}
function fn3() {}
console.log(fn3);    // funciton fn3() {...}

这里可以肯定的是,函数声明优先级的确大于变量声明

对于栗子1,第二个console之前存在一个同名的变量声明,这里就将fn函数cover掉了,我们知道,变量会声明前置但是赋值不会,这里的var fn = 10实则其实只进行了a = 10的操作,对,没错,你没看错,只有赋值没有声明,为什么?因为声明早已在函数声明时就进行了!


这里要补充一个知识点,以function fn() {}为例,我们看见的函数名:fn,它其实是一个存在在栈中的变量,这个变量引用了一个函数对象。

截取自JavaScript编程全解

截取自JavaScript编程全解

也就是说,在声明函数的时候,内部其实是先创建一个Function类的实例,而函数名作为一个变量去引用这个函数对象。

这其实也就是函数就是对象的正确理解。

这在栗子3中也可以看到,在栗子3中,首先function fn3() {}这里,函数声明前置,其实就是声明一个变量,名为fn3,引用了一个函数对象。而后,我们重新对var fn3;进行了一次声明操作,发现并没有任何变化,fn3依旧持有一个函数对象的引用。而不是想当然的undefined


我们在再看栗子2,栗子2和栗子1是作为对照的。

// 栗子1
console.log(fn);    // funciton fn() {...}
var fn = 10;
console.log(fn);    // 10
function fn() {}
console.log(fn);    // 10

// 栗子2
console.log(fn2);    // funciton fn2() {...}
function fn2() {}
console.log(fn2);    // function fn2() {...}
var fn2 = 20;
console.log(fn2);    // 20

主要变化在于一个是函数声明和变量声明的顺序。
其实只要记住一句话,赋值操作不会前置,这里就能理解了。

  • 栗子1的第二个console.log是在赋值操作之后的,一旦进行var fn = 10;(其实是fn = 10),fn的数据类型就是一个数值了。
  • 而栗子2的第二个console.log是在赋值操作之前,数据类型并没有改变,所以是一个函数。

我们就来几个test:

function fn(fn) {
    console.log(fn);

    var fn = 3;
    console.log(fn);
}

fn(10);

// 10
// 3

因为这一问题有些特殊,我直接就附上答案。
最后一个console是3,这没什么疑问,主要关注点在于参数fnvar fn = 3;之间发生了什么?,实参与内部变量声明的关系是什么?

这里我需要附上JS高程里的一些文字(有些长):

ECMAScript不介意传递进来多少个参数,也不在乎传进来的参数是什么数据类型。即便你定义的函数只接收两个参数,在调用函数时也未必一定要传递两个参数,可以是一个、两个、三个甚至不传参数,而解析器不会有什么怨言。之所以这样,原因是ECMAScript中的参数在内部是用一个数组表示的。函数接受到的始终都是这个数组,而不关心数组中包含哪些参数(如果有的话)。如果这个数组中不包含任何元素,无所谓;如果包含多个元素,也没有问题;在函数体内可以通过arguments对象来访问这个数组,从而获取传递给函数的每一个参数。
其实,arguments对象只是与数组类似,但它并不是Array的实例,因为可以使用括号访问它的每一个元素(arguments[0]、arguments[1]以此类推)。
通过访问argument.length可以获取有多少个参数传递给了函数。

重点1:arguments对象内的值和参数不是一个“东西”,是两个......
重点2:arguments对象的值与参数的值保持同步,更改arguments对象内的值会影响参数的值。

栗子1

function doAdd(num1,num2) {
    arguments[1] = 10;
    console.log(arguments[0] + num2);
}

doAdd(0,100);
// 10

栗子2

function fn(a) {
    console.log(arguments.length);      // 3
    console.log(arguments[0]);          // 1
    console.log(a);                     // 1
    var a = 100;
    arguments[0] = 1000;
    console.log(arguments[0]);          // 1000
    console.log(a);                     // 1000
    
 }
fn(1,2,3);
  • arguments对象里有几个元素,不是函数定义的括号里的形参决定的,而是,在调用函数时,你传入几个参数,argument对象内就有几个元素,顺序和传参时相同;
  • function doAdd(num1,num2,num3) {} doAdd();形参定义了三个,但是无传参,那么num1,num2,num3的值就是undefined。arguments.length为0;

正如栗子1,读取arguments[1]的值和读取num2的值,它们并不是访问相同的内存空间,它们的内存空间是独立的。

严格模式下,arguments对象的值和形参的值是独立的
function test(num1,num2){
'use strict';
console.log(num1,arguments[0]);//1 1
arguments[0] = 2;
console.log(num1,arguments[0]);//1 2
num1 = 10;
console.log(num1,arguments[0]);//10 2
}
test(1);

好,回到那个test。

function fn(fn) {
    console.log(fn);

    var fn = 3;
    console.log(fn);
}

fn(10);

// 10
// 3

我的想法是:
参数fn是个什么东西?它肯定不是argument[0],但内部肯定有一个值传递的过程,因为最初的10保存在arguments[0]上。

是变量吗,如果是变量,那么有没有变量声明这一过程?即从undefined到10的过程?
这里我用chrome develop tools看了看,发现当fn进栈的时候,fn就已经为10了;



经过 var fn = 3;fn被重新赋值为3。

结论:

函数的参数是在函数内部可用的,是局部变量

javascript函数参数的作用域于参数类型无关,也就是说不管参数是函数还是其他类型,这个参数的作用域只在接收这个参数的函数内有效,所以说他是个局部变量,只可以在这个方法内部使用


来看下一题:

console.log(a);  
a();           
var a = 3; 
function a() {
    console.log(10);  
}
console.log(a);  
a = 6;        
a();          

分析:
首先函数声明前置,但是!由于赋值操作没到,所以此时的a依旧引用了一个函数。

其实声明操作早已在function a() {...}这里就进行了,整个代码只有一次声明,发生在函数声明处(优先),所以没有发生变量声明前置就没有这一操作。

所以等于就是console了一个函数。
console.log(a); // 第一个弹出:function a() {...} 函数语句

之后调用a函数,a函数进执行栈,执行函数体内部代码,则:
console.log(10); // 第二个弹出:10

这里就到了赋值操作:
a = 3;
到这里,a就不在是一个引用对象了,而是保存一个数值。那么继续执行。
console.log(a); // 第三个弹出:3

然后又进行一次赋值操作:
a = 6;
此时变量a的值为6。

那么,a已经不是函数了,调用a()会发生啥?
当然报错啦233333。
TypeError: a is not a function


改写一下:

console.log(a);         // function a() {...}
var a = 3;              // a = 3
console.log(a);         // 3
a();                    // a is not function
function a() {
    console.log(10);  
}
console.log(a);
a = 6;               
a();               

最后引用一篇文章做总结:

bug达——JS中变量名和函数名重名
1. 函数声明会置顶
2. 变量声明也会置顶
3. 函数声明比变量声明更置顶:(函数在变量上面)
4. 变量和赋值语句一起书写,在js引擎解析时,会将其拆成声明和赋值2部分,声明置顶,赋值保留在原来位置
5. 声明过的变量不会重复声明

面试时,务必看清楚,如果同时存在重名函数声明和变量声明,那么实际操作中,变量名只声明了一次,引用了一个函数。
如果后面有赋值操作,根据右值更改变量a的数据类型,之前的函数引用被断开。
一部一部,其实很简单。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,012评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,628评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,653评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,485评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,574评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,590评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,596评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,340评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,794评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,102评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,276评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,940评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,583评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,201评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,441评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,173评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,136评论 2 352

推荐阅读更多精彩内容

  • 第2章 基本语法 2.1 概述 基本句法和变量 语句 JavaScript程序的执行单位为行(line),也就是一...
    悟名先生阅读 4,145评论 0 13
  • 函数声明和函数表达式有什么区别 (*)解析器会率先读取函数声明,并使其在执行任何代码之前可以访问;函数表达式则必须...
    coolheadedY阅读 388评论 0 1
  • 1.函数声明和函数表达式有什么区别 (*) 区别: 函数声明后面的分号可加可不加,不加也不影响接下来语句的执行,但...
    Sheldon_Yee阅读 399评论 0 1
  • 工厂模式类似于现实生活中的工厂可以产生大量相似的商品,去做同样的事情,实现同样的效果;这时候需要使用工厂模式。简单...
    舟渔行舟阅读 7,744评论 2 17
  • 2015年9月1日,你在分班名单上找到了自己的名字,心里想着,谁会是教你们的老师呢? 原来她是一个眼睛小小的,皮肤...
    海若yan阅读 183评论 0 0