JavaScript(函数篇 III)


\(update : 2022.1.27\)

学习原文


\(① : 闭包\)

闭包就是携带状态的函数

通常对一个Array求和 , 我们立马想到了 reduce 函数.

但是我们现在要求不立即求和 , 而是再后面的代码 , 根据需要在计算怎么办 , 这时候我们可以不返回和 , 而返回一个函数!

function lazy_sum(arr){
	var sum = function () {
		return arr.reduce( function(x,y) {
			return x+y;
		});
	}
	return sum;
}

var f = lazy_sum([1,2,3,4,5]); // 返回的是一个函数
f(); // 调用函数 f , 15;

当 lazy_sum 返回函数 sum 时 , 相关参数和变量都保存在返回的函数中 , 这种就称为闭包 , 我觉得相关参数和变量就是携带得状态.

注意一点 , 即使对 lazy_sum 传入的参数相同 , 但每次返回的都是一个新的函数.

var f1 = lazy_sum([1, 2, 3, 4, 5]);
var f2 = lazy_sum([1, 2, 3, 4, 5]);
f1 === f2; // false

但 f1 , f2 调用的结果互不影响.

再来看看闭包的一个例子 :

function count() {
    var arr = [];
    for (var i=1; i<=3; i++) {
        arr.push(function () {
            return i * i;
        });
    }
    return arr;
}
var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

f1(); // 16
f2(); // 16
f3(); // 16

这里首先要理解 f1,f2,f3 为数组 arr 的元素 , 但 arr 数组的每一元素都返回一个函数

但又出现了一个疑问 , 不应该返回 1, 4, 9 吗? 为什么都是16 .

这里注意一点 , 返回时函数并没有执行 , 调用f()时才执行 , 但调用f()时 , 局部变量 i 早就变成了 4 , 所以三个函数的调用都是 4.

所以作者提醒我们的一点 : 
返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。

如果一定要引用循环变量的话 ?

首先介绍 "创建一个匿名函数并立即执行" 的语法.

(function (x) {
	return x*x;
})(3)	//9

//直观感受一下吧.

function count() {
    var arr = [];
    for (var i=1; i<=3; i++) {
        arr.push((function (n) {
            return function () {
                return n * n;
            }
        })(i));
    }
    return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

f1(); // 1
f2(); // 4
f3(); // 9

"说了这么多,难道闭包就是为了返回一个函数然后延迟执行吗?" //难道不是吗?

作者下面介绍了闭包的实操

计时器

'use strict';

function create_counter(initial) {
    var x = initial || 0;
    return {
        inc: function () {
            x += 1;
            return x;
        }
    }
}

var c1 = create_counter();
c1.inc(); // 1
c1.inc(); // 2
c1.inc(); // 3

var c2 = create_counter(10);
c2.inc(); // 11
c2.inc(); // 12
c2.inc(); // 13

自定义 pow
'use strict';

function make_pow(n) {
    return function (x) {
        return Math.pow(x, n);
    }
}

// 创建两个新函数:
var pow2 = make_pow(2);
var pow3 = make_pow(3);

console.log(pow2(5)); // 25
console.log(pow3(7)); // 343

\(② :箭头函数\)

定义用的就是一个箭头

function (x){
	return x*x;
}

这个函数相当于箭头函数 x => x*x .

箭头函数相当于匿名函数 (没有名字的函数) , 可以是一个表达式 , 连{...} , return 都可以省略 , 还可以是包括多条语句的

x => {
	if(x > 0){
		return x*x;
	}
	else {
		return -x*x;
	}
}

参数不止一个 , 用括号 () 括起来

(x,y) => x*x + y*y; // 两个参数

() => 3.14 // 无参数.

//可变参数
(x,y,...rest) => {
	var i , sum = x+y;
	for(i=0;i ( {foo:x} ) // 话说这里不太明了.

这里又回到了 this .

箭头函数的 this 有两种解释 :

① : 箭头函数的 this 会继承外层函数的 this , 如果没有外层函数的话 , 就继承 window.

② : 箭头函数的 this 指向定义位置 , function 的 this 指向调用位置.

比如这个例子

var obj = {
    birth: 1990,
    getAge: function () {
        var b = this.birth; // 1990
        var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象
        return fn();
    }
};
obj.getAge(); // 25

按照第一个理解 , 箭头函数的外层函数就是 getAge , 指向 getAge 的 this  , 就是 obj.

按照第二个理解 , 箭头函数定义在 getAge 里 , 指向 getAge 的 this , 就是 obj.

这里要注意一个点 , 由于 this 在箭头函数里面指向的对象已经绑定了 , 用 call() , apply() 调用箭头函数时 , 无法对 this 进行绑定 , 即传入的第一个参数被忽略:

var obj = {
    birth: 1990,
    getAge: function (year) {
        var b = this.birth; // 1990
        var fn = (y) => y - this.birth; // this.birth仍是1990
        return fn.call({birth:2000}, year);
    }
};
obj.getAge(2015); // 25 2015 - 1990

接下来在评论区有大佬提供了一份很有趣的代码 : 

name = "window";
var obj = {
  name: "obj",
  func0: () => {
    console.log('in0'+this.name);
    return () => console.log('out0'+this.name);
  },
  func1: function() {
    console.log('in1'+this.name);
    return function() { console.log('out1'+this.name); };
  },
  func2: () => {
    console.log('in2'+this.name);
    return function() { console.log('out2'+this.name); };
  },
  func3: function() {
    console.log('in3'+this.name);
    return () => console.log('out3'+this.name);
  }
}
var env = {
  name: "env",
  func0: obj.func0(), // window
  func1: obj.func1(), // obj
  func2: obj.func2(), // window
  func3: obj.func3()  // obj
}
env.func0(); // window
env.func1(); // env
env.func2(); // env
env.func3(); // obj

解释 : 箭头函数的 this 指向定义的位置 , function 指向调用的位置

func0 箭头函数带箭头 , 所以 in0 指向 window , out0 指向 in0(注意这是箭头 , 指向定义的地方) , 也指向 window .

func1 function带function , in1 指向 obj(调用的地方) , out1 指向 env (调用的地方)

func2 箭头带function , in2 同理 in0 , out2 同理 out1

func3 function带箭头 , in3 指向 obj , out3 指向 in3 (定义的地方) , 指向 obj.