1、函数闭包

1.1 概述

  • JavaScript采用词法作用域,函数的执行依赖于变量作用域,这个作用域是在函数定义时决定的,而不是函数调用时决定的。
  • 为了实现这种词法作用域,JavaScript函数对象的内部状态不仅包含函数的代码逻辑,还必须引用当前的作用域链。
  • 函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性被称为“闭包”
  • 从技术的角度讲,所有的JavaScript函数都是闭包:它们都是对象,它们都关联到作用域链

1.2 嵌套函数

先看下面的代码:

var scope = "globle scope";
function checkscope() {
var scope = "local scope";
function f() {
return scope;
}
return f();
}
console.log(checkscope());
var scope = "globle scope";
function checkscope() {
var scope = "local scope";
function f() {
return scope;
}
return f;
}
var ff = checkscope();
console.log(ff());

它们可以捕捉到局部变量(和参数),并一直保存下来,看起来像这些变量绑定到了在其中定义它们的外部函数。

1.3 闭包原理

  • 在JavaScript中,如果一个对象不再被引用,那么这个对象就会被垃圾回收机制回收
  • 每次调用JavaScript函数的时候,都会为之创建一个新的对象(活动对象Activation Object)用来保存局部变量,把这个对象添加至作用域链中
  • 当函数返回的时候,就从作用域链中将这个绑定变量的对象删除。 如果不存在嵌套的函数,也没有其他引用指向这个绑定对象,它就会被当做垃圾回收掉。 如果定义了嵌套的函数,每个嵌套的函数都各自对应一个作用域链,并且这个作用域链指向一个变量绑定对象。
  • 如果这个函数定义了嵌套的函数,并将它作为返回值返回或者存储在某处的属性里,这时就会有一个外部引用指向这个嵌套的函数。它就不会被当做垃圾回收,并且它所指向的变量绑定对象也不会被当做垃圾回收

1.3.1 实例1

为每个li标签添加点击事件,并输出下标和文本内容

<body>
<ul id="test">
<li>123</li>
<li>456</li>
<li>789</li>
<li>000</li>
</ul>
</body>
<script>
let items = document.querySelectorAll("ul#test li")
console.log(items);
function bindclick(nodes) {
for (var i = 0; i < nodes.length; i++) {
nodes[i].onclick = function () {
console.log(i, nodes[i].innerHTML);
}
}
}
bindclick(items)
</script>

这里li标签内容显示出来后,点击li标签,报错,如:

  • 原因: 作用域不同,所以获取不到li标签的innerHTML。

解决方案一: 将for循环里的var改成let,如下:

<script>
let items = document.querySelectorAll("ul#test li")
console.log(items);
function bindclick(nodes) {
for (let i = 0; i < nodes.length; i++) {
nodes[i].onclick = function () {
console.log(i, nodes[i].innerHTML);
}
}
}
bindclick(items)
</script>

现在就可以正常运行了,如:

  • 解释: let声明的变量会识别块作用域,块作用域中声明的函数就形成了闭包。 每次循环都会创建一个新的函数,则这些函数每一个都会形成闭包。 因此,每次调用特定函数时所访问的i,就是其闭包中引用了外部函数作用域链上的i。所以建议在js的for循环中,使用let进行变量的声明。

解决方案二: 使用立即执行函数。如:

<script>
let items = document.querySelectorAll("ul#test li")
console.log(items);
function bindclick(nodes) {
for (var i = 0; i < nodes.length; i++) {
nodes[i].onclick = (function (index) {
return function () {
console.log(index, nodes[index].innerHTML);
}
})(i)
}
}
bindclick(items)
</script>

运行结果:

  • 解释: 这里的立即执行函数,充当局部作用域,在console的外层形成闭包,每执行一次,都会产生一个AO活动对象,都会有一个index被放在AO对象里并被保留下来,返回的函数赋给onclick。

1.3.2 实例2

function fx(a, b) {
if (fx.count) fx.count++;
else fx.count = 1;
return a + b;
}
fx(1, 2);

fx(2, 3);

fx(3, 4);

fx(4, 5);

console.log(fx.count);

运行结果:4 直接运行到没有错,但这里的fx.count属性是可以被直接赋值的,会让源代码失去意义。改写后的代码:

function genfx() {
let count = 0
return function (a, b) {
count++
console.log(`第${count}次调用函数`)
return a + b
}
}
let fx = genfx()
fx(1, 2)
fx(2, 3)
fx(3, 4)
fx(4, 5)

运行结果: 第1次调用函数 第2次调用函数 第3次调用函数 第4次调用函数

1.4 闭包缺陷

闭包的缺点就是常驻内存会增大内存使用量,并且使用不当很容易造成内存泄露。

如果不是因为某些特殊任务而需要闭包,在没有必要的情况下,在其它函数中创建函数是不明智的,因为闭包对脚本性能具有负面影响,包括处理速度和内存消耗。

2、函数式编程

2.1 高阶函数

在数学和计算机科学中,高阶函数是至少满足下列一个条件的函数: (1)接受一个或多个函数作为输入; (2)输出一个函数;

