你不知道的javascript--上卷--读书笔记2

  • 闭包是什么?

  答:当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。通俗地来说:函数可以嵌套在其他函数中定义,这样它们就可以访问它们被定义时所处的作用域中的任何变量,这就是JavaScript的闭包。

  • 闭包有哪些应用?

  答:函数作为返回值:

function foo() {
var a = 2;
function bar() {   //bar拥有涵盖foo作用域的闭包,并对它保持了引用
console.log( a );        
}
return bar;
}
var baz = foo();
baz(); // 2

  函数作为参数进行传递:

function foo() {    var a = 2;    function baz() {     //baz拥有涵盖foo作用域的闭包,并对它保持了引用        console.log( a ); // 2    }    bar( baz );}function bar(fn) {    fn();}foo();

本质上无论何时何地,如果将函数(访问它们各自的词法作用域)当作第一级的值类型并到处传递,你就会看到闭包在这些函数中的应用。在定时器、事件监听器、 Ajax 请求、跨窗口通信、Web Workers 或者任何其他的异步(或者同步)任务中,只要使用了回调函数,实际上就是在使用闭包!

  • 闭包有哪些作用?

  答:闭包可以访问函数内部的变量;可以让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在;可以封装对象的私有方法和私有属性,实现模块化。

  循环和闭包

for (var i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );
}, i*1000 );
}

结果:输出5个6;

上述代码中,我们的预期是分别输出数字 1~5,每秒一次,每次一个,但实质上却输出了5个6;为什么会这样?

6是循环结束时 i 的最终值,这个不难理解,但为什么会是5个6呢?根据作用域的工作原理,首先声明一个i变量,然后进行迭代,每次迭代对i进行LHS操作,接着定义一个延迟1S输出函数,对i进行RHS操作,看起来好像没什么问题,实质上呢?各个迭代的函数共享同一个i的引用,在执行过程中,循环快还是延迟输出快?由结果不难推知,循环快。由此,私以为执行过程可以与下述代码等效:

var i= 6;
setTimeout( function timer() {
console.log( i );   //6
}, i*1000 );
setTimeout( function timer() {
console.log( i );    //6
}, i*1000 );
setTimeout( function timer() {
console.log( i );   //6
}, i*1000 );
setTimeout( function timer() {
console.log( i );    //6
}, i*1000 );
setTimeout( function timer() {
console.log( i );    //6
}, i*1000 );

如此看来问题就很简单了,在循环的过程中每个迭代我们都需要一个闭包作用域。

在这之前,我们需要了解一些概念:

函数声明: function aaa(){}

函数声明虽然可以实现函数作用域的创建,但由此也带来了一个问题,就是全局变量的污染(aaa 被绑定在所在作用域中)和必须显示的调用这个函数才能执行其中的代码。那么怎么才能同时解决这两种问题呢?

立即调用函数表达式: (function aaa(){})() ;   或者  (function aaa (){}());

由于函数被包含在一对 ( ) 括号内部,因此成为了一个表达式,通过在末尾加上另外一个 ( ) 可以立即执行这个函数,

区分函数声明和表达式最简单的方法是看 function 关键字出现在声明中的位 置(不仅仅是一行代码,而是整个声明中的位置)。如果 function 是声明中 的第一个词,那么就是一个函数声明,否则就是一个函数表达式。

ok,言归正传,上面说到,在循环的过程中每个迭代我们都需要一个闭包作用域。而立即调用函数表达式会通过声明并立即执行一个函数来创建作用域。所以我们可以将上述循环改写成这样:

for (var i=1; i<=5; i++) {
  (function() {
    setTimeout( function timer() {
    console.log( i );
  }, i*1000 );
})();
}

这样可以吗?不可以,虽然每次迭代我们都创建了一个新的作用域,但这个作用域是空的,没有任何变量来存储迭代中i的值。所以我们还需要声明一个变量:

for (var i=1; i<=5; i++) {
(function() {
var j = i;
setTimeout( function timer() {
console.log( j );
}, j*1000 );
})();
}或者for (var i=1; i<=5; i++) {   (function(j) {     setTimeout( function timer() {     console.log( j ); }, j*1000 );   })( i ); }

还有其它方法吗?有,在块作用域那里说过ES6中的:let;let可以将声明的变量绑定到所在的任意的作用域内,也可以说将一个块转换成一个可以被关闭的作用域。比如这样:

for (var i=1; i<=5; i++) {
let j = i; // 是的,闭包的块作用域!
setTimeout( function timer() {
console.log( j );
}, j*1000 );
}
或者
for (let i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );
}, i*1000 );
}
请记住:for循环中let声明,会在每一次迭代中都声明变量,且每次迭代都会使用上一次迭代的结束值来初始化变量。
  • 模块:

  模块的一般形式:创建对象的私有属性和私有方法,然后通过闭包创建一个能够访问对象私有属性和方法的特权方法,最后返回这个函数或者把它保存到能够访问到的地方。

  模块模式具有两个必要条件:

  1. 必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)。

  2. 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并 且可以访问或者修改私有的状态。

  

var foo = (function CoolModule() {
  var something = "cool";
  var another = [1, 2, 3];
  function doSomething() {
    console.log( something );
  }
  function doAnother() {
    console.log( another.join( " ! " ) );
  }
  return {
    doSomething: doSomething,
    doAnother: doAnother
  };
})();
foo.doSomething(); // cool
foo.doAnother(); // 1 ! 2 ! 3
时间: 2024-07-31 14:33:09

你不知道的javascript--上卷--读书笔记2的相关文章

