天天看点

前端笔记之JavaScript(四)关于函数、作用域、闭包那点事

一、自定义函数function

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

1.1函数的定义和调用

语法:

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

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

定义:function myName(){}

执行:myName()

【语法解释】:

 function  定义函数的关键字

 myName    函数名称

 ()         参数集

 {}        函数体,执行的代码都放在{}里面

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

//定义一个函数,函数就是一组语句的集合
function haha(){
   console.log(1);
   console.log(2);
   console.log(3);
   console.log(4);
}
haha();//调用haha函数
haha();//调用haha函数
haha();//调用haha函数      
前端笔记之JavaScript(四)关于函数、作用域、闭包那点事

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

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

 function 函数名称(){

 }

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

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

 函数名称()

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

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

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

1.2函数的参数

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

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

function fun(a){
   console.log("我第"+a+"次说你好!");
}
fun(100);
fun(1);
fun(2);
fun(3);      
前端笔记之JavaScript(四)关于函数、作用域、闭包那点事

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

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

前端笔记之JavaScript(四)关于函数、作用域、闭包那点事

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

function sum(a,b){
   console.log(a + b);
}
sum(3,5);     //8
sum(8,11);    //19
sum("5",12);  //512
sum(10);      //NaN,因为a的值是10,b没有被赋值是undefined,10+undefined=NaN
sum(10,20,30,40,88); //30,后面的参数没有变量接收      

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

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

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

 sum("5",12);  //512,做的是连字符串运算

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

前端笔记之JavaScript(四)关于函数、作用域、闭包那点事
 sum(10);      

因为只给a变量赋值,b没有被赋值,b被隐式的var。b的值是undefined,10+undefined=NaN

前端笔记之JavaScript(四)关于函数、作用域、闭包那点事
 sum(10,20,30,40,88); //30

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

//封装一个函数,计算m+....+n的和
//比如m是4,n是15,4+5+6+7...+15
function sum(m,n){
   var s = 0; //累加器,累加和
   for(var i = m;i <= n;i++){
       s+=i //s = s + i;
   }
   console.log(s);
}
sum(1,100); //计算1到100的和
sum(10,13); //计算10+11+12+13的和
sum(13,10); //输出0,所以你就知道函数顺序关机,定义顺序是什么,传递顺序就是什么。      

1.3函数的返回值

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

function sum(a,b){
   return a+b; //现在这个函数的返回值就是a+b的和
}

//sum没有输出功能,就要用console.log输出
console.log(sum(3,8));       //计算sum(3,8);实际上称为表达式,需要计算,计算后是11
console.log(sum(3,sum(4,5)));//输出12,实际上两次执行了sum函数,先执行内层的,计算出9,然后sum(3,9)就是12      

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

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

前端笔记之JavaScript(四)关于函数、作用域、闭包那点事
 sum(3,sum(4,5));

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

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

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

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

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

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

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

