换个思路理解Javascript中的this

2017/07/27 · JavaScript
· this

原文出处: Leechikit   

在网上很多文章都对 Javascript 中的 this
做了详细的介绍,但大多是介绍各个绑定方式或调用方式下 this
的指向,于是我想有一个统一的思路来更好理解 this
指向,使大家更好判断,以下有部分内容不是原理,而是一种解题思路。

Javascript 中的
this,有时候让人迷惑,所以总结了一下关于this指向的问题。

1. 全局代码中的this

      this在全局上下文中,它的值是全局对象本身(Global
Object),在浏览器中就是Window  Object,如下图示。

      图片 1

      看下面几个例子:

//Global scope
//The implicit property of the global object 
var foo1 = “abc”;
console.log(this.foo1 == window.foo1); console.log(foo1); console.log(this.foo1); console.log(window.foo1);  //true abc abc abc
 
//The implicit property of the global object 
foo2 = “def”;  
console.log(this.foo2 == window.foo2); console.log(foo2); console.log(this.foo2); console.log(window.foo2);      //true def def def
 
//The explicit property of the global object 
this.foo3 = “gh”;  
console.log(this.foo3 == window.foo3); console.log(foo3); console.log(this.foo3); console.log(window.foo3); //true gh gh gh

 

从call方法开始

call 方法允许切换函数执行的上下文环境(context),即 this
绑定的对象。

大多数介绍 this 的文章中都会把 call
方法放到最后介绍,但此文我们要把 call 方法放在第一位介绍,并从
call 方法切入来研究 this ,因为 call 函数是显式绑定 this
的指向,我们来看看它如何模拟实现(不考虑传入 nullundefined
和原始值):

Function.prototype.call = function(thisArg) { var context = thisArg; var
arr = []; var result; context.fn = this; for (let i = 1, len =
arguments.length; i < len; i++) { arr.push(‘arguments[‘ + i + ‘]’);
} result = eval(“context.fn(” + arr + “)”); delete context.fn; return
result; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Function.prototype.call = function(thisArg) {
    var context = thisArg;
    var arr = [];
    var result;
 
    context.fn = this;
 
    for (let i = 1, len = arguments.length; i < len; i++) {
        arr.push(‘arguments[‘ + i + ‘]’);
    }
 
    result = eval("context.fn(" + arr + ")");
 
    delete context.fn;
 
    return result;
}

从以上代码我们可以看到,把调用 call
方法的函数作为第一个参数对象的方法,此时相当于把第一个参数对象作为函数执行的上下文环境,而
this 是指向函数执行的上下文环境的,因此 this
就指向了第一个参数对象,实现了 call
方法切换函数执行上下文环境的功能。

在函数中 this
到底取何值,是在函数真正被调用执行的时候确定下来的,函数定义的时候确定不了。

2. 函数代码中的this

      共有四种函数调用方式:

     
1)方法调用模式:作为对象属性调用,obj.func();(详见8.对象中的this)

 
    2)函数调用模式:指向全局,直接调用func();     (详见1.全局代码中this)

      3)构造器调用模式:使用new调用;  
                (详见6.作为构造器调用的函数中的this)

 
    4)call/apply调用模式:动态改变this指向。        
(详见7.手动设置函数调用时this的值) 

 

     
函数代码中this值的第一个特性,同时也是最主要的特性,即:它并非静态地绑定在函数上。

      this的值是在进入执行上下文(Context
Excution)的阶段确定的,在函数代码中,其值每次都不尽相同。然而进入执行代码阶段,其值就不能改变。

     
如果想给this赋一个新值是不可能的,因为在那时this根本就不是变量了。

      结合例子来分析:

var foo = {
    name: “Foo”
};
var bar= {
    name: “ting”,
    sayName: function() {
        console.log(this ===
bar);
        console.log(“My name is: ” + this.name);
    }
};
bar.sayName();   //true My name is: ting
foo.sayName = bar.sayName;
foo.sayName();      //false My name is: Foo

     
通过上述结果分析可知:调用bar对象的sayName方法时,this指向bar对象。通过赋值方式使得foo的sayName方法指向bar的sayName方法,再调用foo对象的sayName方法时,this指向foo对象。Why?

     
this的值在函数中是非静态的,它的值确定是在执行代码阶段(函数调用时,具体代码执行前)。this的值是由激活上下文代码的调用者提供的,比如调用函数的外层上下文,更重要的是,this的值是由调用表达式的形式决定的。因此说,this并非静态绑定在函数上。

     
由于this非静态地绑定在函数上,那么我们是否可以在函数中动态地修改this的值呢?

