神马是闭包
关于闭包的概念,是婆说婆有理。
闭包是指有权访问另外一个函数作用域中的变量的函数
这概念有点绕,拆分一下。从概念上说,闭包有两个特点:
1、函数
2、能访问另外一个函数作用域中的变量
在ES 6之前,Javascript只有函数作用域的概念,没有块级作用域(但catch捕获的异常 只能在catch块中访问)的概念(IIFE可以创建局部作用域)。每个函数作用域都是封闭的,即外部是访问不到函数作用域中的变量。
function getName() { var name = "美女的名字"; console.log(name); //"美女的名字" } function displayName() { console.log(name); //报错 }
但是为了得到美女的名字,不死心的单身汪把代码改成了这样:
function getName() { var name = "美女的名字"; function displayName() { console.log(name); } return displayName; } var 美女 = getName(); 美女() //"美女的名字"
这下,美女是一个闭包了,单身汪想怎么玩就怎么玩了。(但并不推荐单身汪用中文做变量名的写法,大家不要学)。
关于闭包呢,还想再说三点:
1、闭包可以访问当前函数以外的变量
function getOuter(){ var date = '815'; function getDate(str){ console.log(str + date); //访问外部的date } return getDate('今天是:'); //"今天是:815" } getOuter();
getDate是一个闭包,该函数执行时,会形成一个作用域A,A中并没有定义变量date,但它能在父一级作用域中找到该变量的定义。
2、即使外部函数已经返回,闭包仍能访问外部函数定义的变量
function getOuter(){ var date = '815'; function getDate(str){ console.log(str + date); //访问外部的date } return getDate; //外部函数返回 } var today = getOuter(); today('今天是:'); //"今天是:815" today('明天不是:'); //"明天不是:815"
3、闭包可以更新外部变量的值
function updateCount(){ var count = 0; function getCount(val){ count = val; console.log(count); } return getCount; //外部函数返回 } var count = updateCount(); count(815); //815 count(816); //816
作用域链
为毛闭包就能访问外部函数的变量呢?这就要说说Javascript中的作用域链了。
Javascript中有一个执行环境(execution context)的概念,它定义了变量或函数有权访问的其它数据,决定了他们各自的行为。每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。你可以把它当做Javascript的一个普通对象,但是你只能修改它的属性,却不能引用它。
变量对象也是有父作用域的。当访问一个变量时,解释器会首先在当前作用域查找标示符,如果没有找到,就去父作用域找,直到找到该变量的标示符或者不再存在父作用域了,这就是作用域链。
作用域链和原型继承有点类似,但又有点小区别:如果去查找一个普通对象的属性时,在当前对象和其原型中都找不到时,会返回undefined;但查找的属性在作用域链中不存在的话就会抛出ReferenceError。
作用域链的顶端是全局对象。对于全局环境中的代码,作用域链只包含一个元素:全局对象。所以,在全局环境中定义变量的时候,它们就会被定义到全局对象中。当函数被调用的时候,作用域链就会包含多个作用域对象。
全局环境
关于作用域链讲得略多(红皮书上有关于作用域及执行环境的详细解释),看一个简单地例子:
// my_script.js "use strict"; var foo = 1; var bar = 2;
在全局环境中,创建了两个简单地变量。如前面所说,此时变量对象是全局对象。
Non-nested functions
改动一下代码,创建一个没有函数嵌套的函数:
"use strict"; var foo = 1; var bar = 2; function myFunc() { //-- define local-to-function variables var a = 1; var b = 2; var foo = 3; console.log("inside myFunc"); } console.log("outside"); //-- and then, call it: myFunc();
当myFunc被定义的时候,myFunc的标识符(identifier)就被加到了当前的作用域对象中(在这里就是全局对象),并且这个标识符所引用的是一个函数对象(function object)。函数对象中所包含的是函数的源代码以及其他的属性。其中一个我们所关心的属性就是内部属性[[scope]]。[[scope]]所指向的就是当前的作用域对象。也就是指的就是函数的标识符被创建的时候,我们所能够直接访问的那个作用域对象(在这里就是全局对象)。
比较重要的一点是:myFunc所引用的函数对象,其本身不仅仅含有函数的代码,并且还含有指向其被创建的时候的作用域对象。