【JavaScript】深入了解JavaScript中的匿名函数、立即执行函数和闭包

JS中函数两种命名方式:声明式和表达式

  1. 函数声明式
function fn(){
	console.log("函数声明式");
}
  1. 函数表达式
var fn = function(){
	console.log("函数表达式");
}

(function(){}()),(function(){})()这两种是js中立即执行函数的写法,函数表达式后加上()可以被直接调用,但是把整个声明式函数用()包起来的话,则会被编译器认为是函数表达式,从而可以用()来直接调用,如(function fn(){/*...*/})()
但是如果这个括号加在声明式函数后面,如function fn(){/*...*/}(),则会报错,很多博客说这种写法()会被省略,但实际是会出错,因为不符合js的语法,所以想要通过浏览器的语法检查,就必须加点符号,比如()、+、!等,详细见下方。

匿名函数

JavaScript并不是面向对象的,所以不支持封装,但是可以 通过匿名函数实现封装。
匿名函数:没有函数名的函数,不能单独定义与使用,否则会js语法错误,至少用()包裹起来。

function(){
	console.log("匿名函数");
}

匿名函数作用:

1.用作函数表达式

var sum = function(num1, num2){
	return num1 + num2;
};
console.log(sum(2, 3));

2.作为返回值

function sum(num1, num2){
	return function(){
		return num1 + num2;
	}
}
console.log(sum(2, 3));		//function
console.log(sum(2, 3)());   //5

3.用作定义对象方法

var obj = {
  name: 'umaru',
  age: 17,
  fu: function() {
    console.log(this.name + ' ' + this.age);
  }
};
obj.fu(); 

4.作为回调函数

setTimeout(function() {
  console.log('匿名函数作为回调函数');
}, 1000);

5.用于立即执行函数

在内容形成局部变量和局部函数,防止全局污染。

(function() {
  console.log('立即执行函数是基于匿名函数创建的');
}());

6.用于DOM元素注册事件

<input type="button" value="Click me!" id="btn">
<script>
    var btn = document.querySelector("#btn");
    //给按钮注册点击事件
    btn.onclick = function(){
        console.log('Click event');
    }
</script>

立即执行函数

立即执行函数(IIFE,Immediately-Invoked Function Expression),是一种在定义后就会立即执行的函数,其实质是一种语法。

立即执行函数形式

1. 将匿名函数包裹在一个括号运算符中,后面再跟一个括号。

(function () {
  console.log('立即执行函数');
})(); 
// !!!特别说明:若此立即执行函数后面立马又跟着一个立即执行函数,一定要在结尾加分号,否则后面的立即执行函数会报错!

// 上一个立即执行函数不加分号,下行代码将报错:TypeError: (intermediate value)(...) is not a function
(function (a, b, c) { // 形参
  console.log(a + b + c);  // 6
})(1, 2, 3); // 实参

可以用!、+、-、~来代替常用第一个括号

!function (a, b, c) { 
  console.log(a + b + c);  // 6
}(1, 2, 3);

+function (a, b, c) { 
  console.log(a + b + c);  // 6
}(1, 2, 3);

-function (a, b, c) { 
  console.log(a + b + c);  // 6
}(1, 2, 3);

~function (a, b, c) { 
  console.log(a + b + c);  // 6
}(1, 2, 3);

var fn = function (a, b, c) { 
  console.log(a + b + c);  // 6
}(1, 2, 3);

2. 匿名函数后面跟一个括号,再将整个包裹在一个括号运算符中。

(function () {
  console.log('立即执行函数');
}());

(function (a, b, c) { // 形参
  console.log(a + b + c);  // 6
}(1, 2, 3)); // 实参

立即函数的作用

立即执行函数最本质的作用是:创建一个独立的作用域。
利用这一功能,可以

  • 初始化数据和页面(只执行一次)
  • 模块化开发中,定义私有变量,防止污染全局(独立作用域)
  • 解决闭包中的状态保存问题;(常见的一个函数内部返回多个函数,调用这些函数,打印父函数内部变量的问题)

匿名函数特点:

  1. 页面加载时立即制行
  2. 获取到返回值
  3. 执行完成之后立即释放

闭包经典案例分析

<ul id=”test”>
    <li>这是第一条</li>
    <li>这是第二条</li>
    <li>这是第三条</li>
</ul>

<script>
    var liList=document.getElementsByTagName('li');
    for(var i=0;i<liList.length;i++)
    {
        liList[i].onclick=function(){
            console.log(i);
        }
    };
</script>

       很多人觉得这样的执行效果是点击第一个li,则会输出1,点击第二个li,则会输出二,以此类推。但是真正的执行效果是,不管点击第几个li,都会输出3。
  因为 i 是贯穿整个作用域的,而不是给每个 li 分配了一个 i,用户触发的onclick事件之前,for循环已经执行结束了,而for循环执行完的时候i=3
  但是如果我们用了__立即执行函数给每个 li 创造一个独立作用域,__就可以改写为下面的这样,这样就能实现点击第几条就能输出几的功能。

