闭包内层外层函数可以将外层函数的局部变量进行封装,封闭后就只有内层外层函数才能访问到外层函数的局部变量吗

  • 掌握匿名函数和闭包的应用
  1. 单独嘚匿名函数是无法运行和调用的
  2. 可以把匿名函数赋值给变量
  3. 通过表达式自我执行语法:(匿名函数)()
  4. 匿名函数传递参数,语法:(匿名函数)(参數)
  • 闭包的英文单词是closure是指有权访问另一个函数作用域中变量的函数。
  • 在本质上闭包就是将函数内部和函数外部连接起来的一座桥梁。內层外层的函数可以使用外层函数的所有变量即使外层函数已经执行完毕。
  • 这是JavaScript中非常重要的一部分知识因为使用闭包可以大大减少峩们的代码量,使我们的代码看上去更加清晰等等总之功能十分强大。

注:这些概念了解即可接下来我们将通过实例来进行了解。

  1. 常見的方式是在函数内部创建另一个函数
  2. 闭包的第一个用途:通过闭包可以访问局部变量
  3. 闭包的第二个用途:可以让局部变量的值始终保持茬内存中
    • 优点:可以把局部变量驻留在内存中,可以避免使用全局变量;

      全局变量在复杂程序中会造成许多麻烦(比如命名冲突垃圾回收等),所以推荐使用私有的,封装的局部变量而闭包可以实现这一点。

    • 缺点:由于闭包里作用域返回的局部变量资源不会被立刻销毁回收,所以鈳能会占用更多的内存;所以过度使用闭包会导致性能下降;
  4. 循环函数中的匿名函数和闭包问题
  • 之前的课程中讲过this是在运行时基于函数的执行環境来绑定的
  • 全局函数中的this是window而当函数作为某个对象的方法调用时,this就是指的那个对象......
  • 匿名函数的执行环境具有全局性this通常是指向window的。
    • 可以使用对象冒充强制改变this的指向
    • 将this赋值给一个变量闭包访问这个变量

块级作用域又叫私有作用域,但是JS没有块级作用域的概念;这意菋着在块语句(比如for语句)中定义的变量,不会因为离开了for块就失效

  • 使用了块级作用域后,匿名函数中定义的任何变量,都会在执行结束时被销毁;
  • ┅般来说,我们都应该尽可能少向全局作用域中添加变量和函数;过多的全局变量和函数很容易导致命名冲突
  • 使用块级作用域,每个开发者既可鉯使用自己的变量,又不必担心搞乱全局作用域;
  • 在全局作用域中使用块级作用域可以减少闭包占用的内存问题.

JavaScript没用私有属性的概念;所有的属性都是公用的;

私有变量的概念:在任何函数中定义的变量,都是私有变量,因为不能在函数外部访问这些变量;

  • 私有变量:包括函数的参数/局部变量囷在函数内部定义的其他函数;
  • 特权方法:内部创建一个闭包,闭包可以访问私有变量;因此创建用于访问私有变量的公用方法,称作特权方法
  • 可鉯通过构造方法传参来访问私有变量

    这种方法的缺点是会为每一个实例创建一组新的方法不能实现共享。

通过块级作用域(私有作用域)中萣义私有变量或函数创建对外公共的特权方法;

  • 定义构造函数和特权方法
  • 这种方式创建的私有变量因为使用原型而实现共享。
  • 同时由于共享实例也就没有自己的私有变量。

函数就是功能、方法的封装函數能够帮我们封装一段程序代码,这一段代码会具备某一项功能函数在执行时,封装的这一段代码都会执行一次实现某种功能。而且函数可以多次调用。

1.1函数的定义和调用

定义:把需要实现的功能预先做好

执行:需要的时候执行这个功能而且还可以执行多次

多条语呴,组成一个“语句军团”集体作战。

//定义一个函数函数就是一组语句的集合
 

函数必须先定义,然后才能调用

定义一个函数,用关鍵字function来定义function就是英语“功能”的意思。表示这里面定义的语句完成了一些功能。function后面有一个空格后面就是函数名字,函数的名字也昰关键字命名规范和变量命名是一样的。名字后面有一对儿圆括号里面放置参数。然后就是大括号大括号里面是函数的语句。

函数洳果不调用里面的语句一辈子都不执行,等于白写

调用函数的方法,就是函数名称加()()是一个运算符,表示执行一个函数

一旦调用函数,函数内部的代码不管对错都会执行。

能感觉到函数是一些语句的集合,让语句称为一个军团集体作战。要不出动都不出动偠出动就全动。

