通过JS运行机制的角度说说作用域

前言

任何程序设计语言都有作用域的概念,简单的说,作用域就是变量与函数的可访问范围。JS中的作用域、闭包、this机制和原型往往是最难理解的概念之一。笔者将通过几篇文章和大家谈谈自己的理解,希望对大家的学习有一些帮助。如果有什么理解偏差的地方,希望大家可以评论指出,相互学习。

有过一定编程经验的同学,一定不会对作用域感到陌生,在C/C++/Java中等语言中,作用域从来没有JavaScript中的作用域那样令人困惑以致于成为一个大多数JS开发者都难以跨过的门槛。

作用域形成机制

JS中存在的三种作用域类型:全局作用域,函数作用域和ES6中新加入的块级作用域。

var a = 1;

function foo() {
 var b = 2;
 console.log(a);		// 1
 console.log(b);		// 2
 console.log(c);		// ReferenceError
}

function foo1() {
 var c = 3;
 console.log(a);		// 1
 console.log(b);		// ReferenceError
 console.log(c);		// 3
}

console.log(a);			// 1
console.log(b); 		// ReferenceError
console.log(c);			// ReferenceError
foo();
foo1();

从上面的例子可以看到,每个函数内部形成了属于自己的作用域,函数内部声明的变量仅仅在定义的函数内部才可以访问。全局作用域中可以访问到的有a,foo,foo作用域中可以访问到的有b,foo,a,foo1的作用域中可以访问到的有c,foo,a。因为foo的作用域嵌套在全局作用域之中,当console.log(a);执行的时候,JS在foo的作用域查找不到a,就会到它的上层(这里是foo的上层直接就是全局作用域)查找,发现这里声明了一个a,将它的值打印了出来。这种从里到外的查找就是根据作用域链查找。foo1和foo的作用域没有嵌套关系,所以相互隔离。

如果函数中使用了未声明的变量怎么办?

function foo() {
	a = 2;
}

foo();
console.log(a);		// 2

JS引擎在foo中查找不到a的声明,便会到它的上层(这里是全局作用域中)查找,这个时候还是没有查找到a的声明,在非严格模式下,JS引擎会在全局中自动声明一个a,这个时候,未经声明的变量a实际上泄漏到了全局作用域中。

只有使用未声明的变量才会出现变量泄漏的问题么,其实,不仅仅这种写法会出现,更常见的也会出现在for循环和if代码块中也会出现。

for(var i=1;i<10;i++) {
 console.log(i);
}

console.log(i);		// 10,	这里的i泄漏到了全局作用域中

if(true) {
 var a = 2;
}

console.log(a);		// 2, 这里的a也泄漏到了全局变量之中

如果你学习过C语言系列的语法,往往很容易感到困惑,if和for居然没有作用域,这真是太奇怪了。这一切的问题的根源,都是由于ES6之前没有块级作用域导致的。所以可想而知,if包裹的代码块,同样里面的声明也是暴露出来的~

一切问题的解决直到ES6中引入了let和const得以完美的解决。使用let和const,将可以使用块级作用域,使得声明变量泄漏的问题得以解决。

for(let i=1;i<10;i++) {
 console.log(i);
}

console.log(i);		// ReferenceError

if(true) {
 let a = 2;
}

console.log(a);		// ReferenceError

声明提升机制

对于在JS中声明的不论是变量还是函数,基本上都会存在着变量声明提升的行为,将变量的声明提升到所在作用域的顶端。ES6中的let和const不会,在未声明之前都不可以使用。

看看下面的代码

console.log(a);				// undefined
console.log(b);				// undefined
console.log(foo);			// Function
console.log(foo2);		// ReferenceError

function foo () {
 console.log('声明提升了哈');
}

var a = 1;

var b = function foo2() {
 console.log('不同的函数声明方式提升的结果也不一样哦');
};

