javascript对象、this、作用域和闭包的问题。(相同变量名查找问题)

以下几段代码,输出的结果不一样。希望有大神可以解答一下原理,谢谢!

var myvar = "TheWindow";
var object = {
    myvar : "MyObject",
    getVar : function(){
        return function(){
            return myvar;
        };
    }
};
console.log(object.getVar()());//输出TheWindow

function MyCont(){
    var myvar = "MyObject";
    this.getVar = function(){
        return function(){
            return myvar;
        };
    }
}
var obj = new MyCont();
console.log(obj.getVar()());//输出MyObject

然后我修改了一下,加上了this,输出结果又不同了。

var myvar = "TheWindow";
var object = {
    myvar : "MyObject",
    getVar : function(){
        return function(){
            return this.myvar;//此处加入了this,依旧输出TheWindow
        };
    }
};
console.log(object.getVar()());
function MyCont(){
    var myvar = "MyObject";
    this.getVar = function(){
        return function(){
            return this.myvar;//此处加入了this,输出变成了TheWindow,而不是原先的MyObject
        };
    }
}
var obj = new MyCont();
console.log(obj.getVar()());

然后我再改一下

var myvar = "TheWindow";
var object = {
    myvar1 : "MyObject",
    getVar : function(){
        return function(){
            return myvar1;//虽然object内含有myvar1属性,但是这样写会报错,但是如果写this.myvar1的话就不报错,输出undefined
        };
    }
};
console.log(object.getVar()());


var myvar = "TheWindow";
var object = {
    myvar : "MyObject",
    getVar : function(){
          return myvar;//此处去掉了function,还是输出TheWindow
    }
};
console.log(object.getVar());


var myvar = "TheWindow";
var object = {
    myvar : "MyObject",
    getVar : function(){
          return this.myvar;//对比上面,加入了this,则输出的是对象属性MyObject
    }
};
console.log(object.getVar());

最后还是要感谢一下,好心人帮忙解答问题,万分感谢!

阅读 3.5k
3 个回答

参考文献

这里主要考察的还是对this和作用域的理解,题主可以仔细阅读以下这两篇文章:

基本概念

对this的理解

this值的激活是由于函数调用时上下文的不同而导致的。
另外需要理解引用类型和非引用类型的判定:

  • 如果该表达式是引用类型,那么可根据引用时所处的上下文来确定this的值

  • 如果该表达式不是引用类型,那么this的值为null,而this为null时会默认将this转换为global;那么我们来

明确一下什么是引用类型,什么是非引用类型:

  • 如果标识符,则是引用类型

  • 如果是属性访问器,则是引用类型

  • 其他情况为非引用类型

下面看看引用类型得例子:

var testVar = 10;
function testFunc() {}

var testObj = {
  func: function() {
    .....
  }
};
testObj.func();
testObj['func']();

它们的运行中间结果为:

// 它们的表达式均为标识符,所以属于引用类型,且base均为global,所以它们的this被激活为global
var testVarReference = {
  base: global,
  propertyName: 'testVar'
};
var testFuncReference = {
  base: global,
  propertyName: 'testFunc'
};

// 它表达式均为属性访问器,所以属于引用类型,且base均为testObj,所以它们的this被激活为testObj
var testObjFuncReference = {
  base: testObj,
  propertyName: 'func'
};

另外有两个特殊情况:

  • 在with语句中调用函数,with语句内部所有的this隐式绑定为with对象

  • this在作为构造器调用的函数中的this隐式绑定为该函数构造器构造的对象

对Scope Chain的理解

作用域链是用于搜索上下文代码中出现的标识符的一组对象序列。搜索规则类似于原型链,如果标识符不在当前的作用域中,那就继续在它的父作用域中继续搜索,知道找到为止。

开始分析

我们对以上基本上有一定了解之后,可以开始分析代码了。

代码片段1

var myvar = "TheWindow";
var object = {
    myvar : "MyObject",
    getVar : function(){
        return function(){
            return myvar;
        };
    }
};
console.log(object.getVar()());//输出TheWindow

function MyCont(){
    var myvar = "MyObject";
    this.getVar = function(){
        return function(){
            return myvar;
        };
    }
}
var obj = new MyCont();
console.log(obj.getVar()());//输出MyObject
第一部分:
var myvar = "TheWindow";
var object = {
    myvar : "MyObject",
    getVar : function(){
        return function(){
            return myvar;
        };
    }
};
console.log(object.getVar()());//输出TheWindow

