JavaScript函数表达式与闭包详解:从定义陷阱到内存管理

作者:闭包旅人

时间:2026-10-30

浏览:91

本文深入解析JavaScript中的函数表达式与闭包机制。首先对比函数声明与表达式定义的差异,剖析函数声明提升导致的常见调用错误及解决方案。接着通过示例阐述闭包的定义、形成原因及其对内存的影响,最后探讨匿名函数中this指向的特殊现象,帮助开发者理解闭包在内存保留与对象方法调用中的实际应用。

文章目录

JavaScript函数表达式与闭包基础

JavaScript支持函数闭包的核心原因在于其作为函数式语言的特性,即函数需要能够保存数据。具体而言,这种数据保存指的是函数运行结束后,其内部变量的值依然被保留。这是函数式语言的重要特征之一。

回顾前文介绍的三种函数定义方式。

函数声明语法定义:function fun(num) { return num; } 函数表达式定义:var fun = function (num) { return num; } Function构造函数:var fun = new Function("num", "num", "return num");

函数定义与调用常见错误

在深入分析闭包之前,我们先探讨定义和调用函数时容易出现的错误。

示例1:


sayHi(); //错误:函数还不存在
var sayHi = function () {
  alert("test");
};

示例2:


if (true) {
  function sayHi() {
    alert("1");
  }
} else {
  function sayHi() {
    alert("2");
  }
}
sayHi();//打印结果并不是我们想要的

示例3:


var fun1 = function fun2() {
  alert("test");
}
fun2();//错误:函数还不存在

在示例1中,函数声明式语法定义之前调用函数会报错。解决方案有两种:

1. 若使用函数表达式定义,必须在表达式定义完成后进行调用。


var sayHi = function () {
  alert("test");
};
sayHi()

2. 使用函数声明式。浏览器引擎会执行函数声明提升,在所有代码执行前先读取并处理函数声明。


sayHi(); 
function sayHi () {
  alert("test");
};

在示例2中,预期结果应为打印1,但实际结果是打印2。


if (true) {
  function sayHi() {
   alert("1");
  }
  } else {
  function sayHi() {
   alert("2");
  }
}
sayHi();//打印结果并不是我们想要的

原因在于函数声明提升。浏览器在预解析阶段不会判断if条件,而是直接解析第二个函数定义,从而覆盖了第一个定义。

解决方案:


var sayHi;
if (true) {
  sayHi = function () {
   alert("1");
  }
  } else {
  sayHi = function () {
   alert("2");
  }
}
sayHi();

在示例3中,发现只能使用fun1()调用,而不能使用fun2()调用。

对于这一现象,作者目前尚无确切理解,也未找到相关权威资料解释。

因为1: function fun3() { }; 等效于 var fun3 = function fun3() { }; 如图:

JavaScript函数表达式与闭包基础 

因此,只能使用fun1()调用,而不能使用fun2()调用。

此处作者仍存疑问,若有大神知晓原因,望告知。

既然fun2在外部无法调用,为何在函数内部可以调用?尽管在调试器中仍无法获取fun1。

JavaScript函数表达式与闭包基础 

闭包的定义与内存管理

通过上述三个示例进行热身后,我们继续今天的主题“闭包”。

1. 什么是闭包?

定义:有权访问另一个函数作用域中变量的函数。

我们先从一个示例函数开始:

示例1:


function fun() {
  var a = "张三";
}
fun();//在我们执行完后,变量a就被标记为销毁了

示例2:


function fun() {
  var a = "张三";
  return function () {
    alert("test");
  }
}
var f = fun();//同样,在我们执行完后,变量a就被标记为销毁了

示例3:


function fun() {
  var a = "张三";
  return function () {
    alert(a);
  }
}
var f = fun();//【现在情况发生变化了,如果a被销毁,显然f被调用的话就不能访问到变量a的值了】
f();//【然后变量a的值正常的被访问到了】
//这就是闭包,当函数A 返回的函数B 里面使用到了函数A的变量,那么函数B就使用了闭包。
示例:
function fun() {
  var a = "张三";
  return function () {
   alert(a);
  }
}
var f = fun();//【现在情况发生变化了,如果a被销毁,显然f被调用的话就不能访问到变量a的值了】
f();//【然后变量a的值正常的被访问到了】

显然,滥用闭包会增加内存占用。因此,除非特殊情况,否则尽量避免使用闭包。若必须使用,记得手动设置空引用以释放内存,例如:f = null;。

图解说明(不了解作用域链的同学请先阅读前文“作用域和作用域链”):

JavaScript函数表达式与闭包基础 

匿名函数与this指向问题

2. 什么是匿名函数?(仅解释此概念)

例如:(即没有名字的函数)。

JavaScript函数表达式与闭包基础 

关于对象中函数的返回值是匿名函数时,this指向的怪异现象。

讲解前请保持清醒,避免越看越迷糊。若感到困惑,可直接忽略以下内容。


var name1 = "张三";
var obj = {
  name1: "李四",      
  fun2: function () {
    alert(this.name1);
  },
  fun3: function () {
    return function () {
      alert(this.name1);
    }
  }
}

obj.fun2(); // 打印结果"李四",符合预期。 obj.fun3()(); // 因为返回的是一个函数,所以需要再加一对()来调用。打印结果是"张三",出乎意料。 // 令人不解的是,this为何指向了全局? 我们之前讲过“哪个对象点出来的方法,this就是哪个对象”,那么 obj.fun3()() 打印的是“张三”,意味着this指向了全局作用域。

我们看看下面的示例,或许能明白原因。


var name1 = "张三";
var obj = {
  name1: "李四",      
  fun2: function () {
    alert(this.name1);
  },
  fun3: function () {
    return function () {
      alert(this.name1);
    }
  }
}    
//obj.fun3()();
var obj2 = {};
obj2.name1 = "test";
obj2.fun = obj.fun3();
obj2.fun();//打印结果"test",再次证明了“哪个对象点出来的方法,this就是哪个对象”.
var name1 = "张三";
var obj = {
  name1: "李四",
  fun2: function () {
   alert(this.name1);
  },
  fun3: function () {
    return function () {
     alert(this.name1);
    }
  }
}
//obj.fun3()();
var obj2 = {};
obj2.name1 = "test";
obj2.fun = obj.fun3();
obj2.fun();//打印结果"test",再次证明了“哪个对象点出来的方法,this就是哪个对象”.

分解 obj.fun3()():先执行 obj.fun3() 返回一个匿名函数,该函数处于window作用域,接着调用时this便指向了window。(此解释略显牵强,暂作如此理解。)

闭包形成的原因:内存释放问题。

通常,函数执行完毕后,局部活动对象会被销毁,内存中仅保留全局作用域。但闭包的情况不同。

闭包的活动对象依然保存在内存中。如上例所示,函数调用返回后,变量i属于活动对象,其栈区尚未释放。当调用c()时,变量i的作用域链从b()->a()->全局寻找var i声明所在,找到var i=1;后在闭包内执行++i,最终输出值为2。

以上所述是小编给大家分享的JavaScript基础篇(6)之函数表达式闭包,希望大家喜欢。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章