3 函数

3.5 闭包(closures)

3.5.1 作用域链

与很多程序设计语言不同,javascript不存在大括号级的作用域,但它有函数作用域,即在函数内定义的变量在函数外是不可见的。但如果该变量是在某个代码块中定义的(如在某个if或for语句中),它在代码块外是可见的。

>>>var a=1;function f(){var b=1;return a;}
>>>f();
1
>>>b
b is not defined

变量a是属于全局域的,而变量b的作用域就在函数f()内了。所以

  • 在f()内,a和b都是可见的;
  • 在f()外,a是可见的,b则不可见。

示例:

var a=1;
function f(){
var b=1;
function n(){
var c=3;
}
}

如果我们在函数f()中定义了另一函数n(),那么,在n()中可以访问的变量既可以来自它自身的作用域,也可以来自其“父级”作用域。这就形成了一条作用域链(scope chain),该链的长度(或深度)去取决于我们需要。

3.5.2 词法作用域

在javascript中,每个函数都有一个自己的词法作用域。也就是说,每个函数在被定义时(而非执行时)都会创建一个属于自己的环境(即作用域)。

示例:

>>>function f1(){var a=1;f2();}
>>>function f2(){return a;}
>>>f1();
a is not defined

错误想法:由于局部变量a也在f1()中,所以f2()是可以访问a的。

原因:因为当f2()被定义时(不是执行时),变量a是不可见的。和f1()一样,它那时候只能访问自身作用域和全局作用域中的内容。也就是说,这里的f1()、f2()之间不存在共享的词法作用域。

>>>var a=5;
>>>f1();
5
>>>a=55;
>>>f1();
55
>>>delete a;
true
>>>f1();
a is not defined

我们可以在函数中对变量执行添加、移除和更新等操作,但函数只会看到该变量的最终状态。

如上,再增加一个全局变量a,f2()就可以访问它了,因为f2()知道访问全局环境的途径,它可以访问该环境的所有东西。另外要注意的是,即使f2()还没有被定义,我们也可以在f1()的定义中包含对f2()的调用。因为对于f1()而言,在其所知的作用域中的任何东西都是可用的。

可通过添加/删除操作来实现变量的重复添加,并且完全不会影响程序的运行。

在上例中可以删除f2函数,并重新给它定义一个完全不同的执行体,而最终f1()依然正常工作。因为在这里,f1()只需知道它应该如何访问自身作用域即可,不关心该作用域在什么时候发生了什么事。例如:

>>>delete f2;
true
>>>f1() f2 is not defined
>>>var f2=function(){return a*2;}
>>>var a=5
5
>>>f1();
10

3.5.3 利用闭包突破作用域链

前提: 闭包的概念

可访问空间 G F N
a T F F
b T T F
c T T T

全局作用域G包含各种变量(如a)和函数(如F),每个函数也都会拥有一块属于自己的私用空间,用以存储一些别的变量(如b)和函数(如N)。上图中,a和b之间是不连通的,因为b在F以外是不可见的。但可将c点和b点连通起来,或者说将N与b连通起来。当我们将N的空间扩展到F以外,并止步于全局空间以内时,就产生了闭包

N将会和a一样置身于全局空间,且由于函数还记得它在被定义时所设定的环境,因此它依然可以访问F空间并使用b。有趣的是,因为现在N和a同处于一个空间,但N可以访问b,而a不能。

N如何突破作用域链?只需将它们升级为全局变量(不使用var语句)或通过F传递(或返回)给全局空间即可。具体做法:

3.5.3.1 闭包#1

function f(){
var b="b";
return function(){
return b;
}
} >>>b
b is not defined

f()的返回值,可看成上图中的n,b对其可见。由于f()是一个全局函数,所以可以将其返回值赋值给另一个全局变量,从而生成一个可以访问f()私有空间的新全局函数。

>>>var n=f();
>>>n();
"b"

3.5.3.2 闭包#2

下例结果与闭包#1相同,实现方法上这里f()不再返回函数,而是直接在函数体内创建一个新的全局函数。

首先需要声明一个全局函数的占位符(最好声明,尽管这种占位符非必需),然后将函数f()定义如下:

