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.