<script>
    var liList=document.getElementsByTagName('li');
    for(var i=0;i<liList.length;i++)
    {
        (function(ii) {
           liList[ii].onclick=function(){
               console.log(ii);
           }
       })(i)
    };
</script>

其实ES6语法中的let也可以实现上述的功能。

<script>
     var liList=document.getElementsByTagName('li');
     for(let i=0;i<liList.length;i++)
     {
            liList[i].onclick=function(){
                console.log(i);
             }
     }
</script>

闭包

闭包函数:声明在一个函数中的函数,叫做闭包函数

闭包:内部函数总是可以访问其所在外部函数中的声明的参数和变量,即使在其外部函数被返回(寿命终结)了之后。

创建闭包的常见方式:就是在一个函数内部创建另一个函数。闭包可以有函数名,也可以是匿名函数(没有函数名);

闭包特点

  1. 让外部访问函数内部变量成为可能
  2. 局部变量会常驻在内存中
  3. 可以避免使用全局变量,防止全局变量污染
  4. 会造成内存泄漏(有一块内存空间被长期占用,而不被释放)

闭包的创建

  1. 闭包就是可以创建一个独立的环境,每个闭包里面的环境都是独立的,互不干扰。闭包会发生内存泄漏,__每次外部函数执行的时候,外部函数的引用地址不同,都会重新创建一个新的地址。__但凡是当前活动对象中有被内部子集引用的数据,那么这个时候,这个数据不删除,保留一根指针给内部活动对象。

  2. 闭包内存泄漏为: key = value,key 被删除了 value 常驻内存中;

  3. 局部变量闭包升级版(中间引用的变量)=> 自由变量;

闭包形式

1.直接以匿名函数的形式作为外部函数的返回值(普遍用法)

function outer() {
  var n = 1;

  return function() {
    n++; // 访问 outer 函数作用域中的变量 n,形成闭包
    console.log(n);
  }
}

outer()();

2.在外部函数内定义一个内部函数,并返回内部函数名

function outer() {
  var n = 1;

  function inner() {
    n++; // 访问 outer 函数作用域中的变量 n,形成闭包
    console.log(n);
  }

  return inner;
}

outer()();

3.在外部函数内定义一个立即执行函数

function outer() {
  var n = 1;

  (function() {
    n++; // 访问 outer 函数作用域中的变量 n,形成闭包
    console.log(n);
  })();
}

outer();

闭包的应用场景

  • 保护函数局部变量的安全
  • 在内存中维持一个变量
  • 通过保护变量的安全实现JS私有属性和私有方法(不能被外部访问)

使用闭包的注意点:
由于闭包会携带包含它的函数的作用域,因此会比其他函数占用跟多的内存。过度使用闭包可能会导致内存占用过多。

结论: 闭包找到的是同一地址中父级函数中对应变量最终的值。

示例:

  1. 在返回的闭包后面紧跟一个 () ,立即执行
function outer() {
  var n = 1;

  function inner() {
    n++;
    console.log(n);
  }

  return inner;
}

outer()();  // 2
outer()();  // 2  
  1. 将返回的闭包赋值给一个全局变量,全局变量运行 () 操作,执行闭包
function outer() {
  var n = 1;
  
  function inner() {
    n++;
    console.log(n);
  }

  return inner;
}

var inner = outer();
inner();    // 2
inner();    // 3  (是在 2 的基础上加 1)
  1. 请继续看以下代码,同时使用了1和2的两种方式
function outer() {
  var n = 1;

  function inner() {
    n++;
    console.log(n);
  }

  return inner;
}

var inner = outer();

outer()();  // 2
outer()();  // 2  (Flag1)

inner();    // 2  (Flag2)
inner();    // 3

可以看到,Flag1 跟 Flag2 的值都是 2,假设 outer()() 这样的闭包执行方式会一直保存局部变量在内存的话,那 Flag2 处的值应该是 3。从而说明:闭包并非就一定可以一直保存局部变量在内存,还跟执行方式有关。

  1. 主动释放内存
function outer() {
  var n = 1;

  function inner() {
    n++;
    console.log('n = ', n);
  }

  return inner;
}

var inner = outer();
inner();    // 2  
inner();    // 3

inner = null;  // 解除对闭包的引用,以便释放内存
 
function foo() {
	var a = 2;
	return function fun1() {
		console.log(a)
	}
}
var fun2 = foo()
fun2()  // 2

