Node-JS专精01函数与闭包

函数是什么?

  • 最开始程序员写的大部分是汇编,后来有了 C / C++ 那时候没有函数,只有子程序
    • 比如有四行代码完成特定的功能
    • 给这四行代码起了一个名字 叫做函数
    • 但是那个时候不叫 函数而叫 子程序

子程序

  • 一个或多个语句组成
  • 完成特定功能
  • 功能相对独立

子程序又分三种

  • 函数(有返回值) function
  • 过程(无返回值) procedure
  • 方法 (类或对象中) method

JS 里只有函数和方法

因为JS的所有函数都有返回值,你不写return 但是返回值是 undefined

1
2
3
4
5
6
7
8
9
10
11
// 函数
function fn(){
console.log(1)
}

const a = fn() // undefined

// 方法
var obe = {
fn:fn //方法
}

数学领域里的函数

  • 数学中的函数
    • 两个集合之间的对应关系 如y = f(x) 比如方程式 y = a*x + b
      • X 叫做 定义域 Y 叫做 值域
    • 定义域里的每个元素都对应一项 值域中的元素
  • f()在数学领域是不合法的,因为没有输入
  • 函数式编程
    • 如果你的函数符合数学函数的定义,就是 函数式
1
2
3
4
5
如 f(x) = 3 * x +2
y = f(x)

x = 1 时 y = 5
x = 2 时 y = 8

但是 JS的函数不是数学函数

最大的区别就是,你给我一个值我可能给你返回不同的结果

1
2
3
4
5
6
7
var a = 0
function add(){
a += 1
return a
}
add() // 1
add() // 2

我们平时用的是哪个函数?

是编程里的子程序,还是数学里的函数?

  • 答案是 子程序

函数的返回值由什么确定?

影响因素

  • 调用时输入的参数 params
  • 定义时的环境 env

这个例子说明 a 是定义时的 a ,而不是执行时的 a

1
2
3
4
5
6
7
8
9
10
11
12
let x = 'x'
let a = '1'
function f1(x){
return x + a
}

{
let a = '2'
f1('x') // 值是多少?
}

// 答案是 'x1'

为什么不是 ‘x2’?

  • x 很好理解是 参数 所以 f1('x')x = 'x'
  • a 在这里不是参数 ,它是环境,它由 f1 所处的环境确定的
    • 如果你是参数 则在调用时 确定
    • 如果你是环境 则在 定义时确定

再看

1
2
3
4
5
6
7
8
9
10
11
12
let x = 'x'
let a = '1'
function f1(x){
return x + a
}
a = '3'
{
let a = '2'
f1('x') // 值是多少?
}

// 答案是 'x3'

再看

1
2
3
4
5
6
7
8
9
10
11
12
13
let x = 'x'
let a = '1'
function f1(x){
return x + a
}
a = '3'
{
let a = '2'
f1('x') // 值是多少?
}
a = '4'

// 答案是 'x3'
另一个例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let x = 'x'
let a = '1'
function f1(c){
c()
}
{
let a = '2'
function f2(){
console.log(x + a)
}
f1(f2) // 打印什么?
}

// 打印 'x2'
// 因为 x 和 a 都不是参数,所以只能看定义的地方,从内向外依次找
// 这个例子说明 a 是定义时的a ,而不是执行的 a

这个就是闭包,函数里面可以访问外面的变量

但并不是一定的,Ruby就不允许

1
2
3
4
5
6
7
8
9
10
11
12
# ruby 代码
def f1
a = 1 # 局部变量
def f2
print a # 打印 a
end
f2()
end

f1()

# 报错: NameError(undefined local variable or method 'a' for main: Object)

如果你就要在 ruby里访问局部变量,需要使用 lambda

1
2
3
4
5
6
7
8
def f1
a = 1 # 局部变量
f2 = lambda {print a} # 使用 lambda
f2.call()
end

f1()
# 成功 打印 1

闭包

函数里面可以访问外面的变量,那么这个函数 + 这些变量 = 闭包

闭包 + 时间

常见的考题

请问打印什么?

1
2
3
4
5
6
7
8
9
10
11
12
for(var i=0;i<6;i++){
setTimeout(
()=>console.log(i) //
)
}
// 打印 6个 6
// 因为六个函数共用了一个 i
// 这其实就是闭包

// setTimeout 是在 for循环之后执行的,所以循环已经结束了 那时 i = 6

// 如果你不理解它,那就回想一个成语 “刻舟求剑”

如何打印成 0 1 2 3 4 5呢?

  • 改用 let
1
2
3
4
5
6
7
8
9
for(let i=0;i<6;i++){
setTimeout(
()=>console.log(i)
)
}

// 打印 0 1 2 3 4 5

// let 的魔法:每次循环会把这个值复制一份, 然后把 这个值 分给这个闭包用
  • 立即执行函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*
// 立即执行函数
(function(i){
console.log(i)
})(x)

// 立即执行函数
!function(j){
console.log(j)
}(i)
*/
for(var i=0;i<6;i++){
(function(x){
setTimeout(
()=>console.log(x)
)
})(i)
}

为什么?

  • 用 var 声明的时候 用的都是 一个 i,每个闭包用的同一个 i
  • 用了 let 后,每个闭包分配一个新的 i
结论

闭包的特点

  • 能让一个函数维持住一个变量
  • 但不能维持这个变量的值
  • 尤其是变量的值会变化的时候

对象是穷人的闭包

  • 对象也可以来维持一个变量
  • 如果一门语言不支持闭包,你可以用对象代理
1
2
3
4
5
6
7
8
9
10
11
12
13
var obj = {
i:0,
fn(){
console.log(this.i)
}
}

const handle = function(){
var i = 0;
return function(){
console.log(i)
}
}

闭包是穷人的对象

  • 如果一门语言不支持对象,你可以用闭包代理
1
2
3
4
5
6
7
8
9
10
11
12
// person 有 name age 属性

function createPerson(age,name){
return function(key){
if(key === 'name') return name;
if(key === 'age') return age;
}
}

var person = createPerson(18,'hjx');
person('name') // 'hjx'
person('age') // 18