函数与闭包

函数创建

 创建函数有两种方式,第一种是函数声明。函数声明有一个很重要的特征就是函数声明提升(function declaration hoisting),意思是在执行代脚本前会先读取所有的函数声明。这意味着可以把函数声明放在调用它的语句后面:

sayHi();
function sayHi() {console.log('Hi');}

 第二种方式是函数表达式:

var functionName = function(arg0, arg1, arg2) { /*function body*/ }

既然是叫表达式,那么自然也是要先赋值才能使用,否则会抛出错误:

sayHi(); //throw error!
var sayHi = function {console.log('Hi');}

关于递归

 书上讲的关于使用递归时会犯的一个错就是将递归函数的引用重新赋值:

function factorial(num) {
if(num <= 1) return 1;
return num * factorial(num - 1);
}
var anotherFactorial = factorial; //创建新的变量也指向递归函数
factorial = null;
console.log(anotherFactorial(4)); //throw error

 要解决这种窘境,书上给的一种解决方法是使用arguments.callee(arguments.callee是一个指向正在执行的函数的指针),但这个内置参数在严格模式下是不允许的,不建议使用。 比较推荐的是使用命名函数表达式:

var factorial = (function f(num){
if(num == 1) return 1;
return num * f(num - 1);
});

 这样就不必担心函数的引用被重写而导致上述问题了。但这种方式就使得递归函数失去了之前函数声明提升的特征。

 还有另一种思路是 在创建函数表达式时使用const关键字:const factorial = function(num){...};。 如果有IDE或其他工具做检测的话,可以将问题在代码阶段就暴露出来。


闭包

 闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数,仍以前面的createComparisionFunction()为例子:

书上给的这个闭包的概念算是最表层的描述了,但闭包的底层原理要复杂的多。

MDN上关于闭包的一段解释:

A closure is a special kind of object that combines two things: a function, and the environment in which that function was created. The environment consists of any local variables that were in-scope at the time that the closure was created.

 现在放上书里的例子:

function createComparisionFunction(propertyName) {
return function(object1, object2) {
var value1 = object1[propertyName];
var value2 = object2[propertyName]; if(value1 < value2) {
return -1;
} else if(value1 > value2) {
return 1;
} else {
return 0;
}
};
}

  红宝书在讲述闭包原理的前面几大段里抛出了好多个概念:执行环境、作用域链、活动对象、变量对象。如果还不是很能明白的话可以对照书上图7-1这样理解:函数的执行环境就是个包含着作用域链的东西;变量对象差不多等同于活动对象,就是个用来保存 执行函数时的 局部变量的。

另外,还需记住书上说的一点:作用域链本质上是一个指向活动对象(变量对象 )的指针链表,它只引用但不包含变量对象。

 在执行createComparisonFunction函数的时候,会创建一个活动对象来保存用到的局部变量,然后让createComparisionFunction的执行环境 的 作用域链指向这个活动对象;执行过程中,又创建了匿名函数;学作用域链时我们便知道,函数搜索变量是在作用域链中一个接一个地向上搜索直至找到对应变量名或到达全局作用域中,其本质就是沿着作用域链中指向的活动对象进行遍历。 创建了匿名函数后,会预先初始化它的作用域链并在上面添加外部的createComparisionFunction函数的活动对象; 如下:

(此时是没有匿名函数的执行环境和活动对象的,只有在执行函数时才会创建)

 一般来说,当函数执行完毕后,其执行环境和作用链都会被销毁,而活动对象 也会被回收。但是,在上述创建了匿名函数的情况下,因为匿名函数的作用域链仍在引用着createComparisonFunction的活动对象,所以这个活动对象不会被垃圾收集器回收。(关于js垃圾回收)



在匿名函数执行时的情况就是下面这样的:



 匿名函数还引用着创建它的外部函数的活动对象,因而可以访问到里面的变量。以上这种内部函数附带着外部函数“环境”的机制,就叫做闭包。



如果仍然感觉不明白,可以看下这个知乎话题下的回答:知乎:什么是闭包?

