函数与闭包

函数创建

 创建函数有两种方式,第一种是函数声明。函数声明有一个很重要的特征就是函数声明提升(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. 再谈BOM和DOM(3):DOM节点操作-元素样式修改及DOM内容增删改查

    操作节点,先得选择节点,就得知道节点选择器与DOM节点查找 DOM节点选择器 W3C提供了比较方便的定位节点的方法和属性 getElementById() 一个参数:元素标签的ID getElemen ...

  2. A/B测试助力游戏业务增长

    更多技术交流.求职机会.试用福利,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 中国游戏行业发展现状及挑战 国内市场增长乏力 2021年游戏销售收入2965.13亿元,同比增长6.4% ...

  3. 从此告别写 SQL!DataLeap 帮你零门槛完成“数据探查”

    更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 在日常数据处理工作中,产品.运营.研发或数据分析师经常会面临数据量大且混乱.质量参差不齐的问题,需要花费大量时间和 ...

  4. Kubernetes(K8S) 拉取镜像 ImagePullBackOff pull access denied

    K8S 拉取阿里云镜像  第一次用时,没注意 授权,所以在 kubectl apply 后一直出现  ImagePullBackOff [root@k8smaster ~]# kubectl appl ...

  5. Safari 14.0 的功臣 Webp?

    俗话说:一图胜千言.在网上,图片虽然可以让用户更加简单明了地看到更多信息,但是图片体积也可以抵过上千字节甚至更多.研究表明,打开一个 HTTP 网页,其中图片平均占比为 64%.在图片占比如此高的情况 ...

  6. UVA - 12096 :The SetStack Computer

    题目大意 用集合模拟计算机操作.每执行完一个操作,输出栈顶的集合大小,操作如下: PUSH:空集合压栈 DUP:将栈顶元素再次压栈 UNION:依次弹栈得a,b,求并集后压栈 INTERSECT:依次 ...

  7. POJ3414 Pots( BFS搜索)

    题目: 给你两个容器,分别能装下A升水和B升水,并且可以进行以下操作 FILL(i) 将第i个容器从水龙头里装满(1 ≤ i ≤ 2); DROP(i) 将第i个容器抽干 POUR(i,j) 将第i个 ...

  8. Kotlin 协程真的比 Java 线程更高效吗?

    本文首发于 vivo互联网技术 微信公众号 链接:https://mp.weixin.qq.com/s/-OcCDI4L5GR8vVXSYhXJ7w作者:吴越 网上几乎全部介绍Kotlin的文章都会说 ...

  9. redis管道技术pipeline二——api

    package spring.redis; import org.springframework.beans.factory.InitializingBean; import org.springfr ...

  10. VueTreeselect

    https://www.vue-treeselect.cn/ 官网简介