预编译
js作为一种翻译性语言,编译一行,执行一行。但编译前,js还对整篇进行了一次预编译。为了了解预编译,我们需要认识js运行的大致流程。
js运行过程
- 语法分析,就是检查你的代码有没有什么低级的语法错误;
- 预编译 ,简单理解就是在内存中开辟一些空间,存放一些变量与函数 .
- 解释执行,顾名思义便是执行代码了;
一般来说,预编译在script代码内执行前发生, 但是它大部分会发生在函数执行前。
让我们找些实例分析。
全局预编译
<script>
var a = 1;
console.log(a);
function test(a) {
console.log(a);
var a = 123;
console.log(a);
function a() {}
console.log(a);
var b = function() {}
console.log(b);
function d() {}
}
var c = function (){
console.log("I at C function");
}
console.log(c);
test(2);
</script>
页面产生便创建了GO全局对象(Global Object)(也就是window对象);
- 第一个脚本文件加载;
- 脚本加载完毕后,分析语法是否合法;
- 开始预编译 查找变量声明,作为GO属性,值赋予undefined; 查找函数声明,作为 GO属性,值赋予函数体;
//查找变量
GO/window = {
a: undefined,
c: undefined,
test: function(a) {
console.log(a);
var a = 123;
console.log(a);
function a() {}
console.log(a);
var b = function() {}
console.log(b);
function d() {}
}
}
接下来开始解释执行语句
//抽象描述
GO/window = {
a: 1,
c: function (){
console.log("I at C function");
}
test: function(a) {
console.log(a);
var a = 123;
console.log(a);
function a() {}
console.log(a);
var b = function() {}
console.log(b);
function d() {}
}
}
在执行函数语句之前,也存在预编译过程
- 创建AO活动对象(Active Object);
- 查找形参和变量声明,值赋予undefined;
- 实参值赋给形参;
- 查找函数声明,值赋予函数体;
预编译之前面1、2两小步如下:
AO = {
a:undefined,
b:undefined,
}
第三步
AO = {
a:2,
b:undefined,
}
第四步
AO = {
a:function a() {},
b:undefined
d:function d() {}
}
执行函数时
AO = {
a:function a() {},
b:undefined
d:function d() {}
}
--->
AO = {
a:123,
b:undefined
d:function d() {}
}
--->
AO = {
a:123,
b:function() {}
d:function d() {}
}
预编译阶段发生变量声明和函数声明,没有初始化行为(赋值),匿名函数不参与预编译 ; 只有在解释执行阶段才会进行变量初始化 ;
预编译小结
预编译两个小规则
以上用图示表示:
- 函数声明整体提升-(具体点说,无论函数调用和声明的位置是前是后,系统总会把函数声明移到调用前面)
- 变量 声明提升-(具体点说,无论变量调用和声明的位置是前是后,系统总会把声明移到调用前,注意仅仅只是声明,所以值是undefined)
预编译前奏
- imply global 即任何变量,如果未经声明就赋值,则此变量就位全局变量所有。(全局域就是Window)
- 一切声明的全局变量,全是window的属性; var a = 12;等同于Window.a = 12; 函数预编译发生在函数执行前一刻。
案例分析
题1:
//预编译:GO -> {fn: function () {}}
//解释执行:{fn()}
function fn (a) {
//执行函数fn前预编译:AO -> {a: function () {}, b: undefined[因为函数b()为函数表达式],d: function () {}}
console.log(a); //控制台显示function a () {}
var a = 123;
console.log(a); //控制台显示123
function a () {};
console.log(a); //控制台显示123
console.log(b); //控制台显示undefined
var b = function () {};
console.log(b); //控制台显示function () {}
console.log(d); //控制台显示function d () {}
function d () {};
}
fn(1);
题2:
//预编译:GO -> {test: functiong () {}}
//解释执行:{test()}
function test (a, b) {
//执行函数test前预编译:AO -> {a: 1[因为外部实参传入], b: function () {},d:function () {}}
console.log(a); //控制台显示1
a = 3;
console.log(b); //控制台显示function b () {}
b = 2;
//解释执行函数test:{b: 2}
console.log(b); //控制台显示2
function b () {};
function d () {};
console.log(b); //控制台显示2,因为b已是变量并被赋值
//解释执行函数test:{d: function () {}}
console.log(d);
}
test(1);
浅谈作用域
在 JavaScript 中, 作用域为可访问变量,对象,函数的集合。
JavaScript 函数作用域: 作用域在函数内修改。
JavaScript 局部作用域
变量在函数内声明,变量为局部作用域。
函数作用域内,对外是封闭的,从外层的作用域无法直接访问函数内部的作用域!
function bar() {
var testValue = 'inner';
}
console.log(testValue); // 报错:ReferenceError: testValue is not define
但是我们可以借助一些工具访问内部变量:
- 通过 return 访问函数内部变量:
function bar(value) {
var testValue = 'inner';
return testValue + value;
}
console.log(bar('fun')); // "innerfun"
- 通过 闭包 访问函数内部变量:
function bar(value) {
var testValue = 'inner';
var rusult = testValue + value;
function innser() {
return rusult;
};
return innser();
}
console.log(bar('fun')); // "innerfun"
- 立即执行函数:
<script type="text/javascript">
(function() {
var testValue = 123;
var testFunc = function () { console.log('just test'); };
})();
console.log(window.testValue); // undefined
console.log(window.testFunc); // undefined
</script>
全局作用域
作用域,是指变量的生命周期(一个变量在哪些范围内保持一定值)。
全局变量:
生命周期将存在于整个程序之内。
能被程序中任何函数或者方法访问。
在 JavaScript 内默认是可以被修改的。
显示声明:
带有关键字 var 的声明;
<script type="text/javascript">
var testValue = 123;
var testFunc = function () { console.log('just test') };
/**---------全局变量会挂载到 window 对象上------------**/
console.log(window.testFunc) // ƒ () { console.log('just test') }
console.log(window.testValue) // 123
</script>
我们写的函数如果不经过封装,也会是全局变量,他的生命周期也就是全局作用域;
隐式声明:
不带有声明关键字的变量,JS 会默认帮你声明一个全局变量。
<script type="text/javascript">
function foo(value) {
result = value + 1; // 没有用 var 修饰
return result;
};
foo(123); // 124
console.log(window.result); // 124 <= 到 window全局对象上了
</script>
现在,变量 result 到 window 对象上了。