(大厂面试题)使用第一性原理推导—JS中代码是怎么运行的
前言
自从接触“第一性原理”,这个词在网上被吹得神乎其神。可是它到底是什么?我还没认真考究过。直到今天,通过浏览各位大佬的博文,对第一性原理有了点自己的理解。今天就和大家分享以下两点:
- 我对“第一性原理”的理解
- 从第一性原理的角度聊一聊JS中代码是怎么运行的
什么是第一性原理
当我看到下面马斯克这段话的完整译文,才终于把握到了“第一性原理”的实质,这令人膜拜的“第一性原理”,不就是“解耦合(decoupling)”吗?
-
要搞清楚这个概念,我们不妨试想,我们作为开发人员,希望改进一款开源软件产品的功能,你会怎么做?
你会打开一个新的空白源代码文件,从头开始,一行行的写代码吗?
基本上不会。那你会怎么做呢?
你会读现有软件的源代码,把新的功能实现补充或更新到对应的位置,提交合并请求
在这个过程中,我们都是把前人做的东西作为基础层,而后再在这个层次上,去叠加新的内容。
-
如果大家还没有理解,我们不妨再思考一个问题:为什么现在数据科学这么火热?Python、R和机器学习框架为何这么受到欢迎?甚至让很多非IT人士也在乐此不疲地渴望学习、应用它们?
因为有许许多多的开发者,已经为你写好了实现数据科学工作的各项基本功能。相关的软件包已有成千上 万,而且每天还在不停快速涌现。你根本不需要了解哪些功能究竟是如何实现出来的,只要会搜软件、查文档,直接“拿来主义”调用就能实现酷炫繁复的功能,方便得令人发指。
-
现在,大家再试想一下:如果之前的前提都不成立了呢?假设你目前的工作做所依赖的基础层级存在问题呢?
这就比如“品牌假货”的存在,大家都痛恨假货,但是原本品牌的存在,就会减低大家识别商品质量的成本。如果一个案例中,两种事物同时出现,总会被我们脑补为必然的关联,于是就耦合在一起了。你不难感受到,耦合的结果是非常不利于创新的。而所谓的“第一性原理”,即是一种“解耦合”的思维方式
如何使用“第一性原理”思考-JS中代码是怎么运行的
- 由第一性原理,可推出一下结论和问题
- 代码一定是逐行运行的
- 分为编译阶段和执行阶段,先后顺序
- 编译阶段发生了什么?
- 执行阶段执行了什么?
- 代码运行时,变量是如何查找的?
showName()//函数的执行
var showName = function(){ // 抛去后面的函数赋值看,这其实就是一个变量声明加一个变量赋值
console.log(2)
}
function showName(){ //函数的声明
console.log(1)
}
showName()//函数的执行(调用)
- 接下来通过以这段代码的运行过程为例,探讨所有的问题
- 要探讨编译阶段和执行阶段都发生了什么,不妨将上述代码分为以下两个部分:
- 由上图所示,我们可以看到这段代码真正的运行顺序:先进行函数和变量声明提升,在编译阶段进行变量和函数声明,创建scope对象,执行上下文{showName},为执行阶段准备好执行环境。在执行阶段执行函数调用、变量赋值操作。
- 通过以上的推导,在JS中处于基础层级最重要的几个概念也就浮出来水面。下面带大家粗略的了解下这几个概念
声明提升
-
什么是声明提升?
- 是指在JS代码执行中,Javascript引擎(V8),把变量的声明部分和函数的声明部分提升到代码开头的行为,变量提升后,会给变量设置默认值undefined
-
变量声明提升
- 以下述代码为例:
console.log(showName)// undefined var showName = 'Aaron_hj' console.log(showName)// Aaron_hj
我们不难看出,在第一个打印showName处,变量showName还未声明,但此处不会报错,而是打印值undefined。这就是变量声明提升:变量在声明前已经可用。 实际上,代码运行顺序是:
var showName; //变量声明提升 console.log(showName);// undefined showName = 'Aaron_hj'// 赋值 console.log(showName) //Aaron_hj
注意:声明提前是在JavaScript引擎的预编译时进行,是在代码开始运行之前。并且,只有声明提升,赋值仍在原处
-
函数声明提升
- 以下述代码为例:
showName()// 函数的执行(调用) function showName(){ // 函数的声明 console.log(1)
我们不难看出,在调用showName()处,函数showName()还未声明,但该代码片仍能正常运行。这就是函数声明提升:函数声明语句可以被提升到外部脚本或者外部函数作用域的顶部。 实际上,代码运行顺序是:
function showName(){ // 函数的声明 console.log(1)} showName()// 函数的执行(调用)
注意:虽然函数声明和变量声明都会被提升,但是函数会首先被提升,然后才是变量。
作用域
-
变量作用域
- 变量作用域指变量可以起作用的范围
- 变量又可分为全局变量,局部变量
- 全局变量在全局都有作用
- 局部变量只在函数作用域内起作用
- 所有不使用var定义的变量都视为全局变量
注意:在函数体内,同名的局部变量或者参数的优先级会高于全局变量。也就是说,如果函数内存在和全局变量同名的局部变量或者参数,那么全局变量将会被局部变量覆盖。
-
函数作用域
- 函数作用域是指在函数内声明的所有变量在函数体内始终有定义。再结合声明提升的概念,可理解为Javascript函数中的var声明的变量都被提前到函数体的顶部。(注意:只有声明会提升到函数体的顶部,变量赋值操作仍留在原来的位置)
执行上下文
- 执行上下文是一个Javascript中最基础,但同时也是最重要的概念。每当控制器转到执行阶段代码时,就会进入一个执行上下文。执行上下文可以理解为当前代码的执行环境,它会形成一个作用域,每个函数执行时,都会给对应的函数创建这样一个执行环境。 因此在一个Javascript程序中,必定会产生多个执行上下文。Javascipt引擎会以栈的方式来处理他们,称之为函数调用栈。栈底永远时全局上下文,而栈顶则是正在执行的上下文。为了更加清晰地理解这个过程,根据下面的示例代码,结合图示给大家展示。
var name = 'Aaron_hj';
function showName(){
var anotherName = 'Aaron';
function swapName(){
var tmpName = anotherName;
anothorName = name
name = tmpName;
}
swapName();
}
showName();
第一步:全局上下文入栈
第二步:showName的执行上下文入栈
第三步:swapName的执行上下文入栈
第四步:swapName的执行上下文出栈
第五步:showName的执行上下文出栈
全局上下文在浏览器窗口关闭后出栈
注意:函数中,运行到return会直接终止函数可执行代码的执行,因此会直接将当前上下文弹出栈。
- 总结
- 详细了解了这个过程之后,我们就可以对执行上下文总结一些结论了。
- 单线程
- 同步执行,只有栈顶的上下文处于执行中,其他上下文需要等待
- 全局上下文只有唯一的一个,它在浏览器关闭时出栈
- 函数的执行上下文的个数没有限制
- 每次某个函数被调用,就会有个新的执行上下文为其创建,即使是调用的自身函数,也是如此。
(注:本博文旨在和大家分享博主自己的理解,欢迎大家来评论区一起讨论学习~)
- 详细了解了这个过程之后,我们就可以对执行上下文总结一些结论了。