JS 引擎解释这段代码之前首先对代码中所有的变量进行了声明的提升,函数声明的提升的优先级是高于普通变量的,函数声明会整个提升到所在作用域的顶端(但是以函数表达式方式声明的函数不会),代码实际上是下面这个样子:

function foo () {
 console.log('声明提升了哈');
}
var a;
var b;
var foo2;

console.log(a);
console.log(b);
console.log(foo);
console.log(foo2);

b = function foo2 () {
 console.log('不同的函数声明方式提升的结果也不一样哦');
}

静态作用域机制(词法作用域)

关于JS中的作用域,需要明确的一点就是,JS中只存在静态作用域(词法作用域)。静态作用域是什么意思呢?意思就是它的作用域在你写下代码的时候就已经确定了,和函数的调用顺序无关,了解这一点。就可以对一些常见的现象进行解释。

var a = 2;
function foo() {
 console.log(a);
}

var obj = {
 a: 3,
 foo: foo
}

obj.foo();		// 2

foo中的a在代码写完时就确认了,指向了全局作用域中的a,一旦确定就无法更改了。同理,下面的代码

function foo() {
 console.log(b);			// ReferenceError
}

function foo1 () {
 var b = 1;
 foo();
}

foo1();

这里,JS引擎在全局作用域中查找不到b,所以会抛出一个异常。所以可以明确的道理是,foo的作用域和foo1的作用域仍然是相互独立的,不会因为调用时候的顺序而更改作用域的嵌套顺序,静态作用域在代码书写时就已经确定无法更改了,明白这一点在分析JS代码的时候尤为重要。

坑外话

变量的遮蔽效应

在函数中定义的变量会遮蔽上层作用域中同名的变量,两个变量互不影响。

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

Try-Catch 中的块级作用域

try-catch的catch中会创建一个块级作用域,该作用域内变量的表现同样遵守变量的声明提升规则。

try {
 throw undefined;
}catch(e) {
 a = 1;
 console.log(e);		// undefined
}

console.log(a);			// 1,	变量提升规则
console.log(e);			// ReferenceError,catch的块作用域中定义的变量

隐式声明

以参数形式传入的变量在函数内部实际上存在的隐式的声明,使用时不算作未声明的变量。

function foo(a) {
 a = 1;
 console.log(a);
}

foo();						// 1
console.log(a);		// ReferenceError

本来想一篇文章写完作用域和闭包的,想例子实在是累,就拆作两篇吧,逃~

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对我们的支持。

(0)

