换个思路理解Javascript中的this

换个思路理解Javascript中的this

2017/07/27 · JavaScript
· this

原文出处: Leechikit   

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

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

从call方法开始

callmgm娱乐场, 方法允许切换函数执行的上下文环境(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
到底取何值,是在函数真正被调用执行的时候确定下来的,函数定义的时候确定不了。

对象方法中的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();

mgm娱乐场 1

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

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

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

构造函数中的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 的取值,大体上可以分为以下七种情况:

mgm娱乐场 2

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

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();

mgm娱乐场 3

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

普通函数中的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();

mgm娱乐场 4

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

闭包中的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();

这段代码的上下文如下图:mgm娱乐场 5

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

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

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

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

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

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

参数中的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;

全局中的this

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

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

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

mgm娱乐场 7

function foo(){

复杂情况下的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

图示如下:

mgm娱乐场 8

 

1 赞 6 收藏
评论

mgm娱乐场 9

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 () {

var self = this;

function f(){

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

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

}

f();

}

}

obj.foo();

如果 foo 函数不作为对象方法被调用:

var obj = {

x: 10,

foo: function () {

console.log(this);      //Window

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

}

};

var fn = obj.foo;

fn();

obj.foo 被赋值给一个全局变量,并没有作为 obj 的一个属性被调用,那么此时
this 的值是 window。

情况四:构造函数 prototype 属性

function Foo(){

this.x = 10;

}

Foo.prototype.getX = function () {

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

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

}

相关文章