var n;
function f(){
var b="b";
n=function(){
return b;
}
}
>>>f();
>>>n();
"b"

在f()中定义一个新函数,并且没有在这里使用var语句,因此它应该属于全局的,由于n()是在f()内部定义的,它可以访问f()的作用域,所以即使该函数后来升级成了全局函数,但它依然可以保留对f()作用域的访问权。

3.5.3.3 相关定义与闭包#3

如果一个函数需要在其父级函数返回之后留住对父级作用域的链接的话,就必须要为此建立一个闭包。(f是n的父级函数,在f返回之后,n依然可以访问f中的局部变量b)

而由于函数通常都会将自身的参数视为局部变量。因此创建返回函数时,也可以令其返回父级函数的参数。例如:

function f(arg){

var n=function(){

return arg;

};

arg++;

return n;

}

>>> var m=f(123);

>>>m();

124

注意:当返回函数被调用时(n被赋值时函数并没有被调用,调用是在n被求值,也就是执行return n;语句时被调用的),arg++已经执行过一次递增操作了,所以m()返回的是更新后的值。由此看出,函数所绑定的是作用域本身,而不是该作用域中的变量或变量当前所返回的值。

3.5.3.4 循环中的闭包

function f(){
var a=[];
var i;
for(i=0;i<3;i++){
a[i]=function(){
return i;
}
}
return a;
}
>>>var a=f();
>>>a[0]();
3
>>>a[1]();
3
>>>a[2]();
3

希望输出是1 2 3,由于创建了三个闭包,它们都指向了一个共同的局部变量i。但是闭包不会记录它们的值,它们所拥有的只是一个i的连接(即引用),因此只能返回i的当前值。由于循环结束时i的值为3,所以这三个函数都指向了这一共同值。

纠正:显然需要三个不同的变量,换一种闭包形式:

function f(){
var a=[];
var i;
for(i=0;i<3;i++){
a[i]=(function(x){
return function(){
return x;
}
})(i);
}
return a;
}

在此不再直接创建一个返回i的函数,而是将i传递给了一个自调函数。在该函数中,i就被赋值给了局部变量x,这样使每次迭代中的x拥有各自不同的值。

或者,可以定义一个“正常点”(不使用自调函数)的内部函数实现相同功能。关键:在每次迭代操作中,在中间函数内将i的值“本地化”。

function f(){
function makeClosure(x){
return function(){
return x
}
}
var a=[];
var i;
for(i=0;i<3;i++){
a[i]=makeClosure(x);
}
return a;
}

3.5.4 Getter与Setter

var getValue,setValue;
(function(){
var secret=0;
getValue=function(){
return secret;
}
setValue=function(v){
secret=v;
}
})();

使用两个函数来确保局部变量secret的不可直接访问性。

>>>getValue()
0
>>>setValue(123)
>>>getValue()
123

3.5.5 迭代器

有些复杂的数据结构,通常会有着与数组截然不同的序列规则,这需要将一些“谁是下一个”的复杂逻辑封装成易于使用的next()函数,然后只需简单调用next()就能实现对于相关的遍历操作。

下例接受数组输入的初始化函数,定义了一个私有指针,该指针始终指向数组中的下一个元素。

function step(x){
var i=0;
return function(){
return x[i++];
};
}
>>>var next=setup(['a','b','c']);
>>>next();
"a"
>>>next();
"b"
>>>next();
"c"