var foo = {
    name: “Foo”
};
var person = {
    name: “ting”,
    sayName: function() {
        console.log(this == person);
        this = foo;
        console.log(“My name is: ” + this.name);
    }
};
person.sayName();   //My name is Foo
foo.sayName = person.sayName;
foo.sayName();      //My name is ting

     
在sayName方法中,动态地修改this的值,重新执行以上的代码,发现this的值引用错误。这是由于一旦进入代码执行阶段(函数调用时,执行代码前),this的值确定,不能被改变。

      影响函数代码中this值变化的因素:在通常的函数调用中,this是由激活上下文代码的调用者来提供的,即调用函数的父上下文(parent
context)。this值取决于调用函数的方式。 

     
误区:一些文章或JavaScript书籍中提到:this值取决于函数定义的方式,如果它是全局函数,this设置为全局对象,如果它是一个对象的方法,那么this总是指向这个对象。这是绝对不正确的。

      切记一点:正是调用函数的方式影响了上下文中this的值。

      eg:
全局函数被调用方式的不同形式激活,不同的调用方式导致了不同的this值。

function foo() {
    console.log(this);
}
foo();  //Window

console.log(foo == foo.prototype.constructor);  //true
foo.prototype.constructor();  //foo {}

      eg: 对象定义的方法,执行该方法,this值未必指向该对象。

var foo = {
    bar: function() {
        console.log(this);
        console.log(this === foo);
    }
};
foo.bar(); //foo true
var fooTest = foo.bar;
console.log(fooTest === foo.bar);
//同一个function,不同的调用表达式,this值不同
fooTest(); //Window false

      调用方式如何影响this的值?
为充分理解this值的确定,需要详细分析其内部类型之一,即引用类型(Reference
type)。

 

对象方法中的this

在模拟 call 方法的时候,我们使用了对象方法来改变 this
的指向。调用对象中的方法时,会把对象作为方法的上下文环境来调用。

既然 this
是指向执行函数的上下文环境的,那我们先来研究一下调用函数时的执行上下文情况。

下面我门来看看调用对象方法时执行上下文是如何的:

var foo = { x : 1, getX: function(){ console.log(this.x); } }
foo.getX();

1
2
3
4
5
6
7
var foo = {
    x : 1,
    getX: function(){
        console.log(this.x);
    }
}
foo.getX();

图片 2

从上图中,我们可以看出getX方法的调用者的上下文是foo,因此getX方法中的
this 指向调用者上下文foo,转换成 call
方法为foo.getX.call(foo)

下面我们把其他函数的调用方式都按调用对象方法的思路来转换。

因为 this
的取值是函数执行上下文(context)的一部分,每次调用函数,都会产生一个新的执行上下文环境。当代码中使用了
this,这个 this
的值就直接从执行的上下文中获取了,而不会从作用域链中搜寻。

3. 引用类型(Reference Type)

     
使用伪代码将引用类型的值表示为拥有两个属性的对象,包括base(拥有属性的那个对象),对象中的propertyName(属性名)。

var valueofRefereceType = {
    base: <base object>,
    propertyName: <property name>
};

      引用类型的值只有两种情况:

1)当我们处理一个标识符时

2)或属性访问器

     
标识符的处理过程另作讨论,目前只需知道,该算法的返回值中,总是一个引用类型的值,这对于this来说非常重要。

     
标识符是:变量名、函数名、函数参数名或全局对象中未识别的属性名。例如,下面标识符的值:

var foo;
function bar() { } 

      在操作中间结果的过程中,引用类型对应的值如下:

var fooReference = {
    base: global,
    propertyName: ‘foo’
};

var barReference = {
    base: global,
    propertyName: ‘bar’
};

      伪代码GetValue描述从一个引用类型中得到对象真正的值。