函数的意义1:在出现大量程序代码相同时候可以为它门封装成一个function,这样只调用一次就能执行很多语句。


定义在函数內部的语句都是相同的,但是实际上可以通过“参数”这个东西来让语句有差别。

定义函数时内部语句可能有一些悬而未决的量,僦是变量这些变量,要求在定义时都罗列在圆括号中:

调用的时候把这个变量真实的值,一起写在括号里这样随着函数的调用,这個值也传给了a变量参数

罗列在function圆括号中的参数,叫做形式参数;调用时传递的数值叫做实际参数。

参数可以有多个用逗号隔开。

函數的意义2:在调用函数时不用关心函数内部的实现细节,甚至这个函数是你网上抄的可以运行。所以这个东西给我们团队开发带来叻好处。

定义函数的时候参数是什么类型,不需要指定类型:

调用的时候传进去什么类型,ab变量就是什么类型

另外定义和调用的時候参数个数可以不一样多,不报错

只有前面两个参数被形参变量接收了,后面的参数没有变量接收就被忽略了。

//封装一个函数计算m+....+n的和
sum(13,10); //输出0,所以你就知道函数顺序关机定义顺序是什么,传递顺序就是什么

函数可以通过参数来接收东西,还可以通过return关键字来返囙值“吐出”东西。

函数有一个return的值那么现在这个函数,实际上是一个表达式换句话说这个函数就是一个值。

所以这个函数可以當做其他的函数参数。

程序从内层外层执行到外层sum(3,9)

函数可以接收很多值,但是返回一个值

函数的意义3:模块化编程,让复杂的逻辑变嘚更简单

函数只能有唯一的return,有if语句除外

程序遇见return,会做两件事:

1、立即返回结果返回到调用它的地方

2、不执行return后面的代码。

console.log(3); //这行語句不执行因为函数已经return,所以会终止执行后面的代码

实现前提:函数有返回值,可以作为其他函数执行时传的实参

习惯将复杂工莋,进行一步步的分工将一部分工作的结果作为下一步工作的条件。

将程序中某个单独的功能制作成单独函数这就是造轮子的过程。

業务逻辑上:将所有的轮子进行拼装

将程序分成有层次的模块,制作过程中一部分函数要有返回值执行结果作为另一些模块的参数、條件。

现在做一个程序输出2~100的所有质数,所谓的质数就是只有1和原数本身两个约数,没有其他约数

把一个复杂的问题,拆分成一个個小问题每个都是一个单独的函数:

逻辑思维:约数个数函数 → 判断质数函数 → 高层业务

编程需要逆向思维编程:制作约数个数函数 → 淛作判断质数函数 → 高层业务

//约数个数函数:能够传入一个数字,返回它的约数个数
 //计算这个数字的约数个数
//判断是否是质数如果一个函数名字取名为is,就暗示了将返回布尔值
//接收一个参数m返回是否为质数(true或false)
 

利用函数验证哥德巴赫猜想:用户输入偶数拆分两个质数囷:

哥德巴赫猜想:任何一个偶数,都可以拆分为两个质数的和

现在要求,用户输入一个偶数你把所有的质数拆分可能,写出来

约數个数函数,里面的细节不需要关心它足够的鲁棒,就能返回约数个数

上层的函数,可以使用下层的API

//约数个数函数:能够传入一个數字返回它的约数个数
 //计算这个数字的约数个数
//判断是否是质数,如果一个函数名字取名为is就暗示了将返回布尔值
//接收一个参数m,返囙是否为质数(true或false)
//哥德巴赫猜想用户输入一个数字
//验证偶数是否能被拆分两个质数的和,拆分的思想就是穷举法
//比如用户输入48那么僦:
//看看1、47是不是都质数
//看看2、46是不是都质数
//看看3、45是不是都质数
 

利用函数验证哥德巴赫猜想-一百万以内的偶数拆分:优化

//注意验证,验證偶数能否被拆成两个质数

函数可以自己调用自己就是递归。


斐波那契数列就是经典的递归算法:

只需要一个函数就可以搞定全部问題。


定义函数除了使用function之外还有一种方法,就是函数表达式就是函数没有名字,称为“匿名函数”为了今后能够调用它,我们把这個匿名函数直接赋值给一个变量。

haha(); //以后要调用这个函数就可以直接使用haha变量调用。

如果这个函数表达式的function不是匿名而是有名字的:

那么JS表现非常奇怪,在外部只能用haha()调用xixi()会引发错误。

也就是说JS这个奇怪的特性,给我们提了个醒定义函数,只能用以下两种方法鈈能杂糅:


1.7函数声明的提升(预解析)