function fun(){
   console.log(1);
   console.log(2);
   //return; //返回一个空值,undefined
   return "你好啊!";
   console.log(3); //这行语句不执行,因为函数已经return,所以会终止执行后面的代码。
}
console.log(fun();      
前端笔记之JavaScript(四)关于函数、作用域、闭包那点事

1.4函数模块化编程

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

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

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

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

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

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

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

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

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

函数思维找质数:

//约数个数函数:能够传入一个数字,返回它的约数个数
function yueshugeshu(a){
   //计算这个数字的约数个数
   var count = 0; //累加约数个数
   for(var i = 1;i <= a;i++){
       if(a % i == 0){ //判断是否为约数
           count++;
       }
   }
   return count;
}
//判断是否是质数,如果一个函数名字取名为is,就暗示了将返回布尔值
//要么返回true,要么返回false。
//接收一个参数m,返回是否为质数(true或false)
function isZhishu(m){
   if(yueshugeshu(m) == 2){
       return true;
   }else{
       return false;
   }
}
//寻找1~100的质数
for(var i = 1;i <= 100; i++){
   if(isZhishu(i)){ //可以省略==true的判断
       //isZhishu()给我们返回了true和false
       console.log(i);
   }
}      

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

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

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

比如:

4 = 2 + 2

6 = 3 + 3

8 = 3 + 5

48 = 5 + 43

代码见案例:

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

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

//约数个数函数:能够传入一个数字,返回它的约数个数
function yueshugeshu(a){
   //计算这个数字的约数个数
   var count = 0; //累加约数个数
   for(var i = 1;i <= a;i++){
       if(a % i == 0){ //判断是否为约数
           count++;
       }
   }
   return count;
}
//判断是否是质数,如果一个函数名字取名为is,就暗示了将返回布尔值
//要么返回true,要么返回false。
//接收一个参数m,返回是否为质数(true或false)
function isZhishu(m){
   if(yueshugeshu(m) == 2){
       return true;
   }else{
       return false;
   }
}
//哥德巴赫猜想,用户输入一个数字
//验证偶数是否能被拆分两个质数的和,拆分的思想就是穷举法
//比如用户输入48,那么就:
//看看1、47是不是都质数
//看看2、46是不是都质数
//看看3、45是不是都质数
//...
var even = parseInt(prompt("请输入一个偶数"));
for(var i = 4;i < even;i++){
   if(isZhishu(i) && isZhishu(even - i)){
       console.log(even + "可以拆分为" + i + "与" + (even - i) + "的和");
   }
}      

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

function yueshugeshu(a){
    ...
}
function isZhishu(m){
    ...
}
//注意验证,验证偶数能否被拆成两个质数
waiceng:for(var i = 4 ; i <= 1000000 ; i+=2){
    for(var j = 2 ; j < i ; j++){
        if(isZhishu(j) && isZhishu(i - j)){
            console.log(i +"可以拆分为"+  j +"与"+  (i - j) + "的和");
            continue waiceng;
        }
    }
}      

1.5函数递归

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

function haha(){
   console.log("哈哈");
   haha();//调用自己
}
haha();
function sum(a){
   if(a == 1){
       return 1;
   }else{
       return a + sum(a-1); //10 + 9 + 8 + sum(7)
   }
}
console.log(sum(10));      

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

1 1、1、2、3、5、8、13、21、34、55、89、144、233...

输出斐波那契数列

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

fib(n); 就能得到第n位的数字

fib(2) = 1

fib(3) = 2

fib(4) = 3

fib(5) = 5

...

fib(10) = 55

function fib(n){
   if(n == 1 || n == 2){
       return 1;
   }else{
       return fib(n - 1) + fib(n - 2);
   }
}
// console.log(fib(10));

for(var i = 1;i <= 50;i++){
   console.log(fib(i));
}      

1.6函数表达式

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

var haha = function(){
   console.log("哈哈");
}
// console.log(haha);
haha(); //以后要调用这个函数,就可以直接使用haha变量调用。      

等价于:

function haha(){
   console.log("哈哈");
}
haha();      

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

var haha = function xixi(){
   console.log("哈哈");
}
xixi();  //这是错误的
haha();  //这的对的      

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

前端笔记之JavaScript(四)关于函数、作用域、闭包那点事

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

 function haha(){}
 var haha = function(){}

错误的:

var haha = function xixi(){}

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

//先调用,可以输出,因为有函数声明提升的特性
fun();
fun();
fun();
//后定义
function fun(){
   console.log("我是函数!");
}      

不会报错。

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

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

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

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

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

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

fun();
var fun = function(){  //因为它是函数表达式,而不是function定义法
   alert("我是函数!");
}      
前端笔记之JavaScript(四)关于函数、作用域、闭包那点事

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

函数优先:

aaa(); //现在这个aa到底是函数,还是变量5?
console.log(aaa);//函数优先,遇见同名的标识符,预解析阶段一定把这个标识符给函数
var aaa = 5; //定义一个变量,是5
function aaa(){
   alert("我是aaa函数!")
}      

面试题:

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

前端笔记之JavaScript(四)关于函数、作用域、闭包那点事

1.8函数是一个引用类型

基本类型:Number、String、Boolean、undefined、null

引用类型:Object、function、array、RegExp、Math、Date。

function fun(){}
var haha = function (){}

console.log(typeof fun); //引用类型中的function类型
console.log(typeof haha);//引用类型中的function类型      
前端笔记之JavaScript(四)关于函数、作用域、闭包那点事

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

基本类型:保存值

引用类型:保存地址

前端笔记之JavaScript(四)关于函数、作用域、闭包那点事

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

//基本类型的赋值
var a = 1;
var b = a; //b得到的值是a的副本,a把自己复制了一份,给了b
b = 3;     //改变了b的值,a不受影响
console.log(a); //1
console.log(b); //3      
//引用类型的赋值:
//定义了一变量a,引用了一个function
//这个a变量存储的是这个匿名函数的内存地址
var a = function(){
   alert("我是一根函数");
}
var b = a;  //就是把匿名函数的地址也给了b。
b.xixi = 1; //给b添加一个属性
console.log(a.xixi); //输出a的xixi属性,a也有这个属性了
//b的xixi属性和a的变量都改变了,因为都是指向同一个对象(同一个内存地址)
b.xixi++;
b.xixi++;
b.xixi++;
console.log(a.xixi);
console.log(b.xixi);      
前端笔记之JavaScript(四)关于函数、作用域、闭包那点事

总结:

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

var num=88;

声明(declare):相当于种树时"挖坑"  var num; 只声明没有定义时,num的默认值是undefined

定义(defined):相当于种树时"栽树"  num=88;(给变量赋值)

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

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

function:提前的声明+定义

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

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

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

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

二、作用域

前端笔记之JavaScript(四)关于函数、作用域、闭包那点事

2.1函数能封闭住作业域

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

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

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

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

var a = 100; //定义在全局的变量,在程序任何一个角落都有定义
function fn(){
   console.log("我是函数里面的语句,我认识全局变量a值为:" + a);
}
fn();
console.log("我是函数外面的语句,我认识全局变量a值为:" + a);      
前端笔记之JavaScript(四)关于函数、作用域、闭包那点事

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

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

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

function fn(){
   var a = 100; //定义在函数的变量,局部变量
   console.log("我是函数里面的语句,我认识变量a值为:" + a);
}
fn();
console.log("我是函数外面的语句,我认识变量a值为:" + a);      
前端笔记之JavaScript(四)关于函数、作用域、闭包那点事

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

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

前端笔记之JavaScript(四)关于函数、作用域、闭包那点事

【总结】:

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

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

【原理】:

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

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

2.2作用域链

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

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

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

//变量的作用域,就是它var的时候最内层的function
function outer(){
   var a = 1; //a的作用域是outer
   inner();
   function inner(){
       var b = 2;  //b的作用域是inner
       console.log(a); //能输出1,a在本层没有定义,就往上找
       console.log(b); //能输出2
   }
}
outer();
console.log(a); //报错,因为a的作用域是outer      
前端笔记之JavaScript(四)关于函数、作用域、闭包那点事

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

var a = 10; //全局变量
function fn(){
   console.log(a); //undefined,提升声明了局部变量
   var a = 13;     //把外层的a变量遮蔽了,这函数内部看不见外层的a
   console.log(a); //输出13,变量在当前作用域寻找,找到a定义为13
}
fn();
fn();
fn();
console.log(a); //10,变量在当前作用域寻找,找到全局a      
前端笔记之JavaScript(四)关于函数、作用域、闭包那点事

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

题目:

var a = 1;        //全局变量
var b = 2;        //全局变量
function outer(){
    var a = 3;        //遮蔽了外层的a,a局部变量
        function inner(){
            var b = 4;  //遮蔽了外层的b,b局部变量
            console.log(a);   //① 输出3,a现在在当前层找不到定义的,所以就上一层寻找
            console.log(b);   //② 输出4
    }
    inner();        //调用函数
    console.log(a);    //③ 输出3
    console.log(b); //④ 输出2 b现在在当前层找不到定义的,所以就上一层寻找
}
outer();        //执行函数,控制权交给了outer
console.log(a);    // ⑤ 输出1
console.log(b); // ⑥ 输出2      

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

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

 function fn(){

    a = 100;//这个a第一次赋值时,没有var,所以就自动在全局作用域var了一次

 fn();

 console.log(a);//100

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

 a = 100;

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

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

var a = 0;
var b = 0;
function fn(a,b){
   a = 3;
   b = 4;
   console.log(a,b);
}
fn();
console.log(a);
console.log(b);      
前端笔记之JavaScript(四)关于函数、作用域、闭包那点事

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

2.5全局变量的作用

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

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

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

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

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

var num = 0; //全局变量
function add(){
   num++;
}
function remove(){
   num--;
}
add();
add();
add();
add();
remove();
remove();
add();
add();
console.log(num); //4      

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

var num = 0;
 function baoshu(){
    num++;
    console.log(num);
 }
 baoshu();//1
 baoshu();//2
 baoshu();//3
 

       

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

function baoshu(){
    var num = 0;
    num++;
    console.log(num);
}

baoshu();    //1
baoshu();    //1
baoshu();    //1      

 2.6函数定义也有作用域

function outer(){
   var a = 10;
   function inner(){ //局部函数
       console.log("哈哈");
   }
}
outer();
inner(); //报错,因为全局作用域下,没有inner函数的定义
console.log(a);//报错      

公式:

function 大(){
   function 小(){
 
   }
   小(); 可以运行
}
小(); //不能运行,因为小函数定义在大函数里面,离开大函数就没有作用域。      

三、闭包

闭包有两个作用:

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

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

3.1闭包

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

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

function fn(){

}
fn();

function outer(){
   var a = 100;
   function inner(){
       console.log(a);
   }
}
outer();
inner(); //在全局作用域调用inner,全局没有inner的定义,所以报错      

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

有一个简单可行的方法,就是让outer自己return掉inner。

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

function outer(){
   var a = 888;
   function inner(){
       console.log(a); //888
   }
   return inner; //outer返回了inner的引用
}
var inn = outer();//inn就是inner函数了
inn(); //执行inn,全局作用域下没有a的定义,但是函数闭包,能够把定义函数时的作用域一起记忆住,输出888      

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

前端笔记之JavaScript(四)关于函数、作用域、闭包那点事

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

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

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

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

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

var inn; //全局变量
function outer(){
    var a = 250;
    var b = 500;
    //全局变量inn此时被赋值了一个函数
    //这个函数此时将立即生成闭包,记忆住此时所处的环境
    inn = function inner(){
        console.log(a);
        console.log(b);
    }
}
outer();
var a = 300;
var b = 400;
inn();//一个函数在执行时,找闭包里面的变量,不会理会当前作用域      

闭包题目1:

function outer(x){
   function inner(y){
       console.log(x+y)
   }
   return inner;
}
var inn = outer(3);//接收到了inner函数,inn就是inner
inn(5); //8
inn(7); //10
inn(10); //13      

闭包题目2:

function fun1(x,y){
   function fun2(x){
       console.log(x + y);
   }
   return fun2;
}
var f = fun1(3,4); //f就代表fun2
f();  //NaN
f(6); //10      

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

3.2闭包的性质

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

function outer(){
   var count = 0;
   function inner(){
       count++;
       console.log(count);
   }
   return inner;
}
var inn1 = outer();
var inn2 = outer(); //两个变量引用的是同一个inner函数,实际上两个引用变量中,都是全新闭包
inn1(); //1
inn1(); //2
inn1(); //3
inn1(); //4
inn2(); //1
inn2(); //2
inn1(); //5
inn1(); //6
inn1(); //7      

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

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

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

3.3作用域销毁的问题

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

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

不销毁的作用域:

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

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

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

不立即销毁的作用域:

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

文章都是本人学习时的笔记整理,希望看完后能对您有所帮助,欢迎大家提意见,多多交流。

也有些文章是转载的,如果存在转载文章且没有标注转载地址的,请与我联系,马上处理。

自由转载-非商用-非衍生-保持署名。

继续阅读