function GetValue(value) {
    if( Type(value) != Reference) {
        return value;
    }
    var base = GetBase(value);
    if(base === null) {
        throw return new ReferenceTypeError;
    }
    return base.[[Get]](GetPropertyName(value));
}

     
内部的[[Get]]方法返回对象属性真正的值,包括对原型链中继承的属性分析。

GetValue(fooReference); // 10
GetValue(barReference); // function object “bar”

      属性访问器:点(.)语法或括号([])语法

foo.bar();
foo[‘bar’]();

      在中间计算的返回值中,引用类型的值为:

var fooReference = {
    base: foo,
    propertyName: ‘bar’
};

GetValue(fooReference); // function object “bar”

   
  引用类型的值域函数上下文中的this如何相关?
–从最重要的意义上来说。

 这个关联的过程是这篇文章的核心。

      一个函数上下文中,确定this值的通用规则如下:

1)在一个函数上下文中,this由调用者提供,由调用函数的方式来决定。

2)如果调用括号()的左边是引用类型的值,this值将设为引用类型值的base对象(base
object)。

3)如果调用括号()的左边不是引用类型的值(与引用类型类型不同的任何其它属性),this值为null,不过实际不存在this值为null的情况,因为当this值为null的时候,其值会被隐示转换为全局对象。(注:第5版的ECMAScript,已经不强迫转换为全局变量,而是赋值为undefined。) 

      看些例子,理解函数上下文中this值。

function foo() {
    console.log(this);
}
foo(); //window

      调用括号()的左边是引用类型的值,因为foo是一个标识符。

var fooReference = {
    base: global,
    propertyName: ‘foo’
};

      相应地,this设置为引用类型的base对象,即全局对象。

      使用属性访问器:

var foo = {
    bar: function() {
        console.log(this);
    }
};
foo.bar(); //foo

     
我们再次拥有一个引用类型,其base是foo对象,在函数bar激活时用作this。

var fooBarReference = {
    base: foo,
    propertyName: ‘bar’
};

      但是,使用另外一种形式激活相同的函数,我们得到其它的this值。

var foo = {
  bar: function () {
    console.log(this);
  }
};
var test = foo.bar;
test(); 

//window

     
因为test作为标识符,生成了引用类型的其它值,其base(全局对象)用作this的值。

var testReference = {
    base: global,
    propertyName: ‘test’
};

     
从上述例子中可以分析得知:使用不同的表达式激活函数为何会导致不同的this值,原因在于生成了引用类型(Type
Reference)不同的中间值
。  

      再看两个例子,加深印象。

var foo = function() {
    console.log(this);
};
foo(); //window,because
var fooReference = {
    base: global,
    propertyName: ‘foo’
};

console.log(foo = foo.prototype.constructor);
//另外一种形式的调用表达式
foo.prototype.constructor(); //foo.prototype,because
var fooPrototypeConstructor = {
    base: foo.prototype,
    propertyName: ‘constructor’
};

      通过调用方式动态确定this值的经典例子。

function foo() {
    console.log(this.bar);
}
var x = {bar: 10};
var y = {bar: 20};
x.test = foo;
y.test = foo;

x.test(); //10
y.test(); //20

      

构造函数中的this

function Foo(){ this.x = 1; this.getX = function(){ console.log(this.x);
} } var foo = new Foo(); foo.getX();

1
2
3
4
5
6
7
8
function Foo(){
    this.x = 1;
    this.getX = function(){
        console.log(this.x);
    }
}
var foo = new Foo();
foo.getX();

执行 new
如果不考虑原型链,只考虑上下文的切换,就相当于先创建一个空的对象,然后把这个空的对象作为构造函数的上下文,再去执行构造函数,最后返回这个对象。

var newMethod = function(func){ var context = {}; func.call(context);
return context; } function Foo(){ this.x = 1; this.getX = function(){
console.log(this.x); } } var foo = newMethod(Foo); foo.getX();

1
2
3
4
5
6
7
8
9
10
11
12
13
var newMethod = function(func){
    var context = {};
    func.call(context);
    return context;
}
function Foo(){
    this.x = 1;
    this.getX = function(){
        console.log(this.x);
    }
}
var foo = newMethod(Foo);
foo.getX();

关于 this 的取值,大体上可以分为以下七种情况:

4. 函数调用和非引用类型

     
正如前面所提到的,当调用括号的左边不是引用类型而是其它类型,这个值自动设置为null,结果为全局对象。

      思考如下这种表达式:

