函数与闭包

函数创建

 创建函数有两种方式,第一种是函数声明。函数声明有一个很重要的特征就是函数声明提升(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. 想提高运维效率,那就把MySQL数据库部署到Kubernetes 集群中

    摘要:Kubernetes 很多看起来比较"繁琐"的设计的主要目的,都是希望为开发者提供更多的"可扩展性",给使用者带来更多的"稳定性"和& ...

  2. 下载安装Ipa Guard

    ​ 可以前往ipaguard工具官网下载,工具是免费下载,免费体验使用的.下载地址是https://www.ipaguard.com. 下载后解压工具便ok了,工具是绿色软件,无需其他安装流程.双击I ...

  3. 火山引擎DataLeap一站式数据治理解决方案及平台架构

    更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 在字节跳动内部,DataLeap数据平台数据治理团队致力于建立一站式.全链路的数据治理解决方案平台. 数据治理的概 ...

  4. 字节跳动开源数据集成引擎 BitSail 的演进历程与能力解析

    导读 BitSail 是字节跳动开源数据集成引擎,支持多种异构数据源间的数据同步,并提供离线.实时.全量.增量场景下全域数据集成解决方案,目前支撑了字节内部和火山引擎多个客户的数据集成需求.经过字节跳 ...

  5. 以 100GB SSB 性能测试为例,通过 ByteHouse 云数仓开启你的数据分析之路

    更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 I. 传统数仓的演进:云数仓 近年来,随着数据"爆炸式"的增长,越来越多的数据被产生.收集和存 ...

  6. Java 获取当前天是一年中的第几天

    Java 获取当前天是一年中的第几天 @Test void dayofweed() throws Exception { System.out.println("2023-01-01 第 & ...

  7. wkhtmltopdf 代替 itext 将 url 转成 pdf

    通过 itextpdf 的 HtmlConverter.convertToPdf(); 方法生成的pdf 有点让人失望,CSS啥的不起作用了,(有可能我用得不对) 后来用 wkhtmltopdf.ex ...

  8. 鸿蒙HarmonyOS实战-工具安装和Helloworld案例

    前言 HarmonyOS是华为自主开发的操作系统,它在2020年9月正式发布.它最初被称为鸿蒙OS,后来更名为HarmonyOS.HarmonyOS旨在提供一种可在各种设备上无缝运行的统一操作系统,包 ...

  9. gunicorn 高性能wsgi服务器

    参考: https://zhuanlan.zhihu.com/p/102716258 Gunicorn是什么 Gunicorn Green Unicorn 是一个 UNIX 下的 WSGI HTTP ...

  10. VMware Workstation centos7 虚拟机桥接方式联网,获取动态IP

    在VMware中安装 Centos7 虚拟机,日常需要带着电脑外出.因此在远程连接虚拟机时,就要求与宿主机在同一网段.在不修改路由器的情况下,让宿主与centos7都动态从路由器获取IP地址,就能解决 ...