学习闭包,需要了解的知识点:
1、对象、函数、及之间的关系。
2、原型、隐式原型、原型链。
3、作用域、作用域链、上下文环境、自由变量。
一、 一切(引用类型)都是对象,对象是属性的集合.
1. 值类型,4种:undefined, number, string, boolean .不是对象。
2. 剩下都是对象:函数、数组、对象、null、new Number(10)。
3. 对象是,若干属性的集合。
4.对象都是通过函数创建的。
// var obj = {a:10, b:20};
//var arr = {5,'s', true};
var obj = new Object();
obj.a = 10;
obj.b = 20;
var arr = new Array();
arr[0] = 5;
arr[1] = 's';
arr[2] = true;
console.log(typeof(Object)); //function
console.log(typeof(Array)); //function
二、原型prototype
1. 每个函数都有一个叫prototype的属性,它也是一个对象,并且有一个默认的constuctor的属性,这个属性指向函数本身。
2. 每个对象都有一个隐藏的属性“__proto__”,这个属性引用了创建这个对象的函数的prototype。即:fn.__proto__===Fn.prototype. "__proto__"称为“隐式原型”。
第一个例子:
这就可以解释为什么一个被new 出来的对象f, var f = new Fn();可以使用Fn.prototype.定义的属性和方法,如:Fn.prototype.name='aaa';Fn.prototype.getYear=function(){return 2000;};
另外一个例子:
var obj={};
console.log(obj.__proto__); 跟 console.log(Object.prototype); 打印出的内容是一样的。
obj这个对象的本质上是被Object函数创建的,因此obj.__proto__===Object.prototype。
注意理解两点:
a. Function创建了所有的函数,包括它自己;
b. 所有对象的隐式原型链都最终指向Object.prototype, 而Object.prototype.__proto__指向null.
3. instanceof 表示一种继承关系,或者原型链的结构。
Instanceof的判断队则是:沿着A的__proto__这条线来找,同时沿着B的prototype这条线来找,如果两条线能找到同一个引用,即同一个对象,那么就返回true。如果找到终点还未重合,则返回false。
function Foo(){};
var f1 = new Foo();
console.log(f1 instanceof Foo); //true
console.log(f1 instanceof Object); //true
console.log(Object instanceof Function);//true
console.log(Function instanceof Object);//true
console.log(Function instanceof Function);//true
三、原型链
访问一个对象的属性时,先在其基本属性中查找,如果没有,则沿着__proto__这个链条向上找,这就是原型链。
function Foo(){};
var f1 = new Foo();
f1.a=10;
Foo.prototype.a = 100;
Foo.prototype.b = 200;
console.log(f1.a); //10
console.log(f1.b); //200
访问f1.b时,由于f1的基本属性中是没有b的,于是沿着__proto__找到了Foo.prototype.b. Foo.prototype有 Foo.prototype.a , Foo.prototype.b 两个属性。
四、继承
所有的对象的原型链都会找到Object.prototype,因此所有的对象都会有Object.prototype的方法。这就是继承!
每个函数都有的call, apply方法 ,都有的length, arguments, caller 等属性,为什么每个函数都有,就是因为是继承的!
函数都是有Function函数创建的,因此继承了Function.prototype的方法。 可以用webstorm编辑器,查看Function.prototype后面的提示。
另,那么hasOwnProperty是哪里来的呢? 那是Funtion.prototype继承自Object.prototype的方法。
五、作用域和执行上下文
“执行上下文” 通俗的定义:在执行代码前,对代码的各种初始化动作。要把将要用到的所有变量事先拿出来,有的直接赋值,有的先用undefined占个位。
执行上下文,是调用函数时产生的,例如一个函数被循环调用了100次,那么在这个过程中会产生100个执行上下文环境。
它包括 两种 : 全局上下文 和 函数上下文。
在程序执行之前,会生成全局上下文环境,并在执行时,对全局上下文中的变量赋值。
全局上下文数据内容有:
1 | 普通变量声明 | 仅声明、不赋值。默认为: undefined |
2 | 函数变量(函数表达式)声明 | 仅声明、不赋值。如:function fn(){} |
3 | 函数声明 | 声明+赋值。 |
4 | this | 声明+赋值。 |
函数上下文环境数据内容,除了包含全局上下文数据内容外,还包括:
1 | 参数 | 声明、赋值 |
2 | arguments | 声明、赋值 |
3 | 自由变量的取值作用域 | 声明、赋值 |
“自由变量”:在A作用域中使用的变量x,却没有在A作用域中声明(即在其他作用域中声明的),对于A作用域来说,x就是一个自由变量。
特别强调!!自由变量的取值:
要到创建这个函数的那个作用域中取值——是“创建”,而不是“调用”,无论fn函数将在哪里调用!!切记切记——其实这就是所谓的“静态作用域”。
自由变量取值经典例子:
var max=10,
fn=function(x){
if(x>max){
console.log(x);
}
};
(function(f){
var max =100;
f(15); //15,max的取值是10,而不是100;
}
)
上面描述的只是跨一步作用域去寻找。
如果跨了一步,还没找到呢?——接着跨!——一直跨到全局作用域为止。要是在全局作用域中都没有找到,那就是真的没有了。
这个一步一步“跨”的路线,我们称之为——作用域链。
“作用域”:
1.“javascript没有块级作用域”。所谓“块”,就是大括号“{}”中间的语句。
比如:
var i =10;
if(i>1){
var name='aa';
}
console.log(name); //aa
for(var i=0;i<10;i++){
//...
}
console.log(i); //10
2. 除了全局作用域,只有函数才能创建作用域,每个函数都要创建一个作用域。作用域之间相互独立。
3. 创建一个函数就创建了一个作用域,无论你调用不调用,函数只要创建,它就有独立的作用域。是在函数创建时就产生的,同一个作用域下可能同时存在不同的执行上下文。
4. 作用域有上下级关系!!上下级关系的确定,是看函数是在哪个作用域下创建的。这个作用域就是函数作用域的上级(父级)。
5. 作用域最大的作用就是隔离变量,不同作用域下同名变量不会有冲突。Jquery是最好的例子。
6.作用域只是一个“地盘”,一个抽象的概念,其中没有变量。要通过作用域对应的执行上下文环境来获取变量的值。同一个作用域下,不同的调用会产生不同的执行上下文环境,继而产生不同的变量的值。所以,作用域中变量的值是在执行过程中产生的确定的,而作用域却是在函数创建时就确定了。
7.所以,如果要查找一个作用域下某个变量的值,就需要找到这个作用域对应的执行上下文环境,再在其中寻找变量的值。
两者关系:
例子:
var x=100;
//(全局作用域)
function fn(x){
//(fn作用域)
return function(){
console.log(x);
// (匿名function作用域)
}
}
var f1 = fn(5);
var f2 = fn(10);
f1(); //5
f2(); //10
1.除了全局作用域外,每个函数都要创建一个作用域。作用域之间的变量是相互独立的。因此,全局作用域中的x和fn作用域中的x,两者毫无关系,互不影响,和平相处。
2.在程序执行之前,会生成全局上下文环境,并在执行时,对全局上下文中的变量赋值。
3.程序执行到第11行,调用fn(5),会产生fn(5)的上下文环境,并压栈,并设置为活动状态。
4.执行完第11行,fn(5)的返回值赋值给了f1。此时执行上下文环境又重新回到全局,但是fn(5)的上下文环境不能就此销毁,因为其中有闭包的引用(可翻看前面文章,此处不再赘述)。
5.继续执行第12行,再次调用fn函数——fn(10)。产生fn(5)的上下文环境,并压栈,并设置为活动状态。但是此时fn(5)的上下文环境还在内存中——一个作用域下同时存在两个上下文环境。
六、闭包
只需要知道应用的两种情况即可——函数作为返回值,函数作为参数传递。
有些情况下,函数调用完成之后,其执行上下文环境不会接着被销毁。这就是需要理解闭包的核心内容。
使用闭包会增加内容开销!
看了上面的这些应用,再回到前面的一句话:在动态执行环境中,数据实时地发生变化,为了保持这些非持久型变量的值,我们用闭包这种载体来存储这些动态数据。这就 是闭包的作用。也就说遇到需要存储动态变化的数据或将被回收的数据时,我们可以通过外面再包裹一层函数形成闭包来解决。
当然,闭包会导致很多外部函数的调用对象不能释放,滥用闭包会使得内存泄露,所以在频繁生成闭包的情景下我们要估计下他带来的副作用。
七、可参考的资料
http://zhidao.baidu.com/link?url=IQfb4Ol4ClmqnMvwecZfoB8Tuwltf8P7tK2Ls4o88OrWT3uSwy2NJsh136sn1x7MoANbU1GWoD9tlXg95cxCRM1I6uOY-DoIjV2XppBO6VC
八、闭包应用实例
闭包应用场景
- 实现私有成员
- 保护命名空间
- 避免污染全局变量
- 变量需要长期驻留在内存
- 闭包和匿名函数不是互斥的关系
- 有自由变量的匿名函数是闭包
http://www.jb51.net/article/24156.htm