(function () {
    console.log(this);
})(); //null —> global

     
在这个例子中,我们有一个函数对象但不是引用类型的对象(它不是标识符,也不是属性选择器),相应地,this值最终设为全局对象。

      更多复杂的例子:

var foo = {
    bar: function() {
        console.log(this);
    }
};
foo.bar(); //foo
(foo.bar)(); //foo

(foo.bar = foo.bar)(); //Window
(false || foo.bar)(); //Window
(foo.bar, foo.bar)(); //Window

     
一个属性访问器,它的中间值应该是引用类型的值,但是在某些调用中,我们得到的this值不是base对象,而是global对象。Why?

     
原因在于后面3个的调用中,应用一定的操作符之后,调用括号左边的值不再是引用类型的值。

      第一个例子 — 调用括号左边是引用类型的值,this为base对象,即foo;

      第二个例子 —
组运算符并不适用,如上面所提到的,从引用类型获取一个对象真正值的方法,如GetValue。
相应地,在组运算的返回中,得到的仍是一个引用类型。因此this值再次被设为base对象,即foo;

      第三个例子 —
与组操作符不同,赋值运算符触发了GetValue方法,返回的结果是函数对象,而不是引用类型的值,这意味着this值会被设置为null,最终会被变成全局对象。

      第四个例子 — 逻辑或运算符触发了GetValue方法,返回的结果……

      第五个例子 — 逗号运算符触发了GetValue方法,返回的结果是…… 

 

图片 3

情况一:全局 & 调用普通函数

5. 引用类型和this为null

     
例外的情况:调用括号左侧是引用类型的值,但this却为null,最终被隐示转换为全局对象(global
object)。情况成立的条件是:当引用类型值的base对象恰好为活跃对象(activiation
object)。例如:内部子函数在父函数中被调用。局部变量、内部函数、形式参数存储在给定函数的激活对象中。 
 

function foo() {
  function bar() {
    console.log(this); // Window
  }
  bar(); // the same as AO.bar()
}

foo(); 

     
活跃对象总是作为this返回,值为null(用伪代码表示AO.bar()相当于null.bar())。正如上面例子提到的,this的值最终由null隐示转换为全局对象。

      上述例子的变形:

function foo() {
  function bar() {
    console.log(this); // Window
  }
  return bar(); // the same as AO.bar()
}
foo(); 

      有一种情况与上述情况又不一样:使用with。

     
如果with对象包含函数属性,并且在with语句块中调用该函数。with语句会将该对象添加到作用域链的最前面,即在活跃对象之前。相应地,有了引用类型的值(标识符或属性访问器),其base对象不再是活跃对象,而是with语句对象。另外,值得一提的是,它不仅仅只针对内部函数,也针对全局函数,原因在于with对象比作用域链最前面的对象(全局对象或活跃对象)还要靠前。

var x =10;
with({
    foo: function() {
        console.log(this.x);
    },
    x: 20
}) {
    foo();  //20
}

//because
var fooReference = {
    base: _withObject,
    propertyName: ‘foo’
};

     
同样的情况出现在catch语句的实际参数中函数调用,catch对象添加到作用域的最前端,即活动对象或全局对象的前面。但是,这个特定的行为被确认为ECMAScript-262-3的bug,在ECMAScript-262-5中被修复了。这样,在特定的活动对象中,this不是指向catch对象,而是指向全局对象。

try {
    throw function() {
        console.log(this);
    };
} catch (e) {
    e();  //ES3标准里是_catchObject,ES5标准里是Window
}
//ES3
var eReference = {
    base: _catchObject,
    propertyName: ‘e’
};
//ES5
var eReference = {
    base: global,
    propertyName: ‘e’
};

     
同样的情况出现在命名函数的递归调用中。在第一次递归调用中,base对象是父活跃对象或全局对象,在递归调用中,base对象应该是存储着函数表达式可选名称的特定对象。但是,在这种情况下,this总是指向全局对象。

(function foo(bar) {
  alert(this);
  !bar && foo(1);
})();

 

DOM事件处理函数中的this

DOMElement.addEventListener(‘click’, function(){ console.log(this); });

