关于变量提升,你知道多少?

1,010 阅读6分钟

变量提升

当前上下文中,所有带var和function关键字的进行提前的声明或者定义。

  • var:只声明不定义;
  • function:声明+定义;

变量提升阶段处理过的事情,代码执行阶段不会重复处理。

总结:自执行函数不存在变量提升。因为自执行函数是匿名函数,所以无法在所在上下文中变量提升。

var foo = 'hello';
//自执行函数执行:创建一个函数并且立即把这个函数执行
(function(foo){
    //形成私有上下文---作用域链、形参赋值、变量提升(var foo(已存在))
    console.log(foo);
    var foo = foo || 'world';
    console.log(foo);
})(foo)

变量提升面试题

下面是最新收罗的关于变量提升的一些变态面试题,敢来挑战吗?

{
    console.log(foo);  
    function foo(){};   
    foo=1;
    console.log(foo); 
}
console.log(foo);
/*解析:-----------------------------------------------------------------------------------------
* EC(G)
* 变量提升:function foo----只声明未定义
* 代码执行:
*/
{
    /*
     * EC(BLOCK) 块级上下文
     * 作用域链:<EC(BLOCK),EC(G)>
     * 变量提升:function foo(){}----AF0  声明+定义
     * 代码执行
     	此时foo在全局和私有上下文中都声明了一次(兼容ES3/ES6),和两者都有关系:
     	a.在代码执行时,会把function foo(){}这行代码之前所有对foo的操作不仅认为是私有的,也会给全局映射一份。(不包含这行代码)
     	b.但是之后对foo操作的代码都认为是私有的。
    */
    console.log(foo);  //函数
    function foo(){};  //---这行代码不执行,在变量提升阶段已处理  所以此时全局foo----AF0
    foo=1;
    console.log(foo); //1
}
console.log(foo);  //f foo(){}


/*------------------------------------------------------------------------------------------
* EC(G)
* 变量提升:
* 	function foo;
* 	function foo; (两次一样,全局下只有一个foo,不重复声明)
*/
{
    /*
    * 块级上下文
    * 变量提升:function foo(){1}
    		  function foo(){2}  => 结果
    */
    console.log(foo); //f foo(){2}
    console.log(window.foo); //undefined  
    function foo(){1}  // => 此时不会映射给全局,等待最后一次再处理
    foo=1;
    function foo(){2}  // => 此行之前对foo的操作都会映射给全局一份,全局foo----1
    console.log(foo); //1
}
console.log(foo);   //1


{
    function foo(){}
    foo=1;
    function foo(){}
    foo=2;
    console.log(foo); //2
}
console.log(foo); //1
var a = 0;
if(true){
    a = 1;
    function a(){};
    a = 21;
    console.log(a);
}
console.log(a);

/*
* 最新版本浏览器
  1.向前兼容ES3/5规范
  	1.1判断体和函数体不存在块级上下文,上下文只有全局和私有;
  	1.2不论条件是否成立,带function的都要声明+定义;
  2.向后兼容ES6规范
  	2.1存在块级作用域,大括号中出现let/const/function...都会被认为是块级作用域;
  	2.2不论条件是否成立,带function的只提前声明,不会提前赋值了;
*/

/******************** 旧浏览器 *********************/
/*
	全局作用域
		1.变量提升   var a; function a(){}-----堆内存AF0;
		2.代码执行
			var a=0;   //a -> 0
			a=1;  //a -> 1
			function a(){};  //不执行
			a=21;  //a -> 21
			console.log(a); //输出:21
			console.log(a); //输出:21
*/

/******************** 新浏览器 *********************/
/*
	全局作用域
		1.变量提升   var a; function a;(只声明不赋值)
		2.代码执行
			var a=0;   //a -> 0
			if(true){   //遇到大括号并且里面有function,形成块级作用域
				//变量提升   function a(){}-----AF0   声明加定义
				a=1;  // a -> 1(私有)
				function a(){};   //因为要兼容ES3/ES6,function a在全局下声明过,在私有也处理过,遇到此行代码私有下不再处理,但是浏览器会把当前之前所有对a的操作都映射给全局一份,以此兼容ES3,但是它后面的代码和全局没有任何关系了!  此时全局a->1
				a=21;   // a -> 21(私有)
				console.log(a); //输出:21
			}
			console.log(a);//输出:1
*/

