js闭包的一个问题

先上代码:

var scope = 'global scope';
function checkscope(){
    var scope = 'local scope';
    function f(){return scope;}
    return f();
}
checkscope();

输出:local scope

如果把这段代码改一下,把里面的括号移动到外面:

var scope = 'global scope';
function checkscope(){
    var scope = 'local scope';
    function f(){return scope;}
    return f;
}
checkscope()();

输出:local scope

我的问题:
1.这段代码到底是要干什么?
2.为什么代码会返回local scope而不是global scope?
3.为什么把f后面的()移动到外面还是返回一样的值?f没了括号,return f是什么意思呢?
4.js里面是不是只要是var申明的都是局部变量,没有var的都是全局变量?
5.最后一个问题,我看了网上的不少闭包的文章,但是还是对于闭包这个概念不是太懂,只知道闭包就是里面的函数可以访问外面的函数的成员变量,但是反过来不行,有哪位大神可以举几个通俗易懂的例子帮我理解下到底什么事闭包吗?
谢谢各位啦!:D

阅读 4.8k
6 个回答

你的问题提得很清晰,说明你是经过认真思考了的,那么我也将试着认真一一回答:(序号对应问题答案)。

1.首先你可以认为你定义了一个checkscope函数,加入这是一个工具类,别人需要引用的时候,肯定不知道你内部用了什么变量,这就涉及到一个作用域的问题。至于作用很简单,就是函数内部可以访问外部全局变量,函数外部不能直接访问函数内部局部变量,所以你在用一个模块的时候不会取到模块的局部变量,你不妨直接这样看看:

var scope = 'global scope';
function checkscope(){
    var scope = 'local scope';
    function f(){return scope;}
    return f();
}
checkscope();
alert(scope);  // global scope

2.函数内部定义的scope只能在内部调用,但是外部的变量在函数内部可以访问,你不妨试试把这个改成这样试试:

var scope = 'global scope';
function checkscope(){
    function f(){return scope;}
    return f();
}
alert(checkscope());

会发现外部的scope是可以用的,这个地方之所以没有影响是因为函数内部变量覆盖了。

3.这个问题问得不错,其实自己调试一下就知道,如下:

var scope = 'global scope';
function checkscope(){
    var scope = 'local scope';
    function f(){return scope;}
    return f;
}
var fn = checkscope();
console.log(fn) // "function f(){return scope;}"
console.log(typeof fn) // function
console.log(fn()) // "local scope"

这个问题之所以不清楚,我觉得是对于函数定义的几种方式理解不到位:

第一种:
function fn(){
    // 函数体
}
第二种:
var fn = function(){
    // 函数体
}
第三种:
var fn = new Function();

调用的时候都是有()的,fn()。

第二种方式可以认为是讲函数赋值给一个变量,因为函数也是对象,你上面例子中返回f,其实可以认为就是类似于函数定义的第二种。

4.你的意思肯定是想问是不是函数作用域中,如果不加var是不是内部变量变成了全局变量,这种情况下是的,但是如果不是函数作用域,比如代码块之间,加不加var都可以认为是全局的,所以还是要明确js是函数作用域。

5.至于闭包,这个我觉得还是还是要理解js函数作用域,然后就是匿名函数和闭包的区别,可以看看这篇匿名函数与闭包的区别;然后再看看这一篇吧:学习Javascript闭包

如果想进一步理解,可以看看我这篇文章中的一部分内容:
JavaScript进阶学习(二)—— 基于原型链继承的js工具库的实现方法

  1. 这段代码是个例子,为了说清楚他们的区别。

  2. 第一个返回local scope很好理解,等于是在checkscope函数里定义了个变量,然后定义了个函数并执行返回这个变量。第二个返回的其实是上面的global scope,但是在此之前,被修改成了local scope

  3. 第一个返回的是f(),即函数的执行结果,第二个返回的是函数本身,所以在调用的时候要用

checkscope()();

换种写法就是

var f=checkscope();
f();
  1. javascript中变量的全局和局部都是相对的。比如

var scope = 'global scope';

这句对于这段代码来说,可以将其认为是全局变量。
而不带var的是纯粹的意义上的全局变量。

  1. 闭包很好理解,闭包是指有权访问另外一个函数作用域中的变量的函数。创建闭包的常见方式就是在一个函数内部创建另外一个函数。因为在javascript中没有块级作用域,一般为了给某个函数申明一些只有该函数才能使用的局部变量时,我们就会用到闭包,这样我们可以很大程度上减少全局作用域中的变量,净化全局作用域。但是我个人不推荐使用闭包,因为会占用更多的内存。(这点一般前端从不考虑)