1
2
3
DOMElement.addEventListener(‘click’, function(){
    console.log(this);
});

把函数绑定到DOM事件时,可以当作在DOM上增加一个函数方法,当触发这个事件时调用DOM上对应的事件方法。

DOMElement.clickHandle = function(){ console.log(this); }
DOMElement.clickHandle();

1
2
3
4
DOMElement.clickHandle = function(){
    console.log(this);
}
DOMElement.clickHandle();

图片 4

在全局环境中,this 永远指向 window。

6. 作为构造器调用的函数中的this

     
函数作为构造函数时,通过new操作符创建对象实例的过程包括:首先调用Foo()函数内部的[[construct]]方法,其次,在对象创建之后,调用内部的[[call]]方法。所有相同的函数“Foo”将this设置为新创建的对象

function Foo() {
    console.log(this);
    this.x = 10;
}
var a = new Foo();
console.log(a.x);

     
再看一例:Person不做为构造函数调用,而是作为普通函数在全局作用域中被调用,this指向Window,因此全局对象中的name被修改为mars。至于Person.name的值为空字符串,有点奇怪?
更奇怪的是将代码中Person函数的name全部换为zzz或其它标识,Person.zzz为undefined??
原因是:name是函数的一个内部参数,标识函数的名字,由于赋给Person的函数为匿名函数,因此name为空字符串,但是用其它标识符,由于Person中没有定义该属性,因此值为undefined。

var name = ‘window_mars’;
var Person = function (name) {
    this.name = name;
};
Person(‘mars’);
console.log(name);         //mars
var s1 = Person.name;      //””
var o = new Person(“ting”);
console.log(o.name);       //ting

     
下面的例子可以很好地说明name属性为函数自带的属性值,且其值不能被覆盖。

var name = ‘window_mars’;
var Person = function (name) {
    this.name = name;
};
Person.name = name;
Person.name1 = name;
var s1 = Person.name;      //””
var s2 = Person.name1;     //”window_mars”
var o = new Person(“ting”);
console.log(o.name);       //ting

 

普通函数中的this

var x = 1; function getX(){ console.log(this.x); } getX();

1
2
3
4
5
var x = 1;
function getX(){
    console.log(this.x);
}
getX();

这种情况下,我们创建一个虚拟上下文对象,然后普通函数作为这个虚拟上下文对象的方法调用,此时普通函数中的this就指向了这个虚拟上下文。

那这个虚拟上下文是什么呢?在非严格模式下是全局上下文,浏览器里是
window ,NodeJs里是 Global ;在严格模式下是 undefined

var x = 1; function getX(){ console.log(this.x); } [viturl
context].getX = getX; [viturl context].getX();

1
2
3
4
5
6
7
var x = 1;
function getX(){
    console.log(this.x);
}
 
[viturl context].getX = getX;
[viturl context].getX();

图片 5

console.log(this === window);    //true

7. 手动设置函数调用时this的值(apply()、call()) 

     
Function.prototype原型上定义了两个方法,允许手动设置函数调用时的值,这两个方法分别是:apply()和call()。这两个方法都接受第一个参数作为调用上下文中this的值,而这两个方法的区别是传递参数,对于apply()方法,第二个参数是数组类型(或类数组对象,比如arguments),而对于call()方法接受任意多的参数(逗号分隔)。这两个方法的第一个参数如果不传或为null,那this的值为Window。

      例一: 

var b = 10;
function a(c) {
    console.log(this.b);
    console.log(c);
}
a(20); //this.b is 10, c is 20

a.apply({b:20},30); //this.b is 20, c is 30
b.apply({b:40},[40]); //this.b is 40, c is 40

      例二:

var myObject = {};
var myFunction = function(param1, param2) {
    this.x = param1;
    this.y = param2;
    console.log(this); //Object {x: “kkk”, y: “ooo”} 
};
myFunction.apply(myObject, [“kkk”, “ooo”]);
console.log(myObject); //Object {x: “kkk”, y: “ooo”} 
myFunction.call(myObject, “kkk”, “ooo”);
console.log(myObject); //Object {x: “kkk”, y: “ooo”} 

闭包中的this

var x = 1; var foo = { x: 2, y: 3, getXY: function(){ (function(){
console.log(“x:” + this.x); console.log(“y:” + this.y); })(); } }
foo.getXY();