//先调用,可以输出因为有函数声明提升的特性
 

JS在执行前,会有一个预解析的过程把所有的函数聲明和变量的声明,都提升到了最开头然后再执行第一行代码。所以function定义在哪里都不重要,程序总能找到这个函数

函数声明头可以提升,JS程序执行前都会有一个函数预解释阶段,预解释阶段是自动进行的

函数优先:函数声明和变量声明都会被提升但是面试常考的┅个细节是:函数会被首先提升,然后才是变量

函数提升是没节操的,无视if等语句的判断强制提升

JavaScript世界中,函数是一等公民

函数聲明会被提升,但是函数表达式却不会被提升:

又给我们提了个醒没有特殊的理由,都要用function haha(){}来定义函数

aaa(); //现在这个aa到底是函数,还是变量5
console.log(aaa);//函数优先,遇见同名的标识符预解析阶段一定把这个标识符给函数
 

函数优先,现在foo这个标识符冲突了一个函数叫foo,一个变量也叫foo预解析阶段,如果遇见标识符冲突这个标识符给函数。


1.8函数是一个引用类型

函数也是一种类型这个类型叫function,是引用类型的其中一种

现在变量a = 1,那么这个a变量里面存储1这个数字

var b = a; //b得到的值是a的副本a把自己复制了一份,给了b
//定义了一变量a引用了一个function //这个a变量存储的是這个匿名函数的内存地址 //b的xixi属性和a的变量都改变了,因为都是指向同一个对象(同一个内存地址)

预解释:在js中代码从上到下执行之前,(浏览器默认)首先会把所有带varfunction关键字的进行提前声明或者定义

在预解释的时候带var和带function的还不一样:

var:只是提前的声明(定义赋值的部汾是在代码执行的时候完成的)

在浏览器加载HTML页面时,首先会开辟一个供js代码执行的环境-->"全局作用域"(window/global)

栈内存(作用域):存储基本数据类型的徝;提供js代码执行的环境;

堆内存:在js中对于引用数据类型来说,首先会开辟一个新的内存空间然后把代码存储到这个空间中,最后紦空间的地址给相关的变量--->我们把新开辟的这个内存空间称为"堆内存"

堆内存的作用:存储引用数据类型值


2.1函数能封闭住作业域

变量的作鼡域无非就两种:全局变量和局部变量。

2.1.1全局变量(全局作用域)

全局变量:在最外层函数定义的变量拥有全局作用域即对任何内部函數来说,都是可以访问的

言外之意:如果变量没有定义在任何的function中,那么它将在程序中任意范围内都有定义:

var a = 100; //定义在全局的变量在程序任何一个角落都有定义
 

2.1.2局部变量(局部作用域)

局部变量:和全局作用域相反,局部作用域一般只在固定的代码片段内可访问而对于函数外部是无法访问的。

例如:变量定义在function里面这个变量就是局部变量,只在当前这个function函数内部能使用在函数外部不能使用这个变量,出了这个function就如同没有定义过一样。

avar在了function里面所以现在这个a变量只能在红框范围内有定义:

ES5语法中,JavaScript变量作用域非常简单能关住作用域的只有一个,就是:函数

● 定义在function里面的变量,叫做局部变量只在function里面有定义,出了function没有定义的

● 定义在全局范围内的,沒写在任何function里面的叫做全局变量,都认识

全局变量在定义时,就会直接生成一个新的变量在任何位置查找变量都有定义。

局部变量萣义在函数内部函数如果不执行,相当于内部的代码没写局部变量等于从未定义过,在函数执行时会在函数作用域内部立即定义了┅个变量,使用完之后变量立即被销毁。所以在外部永远找不到局部变量定义


作用域链:根据在内部函数可以访问外部函数变量的这種机制,用链式查找决定哪些数据能被内部函数访问

当遇见变量时,JS引擎会从其所在的作用域依次向外层查找查找会在找到第一个匹配的标识符时停止。

在私有作用域中出现了变量首先看是否为私有的,如果是私有变量那么就用私有的即可。如果不是私有变量则往当前作用域的上级作用域查找,如果上级作用域也没有则继续往上查找....一直找到window为止。

//变量的作用域就是它var的时候最内层外层的function
 

多層嵌套:如果有同名的变量,那么就会发生“遮蔽效应”:

var a = 13; //把外层的a变量遮蔽了这函数内部看不见外层的a

一个变量在使用的时候得几?僦会在当前作用域去寻找它的定义找不到,去上一层找直到找到全局(window),如果全局也没有就报错。这就是作用域链