相关推荐

  • JavaScript的变量作用域深入理解

    在学习JavaScript的变量作用域之前,我们应当明确几点: a.JavaScript的变量作用域是基于其特有的作用域链的. b.JavaScript没有块级作用域. c.函数中声明的变量在整个函数中都有定义. 1.JavaScript的作用域链 首先看下下面这段代码: 复制代码 代码如下: <script type="text/javascript" language="javascript"> var rain = 1; function rain

  • JavaScript的作用域和块级作用域概念理解

    作用域永远都是任何一门编程语言中的重中之重,因为它控制着变量与参数的可见性与生命周期.讲到这里,首先理解两个概念:块级作用域与函数作用域. 什么是块级作用域呢? 任何一对花括号({和})中的语句集都属于一个块,在这之中定义的所有变量在代码块外都是不可见的,我们称之为块级作用域. 函数作用域就好理解了(*^__^*) ,定义在函数中的参数和变量在函数外部是不可见的. 大多数类C语言都拥有块级作用域,JS却没有.请看下文demo: //C语言 #include <stdio.h> void mai

  • 关于javascript 回调函数中变量作用域的讨论

    1.背景 Javascript中的回调函数,相信大家都不陌生,最明显的例子是做Ajax请求时,提供的回调函数, 实际上DOM节点的事件处理方法(onclick,ondblclick等)也是回调函数. 在使用DWR的时候,回调函数可以作为第一个或者最后一个参数出现,如: JScript code function callBack(result){ } myDwrService.doSomething(param1,param2,callBack);//DWR的推荐方式 //或者 myDwrSer

  • JavaScript中的作用域链和闭包

    作用域 全局作用域 局部作用域 作用域链 执行上下文 活动对象 闭包 闭包优化 JavaScript中出现了一个以前没学过的概念--闭包.何为闭包?从表面理解即封闭的包,与作用域有关.所以,说闭包以前先说说作用域. 作用域(scope) 通常来说一段程序代码中使用的变量和函数并不总是可用的,限定其可用性的范围即作用域,作用域的使用提高了程序逻辑的局部性,增强程序的可靠性,减少名字冲突. 全局作用域(Global Scope) 在代码中任何地方都能访问到的对象拥有全局作用域,以下几种情形拥有全局作

  • js作用域及作用域链概念理解及使用

    (1)作用域 一个变量的作用域(scope)是程序源代码中定义的这个变量的区域. 1. 在JS中使用的是词法作用域(lexical scope) 不在任何函数内声明的变量(函数内省略var的也算全局)称作全局变量(global scope) 在函数内声明的变量具有函数作用域(function scope),属于局部变量 局部变量优先级高于全局变量 var name="one"; function test(){ var name="two"; console.log

  • js变量以及其作用域详解

    一.变量的类型 Javascript和Java.C这些语言不同,它是一种无类型.弱检测的语言.它对变量的定义并不需要声明变量类型,我们只要通过赋值的形式,可以将各种类型的数据赋值给同一个变量.例如: 复制代码 代码如下: i=100;//Number类型 i="variable";//String类型 i={x:4};//Object类型 i=[1,2,3];//Array类型 JS的这种特性虽然让我们的编码更加灵活,但也带来了一个弊端,不利于Debug,编译器的弱检测让我们维护冗长的

  • 深入理解Javascript中this的作用域

    大家在使用Javascript的时候经常被this这个家伙搞得晕头转向的.对大多数有OOP开发经验的开发人员来说this是当前作用域中引用普通元素的标识符,但是在Javascript中它却显得古灵精怪的,因为它不是固定不变的,而是随着它的执行环境的改变而改变.在Javascript中this总是指向调用它所在方法的对象. 举一个简单的例子: 复制代码 代码如下: function test(){ alert(this); } var obj=function(){ var name='testO

  • 网易JS面试题与Javascript词法作用域说明

    调用对象位于作用域链的前端,局部变量(在函数内部用var声明的变量).函数参数及Arguments对象都在函数内的作用域中--这意味着它们隐藏了作用域链更上层的任何同名的属性. 2010年9月14日,我去参加网易网页工程师招聘会,应聘JS工程师职位.有幸参加笔试,然后有幸栽在笔试,呵呵.废话少说,抓出音响极深的一题重新研究研究. 题目大概是:写出如下代码的输出结果并进行分析 复制代码 代码如下: var tt = 'aa'; function test(){ alert(tt); var tt

  • javascript 函数及作用域总结介绍

    在js中使用函数注意三点:1.函数被调用时,它是运行在他被声明时的语法环境中的: 2.函数自己无法运行,它总是被对象调用的,函数运行时,函数体内的this指针指向调用该函数的对象,如果调用函数时没有明确指定该对象, this 默认指向 window ( strict 模式除外,本文不涉及 strict 模式): 3.函数是一种带有可执行代码的对象类型数据. 一.声明函数 1.使用 function 关键字 复制代码 代码如下: function myfun(a,b){ //声明名为myfun的函

  • JSP中 Session和作用域的使用

    几乎所有的Web开发语言都支持Session功能,Servlet也不例外. Servlet/JSP中的Session功能是通过作用域(scope)这个概念来实现的. 作用域分为四种,分别为: page 在当前页面有效(仅用于JSP中) request 在当前请求中有效 session 在当前会话中有效 application 在所有应用程序中有效是不是看不太明白?page因为仅用于JSP中,这里只讲述其他三种作用域. 首先要声明的一点,所谓"作用域"就是"信息共享的范围&qu

随机推荐