我们知道函数有很多要素:调用时机、作用域、闭包、形式参数、返回值、调用栈、函数提升、arguments(除了箭头函数)和this(除了箭头函数)
今天我们讲的就是函数里面的一个重要要素:调用时机
函数调用的时机不同,执行的结果就不同我们来看几个例子:
-例1
let a=1
function fn(){
console.log(a)
}问:打印的结果是?
答:不知道,因为没有调用函数fn()
-例2
let a=1
function fn(){
console.log(a)
}
fn()很显然因为调用了函数所以打印出的结果为:1
-例3
let a=1
function fn(){
console.log(a)
}
a=2
fn()问:打印的结果是?
答:2,相比例2,这里在调用函数前对a进行了一次赋值,所以得到的是2。
-例4
let a=1
function fn(){
console.log(a)
}
fn()
a=2
问:打印的结果是?
答:1,因为调用函数的时机在赋值前面。
-例5
let a=1
function fn(){
setTimeout(()=>
{console.log(a)
},0)
}
fn()
a=2问:打印的结果是?
答:2
这里用了个了个定时器setTimeout,它的作用就是在指定的毫秒数后,将定时任务处理的函数添加到执行队列的队尾。因为后面是0,也就是马上将定时任务处理的函数添加到执行队列的队尾。又因为Javascript引擎是单线程运行的,浏览器无论在什么时候都只且只有一个线程在运行的。setTimeout是异步的,其实调用setTimeout时候,会有一个延时事件排入队列,然后setTimeout调用之后的那行代码运行,接着是再下一行代码,直到没有代码,javascript虚拟机才会问,队列里还有吗?如果队列中至少有一个事件适合于触发,比如上面的setTimeout函数,则会调用setTimeout那个函数。所以打印出的是2。
-例6
let i=0
for(i;i<6;i++){
setTimeout(()=>
{console.log(i)
},0)
}
问:打印的结果是?
答:不是0、1、2、3、4、5而是6个6
是不是很奇怪,我们在上面就讲到了,在调用setTimeout的时候,他会等代码执行完,然后才会调用setTimeout那个函数,所以此时i=6,因为for循环执行了6次,就相当于定了6个闹钟,等时间到了,闹钟就会响6次。所以得到的是6个6。
-例7
for(let i=0;i<6;i++){
setTimeout(()=>
{console.log(i)
},0)
}问:打印的结果是?
答:0、1、2、3、4、5
这个理解起来比较麻烦,也是JS的蛋疼之处,JS为了我们方便理解,在函数执行的时候默认重新给i赋值,具体可见方方老师的《我用了两个月才理解let》
我们还可以这样打印出0、1、2、3、4、5
- 使用立即执行函数
let i=0
for( i;i<6;i++){
+function(j){
setTimeout(()=>
{console.log(j)
},0)
}(i)
}- 使用数组
let arr=new Array()
for(let i=0;i<6;i++){
arr.push(i)
}
console.log(arr)
let a=arr.join('')
console.log (a)- 使用中间变量
let i=0
for( i;i<6;i++){
let j=i
setTimeout(()=>
{console.log(j)
},0)
}结果就打印出了0,1,2,3,4,5
接下来说下我对变量的提升的理解:
要搞清楚提升的本质,需要理解 JS 变量的「创建create、初始化initialize 和赋值assign」,可以把创建理解成声明declare。
我们来看看 var 声明的「创建、初始化和赋值」过程
我们看下面代码:
console.log(a)
var a = 1我们发现:
这个并不会报:Uncaught ReferenceError: a is not defined。
而是会输出 undefined。因为变量提升的结果是:
var a
console.log(a)
a = 1也就是说var 声明会在代码执行之前就将创建变量,并将其初始化为 undefined。
接下来看 let 声明的「创建、初始化和赋值」过程
我们看同样的代码,把var改成let
console.log(a)
let a = 1我们发现:
这个代码获取看不出变量是否提升了,但可以肯定的是let和var是不同的(实际上let是块级作用域,var是全局作用域),我们再来看代码:
let a=1{
console.log(a)
let a= 2
}运行结果是:
根据上面可以看出,let确实让a变量提升了,否则的话a的值就是1了、,但是没有让a初始化,所以我们知道let 在变量申明的时候没有给变量初始化。
{
console.log(a);
let a=1;
}上面的错误的信息提示为:a没有初始化之前不能使用,所以我们知道,let a =1 可以让变量的声明提升,但是没有给a 初始化,let的变量没哟哟初始化之前不能使用。
const,其实 const 和 let 只有一个区别,那就是 const 只有「创建」和「初始化」,没有「赋值」过程。
接下来来看 function 声明的「创建、初始化和赋值」过程
假设代码如下:
console.log(a);// undefined
if(true){
console.log(a); // function a
function a(){}
}
如果是变量提升,是不存在块级作用域的,但是函数提升是存在的,这个预解析如下:
var a; // 函数 a 的声明
console.log(a);// undefined
if(true){
function a(){} // 函数 a 的定义
console.log(a); // function a
}其实函数 function a(){} 在经过预解析之后,将函数声明提到函数级作用域最前面,然后将函数定义提升到块级作用域最前面。
注意:这里的函数定义是提升到块级作用域最前面。
这四种声明,用下图就可以快速理解:
所谓暂时死区,就是不能在初始化之前,使用变量。
前端小白,请留言指正!!!