当前位置: 首页 > news >正文

闭包与高阶函数

文中内容均来自于曾探《JavaScript设计模式与开发实践》的学习笔记。

闭包


作用域

变量的作用域,就是指变量的有效范围。

局部变量、全局变量。

变量的搜索是从内到外而非从外到内的。

变量的生命周期

对于全局变量莱索,全局变量的生命周期是永久的,除非我们主动销毁这个全局变量。

而对于函数内用var关键字生命的局部变量来说,当退出函数时,这些局部变量即失去了它们的价值,它们会随着函数调用的结束而被销毁。

闭包:f返回了一个匿名函数的应用,它可以访问到func()被调用时产生的环境,而局部变量a一直处于这个环境中,所以不会被销毁。局部变量的生命周期看起来被延续了。

//现在来看看下面这段代码:

varfunc=function(){

vara=1;

returnfunction(){

a++;

alert ( a );

}

};

varf=func();

f(); // 输出:2

f(); // 输出:3

f(); // 输出:4

f(); // 输出:5

<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="UTF-8">

<title>Document</title>

</head>

<body>

<div>1111111111111</div>

<div>2</div>

<div>3</div>

<div>4</div>

<div>5</div>

<script type="text/javascript">

var nodes = document.getElementsByTagName('div');

// 方法一:

for (let i = 0; i < nodes.length; i++) {

nodes[i].onclick = function(){

alert(i)

}

}

// 方法二:

// for (var i = 0; i < nodes.length; i++) {

// (function(a){

// nodes[a].onclick = function(){

// alert(a)

// }

// })(i)

// }

// 方法三:

// for (var i = 0; i < nodes.length; i++) {

// nodes[i].onclick = (() => {

// var a = i;

// return () => {

// alert(a);

// }

// })();

// }

</script>

</body>

</html>

闭包的作用

  • 封装数据:闭包可以把一些不需要暴露在全局的变量封装成“私有变量”。

//最好是把它们用闭包封闭起来。代码如下:

var mult = (function(){

var cache = {};

var calculate = function(){ // 封闭calculate 函数

var a = 1;

for ( var i = 0, l = arguments.length; i < l; i++ ){

a = a * arguments[i];

}

return a;

};

return function(){

var args = Array.prototype.join.call( arguments, ',' );

if ( args in cache ){

return cache[ args ];

}

return cache[ args ] = calculate.apply( null, arguments );

}

})();

  • 延续局部变量的寿命

//现在我们把img 变量用闭包封闭起来,便能解决请求丢失的问题:

var report = (function(){

var imgs = [];

return function( src ){

var img = new Image();

imgs.push( img );

img.src = src;

}

})();

闭包和面向对象设计

过程和数据的结合是形容面向对象中的“对象”时经常使用的表达。对象以方法的形式包含了过程,而闭包则是在过程中以环境的形式包含了数据。通常用面向对象思想能实现的功能,用闭包也能实现。

//下面来看看这段跟闭包相关的代码:

var extent = function(){

var value = 0;

return {

call: function(){

value++;

console.log( value );

}

}

};

var extent = extent();

extent.call(); // 输出:1

extent.call(); // 输出:2

extent.call(); // 输出:3

//如果换成面向对象的写法,就是:

var extent = {

value: 0,

call: function(){

this.value++;

console.log( this.value );

}

};

extent.call(); // 输出:1

extent.call(); // 输出:2

extent.call(); // 输出:3

//或者:

var Extent = function(){

this.value = 0;

};

Extent.prototype.call = function(){

this.value++;

console.log( this.value );

};

var extent = new Extent();

extent.call();

extent.call();

extent.call();

用闭包实现命令模式

命令接受者会被封闭再闭包形成的环境中。

<script type="text/javascript">

var Tv = {

open: function(){

console.log( '打开电视机' );

},

close: function(){

console.log( '关上电视机' );

}

};

var createCommand = function( receiver ){

var execute = function(){

return receiver.open(); // 执行命令,打开电视机

}

var undo = function(){

return receiver.close(); // 执行命令,关闭电视机

}

return {

execute: execute,

undo: undo

}

};

var setCommand = function( command ){

document.getElementById( 'execute' ).onclick = function(){

command.execute(); // 输出:打开电视机

}

document.getElementById( 'undo' ).onclick = function(){

command.undo(); // 输出:关闭电视机

}

};

setCommand( createCommand( Tv ) );

</script>