《Javascript高级程序设计》读书笔记——函数与闭包的更多相关文章

  1. javascript高级程序设计读书笔记----函数表达式

    定义函数两种方式: 1.函数声明 function sayHi(){ alert("Hi"); } sayHi();//调用函数 2.函数表达式 var sayHi = funct ...

  2. JavaScript高级程序设计-读书笔记(2)

    第6章 面向对象的程序设计 创建对象 1.最简单方式创建Object的实例,如 var person = new Object(); person.name = “Greg”; person.age ...

  3. javascript高级程序设计读书笔记-事件(一)

    读书笔记,写的很乱   事件处理程序   事件处理程序分为三种: 1.html事件2. DOM0级,3,DOM2级别  没有DOM1 同样的事件 DOM0会顶掉html事件   因为他们都是属性  而 ...

  4. javascript高级程序设计读书笔记

    第2章  在html中使用javascript 一般都会把js引用文件放在</body>前面,而不是放在<head>里, 目的是最后读取js文件以提高网页载入速度. 引用js文 ...

  5. JavaScript高级程序设计-读书笔记(7)

    第22章 高级技巧 1.高级函数 (1)安全的类型检测 在任何值上调用Object原生的toString()方法,都会返回一个[object NativeConstructorName]格式的字符串. ...

  6. JavaScript高级程序设计学习笔记--函数表达式

    关于函数声明,它的一个重要特征就是函数声明提升,意思是在执行代码之间会读取函数声明,意思是在执行代码之前会先读取函数声明.这就意味着可以把函数声明放在调用它的语句 后面. sayHi(); funct ...

  7. Javascript高级程序设计读书笔记(第六章)

    第6章  面向对象的程序设计 6.2 创建对象 创建某个类的实例,必须使用new操作符调用构造函数会经历以下四个步骤: 创建一个新对象: 将构造函数的作用域赋给新对象: 执行构造函数中的代码: 返回新 ...

  8. JavaScript高级程序设计 读书笔记

    第一章 JavaScript 简介 第二章 Html中使用JavaScript 第三章 基本概念 第四章 变量,作用域,内存 第五章 引用类型 第六章 面向对象 第七章 函数表达式 第八章 BOM 第 ...

  9. JavaScript高级程序设计-读书笔记(6)

    第20章 JSON JSON是一个轻量级的数据格式,可以简化表示复杂数据结构的工作量 JSON的语法可以表示一下三种类型的值 l        简单值:使用与JavaScript相同的语法,可以在JS ...

  10. JavaScript高级程序设计-读书笔记(5)

    第13章 事件 1.事件流 事件流描述的是从页面中接收事件的顺序.IE的事件流是事件冒泡流,而Netscape Communicator的事件流是事件捕获流. (1)事件冒泡,即事件开始时由最具体的元 ...

随机推荐

  1. 全网呕血整理:关于YOLO v3原理分析

    摘要:YOLO系列的目标检测算法可以说是目标检测史上的宏篇巨作,接下来我们来详细介绍一下YOLO v3算法内容. 算法基本思想 首先通过特征提取网络对输入特征提取特征,得到特定大小的特征图输出.输入图 ...

  2. Git hooks与自动化部署

    好的 commit message 是至关重要的,如果随意编写 log,带来的后果可小可大,但是无论大小都影响了开发的效率和回朔的难度,所以有必要进行 log 规范化检查. 通过自定义的commit ...

  3. hadoop清空回收站命令

    直接删除目录(不放入回收站) hdfs dfs -rm -skipTrash /tmp/aaa 清空回收站 hdfs dfs -expunge 执行完命令后,回收站的数据会在一分钟后清除.

  4. drf-Response drf-request.data 序列化类的使用 反序列化新增、修改、删除数据

    目录 APIView基本使用 使用原生Django写接口(View + JsonResponse) 使用drf写接口(APIView + drf Response) drf 两种导入View的方式 d ...

  5. ORM之Sequelize

    一.环境: Vue.Quasar.Electron.Postgres.Sequelize.sequelize-auto 二.安装 (1)添加Sequelize(全局安装) $npm install - ...

  6. AliSSR 语音超分算法:让在线会议语音更明亮更自然

    超分让在线会议语音更明亮,在线会议已成为日常工作中较为普遍的沟通交流方式,接入会议的方式也呈现多样化,比如电脑入会.手机入会又或是电话入会. 雪雅.曜辰|作者 众所周知,高采样率且高带宽的音频信号富含 ...

  7. Serverless 时代开启,云计算进入业务创新主战场

    作者 | 于洪涛 "我们希望让用户做得更少而收获更多,通过 Serverless 化,让企业使用云服务像用电一样简单." Serverless 化正在成为全新的软件研发范式,阿里云 ...

  8. vue axiox网络请求

    一.首先安装axios ,vue-axios 前提:搭建一个vue3的项目 项目搭建参考:https://www.cnblogs.com/yclh/p/15356171.html 使用npm安装axi ...

  9. 启动vue项目失败,报错Failed at the node-sass@4.14.1 postinstall script.

    https://www.cnblogs.com/xiaodangshan/p/13061618.html

  10. python常见面试题讲解(九)字符个数统计

    题目描述 编写一个函数,计算字符串中含有的不同字符的个数.字符在ACSII码范围内(0~127),换行表示结束符,不算在字符里.不在范围内的不作统计.注意是不同的字符 输入描述: 输入N个字符,字符在 ...