zhengrenzhe's blog   About

JavaScript中的this

js 中的 this 是一个强大的东西,通过它我们可以方便的获取对象的引用,但是 this 能用的地方实在太多了,以至于刚上手可能会觉得有点复杂,下面就总结一下在浏览器和 nodejs 环境中 this 的用法。

在全局环境中

现在js可以运行在browser或nodejs环境中。 在browser中的全局范围时,this等于 window 。 在nodejs中有两种执行js的方式,两种方式的全局范围下的this也不同:

console.log(this); // {}
console.log(global);
/*
{ DTRACE_NET_SERVER_CONNECTION: [Function],
  DTRACE_NET_STREAM_END: [Function],
  ...
  console: [Getter] }
*/

console.log(this === global); // false

a = 'b';

console.log(this.a); // undefined
console.log(global.a); //b

在function中

js中function有四种使用场景:

如果function是event的回调,此时function里的this指向的是绑定这个事件的dom。另外如果在html中使用属性方式添加事件时,this指向的也是这个dom

<button id="s" onclick="console.log(this)"></button>
//
[Log] <button id=​"s" onclick=​"console.log(this)​">​</button>​ (this.html, line 9)

直接执行一个函数的话,函数里的this指向的是全局环境。不过当在严格模式时,this是undefined。

在构造函数里的this,在通过new调用后会指向新生成的实例。

function a(){
    this.name = 'droiz';
}
var b = new a();
console.log(b.name); // droiz

在对象的方法里使用的this,指向这个对象:

var a = {};
a.name = 'droiz';
a.print = function(){
    console.log(this.name)
}

而如果在这个方法里写一个函数,这个函数内部使用this,此时this就会指向全局的this。因为虽然它也是在这个方法里调用的,但是他的调用方式不是通过对象的方法,而是直接执行的,所以属于直接执行的方式,这时this自然就不是这个对象了。

除使用字面量创建的对象外,在对象里使用this的方式还有通过prototype来调用。根据原型继承的原则,对象在自身没有找到指定的属性时,会沿着他的原型链一直向上查找。当使用prototype来让实例继承多种方法时,prototype上的方法中的this指向的是新生成的实例。

function a(){}
a.prototype.print = function(first_argument) {
    console.log(this.name)
};
a.prototype.name = 'droiz';

var b = new a();
b.print(); // droiz

这里虽然使用this是在prototype的方法里,但this指向的并不是prototype,而是这个新生成的实例。查找name属性也会从对象b开始,而不是b的prototype。这时如果在b上重写了name,就会覆盖掉prototype中的name,这时打印的this.name就不是prototype中的name了。

function a(){}
a.prototype.print = function(first_argument) {
    console.log(this.name);
};
a.prototype.name = 'droiz';

var b = new a();
b.name =  'WTF';

b.print(); // WTF

然而还有一点比较迷惑的就是,当一个对象上的方法最为参数传入另一个函数时,此时this指向全局的this。

function a(){
}

a.prototype.name = 'droiz';
a.prototype.print = function(first_argument) {
    console.log(this.name);
};

var b = new a();
b.print(); //droiz

function c(m){
    m();
}

c(b.print); //undefined , 此时this指向全局的this

虽然有些难以理解,但是符合预期,因为在函数c中运行的m,它表现的并不是通过对象的方法来运行的,而是直接来运行的,所以这时函数里的this将指向全局的this。

所以综上,在判断函数中的this是什么是时,可以通过函数调用的表现形式来判断:

更改this

在不同的使用环境下,this是不同的,但也能手动改变他们。改变this有三种函数:

这三个方法都继承自Function构造函数。

bind方法可以返回一个新的函数,这个新的函数与之前的函数有相同的函数体,但不同的是可以通过bing方法传入的第一个参数改变这个新函数的this。

function a(){
    console.log(this.name);
}

var b = {
    name: 'droiz'
}

var c = a.bind(b);
c(); // droiz

正常情况下,函数a运行时,this是全局的this(window或global),通过bind方法,传入第一个参数,他将作为新返回的函数的this值,也就是函数c。a与c有相同的函数体,只是this不同,这时运行函数c就会显示出 doriz 了。bind也可接受更多的参数,在第一个参数之后的参数表示这个新创建函数在执行时将会被传入的参数。

function a(h){
    console.log(h);
}

var c = a.bind(this, 10);
c(); // 10

callapply的作用与传入参数的作用与bind基本相同,只不过bind将返回新函数,call与apply将不会返回新函数,而是在被调用函数上直接执行。

function a(h){
    console.log(this.name)
}

var b = {
    name: 'droiz'
}

a.call(b); // droiz
a.apply(b); // droiz

而call与apply的区别在于,call接受除第一个参数之外的若干个参数,他们将作为被调用函数的参数传入被调用函数中,而apply只接受两个参数,第二个参数是一个数组或类数组对象,数组里的元素将作为被调用函数的参数传入被调用 函数中。

function a(q, w, e){
    console.log(q, w, e);
}

a.call(this, 1, 2, 3); // 123
a.apply(this, [1, 2, 3]); //123

bind, call, apply最大的作用就是可以动态的改变调用函数中的this以及向其他借用其他函数来实现一些功能,这可是非常有用的,下面是一些使用场景:

var arr = [1,2,3,4,5,6,7];
console.log(Math.max.apply(this, arr)); // max本来是接受一系列参数的,通过apply你可以直接借用max来返回数组中的最大值

var nodelist = document.querySelectorAll('body span');
var arr2 = Array.prototype.slice.call(a,0); // querySelectorAll返回的是一个nodelist,这是一个类数组对象,通过调用数组的slice方法,将他变成一个真正的数组。

<button id="s">click</button>
<p id="pp">123</p>

<script>
    var p = document.getElementById('pp');
    document.getElementById('s').addEventListener('click', function(){
        console.log(this)
    }.bind(p));
</script>
// 点击button后将会显示 <p id="pp">123</p>

以上就是一些关于this的总结。

← 快速排序非递归实现  JS中与箭头有关的一些东西 →