闭包和内存泄露

  • 内存泄露的原因:闭包不是罪魁祸首,全局作用域和闭包,对内存方便的影响是一样的,并不能说是内存泄露。是BOM和DOM引起的,如果两个对象之间形成了循环引用,那么这两个对象都无法被回收。

  • 解决方法:手动将变量设为null。

高阶函数


高阶函数是指至少满足下列条件之一的函数:

  • 函数可以作为参数被传递;

  • 函数可以作为返回值输出;

函数作为参数传递

  • 回调函数

var getUserInfo = function( userId, callback ){

$.ajax( 'http://xxx.com/getUserInfo?' + userId, function( data ){

if ( typeof callback === 'function' ){

callback( data );

}

});

}

getUserInfo( 13157, function( data ){

alert ( data.userName );

});

  • Array.prototype.sort

[ 1, 4, 3 ].sort( function( a, b ){

return a - b;

});

函数作为返回值输出

  • 判断数据的类型

//我们还可以用循环语句,来批量注册这些isType 函数:

var Type = {};

for ( var i = 0, type; type = [ 'String', 'Array', 'Number' ][ i++ ]; ){

(function( type ){

Type[ 'is' + type ] = function( obj ){

return Object.prototype.toString.call( obj ) === '[object '+ type +']';

}

})( type )

};

Type.isArray( [] ); // 输出:true

Type.isString( "str" ); // 输出:true

  • 单例模式

var getSingle = function ( fn ) {

var ret;

return function () {

return ret || ( ret = fn.apply( this, arguments ) );

};

};

高阶函数实现AOP

AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。

Function.prototype.before = function( beforefn ){

var __self = this; // 保存原函数的引用

return function(){ // 返回包含了原函数和新函数的"代理"函数

beforefn.apply( this, arguments ); // 执行新函数,修正this

return __self.apply( this, arguments ); // 执行原函数

}

};

Function.prototype.after = function( afterfn ){

var __self = this;

return function(){

var ret = __self.apply( this, arguments );

afterfn.apply( this, arguments );

return ret;

}

};

var func = function(){

console.log( 2 );

};

func = func.before(function(){

console.log( 1 );

}).after(function(){

console.log( 3 );

});

func();

高阶函数的应用

  • currying:函数柯里化,又称部分求值,一个currying的函数首先会接受一些参数,接收了这些参数之后,该函数并不会立刻求值,而是继续返回另外一个函数,刚才传入的函数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。

var currying = function( fn ){

var args = [];

return function(){

if ( arguments.length === 0 ){

return fn.apply( this, args );

}else{

[].push.apply( args, arguments );

return arguments.callee;

}

}

};

var cost = (function(){

var money = 0;

return function(){

for ( var i = 0, l = arguments.length; i < l; i++ ){

money += arguments[ i ];

}

return money;

}

})();

var cost = currying( cost ); // 转化成currying 函数

cost( 100 ); // 未真正求值

cost( 200 ); // 未真正求值

cost( 300 ); // 未真正求值

alert ( cost() ); // 求值并输出:600

  • uncurrying:一个对象未必只能使用它自身的方法,可以使用别的对象的方法,比如call和apply就能实现。

Function.prototype.uncurrying = function () {

var self = this;

return function() {

var obj = Array.prototype.shift.call( arguments );

return self.apply( obj, arguments );

};

};

var push = Array.prototype.push.uncurrying();

(function(){

push( arguments, 4 );

console.log( arguments ); // 输出:[1, 2, 3, 4]

})( 1, 2, 3 );

  • 函数节流:

场景:比如window.resize事件,mousemove事件,上传进度

原理:指定时间间隔内只会执行一次任务。将即将被执行的函数用setTimeout延迟一段时间执行。如果该次延迟执行还没有完成,则忽略接下来的调用该函数的请求。

实现:

