`
focus2008
  • 浏览: 26408 次
  • 性别: 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原型和闭包(01)——一切都是对象 深入理解javascript原型和闭包(02)——函数和对象的关系

    【JavaScript源代码】JS难点同步异步和作用域与闭包及原型和原型链详解.docx

    JS难点同步异步和作用域与闭包及原型和原型链详解  目录 JS三座大山同步异步同步异步区别作用域、闭包函数作用域链块作用域闭包闭包解决用var导致下标错误的问题投票机闭包两个面试题原型、原型链原型对象原型链...

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

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

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

    NULL 博文链接:https://xieyaxiong.iteye.com/blog/1558277

    JS匿名函数、闭包

    即使JavaScript中没有正式的私有对象属性的概念,但可以使用闭包来实现公有方法,而通过公有方法可以访问在包含作用域中定义的变量; 有权访问私有变量的公有方法叫做特权方法; 可以使用构造函数模式、原型模式...

    JavaScript闭包函数

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

    理解_JavaScript_闭包

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

    JavaScript闭包与作用域链实例分析

    JavaScript的闭包与作用域链密不可分,因此本文可以和JavaScript的作用域链相对照分析,一定可以对JavaScript的闭包和作用域链有更深的理解。 下面我们仍然以createComparisonFunction为例进行闭包的分析。 //step1...

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

    主要介绍了JavaScript作用域、闭包、对象与原型链,结合实例形式总结分析了javascript中变量与函数的作用域、闭包、对象、原形链相关概念、用法及注意事项,需要的朋友可以参考下

    JavaScript使用闭包模仿块级作用域操作示例

    正如闭包的定义一样:“闭包指的是有权访问另一个函数作用域中的变量的函数”, 闭包最大的意义就在于闭包可以对另一个函数作用域的变量进行访问,由此,闭包可以延伸出一系列的用法。 模仿块级作用域 JavaScript...

    JavaScript中的作用域链和闭包

    作用域 全局作用域 局部作用域 作用域链 执行上下文 活动对象 闭包 闭包优化 JavaScript中出现了一个以前没学过的概念——闭包。何为闭包?从表面理解即封闭的包,与作用域有关。所以,说闭包以前先说说作用域。 ...

    深入理解javascript作用域和闭包

    作用域是一个变量和函数的作用范围,javascript中函数内声明的所有变量在函数体内始终是可见的,在javascript中有全局作用域和局部作用域,但是没有块级作用域,局部变量的优先级高于全局变量,通过几个示例来了解下...

    JS中的闭包理解。。。。。。。。

    js闭包 • 每个函数都有一个包含词法环境的执行上下文,它的词法环境确定了函数内的变量赋值以及对外部环境的引用。...• JavaScript中闭包作用域是词法作用域,即它在代码写好之后就被静态决定了它的作用域。

    javascript作用域和闭包使用详解

    作用域JavaScript 中有两种作用域:函数作用域和全局作用域。 在一个函数中声明的变量以及该函数的参数享有同一个作用域,即函数作用域。一个简单的函数作用域的例子: 代码如下:function foo() { var bar = 1; {...

    深入理解JavaScript系列

    深入理解JavaScript系列(14):作用域链(Scope Chain) 深入理解JavaScript系列(15):函数(Functions) 深入理解JavaScript系列(16):闭包(Closures) 深入理解JavaScript系列(17):面向对象编程之一般...

    javascript从作用域链谈闭包

    闭包是指有权访问另外一个函数作用域中的变量的函数 这概念有点绕,拆分一下。从概念上说,闭包有两个特点: 1、函数 2、能访问另外一个函数作用域中的变量 在ES 6之前,Javascript只有函数作用域的概念,没有...

    深入Javascript函数、递归与闭包(执行环境、变量对象与作用域链)使用详解

    本篇文章对Javascript中函数、递归与闭包(执行环境、变量对象与作用域链)的使用进行了详细的分析介绍。需要的朋友参考下

    JavaScript作用域链实例详解

    对于JavaScript而言,理解作用域更加重要,因为在JavaScript中,作用域可以用来确定this的值,并且JavaScript有闭包,闭包是可以访问外部环境的作用域的。 每一个JavaScript的函数都是Function对象的一个实例,...

    【JavaScript源代码】详解JavaScript闭包问题.docx

     一、变量的作用域 在介绍闭包之前,我们先理解JavaScript的变量作用域。变量的作用域分为两种:全局变量和局部变量。 var n = 999; //全局变量 function f1() { a = 100; //在这里a也是全局变量 alert(n); }...

Global site tag (gtag.js) - Google Analytics