在上一篇文章中我们通过变量在堆栈中的存储和访问知道了脚本语言的执行规则是 先定义,后执行。那么我们就来验证一下在我们的定义代码中除了var一个对象之外,声明函数和执行函数的时候是否遵从这个执行规则。
我们来看一点简单的代码:
输出的三次a分别是什么?我们可以用先定义后执行的思想分析一下
上面的代码块中 定义代码有:
1. var a //定义变量a
2. function fn(){} //声明一个叫fn的函数
那么问题来了,函数 fn 内部的也有定义代码 var a
,会不会先执行呢?是这样的:因为只有当我们调用函数的时候才会执行函数内部的定义代码,所以不会执行函数内部的var a
,目前只有两个定义代码并且从上往下执行。
当我们没有定义代码之后,我们就会执行 执行代码,也是从上往下执行。
执行代码第一步就是fn();
,那么当调用这个函数的时候就会进入到函数内部里面去,我们首先还是会按照先定义后执行的方式
函数内部:
1.var a //定义函数内部的a
2.console.log(a) //输出a 我们发现只定义没有赋值所以是undefined
3. a=1
当函数内部执行完毕之后,将跳出函数继续往下面执行console.log(a)
默认输出全局环境下的a
所以整体的执行顺序如下图:
最后会依次输出 undefined object object
我们只要记住这个执行顺序来冷静的分析,基本上可以减少很多意想不到的错误。
函数表达式
只有function fn()
这样的函数声明 才是定义代码
而自执行函数(function(){})()
和var fn1 = function(){}
他们都属于 函数表达式不属于定义代码。
我们可以通过代码来看函数表达式与函数声明的区别
为什么会报错呢?按照我们的理解,来分析一下执行顺序:
正因为它是函数表达式,而不是函数声明,所以属于执行代码,按照从上往下的执行顺序,在调用
fn1
之后执行,当调用fn1
时它还没有被赋值为一个函数,所以就会报错了。
在分析原因之前我们先了解一下内存中是如何处理的
当程序代码解析时 看到通过函数声明的方式创建函数
fn(){}
的时候, 这个函数 fn 是创建在于堆中的,这个fn在内存中叫做函数的“引用名称”,同时会有一个内存地址,当在运行时环境试图调用fn()时,会先在栈中检索看看有没有定义fn这个变量,如果栈中没有,就会去堆中找fn的引用名称。
注意:匿名函数是没有引用名称的,它只有内存地址。所以按照上面的说法当我们 var fn1 时 会在栈中开辟一块空间叫 fn1,定义代码结束之后会执行执行代码,会在“运行时环境”中调用fn1()这个函数,然后我们会现在栈中检索发现我们定义了fn1这个变量,但是这个变量里面没有存储任何的内存地址,fn1并不是一个函数,所以就会报错。
如果我们把代码换一个顺序:最后再调用fn1()
那么理所当然的,会把匿名函数的内存地址给fn1 ,这样就会输出1了。