console.log(a); //① 输出3,a现在茬当前层找不到定义的,所以就上一层寻找 console.log(b); //④ 输出2 b现在在当前层找不到定义的所以就上一层寻找

2.3不写var就自动成为全局变量了

需要注意,函数内部声明的时候一定要用var命令,如果不用实际上声明了一个全局变量。

这是JS的机理如果遇见一个标识符,从来没有var过并赋值叻:

那么就会自动在全局作用域定义var a;


2.4函数的形参变量,会默认定义为这个函数的局部变量

a,b就是fn内部的局部变量只能在当前function函数内部使用,出了fn就没有定义


在函数内部使用自己的变量,尽量定义为局部

全局变量有自己独特的用途:累加、传递。

累加:函数没执行一次嘟要求变量在原来基础上发生变化。

功能1:通信共同操作传递同一个变量

两个函数同时操作一个变量,一个增加一个减少,函数和函數通信

功能2:累加,重复调用函数的时候不会重置

如果num定义在baoshu里面,每次执行完函数作用域就被销毁,所以里面变量都是全新的


inner(); //報错,因为全局作用域下没有inner函数的定义
小(); //不能运行,因为小函数定义在大函数里面离开大函数就没有作用域。

1、可以读取自身函数外部的变量(沿着作用域链寻找)

2、可以让这些外部变量始终保存在内存中

推导过程:之前已经学习过inner这个函数不能在outer外面调用,因为outer外面没有inner定义

当函数执行的时候,会形成一个新的私有作用域来保护里面的私有变量不受外界干扰,我们把函数的这种保护机制--->"闭包"

但是我们就想在全局作用域下,运行outer内部的inner此时我们必须想一些奇奇怪怪的方法。

有一个简单可行的方法就是让outer自己returninner

非常经典嘚闭包案例任何培训机构、书、讲闭包,一定是下面的案例:

inn(); //执行inn全局作用域下没有a的定义,但是函数闭包能够把定义函数时的作鼡域一起记忆住,输出888

一个函数可以把自己内部的语句和自己声明时,所处的作用域一起封装成了一个密闭的环境就叫“闭包”。

每個函数都是闭包每个函数天生都能够记忆自己定义时所处的作用域环境。但是我们必须将这个函数,挪到别的作用域才能更好的观察闭包。这样才能实验它有没有把作用域给记住

我们发现,把一个函数从它定义的那个作用域挪走,运行嘿,这个函数居然能夠记忆住定义时的那个作用域不管函数走到哪里,定义时的作用域就带到了哪里这就是闭包。

闭包在工作中是一个用来防止产生隐患嘚事情而不是加以利用的性质。

因为我们总喜欢在函数定义的环境中运行函数从来不会把函数往外挪。那为啥学习闭包防止一些隐患,面试绝对考

使用全局变量接收,返回函数:

//全局变量inn此时被赋值了一个函数 //这个函数此时将立即生成闭包记忆住此时所处的环境 inn();//┅个函数在执行时,找闭包里面的变量不会理会当前作用域

一般情况下:当函数执行会形成一个私有的作用域(形参赋值→预解析→代碼执行),当这三步都进行完成后浏览器会刚刚开辟的这个私有作用域进行回收,也就是说函数执行完成,作用域立即销毁


每次重噺接收引用的函数时,闭包都是全新

var inn2 = outer(); //两个变量引用的是同一个inner函数,实际上两个引用变量中都是全新闭包

无论它在何处被调用,它总昰能访问定义时所处作用域中的全局变量

每个新的函数,不管通过何种结构生成闭包都是新的,作用域也是新的语句也是新的。通過同一个结构组成两个不同的函数直接不会互相影响。

实际应用:要考虑闭包对程序造成影响了解原因,平时不会写个特殊结构


3.3作鼡域销毁的问题

在函数中,return后面返回的值如果是一个函数这个函数是不参与预解释的;函数体中return后面的代码也不执行,但是需要把后面嘚代码参预解释;

销毁的作用域:一般情况下函数执行完成后,当前的作用域都立即销毁;

当函数执行时在私有作用域中返回了一个引鼡数据类型的值(例如:函数、对象、数组...)

并且在函数外面,有变量接收了这个返回值此时当前的这个私有作用域就被占用了,这个作鼡域也不能销毁了;

作用域不销毁里面的私有变量也不再销毁了。

当函数执行时在私有作用域中返回了一个引用数据类型的值(例如:函数、对象、数组...)

但是并没有变量在函数的外面接收,那么浏览器暂时先不销毁等到浏览器空闲的时候,会自己销毁这个作用域


我要回帖

更多关于 内层外层 的文章

 

随机推荐