JavaScript 闭包如何访问外部变量

ane · 2014年02月10日 · 最后由 gerald 回复于 2014年03月07日 · 6006 次阅读
var name = 'World!';
(function () {
    console.log(name);
    if (typeof name === 'undefined') {
        console.log(typeof name);
        console.log(name);
        var name = 'Jack';
        console.log('Goodbye ' + name);
    } else {
        console.log('Hello ' + name);
    }
})();

在不作为参数传递的前提下 闭包中如何访问外部的 name?

window.name = 'World!';

#1 楼 @search 哦,也行吧,如果非浏览器

一般的语言都是词法作用域,或者叫做静态作用域,js 就是。还有一种是动态作用域,是运行时决定的,比较少,最早的 lisp 就是动态作用域。

var name = 'World!';
(function () {
    var name = this.name;
    console.log(name);
    if (typeof name === 'undefined') {
        console.log(typeof name);
        console.log(name);
        var name = 'Jack';
        console.log('Goodbye ' + name);
    } else {
        console.log('Hello ' + name);
    }
}).call(this);

我能想到的是这么改了 但是和你的要求还是有小差距 传参了 不过一般都是 call(this) 这样写

#4 楼 @zj0713001 是不是内部定义 name 变量,就会覆盖外部环境在闭包中作用域上下文?

好诡异的行为,代码改成下面那样就好了

var name = 'World!';
(function () {
    console.log(name);
    if (typeof name === 'undefined') {
        console.log(typeof name);
        console.log(name);
        // var name = 'Jack';
        console.log('Goodbye ' + name);
    } else {
        console.log('Hello ' + name);
    }
})();

#6 楼 @cwheart 这也是我的疑问的地方,是不是内部定义 name,会覆盖闭包对外部的引用环境?

#7 楼 @u1378130755 变量定义之前的行为好古怪呀

#7 楼 @u1378130755 这个不是诡异 这个是 var 声明的预解析 虽然你是在 if 里面 var 的 但是他还是会提到顶部预解析 当你赋值的时候才有值 我写的那个只是应对你的写法 其实最好不要这么写的 这种写法是标准的反面教材...

#9 楼 @zj0713001 貌似是这样的

console.log(a);
var a = '123';

这样不会报错,但是把下面那行注释了就会报错

#10 楼 @cwheart 貌似是会把用到的局部变量预声明一下

#11 楼 @cwheart 我记得 var 变量好像是预习声明,后赋值

#10 楼 @cwheart 如果这样都不报错,只能说明 js 设计的太粗糙。

#10 楼 @cwheart 你这两行都是在一个闭包里 都是 window 下面

console.log(a);
var a = '123';
function b(){
    console.log(a);
    var a = '123';
}
b()

这才是刚刚代码的场景...

#13 楼 @rasefon 他那个一定会报错啊,undefined,都不知道 a 是什么了,就先用

#15 楼 @u1378130755 如果会报错,那就应该是对的。

#16 楼 @rasefon 他可能是用浏览器测试的,正如 14 楼所言,都是包在 window 这个全局下,我用 node 测试 undefined

#17 楼 @u1378130755 只要不重名,闭包就可以访问词法作用域最近的那个变量。理论上应该是就是这样的。

#18 楼 @rasefon 也就是我的想法基本是成立的,重名会覆盖

node 就用 this, 浏览器既可以用 this 也可以用 window

#17 楼 @u1378130755 undefined 跟报错能一样???

#19 楼 @u1378130755 我也觉得会,实际上闭包和类差不多,局部变量就像类的成员变量,如果类成员变量和外部全局的重名了,也会覆盖掉的。

#19 楼 @u1378130755 给你个传送门 不要自己琢磨了 你琢磨的不全对 覆盖只是一部分 http://www.nowamagic.net/librarys/veda/detail/1623

#23 楼 @zj0713001 在天津生活的都是雷锋啊,

#24 楼 @u1378130755 在家休息了半个月 今天刚刚回到帝都开始上班 😄

最经典的闭包内访问外部变量的例子就是

var that = this;

clojure 的 scope 是 function 的。就是有个 function,就有一个 scope。

scope 有点像一棵树(其实就是个属性结构,全局是跟)。假设一个变量出现在子树里面,比如 a,然后编译器看到 a 了,就开始找 a 是什么东西,在本 scope 里面找,如果有定义,那么直接得到值。如果没找到,就去他爹那找,一直找下去,如果到全局了,还没找到,就报错了。

var a = 10; #编译器记录了一个 a到10 的映射。
a+1;

编译器读到 a 的时候,就找 a 是什么,发现 a 是 10,赋值,进行运算。