在上面的例子中,fun1能够访问foo的内部作用域,我们把fun1作为一个值返回。在foo()执行后,把foo()的返回值 fun1 赋值给fun2并调用fun2。打印出了结果2.
此时,我们可以说fun1记住并访问了所在的词法作用域 或者说 fun2访问了另一个函数作用域中的变量(fun2在全局作用域中声明,访问了foo的内部作用域)
由于引擎有自动的垃圾回收机制,在foo()执行后(不再使用),通常foo的整个内部作用域会被销毁,对内存进行回收。而闭包的神奇之处正是可以阻止这件事情的发生,因为fun1依然持有对该作用域的引用,这个引用就叫做闭包。
无论使用何种方式对函数类型的值进行传递,当函数在别处调用时,都可以看到闭包。

function outerFn(){
  var i = 0; 
  function innerFn(){
      i++;
      console.log(i);
  }
  return innerFn;
}
var inner = outerFn();  //每次外部函数执行的时候,都会开辟一块内存空间,外部函数的地址不同,都会重新创建一个新的地址
inner();
inner();
inner();
var inner2 = outerFn();
inner2();
inner2();
inner2();   //1 2 3 1 2 3
var i = 0;
function outerFn(){
  function innnerFn(){
       i++;
       console.log(i);
  }
  return innnerFn;
}
var inner1 = outerFn();
var inner2 = outerFn();
inner1();
inner2();
inner1();
inner2();     //1 2 3 4
function fn(){
	var a = 3;
	return function(){
		return  ++a;                                     
	}
}
alert(fn()());  //4
alert(fn()());  //4
function outerFn(){
var i = 0;
  function innnerFn(){
      i++;
      console.log(i);
  }
  return innnerFn;
}
var inner1 = outerFn();
var inner2 = outerFn();
inner1();
inner2();
inner1();
inner2();    //1 1 2 2
(function() { 
  var m = 0; 
  function getM() { return m; } 
  function seta(val) { m = val; } 
  window.g = getM; 
  window.f = seta; 
})(); 
f(100);
console.info(g());   //100  闭包找到的是同一地址中父级函数中对应变量最终的值
function a() { 
  var i = 0; 
  function b() { alert(++i); } 
  return b; 
} 
var c = a(); 
c();      //1 
c();      //2 
function f() { 
  var count = 0; 
  return  function() { 
      count++; 
      console.info(count); 
  } 
} 
var t1 = f();
t1();     //1 
t1();     //2 
t1();     //3 
  1. ES6柯里化
var add = function(x) { 
  var sum = 1; 
  var tmp = function(x) { 
      sum = sum + x; 
      return tmp;    
  } 
  tmp.toString = function() { 
      return sum; 
  }
  return tmp; 
} 
alert(add(1)(2)(3));     //6
var lis = document.getElementsByTagName("li");
for(var i=0;i<lis.length;i++){
  (function(i){
      lis[i].onclick = function(){
           console.log(i);
      };
  })(i);       //事件处理函数中闭包的写法
}  
function m1(){
     var x = 1;
     return function(){
          console.log(++x);
     }
}
 
m1()();   //2
m1()();   //2
m1()();   //2
 
var m2 = m1();
m2();   //2
m2();   //3
m2();   //4
var  fn=(function(){
   var  i=10;
   function  fn(){
      console.log(++i);
   }
   return   fn;
})() 
fn();   //11
fn();   //12
var  fn=(function(){
   var  i=10;
   function  fn(){
      console.log(++i);
   }
   return   fn;
})() 
fn();   //11
fn();   //12
function love1(){
     var num = 223;
     var me1 = function() {
           console.log(num);
     }
     num++;
     return me1;
}
var loveme1 = love1();
loveme1();   //输出224
function fun(n,o) {
    console.log(o);
    return {
         fun:function(m) {
               return fun(m,n);
         }
    };
}
var a = fun(0);  //undefined
a.fun(1);  //0  
a.fun(2);  //0  
a.fun(3);  //0  
var b = fun(0).fun(1).fun(2).fun(3);   //undefined  0  1  2
var c = fun(0).fun(1);  
c.fun(2);  
c.fun(3);  //undefined  0  1  1
function fn(){
   var arr = [];
   for(var i = 0;i < 5;i ++){
	 arr[i] = function(){
		 return i;
	 }
   }
   return arr;
}
var list = fn();
for(var i = 0,len = list.length;i < len ; i ++){
   console.log(list[i]());
}  //5 5 5 5 5
function fn(){
  var arr = [];
  for(var i = 0;i < 5;i ++){
	arr[i] = (function(i){
		return function (){
			return i;
		};
	})(i);
  }
  return arr;
}
var list = fn();
for(var i = 0,len = list.length;i < len ; i ++){
  console.log(list[i]());
}  //0 1 2 3 4

参考的博客地址:
uakora羊二哥淡定如斯

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值