这是来自真实的鹅厂秋招面试题。更多请看大厂前端面经。
JavaScript是一种描述型脚本语言。它不同于Java或C#等编译性语言,不需要进行编译成中间语言,而是由浏览器直接进行动态解析与执行。也正是由于这个特性,JavaScript代码在HTML文件中的执行顺序和其他语言就不太一样。
1.代码块
「代码块」是指由<script>标签分割的JavaScript代码组成的代码段。浏览器是按照代码块来对JavaScript代码进行编译和执行的,代码块之间相互独立,但是「变量」和「方法」共享。
那么,一个<script>标签形成一个块说明前面的JavaScript代码运行错误并不影响后面<script>标签里的代码运行。
什么意思呢,看个例子就明白了👇:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script>
console.log(s); //s未定义,控制台报错,后面的都不执行了
console.log('test1');
var test1 = '我是test1';
</script>
<script>
console.log('test2'); //控制台输出test2
console.log(test1); //输出undefined。并不是报错未定义
</script>
</head>
<body>
</body>
</html>
从以上的例子可以看出:
代码块一报错并不影响代码块二运行,这就是代码块间的独立性。
代码块二能够调用到代码块一的变量,有人把这个叫做块间共享性。
那为什么代码块二拿到test1并且test1是undefined而不是test1原来的值'我是test1'呢?
为什么代码块二拿到
test1:因为JavaScript语言只有「全局作用域(在浏览器中运行是window)「和」函数作用域」,所以在全局作用域里定义的变量都能够访问。而同一个HTML文件里的JavaScript代码都是浏览器的同一个全局作用域
window,所以代码块之间可以相互访问。为什么是
undefined:
浏览器解析阶段:代码执行之前使用
var定义的变量会「声明提前」(声明式函数也会声明提前),先定义后执行时赋值,所以在运行到console.log(s)之前变量test1已经被声明为undefined。浏览器执行阶段:执行到
console.log(s)因为s未定义报错,后面的代码都不执行了。此时的test1就没有完成将'我是test1'赋值给test1变量的操作,所以代码块二拿到undefined。
ES6的let和const都具有块级作用域,所以将上面代码块一的var改成let后,代码块二就访问不到test1会在控制台报错表示变量未定义。
2.声明提前
JavaScript的函数定义分为两种:声明式函数和赋值式函数。
//声明式
function fn() {
}
//赋值式函数
var fn = function (){
}
在浏览器解析阶段声明式函数会和之前的变量一样先被声明,未赋值。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script>
fn(); //'1'
function fn() {
console.log('1');
}
function fn() {
console.log('2');
}
</script>
<script>
fn(); //声明
var fn = function () {
console.log('赋值');
}
function fn() {
console.log('声明');
}
fn(); //赋值
</script>
</head>
<body>
</body>
</html>
因为function这种形式定义函数会在浏览器预解析的时候声明提前,所以fn函数调用在函数定义之前也可以。
ok,来看最后一个例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script>
fn(); //报错,未定义的fn函数
</script>
<script>
function fn() {
console.log('fn');
}
</script>
</head>
<body>
</body>
</html>
声明函数不是在预解析就被提前了,那为什么会报错呢?
上面说了JS引擎是按照代码块顺序执行的,其实完整的说应该是按照代码块来进行预处理和执行的。也就是说预处理的只是「执行到的代码块的声明函数和变量」,而对于还未加载的代码块,是没法进行预处理的,这也是边编译边处理的核心所在。