Javascript变量提升
图 1 Javascript数据类型存储结构
Javascript 作为一种动态语言,其语法特别是变量具有更多的灵活性。
1. 先看下一个经典的面试题,
var a=1;
function foo(){
a=10;
return;
function a(){ }
}
foo();
console.log(a);
结果输出不是10,是1;如果你还不是清楚背后的原理,那么请继续往下看;
2. Javascript变量
- ①局部变量传参:基本类型数据是是 局部变量传参,不是 按值传递参数,可以理解为所有函数中传递的参数都是深拷贝到一个临时变量后,将临时变量传递到函数执行,即不会影响原始变量;如需改变原始变量,需要手动对原始变量进行重新赋值。
- ②按引用数据传参:引用数据类型传参是 引用传参,及传递数据的引用,所有在函数内部进行的对参数的操作,都会直接影响到原始变量对象。由此也可以看出引用类型数据的存在极大的节约了内存空间。
JS的数据类型有两大类,对应两种不同的存储方式栈存储、堆存储;ECMAScript定义了7中数据类型(ES5增加Symbol) 基本类型(Undefined、Null、Symbol、Boolean、Number、String)和引用数据类型(Object),引用数据类型又包括Date、Array、Function、Math、RegExp等其他核心对象。
由于数据类型及存储结构的不同,JS在执行的过程中其处理方式也不同;但ECMAScript函数参数的传递与其他语言不同,分为 局部变量传参、 按引用传参。
2.1 先来看一个简单例子:局部变量传参
function box(num){
num+=10;
return num;
};
var num=5;
var result=box(num);
console.log(result);//15 函数改变传入参数值,并返回给result
console.log(num); //5 因为不是按值传递,原始变量值未进行改变
2.2 再来看一个例子:按引用传参
function foo(obj){
obj.name='new'
};
var p={};
foo(p);
console.log(p); // {name:'new'} 引用传参,按指针影响原始变量
console.log(p.name);// new
3. 作用域 Scoping、执行环境Context
-
3.1 变量作用域:字面理解即变量的作用范围,亦即变量可被访问到的空间。
- ①.JS只有两种作用域:全局作用域、函数作用域;
-
②.ES6新增加了块级作用域的概念,及仅由
let
关键字声明的变量、其作用范围为{}
代码块。
3.2 执行环境:与作用域相对应,及函数/代码运行时,可访问的变量空间。
- ①.每个函数执行时,都会创建自己的执行环境;当执行到该函数时,函数所属的执行环境会被推入到环境堆栈中去执行;函数执行结束后,将从环境堆栈中弹出销毁,将控制权交给上一级执行环境;
-
②.代码在环境中执行时,会同时生成作用域链,其作用是用于保证函数执行时具有访问权的变量、函数
按需依次向上级进行有序的访问;作用域链的前端,即执行环境的变量对象。
eg:
var global='global context';
var inner='global inner context';
function foo(){
console.log(global); //foo函数内部无global变量,向上级查找global变量
console.log(inner); //foo函数内部有inner变量,无需向上查找inner变量,但此时
var inner='inner context';
console.log(inner);
}
foo();// global context
// undefined
// inner context
4. 变量提升 Hoisting
JS中变量进入一个作用域有以下四种方式:
- 变量声明:如:
var a;
、var fn=function(){}
; - 函数声明:如:
function foo(){}
; - 函数形参:函数的形参存在于函数作用域中;
- 语言自定义构建:所有的作用域都会自动生成
this
、arguments
两个默认变量
JS在执行前会预先创建好执行环境变量,但由于其弱类型语言特征,其变量和函数可同时定义并赋值,为保证执行环境的完整性,及函数执行的有效性,JS会在编译阶段将所有变量、函数的声明事先放入内存,以保证其可访问性。从代码结构来看,好像是把所有变量的声明、函数的声明都放在代码头部事先执行一样;
-
再回过头来看看本篇开始的程序就不难理解了,其执行顺序与下面的代码完全一致;
- 1.将外部函数foo声明提前,foo函数定义提升到代码开始之前;
- 2.将 内部函数a提升到foo函数作用域顶部,此时输出:function(){};
- 3.将a类型由函数类型重新定义为number类型,并赋值10;
- 4.将内部函数执行环境从执行堆栈中弹出销毁;此时外部a值未做改变,仍为1;
- 5.输出外部变量a,其值必然为 1;
var foo;
var a=1;
function foo(){
var a=function(){};
a=10;
return;
}
foo();
console.log(a);//1
5. 动态语言 VS 静态语言
Javascript作为一门动态语言,与静态语言的差别在于变量的和数据类型的维护,亦即底层的存储结构及实现不同。那么具体有什么差异?
-
5.1 静态语言:亦称‘强类型语言’,比如JAVA有一下特点:
- ①变量使用前必须提前声明;②变量使用前必须声明类型;③变量一旦定义,只能改变其值,不能改变其类型; 5.2 动态语言:亦称‘弱类型语言’,比如我们今天讨论的JAvascript,同样有一下三个特点:
- ①变量 使用前不必提前声明;②变量类型使用前也不必提前声明;③变量声明赋值后,可以在任何地方按需要随时改变其类型和值;