四时宝库

程序员的知识宝库

JavaScript:什么是作用域?有哪些作用域?

什么是作用域?

作用域是在运行时代码中的某些特定部分中的变量,函数和对象的可访问性。换句话说,作用域决定了代码块中变量和其他资源的可见性。

示例:

 function func1() {
   var innerVariable = "inner 1";
 }
 
 func1();  // 需要先执行这个函数,否则根本不知道函数内部的数据
 
 console.log(innerVariable); // ReferenceError: innerVariable is not defined

从上面的例子可以体会到作用域的概念,变量innerVariable在全局作用域中没有声明,所以在全局作用域下取值会报错。我们可以这样理解:作用域就是一个独立的区域,包裹着变量,避免变量暴露。也就是说作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突

ES 6之前,JavaScript没有块级作用域,只有全局作用域和函数作用域。ES 6的到来,为我们提供了块级作用域,可通过新增命令letconst来实现。

全局作用域和局部作用域

在代码中任何地方都能访问到的对象拥有全局作用域,一般来说以下几种情形拥有全局作用域:

  • 最外层函数和在最外层函数外面定义的变量拥有全局作用域 var outer = 'outer';
var outer = 'outer';

function outFunc() {
	var inner = 'inner';
	
	function innerFunc(){
		console.log(inner);
	}
	
	innerFunc();
}

console.log(outer);	// outer

outFunc();	// inner

console.log(inner);	// ReferenceError: inner is not defined

innerFunc();	// ReferenceError: innerFunc is not defined
  • 所有未定义直接赋值的变量,自动声明为拥有全局作用域
function outerFunc() {
	a = 1;
	var b = 2; 
}

outerFunc();

console.log(a);	// 1

console.log(b); // ReferenceError: b is not defined
  • 所有window对象的属性拥有全局作用域一般情况下,window对象的内置属性都拥有全局作用域,例如:window.name、window.location、window.top等等。

全局作用域有个弊端:如果我们写了很多行JavaScript代码,变量定义都没;有用函数包括,那么它们就全部都在全局作用域中。这样就会污染全局命名空间,容易引起命名冲突。

例如:

 var data = {x: 100, y: 200};
 
 var data = {a: 'a'}

这就是为何像jQuery、Zepto等库的源码,所有的代码都会放在(function(){...})()中。因为放在里面的所有变量,都不会被外泄和暴露,不会污染到外面,不会对其他的库或者JavaScript脚本造成影响。这是函数作用域的一个体现。

函数作用域是指声明在函数内部的变量,和全局作用域相反,局部作用域一般只在固定代码片段内可访问到,最常见的例如:函数内部。

 function func(){
   var inner = 1;
   
   function innerFunc() {
     console.log(inner);
   }
   
   innerFunc();
 }
 
 console.log(inner); // ReferenceError: inner is not defined
 
 func(); // 1

作用域是分层的,内层作用域可以访问外层作用域的变量,反之则不行

例如:

 function func(a) {
   var b = a * 2;
   
   function innerFunc(c) {
     console.log(a, b, c);
   }
 
   innerFunc(b * 3);
 }
 
 func(2);  // 2, 4, 12
  • func()之外为全局变量作用域
  • func()之内为func作用域
  • innerFunc之内为innerFunc作用域

值得注意的是:块语句,例如if语句、switch语句、for语句、while语句,不会创建一个新的作用域。在块语句中定义的变量将保留在它们已经存在的作用域中。

示例:

 if(true) {
   var name = 'wei';
 }
 
 console.log(name);  // logs: wei
 

块级作用域

块级作用域可通过新增命令letconst声明,所声明的变量在指定块的作用域外无法被访问。块级作用域在如下情况被创建:

  • 在一个函数内部
  • 在一个代码块(由一对括号包裹)内部。

let声明的语法与var的语法一致。基本上可以用let来代替var进行变量声明,但会将变量的作用域限制在当前代码块中。

块级作用域有以下几个特点:

  • 声明不会被提升到当前代码块的顶部let/const声明并不会被提升到当前代码块的顶部,因此需要你手动将let/const声明放置到顶部,以便于让变量在整个代码块内部都可用。

示例:

function func(condition) {
	if(condition) {
		let value = 'blue';
		return value;
	} else {	// value在此处不可以
		return null;
	}
	
	// value 在此处不可用
	
}


  • 禁止重复声明如果一个标识符已经在代码块内部被定义,那么在此代码块内使用同一个标识符进行let声明就会导致抛出错误,例如:
var count = 30;
let count = 40;	// SyntaxError: Identifier 'count' has already been declared

注意:如果在嵌套的作用域内使用let声明一个同名的新变量,则不会抛出错误。例如:

var count = 30;
if(condition) {
	let count = 40;
	...
}


  • 循环中绑定块级作用域的妙用开发者可能最希望实现for循环的块级作用域了,因为可以把声明的计数器变量限制在循环内,例如:
<button>btn 1</button>
<button>btn 2</button>
<button>btn 3</button>

var btns = document.getElementByTagName('button');

for(var i=0; i<btns.length; i++) {
	btns[i].onclick = function() {
		console.log(`点击的是第${i+1}个按钮`)
	}
}

我们要实现的是这样一个需求:点击某个按钮,提示“点击的是第N个按钮”,此处我们先不考虑事件代理,万万没想到,点击任意一个按钮,后台都是弹出“点击的是第4个按钮”,这是因为i是全局变量,执行到点击事件时,此时i的值是3。那该如何修改代码呢?最简单的是用let声明变量i,修改如下:

for(let i=0; i<btns.length; i++) {
	btns[i].onclick = function() {
		console.log(`点击的是第${i+1}个按钮`)
	}
}

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接