总结:

  1. 代码执行时遇到{},除了函数和对象的大括号外,在看到{}中有let/const/function才会把其作为块级作用域,function产生块级作用域也是新版浏览器加入的;
  2. 代码未执行时不会形成块级作用域;
  3. 如果在全局和私有上下文中都声明了一次(兼容ES3/ES6),和两者都有关系: a.在代码执行时,会把function foo(){}这行代码之前所有对foo的操作不仅认为是私有的,也会给全局映射一份。(不包含这行代码) b.但是之后对foo操作的代码都认为是私有的。

感觉自己掌握差不多了哈,那再来一道让你怀疑人生,哈哈哈!

题目A:

var x = 1;
function func(x,y=function anonymous1(){x=2}){
    x=3;
    y();
    console.log(x);   //2
}
func(5);
console.log(x);  //1

解析:

/*
* EC(G) 
* 变量提升:var x; function func(x,y=...){...};
*/
var x = 1;
function func(x,y=function anonymous1(){x=2}){
    /*
    * EC(func) 作用域链<EC(func),EC(G)>
    * 形参赋值:x=5 y=BF0
    * 变量提升
    * 代码执行
    */
    x=3;
    y();
    /*
    * EC(anonymous1) 作用域链<EC(anonymous1),EC(func)>
    * 形参赋值
    * 变量提升
    * 代码执行
    *   x=2;-------非私有,找上级作用域,将EC(func)中x修改为2
    */
    console.log(x);   //2
}
func(5);
console.log(x);  //1

题目B:

var x = 1;
function func(x,y=function anonymous1(){x=2}){
    var x=3;
    y();
    console.log(x);     //3
}
func(5);
console.log(x);    //1

解析:

/*
* EC(G) 
* 变量提升:var x; function func(x,y=...){...};
*/
var x = 1;
function func(x,y=function anonymous1(){x=2}){
    /*
    * EC(func) 作用域链<EC(func),EC(G)>
    * 形参赋值:x=5 y=BF0
    * 变量提升: var x;
    *------------------------
    * 形参赋值默认值+声明变量,所以会单独多形成一个私有的块级上下文(把函数体{}当作块级上下文)
    *  EC(BLOCK)
    *  作用域链:<EC(BLOCK),EC(func)>
    *  变量提升: var x; 会把私有上下文中形参x赋值的值给它
    */
    var x=3;   //EC(BLOCK)中x=3
    y();  //EC(BLOCK)没有y,所以找上级,EC(func)中y执行
    /*
    * EC(anonymous1) 作用域链<EC(anonymous1),EC(func)>
    * 形参赋值
    * 变量提升
    * 代码执行
    *   x=2;-------非私有,找上级作用域,将EC(func)中x修改为2
    */
    console.log(x);   //3----块级上下文x
}
func(5);
console.log(x);  //1----全局x
//提示:可在代码前加debugger 断点调试,看执行过程

题目C:

var x = 1;
function func(x,y=function anonymous1(){x=2}){
    y=function anonymous1(){x=4}  //不会形成2个上下文
    y();
    console.log(x);   
}
func(5);
console.log(x);  


var x = 1;
function func(x,y=function anonymous1(){x=2}){
    x=4;
    function x(){}; //函数名和形参名相同,会形成2个上下文
    y();
    console.log(x);   //4
}
func(5);
console.log(x);   //1

总结:

A.块级上下文中没有自己的this,它用到的this是其上级上下文中的this;

B.函数执行会形成私有上下文,如果:

  1. 有形参赋值默认值;
  2. 函数体中有声明过自己的私有变量(var / let / const,function只有声明的名字和形参中的名字相同才会单独产生块级上下文);

(两个条件缺一不可)则会把函数体{}包起来的看作一个私有的块级上下文,此时函数执行就会有两个上下文;