调用的对象不一样,第一个调用的是this对象,第二个是window对象

  1. 我也不知道

  2. 因为js是词法作用域 你可以搜索一下,我大致解释一下,就是和动态作用域不同,作用域在词法解析阶段既确定了

  3. 一样值是因为词法作用域 return f 是return了这个函数啊

  4. 应该是的吧

1,不知道要干嘛,姑且看做是一段试图理解闭包的测试代码
2,为什么返回local scope,
首先,你得知道作用域的嵌套规则,越是基础的知识点越是感觉自己语死早 不好怎么解释……大概就是……大作用域里套小作用域……;
查找变量的时候是从最里层一直往外一层一层找,直到找到了对应的变量(遮蔽效应)或者一直找到最外层的全局作用域;
其次,你得知道上面说的“遮蔽效应”;
然后,你得知道js的作用域是静态作用域(词法作用域),也就是说,函数的作用域是声明的时候所处的位置决定的,不管你后来在哪里、用什么方式调用,他的作用域查找都是从声明这个函数的地方开始。

你贴出来的两段代码,
代码1:

var scope = 'global scope';
function checkscope(){
    var scope = 'local scope';
    function f(){return scope;}
    return f();
}
checkscope();
alert(scope);  // global scope

代码2:

var scope = 'global scope';
function checkscope(){
    var scope = 'local scope';
    function f(){return scope;}
    return f;
}
checkscope()();

代码1和代码2中,f函数都是声明在checkscope函数内部,checkscope函数又声明在全局中,
嵌套规则都是:全局包含checkscope包含f;
作用域的嵌套查找规则是从最里层的开始找,f函数中的return scope,先在f函数中找scope,没找到,在外层的checkscope中查找,找到了一个变量叫scope(值是‘local scope’),由于遮蔽效应,不会继续向外层查找,所以f函数中的return scope语句,不管f怎么被调用、在哪里被调用,这里的scope都是checkscope中的‘local scope’。

3,为什么把f后面的()移动到外面还是返回一样的值?
代码1
第五行return f();
checkscope函数调用后,返回的是f函数的调用结果。
(你最后一行写的checkscope(),函数名后面加一对小括号就是调用这个函数)。

代码2
第五行 return f;
checkscope函数调用后,返回的是f函数。
f函数是啥?
就是:function f(){return scope;}
调用checkscope,返回f。

checkscope(); //调用checkscope函数
如果你在控制台输入代码2,然后只调用checkscope函数一次,(也就是输入checkscope()),回车,
控制台会输出: function f(){return scope;}
这东东就是checkscope()

你原来的代码里有两个括号的checkscope()()
实际上就是
(function f(){return scope;})()
也就是f()

但是你直接把代码2中的checkscope()()改成(function f(){return scope;})()行吗?
函数声明的地方不一样,
你贴出来的代码2,f函数是在checkscope中定义的,f函数中的变量scope值为‘local scope’,
如果改成(function f(){return scope;})() 这个新的f函数是在全局中定义的,指向的是全局中的变量scope,值为‘global scope’

f没了括号,return f是什么意思呢?
上面说,返回f这个函数,也就是f(){console.log(this.scope);return scope;}
(注意代码1 return f()是返回函数调用的结果,代码2 return f 返回函数,这两个不同哦)

4.js里面是不是只要是var申明的都是局部变量,没有var的都是全局变量?
变量在哪个作用域中,只取决于声明时所处的位置。
如果不用var 声明、直接给一个变量赋值,在严格模式下,会报错,
在非严格模式下,会自动在最外层的全局作用域中创建一个变量。
不推荐不用var 直接给变量赋值来创建全局变量,如果想在函数内部创建全局变量,可以用给window对象添加属性的方法
(浏览器里最外层的全局作用域中所有变量是window对象的属性)
比如你在写testFunction函数时,想创建一个全局变量a,可以这样写

var b = 'global b'
function testFunction(){
    var b = 'inner b'
    //...其他代码
    window.a = '你想给它的值';
    //...其他代码
    console.log(b); //这个b是'inner b'
    console.log(window.b); //这里是'global b';如果你想在函数内部使用一个全局变量,但是函数内部有那个变量的同名变量,可以通过window对象访问那个全局变量
}

我说清楚了吗?
没讲清的地方可以评论里提出来

这段代码就是讲解了一个知识点,js的作用域链的问题

推荐问题
宣传栏