js中的提升!绝对神总结——《你不知道的js 上卷》读书笔记(四)

看到这个题目我们可能会想到若干问题:

1.提升什么东西?

2.提升到哪里?

3.为什么要提升?

4.什么时候提升?

5.提升做什么用?

如果你也有这些疑问,就跟着我一起来学习这篇文章吧!不论你是连提升两个字都没听过的j小白,还是你已经略知一二,这篇文章都可以给你一定的收获!

什么是js的提升?

首先呢我们应该知道,js这门语言他虽然是解释型的语言,但是也是有编译过程的!如果你不知道js编译器做什么可以看看 之前的文章 ,在编译阶段,有一部分工作就是提升!提升可以理解成字面本意,就是把一部分代码从其出现的位置提升到一些代码的前边,也可以说移动到前面!

提升的是什么呢?

**提升的对象有两种:变量声明和函数声明!**也就是说下面这张图中,红色框框的变量声明和绿色框框的函数声明应对应的代码要被提到前边!
图1 代码示例

值得注意的是,提升时不会提升表达式的函数,也就是上文中,foo2会被当作变量声明提升而不是函数声明提升,因为它已经不是严格的函数声明了!!注意,只有严格的声明才会被提升!if else 语句中即使声明了函数也会按照函数表达式来处理哦(这是目前我知道的,不知道其它的会不会有这样的情况)也就是经过编译器处理的代码为var name 和 function name(){}两种形式的才会被提升哦。那么就有人问了,上边代码里var b = 2为什么也提升了呢?这是因为编译器会把它编译成两行代码啦,分别是var b; b = 2,所以看我的框框只框住了提升的代码。当然这两行代码也不是最终执行的代码,只不过要强调一下中间会有这样的过程。

现在我们了解了提升内容,那么如何进行提升呢?提升到前边干什么呢?继续往下看!

提升的规则?

有了需要提升的代码,我们进一步考虑如何提升?
是按照书写顺序一个一个提到前面就可以了吗 ?答案当然是否定的!

js编译器有它自己的提升规则!
有一些可以确定的点:函数变量优先提升!

至于编译器底层到底是怎么处理其他的逻辑的,我们再议论!

这里为什么要强调函数优先提升这个问题呢?因为这里存在函数和变量重名的问题!

这个问题好像还没有那么的一针见血,继续想一想,我们提升的这些代码,都是声明,编译器把这些代码提升是为了干啥呢?执行!那他是怎么执行的呢!声明的代码是用来干嘛的——开辟空间!!!(开辟空间这件事是由作用域处理的,但是开辟空间这个指令是在编译阶段产生的。)那开辟空间以什么为依据呢?name!!! 如果出现了变量和函数重名?怎么办呢?这个时编译器定义了自己的规则,函数的声明优先于变量的声明!!具体体现在初始化时候,因为你只有名字的话两个名字一样,到时候怎么知道到底是变量声明还是函数声明呢?于是又有了一条规则:**开辟空间时,函数声名会初始化为函数体,变量声明初始化为undefined!**这个时候你又会问了,那函数重名的情况呢!比如我们定义了两个重名函数,但是他们的函数体不同,这时后面声明的函数会覆盖之前声明的函数。

这个时候我们已经明白了变量提升的本质:提前开辟空间!!也就是说变量和函数声明这段代码会在编译阶段提前运行,为变量或者函数提前开辟好空间!!并且做了相应的初始化。

提升到哪里呢

这个时候问题又来了?开辟空间?在哪里开辟呢?也就是他被提升到了哪里?我们都知道js是有作用域的,开辟空间这件事本身就是作用域来完成的。也就是说变量和函数都是有归属的,他们在编写时就注定了他们属于某一个作用域,也就是js采用的是词法作用域,声明的位置决定了其空间所属的作用域,也就是有没有权限访问,如何访问的问题!

其实在编译器中,提升是提升到声明所处的作用域的最前边,说的也是这个意思!在上边的代码里,我们所有的声明都处在最外层作用域,除了一个var c;这个声明在一个比较大的红框框里,他的声明提前只会被提前到作用域也就是函数foo1的作用域的最前边,也就是说,编译器在编译时依然需要作用域来配合的!因为声明编写的位置决定了其作用域,也就是如何找到他。

