Javascript变量作用域、变量提升

Javascript变量提升

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(){};
  • 函数形参:函数的形参存在于函数作用域中;
  • 语言自定义构建:所有的作用域都会自动生成thisarguments两个默认变量

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,同样有一下三个特点:
①变量 使用前不必提前声明;②变量类型使用前也不必提前声明;③变量声明赋值后,可以在任何地方按需要随时改变其类型和值;
  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值