简述:
最近学习了javascript函数表达式和闭包这一块, 记录下自己的学习笔记!
学习ECMAscript,函数表达式和'面向对象'这二块是难点,也是重点。
参考书:<javascript高级程序设计> 工具: EditPlus 浏览器 IE11 ,chrome
1 javascript定义函数的方法主要有二种:
(1)函数声明
function p(){//code}
(2)函数表达式: 也叫匿名函数
var p=function(){};
简单阐述有什么区别?
1 <script type="text/javascript"> 2 <!-- 3 p(); 4 function p(){ 5 alert('函数声明'); 6 } 7 //--> 8 </script>
使用函数声明的方式,上面这样调用没有问题,原因是javascript存在'函数声明提升'。但是var p=function(){} 这样定义就会出错,原因是找不到这个函数(不存在函数声明提升);
2 函数的一些特征:
(1)Function对象实例
函数名是指针,函数是对象;所以函数是没有重载的;
<script type="text/javascript"> function a(){alert('a');} function a(){alert('123');} a(); </script>
结果:123 很简单:第二个函数和第一函数是不同的对象,a现在指向它,不再指向第一个函数对象,访问的自然也就是第一个函数。
(2)既然是实例:那么就会有方法和属性---要有这个意识
比如caller 可以获取调用这个函数的函数的引用; 可以看看下面代码
1 function a(){ 2 b(); 3 } 4 function b(){ 5 alert(b.caller); 6 } 7 a(); // function a(){b();}
(3)二个重要的内部属性 arguments,this
this不用多说,谁调用这个函数就代表谁,注意:函数是一定有调用者的,不可能自行调用。
arguments 是用来存放函数参数的一个对象,这个对象有个叫callee的属性,代表拥有该arguments的函数,换句话说是正在运行的函数引用
可以看看下面代码
function c(){ alert(arguments.callee); } c();//function c(){//code}
3 函数中定义变量
定义变量可以这样 var x=1; x=0;区别就在有没有var声明,在window全局中基本没有区别,但是function中是不一样的;一句话来说,var声明
就是局部变量,不声明那就是window的变量(全局变量);看看下面代码
1 function d(){ 2 name='window name'; 3 } 4 d(); 5 alert(window.name);//window name
第2行改为var name='window name' 第5行报错,undefined
函数表达式基础的差不多了,接下来重点来了!
4 作用域链
理解作用域链是理解闭包的关键;
(1)什么是执行环境
执行环境有二种: 全局执行环境;局部执行环境---function里面;
执行流进入函数执行时,创建执行环境;函数执行结束,回收!
(2)变量对象
理解变量对象非常重要,变量对象在函数中也称为 活动对象,我还是喜欢说成变量对象。
每一个执行环境都有一个变量对象,变量对象会保存这个执行环境的变量和方法;我们不难想到全局的变量对象是谁? window对象
我们在全局中添加变量和方法都是添加到window里。函数执行时 执行环境中就会创建变量对象;一般来说:函数调用完毕之后无论是执行环境(出栈),还是变量对象都是回收的。闭包的出现使得变量对象不回收;
(3)作用域链?
什么是作用域链:作用域链的本质是 指向变量对象的一组有序指针;字面上很难理解,我第一次看不明白!
有什么作用:在一个执行环境中对变量和方法进行有序(正确)访问;
什么时候创建: 一个函数声明的时候就创建了,作用域链会保存在一个函数的内部属性[[Scope]]中;
注意:执行流进入函数是: 创建执行环境->将作用域链(利用[[Scope]]属性) 复制到执行环境中->创建变量对象(活动对象)->将变量对象的引用(指针)导入作用域链的最前端->执行代码
具体请看下面的代码
1 function compare(value1,value2){ 2 if(value1<value2){return 1;} 3 else if(value1>value2){return -1;} 4 else{return 0;} 5 } 6 var result=compare(5,10)//1
作用域链图解
我们可以看到,作用域链是什么:有序指针,前面说的作用域最前端在图中就是0位置。 查找变量或者函数是最前端开始的,0指向的活动对象没有你要找的变量,再去1找。0-1其实
从代码的角度上看其实就是从函数内部一直向函数外部找标识符(变量或者函数),找到立即停止,不会再向上查找。这就是作用域链!
5 闭包
定义: 闭包是有权访问另外一个函数作用域变量的函数;注意闭包是一个函数!
创建闭包的主要的方法:在一个函数里面创建一个函数(闭包); 比如
<script type="text/javascript"> function A(value){ var num=value; return function(){ //闭包 alert(num); } }
var B = A(0);
alert(B()); //0 </script>
在这里匿名函数是一个闭包,一般情况下: 一个函数执行完毕之后,无论是作用域链还是变量对象都会回收,但是这个匿名函数的作用域链里有A()变量对象的引用,所以没有消除。
还可以访问变量对象的num;这就是闭包的好处!但是闭包的出现加大内存负担,就这里而已,我们即使后面不再使用B函数了,A()变量对象也不会消失,javascript不知道你什么时候还会再用,当然我们可以这样 B=null; 这样的话匿名函数没有引用,被回收,A()变量对象也一起回收!
《javascript高级程序设计》中尼古拉斯大神建议我们:可以不使用闭包尽量不使用,万不得已才使用!
6 块级作用域
我们都知道javascript是没有块级作用域的,比如{}; if(i=0){} while(){} for(){}这些都不会形成块级作用域; 那么怎么创建 java c#类似功能的块级作用域?
语法:
(function(){
//块级作用域
})();
注意: 我们创建一个匿名函数,立即调用,里面的代码都要运行一遍,而在window中看不见,这不就是块级作用域吗? 还有一个好处,这个匿名函数没有指针,
调用后回收,不会产生垃圾(里面的方法,变量都不需要再访问的)。简直就是完美的块级作用域! 注意格式 (匿名函数)其实就是一个指针,再加上()就是调用了。
7 构造函数中的闭包
(1) 我们知道怎么为对象添加'私有变量' 这样
1 function Person(name,age){ 2 this.name=name;//共有属性 3 this.age=age; 4 5 var school="一中"; //私有属性 6 this.GetSchool=function (){return school;} 7 }
我们这个school是私有的变量,因为闭包的原因, 对象实例自然可以访问这个变量; 比如 var p=new Person('nos',20); p.GetSchool(); // 一中;
(2)现在来一个奇葩: 静态私有属性 ;
(function(){ var school=""; //静态私有; Person=function(name,age,value){ //构造函数 this.name=name;this.age=age; school=value; }; Person.prototype.GetSchool=function(){alert(school);} })(); var p=new Person('andy',21,'一中'); p.GetSchool();//一中 var pp=new Person('nos',29,'二中'); pp.GetSchool();//二中 p.GetSchool();//二中
从结果上看 school是对象共有的,私有的属性, 即静态私有属性;
我们看看构造函数是怎么定义的: 没有使用var ,前面说过即使在函数里面定义,没有使用var申明,就是window的,为全局的。自然可以在全局使用!
这个匿名函数中调用了一次,只产生一个对象变量,school都是同一个,实现共享。