分析过程:

  • 这里考虑的是在函数getVar中查找标识符myVar的过程;而不是考虑考察this变量的值

  • 在getVar函数作用域中没有搜索到myVar标识符,继续查找父作用域

  • 父作用域中有定义myVar="TheWindow",搜索结束

  • 输出TheWindow

第二部分
function MyCont(){
    var myvar = "MyObject";
    this.getVar = function(){
        return function(){
            return myvar;
        };
    }
}
var obj = new MyCont();
console.log(obj.getVar()());//输出MyObject

分析过程:

  • 同样在作用域链中搜索myVar标识符

  • 在构造函数MyCount中有对myVar的定义,搜索结束

  • 输出MyObject

代码片段2

var myvar = "TheWindow";
var object = {
    myvar : "MyObject",
    getVar : function(){
        return function(){
            return this.myvar;//此处加入了this,依旧输出TheWindow
        };
    }
};
console.log(object.getVar()());
function MyCont(){
    var myvar = "MyObject";
    this.getVar = function(){
        return function(){
            return this.myvar;//此处加入了this,输出变成了TheWindow,而不是原先的MyObject
        };
    }
}
var obj = new MyCont();
console.log(obj.getVar()());
第一部分
var myvar = "TheWindow";
var object = {
    myvar : "MyObject",
    getVar : function(){
        return function(){
            return this.myvar;//此处加入了this,依旧输出TheWindow
        };
    }
};
console.log(object.getVar()());

分析过程:

  • 考察this值得判定

  • 表达式“object.getVar()()”:对于第一次函数调用,其中getVar是属性访问器,所以它的this值是object

  • 由于object.getVar调用之后返回一个匿名函数,所以在第二次函数调用时,也就是第二个括号执行时,由于括号左边的表达式既不是标识符,也不是属性访问器,所以它是一个非引用类型,此时this值为null

  • this值为null,默认转换为global

  • 输出TheWindow

第二部分
function MyCont(){
    var myvar = "MyObject";
    this.getVar = function(){
        return function(){
            return this.myvar;//此处加入了this,输出变成了TheWindow,而不是原先的MyObject
        };
    }
}
var obj = new MyCont();
console.log(obj.getVar()());

分析过程:

  • 首先要确定,这里搜索变量用到的this值,而不是作用域链中搜索

  • 最后的括号左边表达式是非引用类型,this值为global

  • 输出TheWindow

代码片段3

var myvar = "TheWindow";
var object = {
    myvar1 : "MyObject",
    getVar : function(){
        return function(){
            return myvar1;//虽然object内含有myvar1属性,但是这样写会报错,但是如果写this.myvar1的话就不报错,输出undefined
        };
    }
};
console.log(object.getVar()());


var myvar = "TheWindow";
var object = {
    myvar : "MyObject",
    getVar : function(){
          return myvar;//此处去掉了function,还是输出TheWindow
    }
};
console.log(object.getVar());


var myvar = "TheWindow";
var object = {
    myvar : "MyObject",
    getVar : function(){
          return this.myvar;//对比上面,加入了this,则输出的是对象属性MyObject
    }
};
console.log(object.getVar());
第一部分
var myvar = "TheWindow";
var object = {
    myvar1 : "MyObject",
    getVar : function(){
        return function(){
            return myvar1;//虽然object内含有myvar1属性,但是这样写会报错,但是如果写this.myvar1的话就不报错,输出undefined
        };
    }
};
console.log(object.getVar()());

分析过程:

  • myVar1左边没有this,此处myVar1通过作用域链搜索

  • 搜索失败,找不到该标识符

  • 而改成this.myVar1时,根据以上的分析,此处的this隐式转换为global,而global this变量也没有myVar1的声明:在chrome中会输出undefined,而在node下运行会报错!

第二部分
var myvar = "TheWindow";
var object = {
    myvar : "MyObject",
    getVar : function(){
          return myvar;//此处去掉了function,还是输出TheWindow
    }
};
console.log(object.getVar());

分析过程:

  • myvar左边没有this,使用作用域链搜索

  • 输出TheWindow

第三部分
var myvar = "TheWindow";
var object = {
    myvar : "MyObject",
    getVar : function(){
          return this.myvar;//对比上面,加入了this,则输出的是对象属性MyObject
    }
};
console.log(object.getVar());

分析过程:

  • myvar左边有this,需要判定this值

  • object.getVar为属性访问器,此处this为object

  • 输出MyObject