你不知道的javascript 上卷 读书笔记

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> </body> <script type="text/javascript"> { let j; for(j=0;j<10;j

《你不知道的JavaScript》读书笔记(二)词法作用域

JavaScript 采用的是 词法作用域 的工作模型. 定义 词法化:大部分标准语言编译器的第一个工作阶段叫词法化(单词化),这个过程会对源代码中的字符进行检查,如果是有状态的解析过程,还会赋予单词意义. 词法作用域:定义在 词法阶段 的作用域. 词法作用域由谁决定:由你在写代码时将 变量 和 块作用域 写在哪里来决定.因此大部分情况下,词法分析器处理代码时会保持作用于不变. [例] function foo(a){ var b = a * 2; function bar(c){ consol

《你不知道的JavaScript》读书笔记(一)作用域

名词 引擎:从头到尾负责整个 JavaScript 程序的 编译 及 执行 过程. 编译器:负责 语法分析 及 代码生成. 作用域:负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限. LHS:赋值操作的左侧(理解为赋值操作的目标是谁,LHS 查询 试图找到变量的容器本身,并对其 赋值 ). RHS:赋值操作的右侧(理解为谁是赋值操作的源头,RHS 查询 就是 查找 某个变量的值). JavaScript 的编译 Java

《编写可维护的javascript》读书笔记(中)——编程实践

上篇读书笔记系列之:<编写可维护的javascript>读书笔记(上) 上篇说的是编程风格,记录的都是最重要的点,不讲废话,写的比较简洁,而本篇将加入一些实例,因为那样比较容易说明问题. 二.编程实践 1.UI松耦合 第一.将css从javascript中抽离(要改变dom样式数据,应该去操作dom的class名而非dom的style属性,后续要修改此样式只需到对应的css文件中修改而不用修改js文件): 第二.将javascript从HTML中抽离,比如下面的写法是不好的 <!-- 不

你不知道的javascript--上卷--读书笔记1

作用域是什么? 答:在<你不知道的javascript>书中提到,作用域就是根据名称查找变量的一套规则.古语有"无规矩不成方圆",但是没有方圆,规矩又给谁用?所以个人理解作用域就是"规矩"+"方圆".作用域是在创建的时候就确定的. 谁有作用域? 答:全局,函数. 为什么要有作用域? 答:前面已经说了,作用域是"规矩"+"方圆".作用域的最大用处就是隔离变量,不同作用域下同名变量不会有冲突.举个例

《编写可维护的javascript》读书笔记(上)

最近在读<编写可维护的javascript>这本书,为了加深记忆,简单做个笔记,同时也让没有读过的同学有一个大概的了解. 一.编程风格 程序是写给人读的,所以一个团队的编程风格要保持一致. 1.缩进:一种是利用制表符缩进,一种是使用空格符缩进,各有利弊,任选一种,保持一致即可.个人比较喜欢制表符缩进. 2.语句结尾需要加上分号,避免没必要的BUG. 3.命名:首先要语义化,使用驼峰式命名法,小驼峰即首字母小写,之后每个单词首字母大写:大驼峰即首字母大写,之后同小驼峰:变量名前缀应该是名词(my

JavaScript设计模式:读书笔记(未完)

该篇随我读书的进度持续更新阅读书目:<JavaScript设计模式> 2016/3/30 2016/3/31 2016/3/30: 模式是一种可复用的解决方案,可用于解决软件设计中遇到的常见问题./将解决问题的方法制作成模板,并且这些模板可应用于多种不同的情况.有效模式的附加要求:适合性,实用性,适用性. 模式的优点: 防止局部问题引起大问题,模式让我们的代码更有组织性 模式通常是通用的解决方式,不管我们开发哪种应用程序,都可以用模式优化我们代码的结构 模式确实可以让我们避免代码复用,使代码更

《高性能 JavaScript》读书笔记(一)

一. 加载和执行——优化JavaScript规则: 1. 将脚本放在底部:2. 减少页面中外链脚本文件的数量: 比如,下载单个100kb的文件将比下载4个25kb的文件更快.这个可以通过离线打包工具或者类似Yahoo!combo handler的实时在线服务来实现:3. HTML4为<script>标签定义了一个扩展属性:defer:  指明本元素所含的脚本不会修改DOM,因此代码能安全的延迟执行,例如: <script type="text/javascript"

《高性能Javascript》读书笔记-2

第二章 数据存取 字面量: 代表自身 不存特定位置   :字符串 数字 bool 对象 array 函数  正则表达 本地变量: var 数组: 存储在js数组对象内部,以数字做索引 对象成员 存储在js对象内部  以字符串做索引 JavaScript中有四种基本的数据存取位置: 直接量,变量,数组元素(以数字作为索引),对象成员(以字符床作为索引) 每一个js函数都表示一个对象, 是fun对象的一个实列 函数每一次执行对应的执行环境都是独一无二的,所以多次调用同一个函数就会导致创建多个执行环境

《高性能Javascript》读书笔记-1

第一章 加载和执行 当浏览器执行JavaScript代码时,不能同时做其他任何事情(单一进程),意味着<script>标签每次出现都霸道地让页面等带脚本的解析和执行(每个文件必须等到前一个文件下载并执行完成才会开始下载),所以头部的JS和CSS用来渲染页面,交互行为(几乎所有)的JS放在<body>底部: 在</body>关闭之前 将所以script标签放到页面底部,能确保脚本执行前页面完成渲染 减少外链脚本数量将会改善性能(合并JS) 任何网站都可以使用一个把制定文件