2.2 函数链式调用

在设计函数(作为对象方法)返回值时,将处理后的当前对象(上下文)返回,以便进行紧凑的方法调用。

举个例子:

let str = "abc,123"
let arr = str.split(",").reverse().join("-");
console.log(arr);

这里表示先将str字符串按逗号分割成数组,然后翻转数组,加入字符-后返回新的字符串。

输出结果: 123-abc

2.3 偏函数

固定一个函数的一个或者多个参数,返回一个新的函数,这个函数用于接受剩余的参数

2.3.1 实例1

let sum = function (x, y) {
return x + y;
};
let succ = sum.bind(null, 1);
console.log(succ(2));

这里的succ被称为偏函数。

2.3.2 优点

  • 通过创建一个名称易懂的独立函数,调用是无需每次传入第一个参数,因为第一个参数通过bind提供了固定值
  • 当有一个很通用的函数,为了方便提供一个较常用的变体。例如:有函数send(from, to, text),可以创建一个针对当前用户的send函数的变体:sendTo(to, text)

2.3.3 缺陷

  • 如果想固定一些参数,但不绑定this。 bind不允许这样做,因为必须指定第一个参数(要么指定对象,要么null),不能忽略上下文并跳转到参数。

2.3.4 实例2

实现一个自定义偏函数生成器

function partial(func, ...argsBound) {
return function (...args) {
return func.call(this, ...argsBound, ...args);
};
}

2.3.5 实例3

自定义偏函数

function partial(func, ...argsBound) {
return function (...args) {
return func.call(this, ...argsBound, ...args);
};
}
let jack = {

firstName: "Jack",

say(time, phrase) {

console.log([${time}] ${this.firstName}: ${phrase});

},

};

let tom = {

firstName: "Tom",

};

jack.sayNow = partial(

jack.say,

new Date().getHours() + ":" + new Date().getMinutes()

);

tom.sayNow = jack.sayNow;

jack.sayNow("Hello");

tom.sayNow("World");

输出结果: [17:32] Jack: Hello [17:32] Tom: World

2.4 函数柯里化

转换一个调用函数f(a,b,c)为f(a)(b)(c)方式调用。

2.4.1 实例1

针对两个参数的函数柯里化

function curry(func) {
return function (a) {
return function (b) {
return func(a, b)
}
}
} function sum(a, b) {

return a + b

}
let currysum = curry(sum)

console.log(currysum(1)(2));

输出结果:3

2.4.2 实例2

实现高级柯里化函数,针对n个参数的函数

function curry(func) {
return function curried(...args) {
if (args.length >= func.length) {
return func.apply(this, args);
} else {
return function pass(...args2) {
return curried.apply(this, args.concat(args2));
};
}
};
}

注意: (1)如果传递args数与原函数已经定义的参数个数一样或更长,那么直接调用func函数。 (2)获得偏函数:否则,不调用func函数,返回另一个包装器pass,提供连接之前的参数一起做为新参数重新应用curried。然后再次执行一个新调用,返回一个新偏函数(如果参数不够)或最终结果。

2.4.3 实例3

function curry(func) {
return function (a) {
return function (b) {
return function (c) {
return func(a, b, c)
}
    }
}

}

function sum(a, b, c) {

return a + b + c

}

let curriedsum = curry(sum)

console.log(curriedsum(1)(2)(3)); // 6

2.4.4 实例4

// 实现高级柯里化函数,针对n个参数的函数
function curry(func) {
return function curried(...args) {
if (args.length >= func.length) {
return func.apply(this, args)
} else {
return function pass(...args2) {
return curried.apply(this, args.concat(args2))
}
}
}
}
//有日志函数log(date, importance, message),用于格式化输出日志信息。

function log(date, importance, message) {

console.log(

[${date.getHours()}:${date.getMinutes()}] [${importance}] ${message}

);

}

log = curry(log);

let todayLog = log(new Date());

todayLog("INFO", "users has been initialized");

let todayDebug = todayLog("DEBUG");

todayDebug("fixed login error");

运行结果:
[17:38] [INFO] users has been initialized [17:38] [DEBUG] fixed login error