javascript面向对象编程笔记(函数之闭包)的更多相关文章

  1. javascript面向对象编程笔记(基本数据类型,数组,循环及条件表达式)

    javascript面向对象编程指南 最近在看这本书,以下是我的笔记,仅供参考. 第二章 基本数据类型.数组.循环及条件表达式 2.1 变量 区分大小写 2.3 基本数据类型 数字:包括浮点数与整数 ...

  2. javascript面向对象编程笔记

    对象:一切事物皆是对象.对象是一个整体,对外提供一些操作.比如说一个收音机是一个对象,我们不需要知道它的内部结构是什么,只需要会使用外部的按钮就可以使用收音机. 面向对象:面向对象语言的标志是他们都有 ...

  3. javascript面向对象编程笔记(函数)

    第三章 函数 3.1 什么是函数 一般来说,函数声明通常由以下几部分组成: function子句 函数名称 函数所需参数 函数体 return子句.如果某个函数没有显示的返回值,默认它的返回值为und ...

  4. 《JavaScript面向对象编程指南(第2版)》读书笔记(一)

    目录 一.对象 1.1 获取属性值的方式 1.2 获取动态生成的属性的值 二.数组 2.1 检测是否为数组 2.2 增加数组长度导致未赋值的位置为undefined 2.3 用闭包实现简易迭代器 三. ...

  5. 《JavaScript面向对象编程指南》读书笔记②

    概述 <JavaScript面向对象编程指南>读书笔记① 这里只记录一下我看JavaScript面向对象编程指南记录下的一些东西.那些简单的知识我没有记录,我只记录几个容易遗漏的或者精彩的 ...

  6. 闭包初体验 -《JavaScript面向对象编程指南》

    下面是我对闭包的理解:(把他们整理出来,整理的过程也是在梳理) 参考<JavaScript面向对象编程指南> 1.首先,在理解闭包之前: 我们首先应该清楚下作用域和作用域链 作用域:每个函 ...

  7. JavaScript面向对象编程学习笔记

    1  Javascript 面向对象编程 所谓"构造函数",其实就是一个普通函数,但是内部使用了this变量.对构造函数使用new运算符,就能生成实例,并且this变量会绑定在实例 ...

  8. 《JavaScript面向对象编程指南(第2版)》读书笔记(二)

    <JavaScript面向对象编程指南(第2版)>读书笔记(一) <JavaScript面向对象编程指南(第2版)>读书笔记(二) 目录 一.基本类型 1.1 字符串 1.2 ...

  9. 《JavaScript面向对象编程指南》读书笔记①

    概述 JavaScript快忘完了,想看一本专业书拾遗,所以看了这本<JavaScript面向对象编程指南>. 个人觉得这本书讲的很透彻很易懂,一些原来有疑惑的地方在这本书里面豁然开朗,看 ...

随机推荐

  1. 利用爬虫爬取指定用户的CSDN博客文章转为md格式,目的是完成博客迁移博文到Hexo等静态博客

    文章目录 功能 爬取的方式: 设置生成的md文件命名规则: 设置md文件的头部信息 是否显示csdn中的锚点"文章目录"字样,以及下面具体的锚点 默认false(因为csdn中是集 ...

  2. 5. Jmeter常用快捷键

    快捷键 功能 备注 Ctrl + C 复制 可复制组件 Ctrl + V 粘贴 可粘贴组件 Ctrl + Shift + C 复制粘贴当前组件到下一行   Ctrl + R 运行测试计划   Ctrl ...

  3. vbs 之 excel 使用VBScript 操作excel

    打开excel及新建工作薄 '' 2. Method ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 2.1 CreateO ...

  4. linux更新grub内核启动参数的方法

    #!/bin/bash set -x set -e export PS4=+{$LINENO:${FUNCNAME[0]}} trap 'echo "---NEWKERNARGS=$NEWK ...

  5. QT MSVC2017 ratio chrono

    如果引用了stdint.h可能会引发一些列错误,各种未申明和语法错误. 参加以下帖子解决问题 https://github.com/ftylitak/qzxing/issues/54 When com ...

  6. openssl编译方法

    受不了了,终于编译成功了openssl,写一下编译方法吧 准备: 0:要编译openssl,必不可少的是代码,去下载 https://www.openssl.org/source/ 1:要有一个VS系 ...

  7. zdump - 时区输出器

    SYNOPSIS 总览 zdump [ -v ] [ -c cutoffyear ] [ zonename ... ] 描述 Zdump 对命令行中的每一个 zonename 输出其当前时间. 提供了 ...

  8. linux 6 timezone修改

    linux 6 / Amazon linux 因为正好在使用Amazon 的linux AMI  又遇到了需要修改系统时区这个case 所以就调查了一下修改方法,因为Amazon的linux版本是由A ...

  9. js排他功能示例

    <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...

  10. vue 监听的使用

    watch:{    监听的属性:function(旧值,新值) {       } }   代码: <!DOCTYPE html> <html lang="en" ...