因为后面有 var 进行了变量声明,所以匿名函数所访问的 name 是函数内声明的 name。即便声明语句实际上并没有被执行。比如

var name = "outer";
(function () {
  console.log(name);
  if (false) {
    var name = "inner";
  }
  console.log(name);
})();

如果内部没有 var name 的话,其实可以直接拿到 name

var name = 'World!';
(function () {
    console.log(name);
})();

所以我的建议是,不要重复变量名。

#27 楼 @hisea 这个顶,经典,

#27 楼 @hisea

var name = 'World!';
var that=this;  
(   
    function () {
    console.log(that.name);
    if (typeof name === 'undefined') {
        var name = 'Jack';
        console.log('Goodbye ' + name);
    } else {
        console.log('Hello ' + name);
    }
})();

我这样修改,似乎也不对啊,是用 node 执行的,若果是浏览器,this 会指向 window 全局环境,node 好像也应该有全局环境吧

#28 楼 @yfractal 我觉得你的子树与根不合适,我举个例子

var name = "The Window";
  var object = {
    name : "My Object",
    getNameFunc : function(){
      return function(){
        return this.name;
      };
    }
  };
  alert(object.getNameFunc()());

按照你的逻辑,似乎应该是 My Object,事实上,看这个http://davidshariff.com/blog/what-is-the-execution-context-in-javascript/

@u1378130755 我有点过于随便了,而且是拿 scheme 往上套的(毕竟 js 的作者很大程度用了 scheme 的东西)。。。但应该大体是那个意思(也许差的很远。。。)。

这个是 this 的原因。在 JavaScript 中,那个东西叫了 方程,谁就是 this,如果没人叫的话,就是 window(其实叫 global 也差不多。。。)。

第一次,是 object call 了 getNameFunc,然后返回的是一个方程,随后,这个方程又被 call 了(第二括号),而这个时候已经没有 人叫这个方程了,所以 this 是 window,顾返回“The Window”.

35 楼 已删除

#34 楼 @yfractal 你这样解释,豁然开朗

按照 hisea 的说法应该是这样,在浏览器中是返回“My Object”的。 var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ var that = this; return function(){ return that.name; }; } }; alert(object.getNameFunc()());

#37 楼 @yfractal 没错,因为 that 指的是 getNameFunc 的调用者 object

#36 楼 @u1378130755 那就好!嘿嘿。JavaScript 坑太多了。但我说的都不严谨。。。就是那个意思吧。

var that = this;

是较常用的一个有用法,因为 this 在 callback 中会改变,所以嵌套定义 function 的时候经常使用。

具体到你的例子, 我的意思是这样:

var namez = 'World!';
(function () {
    var name = namez;
    console.log(name);
    if (typeof name === 'undefined') {
        console.log(typeof name);
        console.log(name);
        var name = 'Jack';
        console.log('Goodbye ' + name);
    } else {
        console.log('Hello ' + name);
    }
})();
//= require underscore
window.namespace = _.memoize(function(ns) {
  ns = ns || '';
  var parts = ns.split('.');
  var current = window;
  for (var i=0,len=parts.length; i < len; i++) {
    var part = parts[i];
    if (_.isUndefined(current[part])) {
      current[part] = {};
    }
    current = current[part];
  }
  return current;
});
namespace("app.utility");

app.utility.namez = 'hello'

(function(){
  console.log(app.utility.namez);
})()

希望有帮助

弄明白这个问题,首先你要理解 Javascript 作用域的问题,嵌套函数与被嵌套函数都有一着不同的作用链。函数在查找属性时首先会从作用链的第一个作用域查找属性,如果没有找到则继续去第二级作用域去找,到最后走到了全局环境(在浏览器中这个东西就是 window 对象)发现还是没有就会报错,这个过程是由下而上的。

看楼主的例子,总觉得是在 Coffeescript 里,如果不是的话可以这样:

var scp = { name: 'Foobar' };
(function () {
    console.log(name);
    if (typeof name === 'undefined') {
        console.log(typeof name);
        console.log(name);
        var name = 'Jack';
        console.log('Goodbye ' + name);
    } else {
        console.log('Hello ' + name);
    }
}).apply(scp);

不会污染全局……

变量声明提升 代码大概是这个意思

var name = 'World!';
(function () {
    var name; //变量声明提升
    console.log(name);
    if (typeof name === 'undefined') {
        console.log(typeof name);
        console.log(name);
       name = 'Jack';
        console.log('Goodbye ' + name);
    } else {
        console.log('Hello ' + name);
    }
})();
需要 登录 后方可回复, 如果你还没有账号请 注册新账号