JS 学习笔记 (六) 函数式编程的更多相关文章

  1. python学习笔记(六) 函数式编程

    一 函数对象 函数同样可以作为对象复制给一个变量,如下: f = abs; print(f(-10)) f = 'abs'; print(f) def add(a,b,f): return f(a) ...

  2. Clojure学习笔记(二)——函数式编程

    定义 “函数式编程”是一种编程范式(programming paradigm),即如何编写程序的方法论.主要思想是把运算过程尽量写成一系列嵌套的函数调用. 举例来说,现在有这样一个数学表达式: (1 ...

  3. python学习笔记011——函数式编程

    1 函数式编程 面向对象 ,面向过程 ,函数式编程 侧重函数的作用,注重函数结果的传递 函数可以被赋值,也可以接受其他的值 2 函数式编程特点 1.函数是一等公民 与其他变量一样,可以赋值和被赋值,可 ...

  4. Python学习笔记6 函数式编程_20170619

    廖雪峰python3学习笔记: # 高阶函数 将函数作为参数传入,这样的函数就是高阶函数(有点像C++的函数指针) def add(x, y): return x+y def mins(x, y): ...

  5. Node.js学习笔记(六) --- Nodejs 的非阻塞 I/O、 异步、 事件驱动

    1. Nodejs 的单线程 非阻塞 I/O 事件驱动在 Java. PHP 或者.net 等服务器端语言中,会为每一个客户端连接创建一个新的线程.而每个线程需要耗费大约 2MB 内存.也就是说,理论 ...

  6. JS 学习笔记 (七) 面向对象编程OOP

    1.前言 创建对象有很多种方法,最常见的是字面量创建和new Object()创建.但是在需要创建多个相同结构的对象时,这两种方法就不太方便了. 如:创建多个学生信息的对象 let tom = { n ...

  7. Python学习笔记之函数式编程

    python中的高阶函数 高阶函数就是 变量名指向函数,下面代码中的变量abs其实是一个函数,返回数字的绝对值,如abs(-10) 返回 10 def add(x,y,f): return f(x) ...

  8. python学习笔记1 -- 函数式编程之高阶函数 使用函数作为返回值

    使用函数作为返回值,看起来就很高端有木有,前面了解过函数名本身就是一个变量,就比如abs()函数,abs只是变量名,而abs()才是函数调用,那么我们如果把ads这个变量作为返回值返回会怎么样呢,这就 ...

  9. python学习笔记1 -- 函数式编程之高阶函数 sorted排序

    python提供了很强大的内置排序函数,妈妈再也不担心我不会写冒泡排序了呀,sorted函数就是这个排序函数,该函数参数准确的说有四个,sorted(参数1,参数2,参数3,参数4). 参数1 是需要 ...

随机推荐

  1. 大数据Hadoop入门教程 | (二)Linux

    使用finalShell可以提供文件目录图形化 完整Linux命令整理参考大佬博客:Linux常见文件管理命令 - Mr_Walker - 博客园 Linux文件系统基础知识 Linux文件系统概念 ...

  2. LOJ6077「2017 山东一轮集训 Day7」逆序对 (生成函数+多项式exp?朴素DP!)

    题面 给定 n , k n,k n,k ,求长度为 n n n 逆序对个数为 k k k 的排列个数,对 1 e 9 + 7 \rm1e9+7 1e9+7 取模. 1 ≤ n , k ≤ 100   ...

  3. HDU6848改编题(弱化)——客星璀璨之夜(stars)

    「 客星璀璨之夜 」(stars) " 虽然不清楚是不是那两人的力量 在那个风暴肆虐的夜晚,的确有一瞬 真的在那一瞬间,在云破天开的时候 透过空隙中看到的璀璨星空,不知为何倒映眼中不能忘怀 ...

  4. 【IDEA】IDEA打开欢迎页面

    概述 IDEA在默认情况下,会进入最后一个项目.如果项目比较大的话会加载的比较久,这个就比较烦人了,目前我觉得最好的办法就是在设置中直接进入欢迎页面. 解决方案 ① 进入设置 ② Appearance ...

  5. 输入a、b、c三个整数,按先大后小的顺序输出a、b和c。注意请使用指针变量的方式进行比较和输出。

    `void swap(int *a,int *b,int c){ if(a < *b){ int temp = *a; //防止temp没有初始化 随机存放地址指向系统工作区间 可以对temp初 ...

  6. HC32L110(四) HC32L110的startup启动文件和ld连接脚本

    目录 HC32L110(一) HC32L110芯片介绍和Win10下的烧录 HC32L110(二) HC32L110在Ubuntu下的烧录 HC32L110(三) HC32L110的GCC工具链和VS ...

  7. KingbaseES 参数 - ora_statement_level_rollback

    参数 ora_statement_level_rollback 控制KingbaseES 是否实现类似oracle 语句级的回滚.当该参数打开时,如果事务操作失败,仅会回滚最后一条操作,避免了全部操作 ...

  8. spark 执行spark-example

    1. 找到CDH 安装spark的目录 执行 which spark-shell /usr/bin/spark-shell 执行 ll /usr/bin/spark-shell lrwxrwxrwx ...

  9. Kubernetes后台数据库etcd:安装部署etcd集群,数据备份与恢复

    目录 一.系统环境 二.前言 三.etcd数据库 3.1 概述 四.安装部署etcd单节点 4.1 环境介绍 4.2 配置节点的基本环境 4.3 安装部署etcd单节点 4.4 使用客户端访问etcd ...

  10. 凭借SpringBoot整合Neo4j,我理清了《雷神》中错综复杂的人物关系

    原创:微信公众号 码农参上,欢迎分享,转载请保留出处. 哈喽大家好啊,我是Hydra. 虽然距离中秋放假还要熬过漫长的两天,不过也有个好消息,今天是<雷神4>上线Disney+流媒体的日子 ...