JS函数以及闭包的概念 | 青训营笔记

127 阅读5分钟

JS函数以及闭包的概念 | 青训营笔记

这是我参与「第四届青训营」笔记创作活动的第2\color{CornflowerBlue}这是我参与「第四届青训营」笔记创作活动的第2天

❀函数表达式\color{deeppink}❀函数表达式

JavaScript 函数可以通过一个表达式定义。
函数表达式可以存储在变量中:

var x = function (a, b) {return a * b};

在函数表达式存储在变量后,变量也可作为一个函数使用:\colorbox{pink}{在函数表达式存储在变量后,变量也可作为一个函数使用:}

var x = function (a, b) {return a * b};
var z = x(4, 3);

以上函数实际上是一个 匿名函数 (函数没有名称)。
函数存储在变量中,不需要函数名称,通常通过变量名来调用。

Function()构造函数\color{deeppink}❀Function() 构造函数

在以上实例中,函数通过关键字 function 定义。
函数同样可以通过内置的 JavaScript 函数构造器(Function())定义。

var myFunction = new Function("a", "b", "return a * b");
var x = myFunction(4, 3);

注意:\colorbox{coral}{注意:}在 JavaScript 中,很多时候,需要避免使用 new 关键字。

❀函数提升(Hoisting\color{deeppink}❀函数提升(Hoisting)

提升(Hoisting)是 JavaScript 默认将当前作用域提升到前面去的行为。
提升(Hoisting)应用在变量的声明与函数的声明。\colorbox{coral}{变量的声明与函数的声明。}
因此,函数可以在声明之前调用:

myFunction(5);

function myFunction(y) {
    return y * y;
}

注意:\colorbox{coral}{注意:}使用表达式定义函数时无法提升。

❀自调用函数\color{deeppink}❀自调用函数

函数表达式可以 "自调用"。
自调用表达式会自动调用。

如果表达式后面紧跟 () ,则会自动调用。
不能自调用声明的函数。
通过添加括号,来说明它是一个函数表达式:

(function () {
    var x = "Hello!!";      // 我将调用自己
})();

以上函数实际上是一个 匿名自我调用的函数 (没有函数名)。


❀函数可作为一个值使用\color{deeppink}❀函数可作为一个值使用

JavaScript 函数作为一个值使用:

function myFunction(a, b) {
    return a * b;
}

var x = myFunction(4, 3);

JavaScript 函数可作为表达式使用:

function myFunction(a, b) {
    return a * b;
}

var x = myFunction(4, 3) * 2;

❀箭头函数\color{deeppink}❀箭头函数

ES6 新增了箭头函数。 箭头函数表达式的语法比普通函数表达式更简洁。

(参数1, 参数2, …, 参数N) => { 函数声明 }

(参数1, 参数2, …, 参数N) => 表达式(单一)
// 相当于:(参数1, 参数2, …, 参数N) =>{ return 表达式; }

当只有一个参数时,圆括号是可选的:

(单一参数) => {函数声明}
单一参数 => {函数声明}

没有参数的函数应该写成一对圆括号:

() => {函数声明}

!使用要点\color{deeppink}!使用要点

有的箭头函数都没有自己的 this。 不适合定义一个 对象的方法

当我们使用箭头函数的时候,箭头函数会默认帮我们绑定外层 this 的值,所以在箭头函数中 this 的值和外层的 this 是一样的。

箭头函数是不能提升的,所以需要在使用之前定义。

使用 const 比使用 var 更安全,因为函数表达式始终是一个常量。\colorbox{pink}{使用 const 比使用 var 更安全,因为函数表达式始终是一个常量。}

如果函数部分只是一个语句,则可以省略 return 关键字和大括号 {},这样做是一个比较好的习惯:

const x = (x, y) => { return x * y };
// ES5 
var x = function(x, y) { return x * y; } 
// ES6 
const x = (x, y) => x * y;

JavaScript 闭包\color{skyblue}❀JavaScript 闭包

JavaScript 变量可以是局部变量或全局变量。
若想实现私有变量,可以用到闭包\colorbox{skyblue}{闭包}

全局变量\color{skyblue}全局变量

函数可以访问由函数内部定义的变量,如:

function myFunction() 
{ 
    var a = 4; 
    return a * a; 
}

函数也可以访问函数外部定义的变量,如:

var a = 4; 
function myFunction() 
{ 
    return a * a; 
}

后面一个实例中, a 是一个 全局 变量。
在web页面中全局变量属于 window 对象。
全局变量可应用于页面上的所有脚本。

在第一个实例中, a 是一个 局部 变量。
局部变量只能用于定义它函数内部。对于其他的函数或脚本代码是不可用的。

全局和局部变量即便名称相同,也是两个不同的变量。修改其中一个,不会影响另一个的值。\color{blue}修改其中一个,不会影响另一个的值。

Note变量声明时如果不使用 var 关键字,那么它就是一个全局变量,即便它在函数内定义。

变量生命周期\color{skyblue}变量生命周期

全局变量的作用域是全局性的,即在整个JavaScript程序中,全局变量处处都在。

而在函数内部声明的变量,只在函数内部起作用。这些变量是局部变量,作用域是局部性的;函数的参数也是局部性的,只在函数内部起作用。

计数器困境\color{skyblue}计数器困境

如果你想统计一些数值,且该计数器在所有函数中都是可用的。

你可以使用全局变量,函数设置计数器递增:

var counter = 0; 
function add() 
{ 
    return counter += 1; 
} 
add(); 
add(); 
add(); 
// 计数器现在为 3

计数器数值在执行 add() 函数时发生变化。

但问题来了,页面上的任何脚本都能改变计数器,即便没有调用 add() 函数。


如果我在函数内声明计数器,如果没有调用函数将无法修改计数器的值:

function add() 
{ 
    var counter = 0; 
    return counter += 1; 
} 
add(); 
add(); 
add(); 
// 本意是想输出 3, 但事与愿违,输出的都是 1 !

每次我调用 add() 函数,都会重新初始化局部变量counter=0;因此调用一次add()函数,计数器都会设置为 1。


JavaScript 内嵌函数可以解决该问题。\colorbox{orange}{JavaScript 内嵌函数可以解决该问题。}


JavaScript内嵌函数\color{skyblue}JavaScript 内嵌函数

所有函数都能访问全局变量。  
实际上,在 JavaScript 中,所有函数都能访问它们上一层的作用域。
JavaScript 支持嵌套函数。嵌套函数可以访问上一层的函数变量。
该实例中,内嵌函数 plus()  可以访问父函数的 counter 变量:

function add() 
{ 
    var counter = 0; 
    function plus() 
    {
        counter += 1;
    } 
    plus(); 
    return counter; 
}

如果我们能在外部访问 plus()  函数,这样就能解决计数器的困境。

我们同样需要确保 counter = 0 只执行一次。

解决方法便是使用闭包\colorbox{skyblue}{解决方法便是使用闭包}


JavaScript闭包\color{skyblue}JavaScript 闭包

var add = (function () { 
    var counter = 0; 
    return function () {return counter += 1;} 
})(); 
add(); 
add(); 
add(); 
// 计数器为 3

自我调用函数只执行一次。设置计数器为 0。并返回函数表达式。

add变量可以作为一个函数使用。非常棒的部分是它可以访问函数上一层作用域的计数器。

这个叫作 JavaScript 闭包。 它使得函数拥有私有变量变成可能。

计数器受匿名函数的作用域保护,只能通过 add 方法修改。

Note闭包是一种保护私有变量的机制,在函数执行时形成私有的作用域,保护里面的私有变量不受外界干扰。直观的说就是形成一个不销毁的栈环境。

❀❀❀总结❀❀❀\color{skyblue}❀❀❀总结❀❀❀

js中一个重要的概念就是闭包,闭包实际上就是为了隐藏一个变量,例如上诉counter变量,为了使其隐藏在add函数中,即必须通过add函数才可以对counter变量值进行修改,除此之外,没有其他方法可以修改。
总而言之:
「函数」和「函数内部能访问到的变量」的总和,就是一个闭包。