1
2
3
4
5
6
7
8
9
10
11
12
var x = 1;
var foo = {
    x: 2,
    y: 3,
    getXY: function(){
        (function(){
            console.log("x:" + this.x);
            console.log("y:" + this.y);
        })();
    }
}
foo.getXY();

这段代码的上下文如下图:图片 6

这里需要注意的是,我们再研究函数中的 this 指向时,只需要关注
this 所在的函数是如何调用的, this
所在函数外的函数调用都是浮云,是不需要关注的。因此在所有的图示中,我们只需要关注红色框中的内容。

因此这段代码我们关注的部分只有:

(function(){ console.log(this.x); })();

1
2
3
(function(){
    console.log(this.x);
})();

与普通函数调用一样,创建一个虚拟上下文对象,然后普通函数作为这个虚拟上下文对象的方法立即调用,匿名函数中的
this
也就指向了这个虚拟上下文。图片 7

普通函数在调用时候(注意不是构造函数,前面不加 new),其中的 this 也是指向
window。

8. 对象中的this

     
对象中this值的判断,其实最终也牵涉到函数中this值的判断,因此可按照函数上下文中this值的判断规则来确定this值。
看下面几个例子。

     
函数getName()上下文中this值的判断,引用类型中间值的base对象是person对象,this值为person对象,如下面代码所示。

var person = {
    name: “ting”,
    getName: function() {
        console.log(this); //Object {name: “ting”, getName: function}
        console.log(this.name); //ting
    }
};
person.getName();

     
函数getName()和getGlobalName()上下文中this值的判断,this值分别为person对象和全局对象。

var name = “global-ting”;
function getGlobalName () {
    return this.name;
}
var person = {
    name: “ting”,
    getName: function() {
        return this.name;
    }
};
var s1 = person.getName(); //”ting”
var s2 = getGlobalName();  //”global-ting”

      上述例子的变形:

var name = “global-ting”;
function getGlobalName () {
    return this.name;
}
var person = {
    name: “ting”,
    getName: getGlobalName
};
var s1 = person.getName(); //”ting”
var s2 = getGlobalName();  //”global-ting”

      上述例子的变形:

function getGlobalName () {
    return this.name;
}
var person = {
    name: “ting”,
    getName: getGlobalName
};
var hehe = {
    name: “haha”,
    getName: getGlobalName
}
var s1 = person.getName(); //”ting”
var s2 = getGlobalName();  //”global-ting”
var s3 = hehe.getName(); //”haha”

参数中的this

var x = 1; var foo = { x: 2, getX: function(){ console.log(this.x); } }
setTimeout(foo.getX, 1000);

1
2
3
4
5
6
7
8
var x = 1;
var foo = {
    x: 2,
    getX: function(){
        console.log(this.x);
    }
}
setTimeout(foo.getX, 1000);

函数参数是值传递的,因此上面代码等同于以下代码:

var getX = function(){ console.log(this.x); }; setTimeout(getX, 1000);

1
2
3
4
var getX = function(){
    console.log(this.x);
};
setTimeout(getX, 1000);

然后我们又回到了普通函数调用的问题。

var x = 10;

9. DOM事件中的this

      示例一:el指向绑定该事件的dom元素,事件处理程序中的this指向Window

<script type=”text/javascript”>
        function test_this(el) {
            var element = el;
            var scope = this;
        }
</script>
<div id=”news” class=”overlay” onclick=”test_this(this);”>点击我</div>

图片 8   

     
示例二:event指向触发的事件MouseEvent,this指向绑定该事件的DOM元素。

<script type=”text/javascript”>
    function test_this(event) {
        var e = event;
        var scope = this;
    }
    document.getElementById(“news”).onclick = test_this;
</script>
<div id=”news” class=”overlay”>点击我</div>

图片 9 
 图片 10

     
示例三:与示例二的结果一致,即event指向事件MouseEvent,this为绑定该事件的DOM元素。

<script type=”text/javascript”>
    function test_this(event) {
        var e = evemt;
        var scope = this;
    }
    document.getElementById(“news”).addEventListener(“click”, test_this, false);
</script>
<div id=”news” class=”overlay”>点击我</div>

 

全局中的this

全局中的 this 指向全局的上下文

