学习到了一些关于预编译和作用域的知识和大家分享一下!
众所周知javascript是解释性语言,主要特点为解释一行执行一行。
而在js运行时会进行三件事:1语法分析 2.预编译 3.解释执行
语法分析会在代码执行前对代码进行通篇检查,以排除一些低级错误
预编译发生在代码执行的前一刻
解释执行顾名思义就是执行代码
我先给大家举几个预编译的小例子:
var a = 123;
console.log(a);
此时他返回的值会是123;
但如果我们调换位置:
console.log(a);
var a = 123;
我们得到的结果便会是undefined。(由于js解释性语言的原因,先执行console.log,而由于预编译的原因浏览器并不会报错)
如果我们在次尝试不定义变量直接获取:
console.log(a); 此时我们会发现浏览器会进行报错。//Uncaught ReferenceError: a is not defined
这条语句提示我们a没有定义
到这里有些人会有疑问为什么在console.log前,后和不定义a会有如此大的差别,这就是预编译起到的作用
在具体讲解预编译之前要先帮大家了解几点小知识,如下:
预编译的前奏
1. imply global 暗示全局变量:即任何变量,如果变量未经声明就赋值,此变量就为全局对象(window)所有。
简单解释起来就是,如果在js中我们在赋值时经常先定义一个变量,如:var a = 123;
此时123这个数字便被赋予了a这个变量,此时我们在控制台console.log(a)就会得到a的值123;
这时就会我们就会有个疑问,如果我们没有定义变量直接进行赋值还会打印出值么。
答案是肯定的,此时如果我们未定义变量直接赋值,如:
b = 456;
此时a会被认为是全局的一个对象,即window下的一个值,此时我们在控制台下console.log(b)同样可以得到b的值456。
此时我们可以认为 b = 123 ————> window.b =456
2. 一切声明的全局变量,全是window。
简单说明就是我们 var c = 789 ===> window.c = 789(即:c =789)
预编译(粗浅版本)
下面我们来看一下对预编译最基本的肤浅理解(该理解非常粗浅无法解决复杂问题):
函数声明整体提升
变量,声明提升
个人理解:在函数执行前函数声明(function(){})会整体提升至逻辑的最前方,
变量则会把自身的声明提升到程序的最最前方。
//注释 var a =123;是变量声明+赋值
var a:变量声明
a = 123 变量赋值
在预编译时我们不看赋值,只把变量的声明提升,因此若在赋值前打印会提示undefined;
此时我们就可以理解之前的问题了:
先打印再定义:
console.log(a);
var a = 123;
在执行前var a提升至代码的最最前端 ,提升后我们再打印 a,由于此时a未被赋值因此打印提示undefined。
console.log(a);
不定义:
console.log(a);
此时由于没有a的定义,所以会报错提示a is not defined;即:a未定义。
看似很好理解但是如果代码稍微复杂便无法解决,例如:
console.log(a); fun
function a(a) {
var a = 456;
var a =function () {
}
a();
}
var a = 123;
下面我们来看一下真正的预编译:
预编译(精装版本):
预编译的四部曲:
1.创建GO/AO对象
2.找形参和变量声明,将变量和形参名作为AO属性名,值为undefined
3.将实参值和形参统一
4.在函数体里面找函数声明,值赋予函数体
以此为例:
再次函数中我们来进行预编译:
1.创建AO对象:我们隐式的在函数中创建了一个AO的对象来盛放函数中的变量,此时对象中并没有值;
2.找形参和变量声明,将变量和形参名作为AO属性名,值为undefined:我们在第二个过程中需按照变量和形参
3.将实参值和形参统一:此时将实参带入函数中由于在函数外 f(1),因此AO中a = 1
4.在函数体里面找函数声明,值赋予函数体:由于在函数中有 function a() {} ,这一函数因此此时AO中 a = function a() {}
在进行完预编译后此时若执行函数则会以AO为基础对函数中的变量进行赋值:此时函数中有两次打印一次在函数开头,一次在函数为a赋值之后
再赋值前由于AO中值不变因此a所打印出的值为 function a() {}
在赋值后AO中a = 123,所以此时打印出的值为123。