大厂前端面试题 var x = 100;console.log打印100?
前言
大家面试的时候,会不会碰到这么一道题目。
var global = 100;
function fn(){
console.log(global);
}
fn();
面试官问,这段代码打印的是什么?心中暗喜,这不简简单单打印的就是100嘛,这面试官问的也特简单了吧,于是脱口而出100,然后面试官继续问,为什么是100?然后你仔细一思索,于是这么回答到:因为在全局域中定义了一个变量global,函数作用域能访问到全局作用域的变量,所以打印的是100。于是面试官说,你还是回家等消息吧。然后你百思不得其解,我回答的没错呀,为什么面试官叫我回家呢?你回答的确实没错,但是还是那句话,你回答的还不够优秀,没有答到面试官的心窝窝里面去。
接下来就由蘑菇头来解答一下这道简单但是其实并不简单的面试题。
首先上述回答一点问题都没有,但是我们要知道,大厂考这么简单的一道题,那么他想考察的知识点肯定不会这么简单,你以为他想考察的是作用域,其实,面试官真正想要考察的是编译原理。他想知道你对这么一段代码的理解程度。我们从编译原理的角度来分析这里为什么打印的是100。
预编译
我们先清楚一个概念,什么是预编译。代码在执行前需要进行编译操作,用于确定变量的作用域,提高运行效率。预编译的过程包括词法分析、语法分析、语义分析、代码生成等步骤,用于确定各种代码之间的关联。
所以上面这段代码会执行一次预编译的过程。在此之前,我们先了解一下v8是怎么理解一个函数的,比如下面这个函数
function foo(){//函数的声明
}
foo();//函数的调用
foo函数,在JavaScript中它是一个对象,按道理来说对象身上就会有一些属性和方法,所以foo函数身上也会有一些属性和方法如:
foo.name //"foo";
foo.length //0; 表示函数参数的长度
foo.prototype //undefined; 表示函数的原型
foo.[[scope]] //函数的作用域属性 我们无法访问 v8引擎内部属性 -隐式属性
代码是一行一行执行的,当v8扫描到函数的声明时,并不会进行编译,当碰到函数的调用时,v8才会进行编译,他会先找到这个函数的声明,并且创建AO对象。
OK,当我们了解这些之后,我们可以完整的分析一下上述代码v8是如何理解的。
var global = 100;
function fn(){
console.log(global);
}
fn();
首先,v8会先创建GO对象,然后在全局作用域下找所有的变量声明,将变量名当做key,值为undefined存入GO对象中,所以现在GO对象中有一个global属性,然后在全局中找函数声明,将函数名作为key,值为函数体存入,现在GO对象中有global和fn。执行语句,将global的值更新为100,然后开始执行函数体fn(),在执行fn()函数体之前,会进行编译,v8会创建AO对象,然后在函数作用域下找所有的形参和变量名,将形参和变量名作为key,值为undefined存入AO对象中,然后就是形参和实参统一,将AO对象形参key的值更新为实参的值,然后在函数体内找函数声明,将函数名作为AO的属性名值为该函数体,并且该AO对象指向会指向GO对象。这个函数编译完成,执行fn,打印global,会先在当前作用域AO中查找,如果没有找到,则会在上一级作用域中查找,直到全局作用域中查找。如果全局作用域中也没有找到,则会报错,。在全局中找到一个global值为100,所以打印100。如果该函数体fn内还有函数执行,会再次执行上述过程,在执行语句调用前编译并且创建新的AO对象并且指向上一个AO对象也就是fn的作用域。
//GO AO 对象更新过程
GO:{
global:undefined --> 100,
fn:function(){}
}
AO:{
//找不到global变量,去上一级GO作用域找
}
当我们知道V8的编译原理之后,如果碰到以下问题,那我们就能神挡杀神,佛挡杀佛。
function fn(a){
console.log(a);//function a(){}
var a = 123;
console.log(a);//123
function a(){}
console.log(a);//123
var b = function(){}
console.log(b);//function(){}
function c(){}
var c = a;
console.log(c);//123
}
fn(1);
问打印的是什么?把自己想像成v8,看看他是怎么理解的。
//赋值过程
GO{
fn:fn(){}
}
AO{
a:undefined --> 1 --> function a(){} -->123
b:undefined --> function(){}
c:undefined -->function c(){} -->123
}
总结
今天我们学习了v8的编译过程,主要可以分成两种:
1、发生在全局
a.创建GO对象
b.找变量声明,将变量名key和值undefined存入GO对象中
c.在全局找函数声明,将函数名作为GO的属性名,值为该函数体
d.执行函数体,函数体中使用变量时,会先在当前作用域中查找,如果没有找到,则会在上一级作用域中查找,直到全局作用域中查找。如果全局作用域中也没有找到,则会报错。
全局代码先编译,然后执行,在执行过程中可能会碰到其他函数调用,函数调用时,会先编译该函数,然后执行。
2、发生在函数体内
a.创建函数作用域AO对象,找形参和变量名,将变量名key和值undefined存入AO对象中
b.形参和实参统一
c.在函数体内找函数声明,将函数名作为AO的属性名,值为该函数体
转载自:https://juejin.cn/post/7362102742762209332