`
focus2008
  • 浏览: 27371 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类
最新评论

彻底理解javascript中的函数、闭包、作用域和作用域链

阅读更多
1.函数
javascript中的函数可以分为三种:
1)全局函数;
2)对象中定义的函数;
3)函数中定义的函数,即局部函数,也可以叫私有函数或者内部函数;
// 全局函数
function gf(){
   alert('global fucntion');
}
// 对象中定义的函数
var v = {};
v.init = function(){
   alert('function of object');
}
// 私有函数
function gf2(){
   return function pf(){
      alert('private function');
   }
}
// 调用它们:
gf();	// 弹出global fucntion
v.init();	// 弹出function of object
var a=gf2();	
a();	// 弹出private function


2. 作用域
javascript中变量没有块级作用域,只有全局作用域和函数作用域,还有对象作用域
// 全局变量
var gv = 'global variable';
// 对象中的变量,或者说属性,对象作用域可以用于来模拟实现“命名空间”
var obj = {};
obj.v = 'variable of object';
// 函数作用域
function gf(){
   var fa = 'variable of fucntion';
   return fa;
}
alert(gv);	// 全局变量,可以在任何地方直接使用gv来访问
alert(obj.v);	// 只能通过对象访问v
//alert(v);	// ReferenceError: v is not defined
alert(gf());	// 只能通过函数访问fa
//alert(fa);	// ReferenceError: fa is not defined


3. 作用域链

作用域链其实是一条法则,或者说规则,用来确定变量的作用域,确定变量来自哪里,变量是在哪里声明的:总是先在最小的作用域中寻找他的声明,如果没有找到,就到上一级的作用域中寻找,如果没有,就在到上一级寻找,一直寻找到全局作用域中,这样就形成了一条寻找变量来自哪里的链条。作用域链:即如何寻找变量来自哪里(在哪里声明)的链条。如下例:
var gv = 1;
function gf(){
   var fv = 2;
   return function pf(){
      gv = 100;
      fv = 200;
   };
}
var f = gf();
alert(gv);	// 弹出1
f();
alert(gv);	// 弹出100

上面代码中pf()函数中的变量gv和fv来自哪里?首先在pf()的函数作用域中寻找,没有找到他们的声明或者定义,那么就往上一级作用域 gf() 的函数作用域中寻找,我们找到了fv的声明和定义,但是gv还是没有找到,就继续到上一级作用域,也就是全局作用域中寻找。
alert(gv)在f()调用的前后值不一样,也就证明了全局作用域中的gv被函数pf()给修改了,也证明了作用域链就是这样一步一步向上一级寻找,这样运作的。

4. 函数的定义和调用
javascript中函数的定义和调用有两条十分重要,一定要注意:
1)函数的定义是在预处理过程中完成的;
1)函数中的变量的作用域是在该函数的定义时,通过作用域链法则确定的,变量的声明也是在函数定义时进行的,也就是在预处理过程中完成,所以就有了“变量的声明提前”的说法;
2)函数中的变量的值,是在该函数运行时,也就是调用时确定下来的;不是在定义函数时确定的;这一点一定要切记!!!
函数的作用域也称为函数的上下文,或者说执行环境。
所以函数的定义和调用是分开的,函数的定义确定作用域,分配内存;函数执行时,才根据作用域链来确定变量的值。
例子1:
function f1(){
	var a = 1;
	f2();
}
function f2(){return a;}
f1();	//ReferenceError: a is not defined

上面代码:函数f2()定义时,根据作用域法则来确定a的作用域:在函数f2()的作用域,在全局作用域都没有找到a的声明和定义,所以f2()函数中的a是没有声明和定义的;
所以在f1();调用是,fa()中的f2()会报错: a is not defined。f2()函数中a的作用域不会在运行时才去确定!运行时只会去确定他的值,如果他进行过声明和定义的话。
例子2:
function f(){
	var a=[];
	var i;
	for(i=0; i<3; i++){
		a[i] = function(){
			return i;
		}
	}
	return a;
}
var a = f();
console.log(a[0]());  //3
console.log(a[1]());  //3
console.log(a[2]());  //3

上面的结果为什么不是0,1,2,而是3,3,3呢?
分析如下:
1)定义函数f()时,确定了a和i的作用域,是在f()函数的作用域中;定义f()函数的同时,也定义了三个私有匿名函数,定义时也确定了三个私有匿名私有函数中变量i的作用域,根据作用域链法则,显然 i 是来自f()函数的作用域中的。注意此时只确定作用域,不能确定 i 的值,因为 i 来自函数f(),那么 i 的值的确定是发生在调用f()时。
而var a = f(); f()函数运行之后,i 的值才确定的,显然此时 i == 3;
所以调用 a[0](); a[1](); a[2](); 时,输出的 i 的值都是 3,而不是0,1,2.
例子3:
function ff(){
	var i = g;
	return function(){
		return i++;
	}
}
var g = 100;
console.log(g);	//100
var af = ff();
console.log(af());	//100
console.log(g);	//100
g = 1000;
console.log(g);	//1000
console.log(af());	//101

var af2 = ff();
console.log(af2());	//1000
console.log(af2());	//1001
console.log(af());	//102

结果分析:
1)上面代码,最前面输出的两个100,很容易理解。
2)第三个console.log(g) == 100; 说明 ff()中的变量 i 的值是在调用ff()函数时,从全局变量 g 获得的,但是获得之后,改变 i 的值并不能同时改变 g 的值。i 只是在ff()调用时的那一刻从 g 哪里获得值,ff()不运行,那么 i 就和 g 没有关系了。
3)第4个输出值1000也好理解。第5个输出值,因为上一次调用af()使得 i 自增了, 所以输出了101,此时有自增 了 1 一次。
4)第6个和第7个值分别输出了:1000和1001是因为,var af2 = ff(); 使得ff()函数又调用了一次,所以ff()函数中 的 i 在ff()函数运行时,又从 g 那里获得值,而此时 g == 1000了,所以ff()函数中的 i 也等于 1000 了,所以分别输出 1000 和 1001.
5)最后一个af()输出 102而不是1002,是因为 af 函数 和 af2 函数的定义是发生在两个不同的时间点,是在ff()的两次运行生成的两个定义,注意这个例子和上面那个例子的不同,上面那个例子中是在定义全局函数时,同时也定义了三个私有函数。而本例,af 和 af2 函数的定义是在全局函数ff()的两次运行时才生成的,两次运行生成了两个函数的上下文,而两次运行时 i 的值不一样,所以生成的两个函数的上下文中的 i 也不一样的。因为 i 在ff函数的局部变量中,所以生成af 和 af2函数时上下文中的 i 的值是由那次 ff() 的运行时 i 的值确定的。因为 g 的值改变了,所以af 和 af2 上下文中的 i 的值也变了。

af 和 af2 是两个不同的函数,是不会共享上下文的。他们的上下文,也就是作用域,由生成他们定义的那次ff()函数的运行来确定(函数在其的定义时确定其作用域),而运行ff()函数又确定他自己的局部变量 i 的值(函数运行时确定其作用域中变量的值:两次分别为100和1000;),所以af 和 af2 上下文作用域中的 i 的值分别为100 和 1000。

例子4:
<script type="text/javascript">
    console.log(f1);//f1()  
    console.log(f2);// undefined
    function f1(){
       console.log("f1!");
    }
    var f2 = function(){
        console.log("2!");
    }
</script> 

分析结果:
    函数的定义是在预处理过程完成的,所以运行时,可以打印出f1(),而变量 f2 声明是在预处理过程完成的,但是定义也就是赋值是在运行时完成的,所以打印 undefined (此时还没有定义)

5. 闭包
上面两个例子(2,3)中都使用了javascript中的闭包技术。
闭包,就是将私有函数返回给一个全局变量,而因为私有函数中可以访问的变量的作用域是在定义它的时候确定的,也就是说私有函数可以访问它的父函数中定义的变量,那么将私有函数返回给全局变量之后,导致了在全局作用域中可以访问函数作用域中定义的变量了。这种在全局作用域中访问函数作用域的技术就是闭包技术。
例子:
function f(){
   var b = "b";
   return function(){
      return b;
   }
}
//console.log(b);  //ReferenceError: b is not defined
var a = f();
console.log(a());  // 这里在全局作用域中访问了函数作用域中的变量b 


0
0
分享到:
评论

相关推荐

    javascript 闭包、匿名函数、作用域链

    JavaScript中的闭包、匿名函数和作用域链是编程中至关重要的概念,它们是理解JavaScript运行机制的关键。在本文中,我们将深入探讨这三个概念,并通过实际示例来展示它们的运用。 首先,我们来讨论“闭包”。闭包是...

    深入理解JavaScript作用域和作用域链

    JavaScript作用域是编程中至关重要的...在实际编程中,我们还会遇到闭包、块级作用域(ES6中的`let`和`const`)等更高级的概念,这些都是基于作用域链的扩展,理解它们对于成为精通JavaScript的开发者来说必不可少。

    深入理解javascript原型和闭包

    深入理解javascript原型和闭包(01)——一切都是对象 深入理解javascript原型和闭包(02)——函数和对象的关系

    深度探讨javascript函数的原型链和闭包

    闭包是理解作用域链的关键概念,它允许内部函数记住其定义时的作用域,即使外部函数已经完成执行。闭包常常用于封装变量和实现私有方法,提高代码的封装性和安全性。 总结一下,JavaScript中的函数不仅是一种数据...

    JavaScript 中的闭包是指内部函数可以访问外部函数作用域中的变量

    如果是在全局作用域中声明的,则该变量在整个程序中都可见。这种作用域规则确保了闭包能够在合适的情况下访问到正确的变量。 2. **函数作为值传递**:JavaScript允许将函数作为一个普通的值来对待,这意味着函数...

    Web前端面试题目JavaScript(作用域,原型。原型链,闭包,封装函数).txt

    前端面试题,包含JavaScript的闭包,作用域,原型,原型链,上下文环境以及DOM,BOM封装函数深度克隆,以及一些常见的·JS问题,试题简单但是容易混淆,作为前端工程师必考题

    深入理解javascript原型和闭包.pdf

    JavaScript原型和闭包是这门语言中两个比较难以理解且与其他面向对象语言区别较大的概念。...无论是原型链和作用域链的理解,还是函数作为一等对象的灵活使用,都是深入学习和应用JavaScript不可或缺的部分。

    JavaScript中的作用域链和闭包

    在JavaScript中,作用域和闭包是两个非常重要的概念,它们对于理解和编写高效、可靠的代码至关重要。让我们深入探讨这两个主题。 首先,我们来看一下**作用域**。作用域决定了变量和函数在何处可以被访问。主要有两...

    深入理解javascript原型和闭包1

    JavaScript通过作用域链解决自由变量的查找问题,作用域链连接了当前作用域与上层作用域,允许在不同作用域中访问变量。 (15)——闭包 闭包是JavaScript中一种强大的特性,它允许函数访问并操作其词法作用域内的...

    JavaScript:函数与作用域

    - **特性**:闭包使得函数可以访问并操作外部作用域中的变量,即使外部函数已经执行完毕。 - **示例**: ```javascript function outerFunction() { var outerVar = "外部变量"; function innerFunction() { ...

    闭包作用域

    在JavaScript中,闭包(Closure)是一个非常重要的概念,它允许一个函数访问并操作其外部作用域中的变量,即使该函数在其外部作用域之外被调用。这种特性使得闭包成为一种强大的工具,能够实现诸如数据封装、私有...

    10-作用域链和闭包:代码中出现相同的变量,JavaScript引擎是如何选择的?_For_vip_user_0011

    "作用域链和闭包:代码中出现相同的变量,JavaScript引擎是如何选择的?" 标题“作用域链和闭包:代码中出现相同的变量,JavaScript引擎是如何选择的?”中,我们可以看到JavaScript引擎是如何选择相同的变量的。...

    javascript闭包的理解

    这是因为在JavaScript的词法作用域中,内部函数可以访问外部函数的作用域链。嵌套函数实际上成为了连接外部和内部作用域的桥梁。闭包的作用有两个:一是可以读取函数内部的变量;二是可以让这些局部变量保存在内存中...

    浅析javascript语言中的函数闭包现象.pdf

    在链式作用域中,子函数可以访问父函数的所有变量,但子函数的变量对父函数来说是不可见的。当`f2`返回`f3`时,`f3`就形成了一个闭包,因为它保留了对外部环境`f2`的引用,包括`n`这个变量。 闭包的应用非常广泛,...

    JavaScript中作用域链的概念及用途讲解

    接着查找`fruit`,不在当前作用域,于是继续在全局作用域中找到`fruit`并使用。 而在全局环境中,当尝试访问`fruit`和`color`时,由于全局作用域的变量对象只包含`fruit`,所以`color`会导致一个`undefined`的错误...

    JavaScript作用域、闭包、对象与原型链概念及用法实例总结

    JavaScript是Web开发中不可或缺的一部分,它提供了丰富的特性,如作用域、闭包、对象和原型链,这些都是理解和编写高效代码的关键。以下是对这些概念的详细解释: 1. **JavaScript变量作用域** - **函数作用域**:...

    理解javascript函数式编程中的闭包(closure)_.docx

    本篇文章主要探讨JavaScript函数式编程中的一个重要概念——闭包(closure)。闭包是一种特殊的函数,它能记住其定义时的作用域,即使在函数执行完毕后,仍然可以访问到该作用域内的变量。在JavaScript中,每个函数...

    JavaScript 匿名函数和闭包介绍

    闭包则是能够访问其所在词法作用域的函数,即使是在该作用域已经结束之后。 首先,让我们详细探讨一下匿名函数。在JavaScript中,匿名函数的声明通常采用以下几种形式: ```javascript // 匿名函数表达式赋值给变量...

    JavaScript闭包函数

    闭包是ECMAScript (JavaScript)最强大的特性之一,但用好闭包的前提是必须理解闭包。闭包的创建相对容易,人们甚至会在...而闭包工作机制的实现很大程度上有赖于标识符(或者说对象属性)解析过程中作用域的角色。

    理解_JavaScript_闭包

    本文结合 ECMA 262 规范详解了闭包的内部工作机制,让 JavaScript 编程人员对闭包的理解从“嵌套的函数”深入到“标识符解析、执行环境和作用域链”等等 JavaScript 对象背后的运行机制当中,真正领会到闭包的实质。

Global site tag (gtag.js) - Google Analytics