var x = 1; console.log(this.x);

1
2
var x = 1;
console.log(this.x);

图片 11

function foo(){

10. 检测一下

     
exam1:baz为全局变量,函数执行完后,没有被释放;bar为局部变量,函数立即执行完,该变量被释放,因此报错。

(function () {
    baz = 5;
    var bar = 10;
})();
console.log(baz); //5

console.log(bar); //报错,ReferenceError:bar is not defined.

      exam2:严格模式下会报错:ReferenceError:bar is not defined.

“use strict”;
(function () {
    baz = 5;   //报错
    var bar = 10;
})();
console.log(baz);

      exam3:两处的a都是在全局域中被操作。

a = 2;
var a = 1;
console.log(a); //1

 

      终于整理完了^-^.

 

 

 

时间:2014-10-19

地点:合肥 

引用:

        

        

 

复杂情况下的this

var x = 1; var a = { x: 2, b: function(){ return function(){ return
function foo(){ console.log(this.x); } } } }; (function(){ var x = 3;
a.b()()(); })();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var x = 1;
var a = {
    x: 2,
    b: function(){
        return function(){
            return function foo(){
                console.log(this.x);
            }        
        }
    }
};
 
(function(){
    var x = 3;
    a.b()()();
})();

看到上面的情况是有很多个函数,但我们只需要关注 this
所在函数的调用方式,首先我们来简化一下如下:

var x = 1; (function(){ var x = 3; var foo = function(){
console.log(this.x); } foo(); });

1
2
3
4
5
6
7
8
var x = 1;
(function(){
    var x = 3;
    var foo = function(){
        console.log(this.x);
    }
    foo();
});

this 所在的函数 foo
是个普通函数,我们创建一个虚拟上下文对象,然后普通函数作为这个虚拟上下文对象的方法立即调用。因此这个
this指向了这个虚拟上下文。在非严格模式下是全局上下文,浏览器里是
window ,NodeJs里是 Global ;在严格模式下是 undefined

console.log(this);    //Window

总结

在需要判断 this 的指向时,我们可以安装这种思路来理解:

  • 判断 this 在全局中OR函数中,若在全局中则 this
    指向全局,若在函数中则只关注这个函数并继续判断。
  • 判断 this 所在函数是否作为对象方法调用,若是则 this
    指向这个对象,否则继续操作。
  • 创建一个虚拟上下文,并把this所在函数作为这个虚拟上下文的方法,此时
    this 指向这个虚拟上下文。
  • 在非严格模式下虚拟上下文是全局上下文,浏览器里是 window
    ,Node.js里是 Global ;在严格模式下是 undefined

图示如下:

图片 12

 

1 赞 6 收藏
评论

图片 13

console.log(this.x);  //10

}

foo();

情况二:构造函数

所谓的构造函数就是由一个函数 new
出来的对象,一般构造函数的函数名首字母大写,例如像
Object,Function,Array 这些都属于构造函数。

function Foo(){

this.x = 10;

console.log(this);    //Foo {x:10}

}

var foo = new Foo();

console.log(foo.x);      //10

上述代码,如果函数作为构造函数使用,那么其中的 this 就代表它即将 new
出来的对象。

但是如果直接调用 Foo 函数,而不是 new Foo(),那就变成情况1,这时候 Foo()
就变成普通函数。

function Foo(){

this.x = 10;

console.log(this);    //Window

}

var foo = Foo();

console.log(foo.x);      //undefined

情况三:对象方法

如果函数作为对象的方法时,方法中的 this 指向该对象。

var obj = {

x: 10,

foo: function () {

console.log(this);        //Object

console.log(this.x);      //10

}

};

obj.foo();

注意:若是在对象方法中定义函数,那么情况就不同了。

var obj = {

x: 10,

foo: function () {

function f(){

console.log(this);      //Window

console.log(this.x);    //undefined

}

f();

}

}

obj.foo();

可以这么理解:函数 f 虽然是在 obj.foo
内部定义的,但它仍然属于一个普通函数,this 仍指向 window。

在这里,如果想要调用上层作用域中的变量 obj.x,可以使用 self 缓存外部
this 变量。

var obj = {

x: 10,

foo: function () {

发表评论

电子邮件地址不会被公开。 必填项已用*标注