js 作用域

153 阅读3分钟

1.var和let/const

JS中作用域有:全局作用域函数作用域。没有块作用域的概念。ECMAScript 6(简称ES6)中新增了块级作用域 { }, 块作用域由 { } 包括,if语句和for语句里面的{ }也属于块作用域

{
	var a = 1;
	console.log(a); // 1
}
console.log(a); // 1
// 可见,通过var定义的变量可以跨块作用域访问到。

(function A() {
	var b = 2;
	console.log(b); // 2
})();
// console.log(b); // 报错,
// 可见,通过var定义的变量不能跨函数作用域访问到

if(true) {
	var c = 3;
}
console.log(c); // 3
for(var i = 0; i < 4; i++) {
	var d = 5;
};
console.log(i);	// 4   (循环结束i已经是4,所以此处i为4)
console.log(d); // 5
// if语句和for语句中用var定义的变量可以在外面访问到,
// 可见,if语句和for语句属于块作用域,不属于函数作用域。

{
	var a = 1;
	let b = 2;
	const c = 3;	
		
	{
		console.log(a);		// 1	子作用域可以访问到父作用域的变量
		console.log(b);		// 2	子作用域可以访问到父作用域的变量
		console.log(c);		// 3	子作用域可以访问到父作用域的变量

		var aa = 11;
		let bb = 22;
		const cc = 33;
	}
		
	console.log(aa);	// 11	// 可以跨块访问到子 块作用域 的变量
	// console.log(bb);	// 报错	bb is not defined
	// console.log(cc);	// 报错	cc is not defined
}

来一些举例

function b(){
    'use strict';
    var a=0;
    {
        var a=1;
        console.log(a);
    }
    console.log(a);
}
b();
function c(){
    'use strict';
    let a=0;
    {
        let a=1;
        console.log(a);
    }
    console.log(a);
}
c();
// 结果输出1,1,1,0

function b(){
    'use strict';
    var a=0;
    var a=1;
    {
        console.log(a);
    }
    console.log(a);
}
b()
// 输出1,1

function b(){
    'use strict';
    var a=0;
    {
        let a=1;
        console.log(a);
    }
    console.log(a);
}
b()
// 输出1,0

function b(){
    'use strict';
    let a=0;
    {
        let a=1;
        console.log(a);
    }
    console.log(a);
}
// 输出1,0

function b(){
    'use strict';
    let a=0;
    {
        var a=1;
        console.log(a);
    }
    console.log(a);
}
// 报错,Uncaught SyntaxError: Identifier 'a' has already been declared,不允许重复声明a
  1. 允许块级作用域任意嵌套
{{{let tmp = "hello world"}}}
  1. 外层作用域无法读取内层作用域的变量;
{{{
   {let tmp = "hello world";}
   console.log(tmp);  //error
}}}
  1. 内层作用域可以定义外层作用域的同名变量
{{{
   let tmp = "hello world";
   {let tmp = "hello world";}
}}}

2.var变量和函数function的提升

var temp = new Date();
function  f(){
     console.log(temp);
     if(false){
         var temp = "hello";
    }
}
f(); //undefined var变量的提升
(function(){
    if(false){
        function f(){
            console.log("inside");
        }
    }
   console.log('f',f)
}())
// 打印undefined, 正常来说要打印出function f(){},
if 条件句中的 function 会被编译成 函数表达式, 声明会被提升到当前作用域的最顶部, 
但是赋值会被留在原地. 这也就解释了为什么我们打印 f 的时候会是 undefined 而不是报错, 
当然, 如果 if 条件改为 true, f 函数会完成赋值, 最终也是可以调用的

3.var变量和函数function提升示例

// 代码段1
function foo() {
  var a = 1;
  function a() {}
  console.log(a);
}
foo();

// 代码段2
function foo() {
  var a;
  function a() {}
  console.log(a);
}
foo();

答案是:代码段1打印的是1,代码段2打印的是 a() 函数

function foo() {
  console.log(a);
  var a = 1;
  console.log(a);
  function a() {}
  console.log(a);
}
foo();
上面这段代码实际看起来是下面这样的
function foo() {
  var a;
  function a() {}
  console.log(a); // a()
  a = 1;
  console.log(a); // 1
  console.log(a); // 1
}
foo();
函数的提升会在变量声明的后面,只是变量先声明后再赋值,而函数是整个移上去了。

只有声明的变量和函数才会进行提升,隐式全局变量不会提升
下面的栗子中,b不会进行变量提升。
function foo() {
  console.log(a);
  console.log(b); // 报错
  b = 'aaa';
  var a = 'bbb';
  console.log(a);
  console.log(b);
}
foo();

4.let,const为什么会具有这样的特性

let,const声明在词法环境上,词法环境、变量环境、见上一篇文章--# js 执行上下文

词法环境会形成一个小型的栈结构,当前代码块内的let/const生命的变量,会遵循“后进先出”的原则被推入首位,查询变量的时候,会先从栈的首位开始查找,执行完后从栈顶依次弹出,进而我们无法在代码块外访问到我们在代码块内声明的变量,因而这就是let/const命令能形成块级作用域的原因