两个独立的函数,能形成闭包函数嘛?

image.png
分别定义了两个函数f1和f2,像我这样能否形成闭包效果?提示a没有定义!
按照我的理解既然f1(f2()),f2中的这个a应该向上继续查找,查找到f1中定义的变量a即可获取。

阅读 1.4k
3 个回答

接上一个问题 javascript - 为何说这个参数不是一个函数? 里面已经解释了为什么会提示 a is not defined

你需要稍微改写以下你的设计,让在 f1 函数执行时,返回未执行的 f2 函数,但我猜测你会改写成这样:

function f1(fn){
    var a = 1;
    return fn
}
function f2(){
    console.log(a)
}
var s = f1(f2())
s()
// Uncaught TypeError: fn is not a function

这个时候控制台又会提示 Uncaught TypeError: fn is not a function。你肯定又会疑惑,我明明直接返回了 fn 了啊,为什么还会提示我 fn 不是一个函数?
fn2 不是一个函数是因为,你在传入的时候就调用了他,直接就执行了 f2,输出了 console.log(a+1)
所以你在 fn1 里面去执行 fn 的时候其实你是在执行 undefined() 显然 undefined 并不是一个函数所以提示错误了。

所以你再稍微改写一下:

function f1(fn){
    var a = 1;
    return fn
}
function f2(){
    console.log(a)
}
var s= f1(f2)
s()
// Uncaught ReferenceError: a is not defined

好嘛,怎么又回到了最开始的异常?变量 a 没有被找到。依然是当时的原因:

f2 的定义在外部,所以他会去找 f2 函数定义是申明的变量 a 显然没有,然后就回去找全局的变量 a 显然也没有,所以就会在控制太输出 a is not defined

那要怎么解决呢?如果我直接说 你在返回的时候把变量 a 传入进去不就好了嘛,那我觉得你可能又会改写错,比如说以下这样:

function f1(fn){
    var a = 1;
    return fn(a)
}
function f2(a){
    console.log(a)
}
var s= f1(f2)
s()
// 1
// Uncaught TypeError: s is not a function

怎么又提示 Uncaught TypeError: s is not a function 了。
之前明明已经输出 1 了啊。这是因为 f1(f2) 返回的是 return fn(a) 执行的结果,并不是返回了 f2 这个函数。

所以其实你要改成这样才可以:

function f1(fn){
    var a = 1;
    return function(){
        fn(a)
    }
}
function f2(a){
    console.log(a)
}
var s= f1(f2)
s()
// 1

同样如果你要操作闭包内的变量 a 其实也是可以的,比如说:

function f1(fn){
    var a = 1;
    return function(){
        a+=1
        fn(a)
    }
}
function f2(a){
    console.log(a)
}
var s= f1(f2)
s()
// 2
s()
// 3

但是你依旧不能在 f2 函数内操作,比如说:

function f1(fn){
    var a = 1;
    return function(){
        fn(a)
    }
}
function f2(a){
    a+=1
    console.log(a)
}
var s= f1(f2)
s()
// 2
s()
// 2

但是呢,可以换一个思路,把变量使用对象包裹起来,使 f2 去修改这个 data.a 所引用的内存地址上的值:

function f1(fn){
    var data = {a:1};
    return function(){
        fn(data)
    }
}
function f2(data){
    data.a+=1
    console.log(data.a)
}
var s= f1(f2)
s()
// 2
s()
// 3

以上。

所谓“向上查找”,查找的区域在函数定义的时候已经确定,和运行时怎么使用没有关系。
所以 f2 中的 a 如果在 f2 中找不到,只会向全局作用域去找,和 f1 无关。

按照你这种思路去设计 JS 的话,假如一个变量本来要设计成访问全局接口的:

function foo(){
    // setTimeout 在浏览器中等价于 window.setTimeout
    setTimeout(console.log, 1e3);
}

但是一个调用就被改写了:

function bar(){
    const setTimeout = console.error;
    // 👆,按照你的想法,foo 中此时的 setTimeout 应该是
    // 这里重新定义的 setTimeout 而不是 window.setTimeout
    // ,因此下面代码应该报错
    foo();
    // 然而并没有
}

bar();

你觉得这种设计合理吗?
我觉得是不合理的,ES5- 有一个很大的问题就是 this 的指向在运行时才能确定,这一点被很多人喷了,要是连变量指向都这么搞,JS 早二十年就胎死腹中了。

另外,“闭包”把变量私有化的现象,本来就是因为函数中访问的变量查找的区域在定义时就已经确定,所以在返回的函数还被引用的情况下需要保留其内部引用的其他变量(不然就破坏了原来定好的查找范围了):

function outer(){
    let a = 0;
    return function innter(){
        console.log(a ++);
    }
}
const test = outer();

outer = null;

test();
test();

假如按照你的想法来,那么下面的用法应该报错才对:

const a = 1000;
// 因为这里调用 test 的时候 a 已经被改写了
// 但是 a 是一个 const ,a++ 是要出错的
// 那么这里应该报错
test();
// 然而并不会报错
var a;
function f1(fn){
    var a = 1;
    return fn()
}
function f2(){
    console.log(a)
}

或者

function f1(){
    var a = 1;
    return (function f2(){
        console.log(a)
    })()
}

这两种是可以接受的,var变量在执行过程中会提权到作用域的最前面。按你写的,a只会在f1中存在。而f2和f1是平级的,都是全局范围的。所以f2只能取到全局范围和f2范围内的变量。所以f2读不到。
按第一种方式改了以后,实际上把a的提权放到了全局上,这样f1和f2就都能访问到。
按第二种方式改了以后,实际上把f2变成f1作用域中的函数,与a平级。他们就可以访问了。

第一种的缺陷就是污染了全局变量,尤其是a这种指代不明的变量名,对实际业务来说是非常致命的,在开发后期容易引起难以修正的bug。
第二中的缺陷就是f2不再是全局函数。除了f1内部可以调用,其他位置不能调用。

其他回答也提供了参数传递的方式,不再赘述。

推荐问题
宣传栏