swapColor(); //这里只能访问color,不能访问anotherColor、tempColor console.log(color); //blue console.log(anotherColor); //anotherColor is not defined console.log(tempColor); //tempColor is not defined }changeColor(); //这里只能访问color console.log(color); //blue console.log(anotherColor); //anotherColor is not defined console.log(tempColor); //tempColor is not defined 还有几个坑需要注意一下:1、var和函数的提前声明var color = "red";function changeColor(){ var color = "yellow"; return color; }var result = changeColor(); console.log(result);再如:function fn(a) { console.log(a); var a = 2; function a() {} console.log(a); } fn(1); //输出:function a() {} ,22、Javascript中没有块级作用域,但是有词法作用域,比如:function f1(){var a=1;f2();} function f2(){return a;} var result = f1(); console.log(result); //输出结果:a is not defined3、在函数内部不用var关键字申明变量,则默认该变量为全局变量,比如:function add(a,b){ var sum = a+b;//次世代sum为add函数内部的变量,仅限在函数内部使用,在函数外面不可以使用 return sum; } var result = add(1,2); console.log(result); //3 console.log(sum); //sum is not defined //不使用var关键字声明变量 function add(a,b){ sum = a+b;//此时的sum为全局变量,在函数之外也可以调用 return sum; } var result = add(1,2); console.log(result); //3 console.log(sum); //3补充:在JavaScript中如果不创建变量,直接去使用,则报错:12 console.log(xxoo);// 报错:Uncaught ReferenceError: xxoo is not defined JavaScript中如果创建值而不赋值,则该值为 undefined,如:123 var xxoo;console.log(xxoo);// 输出:undefined 在函数内如果这么写:1234567 function Foo(){ console.log(xo); var xo = "seven";}Foo();// 输出:undefined 上述代码,不报错而是输出 undefined,其原因是:JavaScript的函数在被执行之前,会将其中的变量全部声明,而不赋值。所以,相当于上述实例中,函数在“预编译”时,已经执行了var xo;所以上述代码中输出的是undefined。注意:我们平时在声明变量时一定要注意!!!还有不要滥用全局变量(在forin循环的时候特别注意)!!!4、词法作用域是不可逆的,我们可以从下面的例子中看到结果:// name = undefined var scope1 = function () { // name = undefined var scope2 = function () { // name = undefined var scope3 = function () { var name = "Todd"; // locally scoped }; }; };--------------------------------------------------------------------------------前面我们了解了作用域的一些基本知识,我们发现有作用域的存在能帮我们省去不少事,但是于此同时,也给我们带来了很多麻烦,比如说我们想在下面的函数A中,调用函数B,我们该怎么办呢?function A(){ function B(){ // } }思路:我们给函数B设一个返回值,然后在函数A中调用,代码如下:function A(){ function B(){ console.log("Hello foodoir!"); } return B; } var c = A(); c();//Hello foodoir!这样我们就可以得到我们想要的结果。这样,我们基本上到了一个最简单的闭包形式。我们再回过头分析代码:(1)定义了一个普通函数A(2)在A中定义了普通函数B(3)在A中返回B(确切的讲,在A中返回B的引用)(4)执行A(),把A的返回结果赋值给变量 c(5)执行 c() 把这5步操作总结成一句话:函数A的内部函数B被函数A外的一个变量 c 引用。当一个内部函数被其外部函数之外的变量引用时,就形成了一个闭包。思考:我们还有没有其他的方法?思路:使用匿名函数function A(){ //匿名函数 var B = function(x,y) { return x+y; } console.log(B(1,2));//3 return B(1,2); } var c = A(); console.log(c);//3然而,在Javascript高级程序设计中是这样描述闭包的“闭包是指有权访问另一个函数作用域中的变量的函数”,但是我们看匿名函数的例子,很明显,这种方法不可取! 通过这个例子,能让我们更好的理解闭包。下面我们再来看下面的几种闭包demo1:function fn(){ var b = "foodoir"; return function(){ console.log(b);//foodoir return b; } } //console.log(b);//b is not defined var result = fn(); console.log(result());//foodoirdemo2:var n; function f(){ var b = "foodoir"; n = function(){ return b; } } f(); console.log(n());//foodoirdemo3://相关定义与闭包 function f(arg){ var n = function(){ return arg; }; arg++; return n; } var m = f(123); console.log(m());//124 //注意,当我们返回函数被调用时,arg++已经执行过一次递增操作了,所以m()返回的是更新后的值。demo4:闭包中的读取与修改//闭包中的设置与修改 var getValue,setValue; (function(){ var n = 0; getValue = function(){ return n; }; setValue = function(x){ n = x; } })(); //console.log(n); console.log(getValue());//0 console.log(setValue());//undefinedsetValue(123); console.log(getValue());//123demo5:用闭包实现迭代效果//用闭包实现迭代器效果 function test(x){ //得到一个数组内部指针的函数 var i=0; return function(){ return x[i++]; }; } var next = test(["a","b","c","d"]); console.log(next());//a console.log(next());//b console.log(next());//c console.log(next());//ddemo6:循环中的闭包//循环中的闭包 function fn(){ var a = []; for(var i=0;i<3;i++){ a[i] = function(){ return i; } } return a; } var a = fn(); console.log(a[0]());//3 console.log(a[1]());//3 console.log(a[2]());//3/* * 我们这里创建的三个闭包,结果都指向一个共同的局部变量i。 * 但是闭包并不会记录它们的值,它们所拥有的只是一个i的连接,因此只能返回i的当前值。 * 由于循环结束时i的值为3,所以这三个函数都指向了3这???个共同值。 * */思考:如何使结果输出分别为0、1、2呢?思路一:我们可以尝试使用自调用函数function fn(){ var a = []; for(var i=0;i<3;i++){ a[i] = (function(x){ return function(){ return x; } })(i); } return a; } var a = fn(); console.log(a[0]());//0 console.log(a[1]());//1 console.log(a[2]());//2思路二:我们将i值本地化function fa(){ function fb(x){ return function(){ return x; } } var a = []; for(var i=0;i<3;i++){ a[i] = fb(i) } return a; } console.log(a[0]());//0 console.log(a[1]());//1 console.log(a[2]());//2------------------------------------------------------分界线------------------------------------------------------- 在这里,我们来对闭包进行更深一步的操作我们再将demo1的例子进行扩展代码示例如下:function funcTest(){ var tmpNum=100; //私有变量 //在函数funcTest内 //定义另外的函数作为funcTest的方法函数 function innerFuncTest( { alert(tmpNum); //引用外层函数funcTest的临时变量tmpNum }
return innerFuncTest; //返回内部函数 }
//调用函数 var myFuncTest=funcTest(); myFuncTest();//弹出100到样,我们对闭包的概念和用法有更加熟悉闭包和this相关闭包应用举例,模拟类的私有属性,利用闭包的性质,局部变量只有在sayAge方法中才可以访问,而name在外部也访问,从而实现了类的私有属性。function User(){ this.name = "foodoir"; //共有属性 var age = 21; //私有属性 this.sayAge=function(){ console.log("my age is " + age); } } var user = new User(); console.log(user.name); //"foodoir" console.log(user.age); //"undefined" user.sayAge(); //"my age is 21"关于闭包更深入的了解 前面在demo6中,我们了解了用自调用方法来实现闭包,下面我们用这种方法来进行更复杂的操作(写一个简单的组件)。(function(document){ var viewport; var obj = { init:function(id){ viewport = document.querySelector("#"+id); }, addChild:function(child){ viewport.appendChild(child); }, removeChild:function(child){ viewport.removeChild(child); } } window.jView = obj; })(document);这个组件的作用是:初始化一个容器,然后可以给这个容器添加子容器,也可以移除一个容器。功能很简单,但这里涉及到了另外一个概念:立即执行函数。 简单了解一下就行。主要是要理解这种写法是怎么实现闭包功能的。闭包并不是万能的,它也有它的缺点 1、闭包会使得函数中的变量都保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页性能问题。另外在IE下有可能引发内存泄漏 (内存泄漏指当你的页面跳转的时候 内存不会释放 一直占用你的CPU 只有当你关闭了浏览器才会被释放); 2、闭包会在父函数外部改变父函数内部的变量的值,所以不要随便改动父函数内部的值。更多参考资料: 《Javascript高级程序设计(第三版)》第四章、第七章 《Javascript面向对象编程指南》第三章JavaScript面向对象编程指南 PDF书签版 http://www.linuxidc.com/Linux/2016-04/130052.htmJavaScript高级程序设计(第3版)高清完整PDF中文+英文+源码 http://www.linuxidc.com/Linux/2014-09/107426.htm作者的话: 这篇文章主要先是通过几个简单的例子介绍作用域链(顺便补充了几个和作用域链相关的易出错的小知识),然后通过提问慢慢过渡到闭包(在闭包这部分介绍了几种常见闭包的例子),后面又进一步讲到了关于闭包的更高级的用法。后面遇到关于闭包的较好的用法会继续更新。本文永久更新链接地址:http://www.linuxidc.com/Linux/2016-10/135874.htm