var throttle = function ( fn, interval ) {

var __self = fn, // 保存需要被延迟执行的函数引用

timer, // 定时器

firstTime = true; // 是否是第一次调用

return function () {

var args = arguments,

__me = this;

if ( firstTime ) { // 如果是第一次调用,不需延迟执行

__self.apply(__me, args);

return firstTime = false;

}

if ( timer ) { // 如果定时器还在,说明前一次延迟执行还没有完成

return false;

timer = setTimeout(function () { // 延迟一段时间执行

clearTimeout(timer);

timer = null;

__self.apply(__me, args);

}, interval || 500 );

};

};

  • 防抖:

场景:用户名输入后,判断用户是否存在

原理:任务频繁触发的情况下,只有任务触发的间隔超过指定间隔的时候,任务才会执行。

  • 分时函数

var timeChunk = function( ary, fn, count ){

var obj,

t;

var len = ary.length;

var start = function(){

for ( var i = 0; i < Math.min( count || 1, ary.length ); i++ ){

var obj = ary.shift();

fn( obj );

}

};

return function(){

t = setInterval(function(){

if ( ary.length === 0 ){ // 如果全部节点都已经被创建好

return clearInterval( t );

}

start();

}, 200 ); // 分批执行的时间间隔,也可以用参数的形式传入

};

};

var ary = [];

for ( var i = 1; i <= 1000; i++ ){

ary.push( i );

};

var renderFriendList = timeChunk( ary, function( n ){

var div = document.createElement( 'div' );

div.innerHTML = n;

document.body.appendChild( div );

}, 8 );

renderFriendList();

  • 惰性加载函数

<html>

<body>

<div id="div1">点我绑定事件</div>

<script>

var addEvent = function( elem, type, handler ){

if ( window.addEventListener ){

addEvent = function( elem, type, handler ){

elem.addEventListener( type, handler, false );

}

}else if ( window.attachEvent ){

addEvent = function( elem, type, handler ){

elem.attachEvent( 'on' + type, handler );

}

}

addEvent( elem, type, handler );

};

var div = document.getElementById( 'div1' );

addEvent( div, 'click', function(){

alert (1);

});

addEvent( div, 'click', function(){

alert (2);

});

</script>

</body>

</html>

总结


很多模式都是通过闭包和高阶函数实现的。相对于模式的实现过程,我们更关注的是模式可以帮助我们完成什么!!!

相关文章:

闭包与高阶函数

文中内容均来自于曾探《JavaScript设计模式与开发实践》的学习笔记。闭包作用域变量的作用域&#xff0c;就是指变量的有效范围。局部变量、全局变量。变量的搜索是从内到外而非从外到内的。变量的生命周期对于全局变量莱索&#xff0c;全局变量的生命周期是永久的&#xff0c;…...

人工智能轨道交通行业周刊-第35期(2023.2.20-2.26)

本期关键词&#xff1a;重庆智慧轨道、智能运维主机、标准轨距、地方铁路公报、景深、机器视觉应用 1 整理涉及公众号名单 1.1 行业类 RT轨道交通人民铁道世界轨道交通资讯网铁路信号技术交流北京铁路轨道交通网上榜铁路视点ITS World轨道交通联盟VSTR铁路与城市轨道交通Rai…...

快慢指针判断链表是否有环

快慢指针判断链表是否有环 单链表有可能存在环&#xff0c;有些情况下要判断一个单链表是否有环。数组的有个快慢指针的方法&#xff0c;其实单链表和数组有相似的地方&#xff0c;可以使用快慢指针的方法。具体做法如下&#xff1a; 首先创建两个指针&#xff0c;它们初始时…...

《MongoDB入门教程》第26篇 聚合统计之$max/$min表达式

本文将会介绍两个 MongoDB 表达式&#xff0c;返回一组数据中最大值的 $max 表达式&#xff0c;以及返回一组数据中最小值的 $min 表达式。 $max 表达式 $max 表达式用于返回一组数据中的最大值&#xff0c;语法如下&#xff1a; { $max: <expression> }$max 表达式在…...

FPGA纯verilog解码SDI视频 纯逻辑资源实现 提供2套工程源码和技术支持

目录1、前言2、硬件电路解析SDI摄像头Gv8601a单端转差GTX解串SDI解码VGA时序恢复YUV转RGB图像输出FDMA图像缓存HDMI输出3、工程1详解&#xff1a;无缓存输出4、工程2详解&#xff1a;缓存3帧输出5、上板调试验证并演示6、福利&#xff1a;工程代码的获取1、前言 FPGA实现SDI视…...

JVM篇之垃圾回收

一.如何判断对象可以回收 1.引用计数法 只要一个对象被其他变量所引用&#xff0c;就让它的计数加1&#xff0c;被引用了两次就让它的计数变成2&#xff0c;当这个变量的计数变成0时&#xff0c;就可以被垃圾回收&#xff1b; 弊端&#xff1a;当出现如下图的情况&#xff0…...

尝试用程序计算Π(3.141592653......)

文章目录1. π\piπ2. 用微积分来计算π\piπ2.1 原理2.2 代码2.3 结果2.4 分析1. π\piπ π\piπ的重要性或者地位不用多说&#xff0c;有时候还是很好奇&#xff0c;精确地π\piπ值是怎么计算出来的。研究π\piπ的精确计算应该是很多数学家计算机科学家努力的方向&#xf…...

【异常检测三件套】系列3--时序异常检测综述

写在前面: 异常检测共包含3个内容,从多个方面剖析异常检测方法,本文为第三篇。过往内容请查看以下链接: 【异常检测三件套】系列1--14种异常检测算法https://blog.csdn.net/allein_STR/article/details/128114175?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%…...

关于SAP 错误日志解析

有时候启动或操作sap会出现故障&#xff0c;只是察看sap用户当前目录下的日志文件可能不得要领&#xff0c;此时有必要察看work目录下的一些trace. 以Linux系统为例&#xff0c;其他的也差不多。 instance说明 如下 DVEBMGS?? ABAP Central Instance D?? …...

java:自定义变量加载到系统变量后替换shell模版并执行shell

这里的需求前提是&#xff0c;在项目中进行某些操作前&#xff0c;需要在命令后对shell配置文件的进行修改&#xff08;如ip、port&#xff09;&#xff0c;这个对于用户是不友好的&#xff0c;需要改为用户页面输入ip、port&#xff0c;后台自动去操作修改配置&#xff1b;那么…...

Redis高级删除策略与数据淘汰

第二章&#xff1a;Redis高级 学习目标 目标1&#xff1a;能够说出redis中的数据删除策与略淘汰策略 目标2&#xff1a;能够说出主从复制的概念&#xff0c;工作流程以及场景问题及解决方案 目标3&#xff1a;能够说出哨兵的作用以及工作原理&#xff0c;以及如何启用哨兵 …...

社畜大学生的Python之pandas学习笔记,保姆入门级教学

接上期&#xff0c;上篇介绍了 NumPy&#xff0c;本篇介绍 pandas。 目录 pandas 入门pandas 的数据结构介绍基本功能汇总和计算描述统计处理缺失数据层次化索引 pandas 入门 Pandas 是基于 Numpy 构建的&#xff0c;让以 NumPy 为中心的应用变的更加简单。 Pandas是基于Numpy…...

20_FreeRTOS低功耗模式

目录 低功耗模式简介 STM32低功耗模式 Tickless模式详解 Tickless模式相关配置 实验源码 低功耗模式简介 很多应用场合对于功耗的要求很严格,比如可穿戴低功耗产品、物联网低功耗产品等。 一般MCU都有相应的低功耗模式,裸机开发时可以使用MCU的低功耗模式。 FreeRTOS也…...

Hive的使用方式

操作Hive可以在Shell命令行下操作&#xff0c;或者是使用JDBC代码的方式操作 针对命令行这种方式&#xff0c;其实还有两种使用 第一个是使用bin目录下的hive命令&#xff0c;这个是从hive一开始就支持的使用方式 后来又出现一个beeline命令&#xff0c;它是通过HiveServer2服…...

Flume三大核心组件

Flume的三大核心组件&#xff1a; Source&#xff1a;数据源 Channel&#xff1a;临时存储数据的管道 Sink&#xff1a;目的地 Source&#xff1a;数据源&#xff1a;通过source组件可以指定让Flume读取哪里的数据&#xff0c;然后将数据传递给后面的 channel Flume内置支持读…...

数据结构(六)二叉树

一、树形结构概念树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的。它具有以下的特点&#xff1a;1、有一个…...

Docker buildx 的跨平台编译

docker buildx 默认的 docker build 命令无法完成跨平台构建任务&#xff0c;我们需要为 docker 命令行安装 buildx 插件扩展其功能。buildx 能够使用由 Moby BuildKit 提供的构建镜像额外特性&#xff0c;它能够创建多个 builder 实例&#xff0c;在多个节点并行地执行构建任…...

【java基础】方法重载和方法重写

文章目录方法重载方法重写方法重载 方法重载就是可以在一个类里面定义多个相同名称的方法&#xff0c;只需要参数列表的个数或者类型不同就行。 public class Overload {public int add(int a, int b) {return a b;}public double add(double a, double b) {return a b;}}对…...

Gradle7.4安装与基本使用

文章目录一.前言二.下载Gradle三.Gradle镜像源-全局级配置四.配置Gradle wrapper-项目级配置五.Gradle对测试的支持五.生命周期5.1 settings文件六.Gradle任务入门6.1 任务行为6.2 任务依赖方式七. Dependencies依赖引入7.1 依赖冲突及解决方案八.Gradle整合多模块SpringBoot九…...

[系统安全] 虚拟化安全之虚拟化概述

本文为笔者从零基础学习系统安全相关内容的笔记,如果您对系统安全、逆向分析等内容感兴趣或者想要了解一些内容,欢迎关注。本系列文章将会随着笔者在未来三年的读研过程中持续更新,由于笔者现阶段还处于初学阶段,不可避免参照复现各类书籍内容,如书籍作者认为侵权请告知,…...

如何从零开始系统的学习项目管理?

经常会有人问&#xff0c;项目管理到底应该学习一些什么&#xff1f;学习考证之后能得到什么价值&#xff1f; 以下我就总结一下内容 一&#xff0c;学习项目管理有用吗&#xff1f; 有效的项目管理带来的益处大致包括以下几个方面&#xff1a;更有效达成业务目标、满足相关…...

面试题-----

面试题---- 一.HTML 1.常用哪些浏览器进行测试&#xff0c;对应有哪些内核&#xff1f; ①IE------------------->Trident ②Chrome---------->以前是Webkit现在是Blink ③Firefox------------>Gecko ④Safari-------------->Webkit ⑤Opera--------------&…...

线材-电子线载流能力

今天来讲的是关于电子线的一个小知识&#xff0c;可能只做板子的工程师遇到此方面的问题会比较少&#xff0c;做整机的工程师则必然会遇到此方面问题&#xff0c;那就是线材问题。 下面主要说下电子线的过电流能力。&#xff08;文末有工具下载&#xff09;电子线&#xff08;h…...

单变量回归问题

单变量回归问题 对于某房价问题&#xff0c;x为房屋大小&#xff0c;h即为预估房价&#xff0c;模型公式为&#xff1a; hθ(x)θ0θ1xh_{\theta}(x)\theta_{0}\theta_{1}x hθ​(x)θ0​θ1​x 要利用训练集拟合该公式&#xff08;主要是计算θ0、θ1\theta_{0}、\theta_{1}θ…...

ubuntu/linux系统知识(36)linux网卡命名规则

文章目录背景命名规范系统默认命名规则优势背景 很久以前Linux 操作系统的网卡设备的传统命名方式是 eth0、eth1、eth2等&#xff0c;属于biosdevname 命名规范。 服务器通常有多块网卡&#xff0c;有板载集成的&#xff0c;同时也有插在PCIe插槽的。Linux系统的命名原来是et…...

java的一些冷知识

接口并没有继承Object类首先接口是一种特殊的类&#xff0c;理由就是将其编译后是一个class文件大家都知道java类都继承自Object&#xff0c;但是接口其实是并没有继承Object类的 可以自己写代码测试: 获取接口类的class对象后遍历它的methods&#xff0c;可以发现是不存在Obje…...

java代理模式

代理模式 为什么要学习代理模式&#xff1f;因为这是SpringAOP的底层&#xff01; 【SpringAOP和SpingMVC}】 代理模式的分类&#xff1a; 静态代理 动态代理 代理就像这里的中介&#xff0c;帮助你去做向房东租房&#xff0c;你不能直接解出房东&#xff0c;而房东和中介…...

JUC包:CountDownLatch源码+实例讲解

1 缘起 有一次听到同事谈及AQS时&#xff0c;我有很多点懵&#xff0c; 只知道入队和出队&#xff0c;CLH&#xff08;Craig&#xff0c;Landin and Hagersten&#xff09;锁&#xff0c;并不了解AQS的应用&#xff0c; 同时结合之前遇到的多线程等待应用场景&#xff0c;发现…...

Log4j2基本使用

文章目录1. Log4j2入门2. Log4j2配置3. Log4j2异步日志4. Log4j2的性能Apache Log4j 2是对Log4j的升级版&#xff0c;参考了logback的一些优秀的设计&#xff0c;并且修复了一些问题&#xff0c;因此带 来了一些重大的提升&#xff0c;主要有&#xff1a; 异常处理&#xff0c…...

A2L在CAN FD总线的使用

文章目录 前言CAN时间参数BTL CyclesTime Quantum时间份额SWJ同步跳转宽度波特率计算采样点计算CAN FD的第二采样点SSP推荐配置A2L配置总结前言 A2L作为XCP标定协议的载体,包括了总线信息的定义。本文介绍如何将基于CAN总线的A2L扩展为支持CAN-FD的A2L CAN时间参数 在介绍配…...