主要是this函数的指向问题:

当函数作为对象方法调用时,该函数的this值设为调用该方法的对象。

所以在对象中返回this.myvar指向的对象的myvar,返回MyObject,返回myvar则是TheWindow
在函数内使用

this.getVar = function(){
    return function(){ // 这里的this和一般定义函数一样是window
        return this.myvar;
    };
}

这里的this和一般定义函数一样是window,所以使用this.myvar返回window.myvar,返回myvar作用域一层一层向上找,找到了函数里的var myvar = 'MyObejct',这里如果不定义的话回继续向上找到全局定义的myvar='TheWindow'

让我来猜一下:
先把直接创建对象的拿出来说一说:

var myvar = "TheWindow";
var object = {
    myvar : "MyObject",
    getVar : function(){
        return function(){
            return myvar;
        };
    }
};
console.log(object.getVar()());//输出TheWindow
/*object.getVar() 返回一个匿名函数:function(){return myvar;},在全局范围内执行,所以输出TheWindow*/

var myvar = "TheWindow";
var object = {
    myvar : "MyObject",
    getVar : function(){
        return function(){
            return this.myvar;//此处加入了this,依旧输出TheWindow
        };
    }
};
console.log(object.getVar()());
/*同上,在全局范围内返回 this.myvar,this指向window*/

var myvar = "TheWindow";
var object = {
    myvar1 : "MyObject",
    getVar : function(){
        return function(){
            return this.myvar1;//虽然object内含有myvar1属性,但是这样写会报错,但是如果写this.myvar1的话就不报错,输出undefined
        };
    }
};
console.log(object.getVar()());
/*object.getVar()()最后返回一个myvar1变量,你可以对比以下两种情况:
console.log(myvar1);//not defined,报错
console.log(this.myvar1);//undefined
*/

var myvar = "TheWindow";
var object = {
    myvar : "MyObject",
    getVar : function(){
          return myvar;//此处去掉了function,还是输出TheWindow
    }
};
console.log(object.getVar());
/*因为object.getVar()是指在当前的环境(window全局)下执行函数,所以myvar是全局的*/
补充:2016.11.20
因为看了这篇文章:https://zhuanlan.zhihu.com/p/23804247
我觉得自己之前的理解是错的,就是object.getVar()这种情况,我把它内部的this当成window,(我记得权威指南里写到过:直接调用对象的方法的话,方法内的this指向的是window,而不是这个对象。
但看了上面链接里的文章,我觉得应该是自己之前记错了(或者理解错了)。
回到上面的结果,return myvar,在函数内执行这行代码,它应该会在函数内,参数,函数外这三个地方去寻找变量或参数,而函数个,对象object内并没有变量myvar,那个myvar是object.myvar,并不是变量myvar,所以,它找到了再外层的变量myvar.

var myvar = "TheWindow";
var object = {
    myvar : "MyObject",
    getVar : function(){
          return this.myvar;//对比上面,加入了this,则输出的是对象属性MyObject
    }
};
console.log(object.getVar());
/*其实以上都创建了object对象,对象内的this当然是指向object,但在全局范围内调用对象的方法,它的作用域的全局*/

再来说一说用function创建的,可以是一个函数,可以是有闭包的一个函数,可以是一个构造函数

function MyCont(){
    var myvar = "MyObject";//这里定义的只是局部变量,而不是对象的属性
    this.var="var"; //用this定义的才是对象的属性
    this.getVar = function(){
        return function(){
            return this.myvar;//此处加入了this,输出变成了TheWindow,而不是原先的MyObject
            //这个this并不是指向obj,而是指向调用这个匿名函数的对象
        };
    }
    this.get=function(){
        return this.var;//这里的this,是obj
    }
}
var obj = new MyCont();
console.log(obj.getVar());//返回function(){return this.myvar;};
console.log(obj.getVar()());//所以,这里(全局)实际上执行的是一个匿名函数function(){return this.myvar;};
console.log(obj.get());//返回var



function MyCont(){
    var myvar = "MyObject";
    this.getVar = function(){
        return function(){
            return myvar;
        };
    }
}
var obj = new MyCont();
console.log(obj.getVar());//返回function(){return myvar;};
console.log(obj.getVar()());//输出MyObject,同上,全局范围执行的匿名函数,myvar不是应该在全局内查找吗?
//为什么显示的却是局部的变量?我猜:它肯定就是闭包了。
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题