JavaScript 闭包如何访问外部变量

ane · 2014年02月10日 · 最后由 gerald 回复于 2014年03月07日 · 4365 次阅读
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?

共收到 43 条回复

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);
    }
})();
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册