总结

1.提升的本质:将所有的声明代码移到当前作用域的最顶端,在编译阶段提前进行处理,为其开辟空间和初始化!
2.提升的内容:函数声明和变量声明代码
3.提升到哪里:当前作用域的顶端(也就是说提升归提升,不能改变其作用域,声明代码在哪里就在哪个作用域)
4.什么时候提升:编译阶段,代码执行前

代码执行情况

结果图

在这里插入图片描述

符合你的预期吗?

来盘一盘

模拟变量提升

我们手动提升一下代码!模拟一下!
图2 模拟变量提升

上边这张图我们手动进行了一个变量提升,将所有的声明代码手动提升到了前边,不要忘了var c;移到了foo1的最前面!
还有我们之前提到的if语句中的函数声明按照表达式处理,因此foo2、foo5属于变量开辟空间。
其他的都是按照规则提前去。
在这里我们将函数声明放在了前边,事实上编译器内部不一定是这么处理的,我们这里只是为了体现函数声明优先。

按照之前的提升规则,我们来手动的开辟空间啦!!
图3 声明提升空间开辟情况
先来解释一下这个图!我们按照图2声明提升图中的预期,一步一步进行空间的开辟。

先在全局作用域中开辟foo1的空间,由于函数属于对象,栈中存地址,指向真正的堆中内容,并创建自己的作用域,为变量c开辟自己的空间初始化undefined存在栈里边。

同理执行foo4。

来到第4行,我们为变量a创建自己的空间,初始化为undefined,存在栈中。

同理执行b、foo2

接下来为foo4开辟空间,因为foo4属于变量开辟空间,由于已经存在同名的函数foo4,属于重复声明,因此处忽略不做处理!

然后执行d、foo5

所以在声明提升之后我们就关联了作用域,并且为他们开辟好了空间!

详情就是上图3啦

模拟执行

用于执行的代码 :
图4 执行代码
由于js是解释型语言,解释一行,执行一行。我们来看看他的执行!!
1、给a赋值为1 ,此时栈中a的值不再是undefined而是1
2、输出1,1
3、输出2、undefined, 此时b没有人给他赋值,还是undefined
4、给b赋值为2,此时栈中b的值为2
5、执行 foo1函数,
5.1 查找到栈中的地址,顺着地址找到函数对象,执行函数
5.2 输出3,undefined foo1作用域中的c没有被改变,还是undefined
5.3 给foo1作用域中的c赋值为3
6、执行foo2函数 由于foo2被声明为变量,所以直接报错了,typeerror!类型错误,foo2不是一个函数!

到此由于报错了下面的代码就不执行了!!
为了继续往下给大家演示一下,我们执行的时候提前把一些会报错的代码通过try catch 抛出异常哈!!

7、执行foo3函数,由于没有声明,所以会报出ReferenceError错误
8、foo2赋值,即栈中foo2所存的地址改变,指向新的地址,堆中开辟新的空间,产生新的作用域,还有新的变量和函数声明!(之后在执行foo2就可以输出4,foo2啦)
9、执行foo4函数,输出5,foo4
10、同8一样,foo4赋值(之后在执行foo5就可以输出6,foo4啦)
11、执行foo5,由于foo5被声明为变量,所以直接报错了,typeerror!类型错误,foo5不是一个函数!
12、d赋值为true,
13、if执行,同8一样,foo5赋值(之后在执行foo5就可以输出7,foo5啦)

执行结果

图5 执行结果

下边真的可以不看 -----

想学习一些前端的书籍吗,我都帮你整理好啦!评论打出你想读的书,给你最全的笔记干货

超级全的前端知识,面试必备、系统复习必备哟哟哟

有想法评论提出哈,欢迎交流,小编也是渣渣一枚呢~一起进步呗

这次真的可以不看 -----

点个收藏呗,要不赞一个呗,小编手都敲累了,但还是持续加更呢~

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值