JavaScript中this关键字

this一直是js中一个老生常谈的东西,可是咱们究竟该如何来理解它呢?
在《JavaScript高级程序设计》中,对this的解释是:javascript

this对象是在运行时基于函数的执行环境绑定的。html

咱们来逐字解读这句话:java

  • this是一个对象
  • this的产生与函数有关
  • this与执行环境绑定

说通俗一点就是,“谁调用的这个函数,this就是谁”。web


1、函数直接调用中的this

举个栗子:数组

var x = 1;

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

testThis();  //1复制代码

js中有一个全局对象window,直接调用函数testThis时,就至关于调用window下的testThis方法,包括直接声明的变量也都是挂载在window对象下的。浏览器

var x = 1;
function testThis() {
    this.innerX = 10;
    return 1;
}
testThis() === window.testThis();  // true
innerX === window.innerX;  // true
x === window.x;  // true复制代码

同理,在匿名函数中使用this也是指向的window,由于匿名函数的执行环境具备全局性。app

(function () {
    console.log(this); //window
})();复制代码

可是呢,凡事都有例外,js的例外就是严格模式。在严格模式中,禁止this关键字指向全局对象。函数

(function () {
 'use strict';
    console.log(this); //undefined
})();复制代码

2、对象方法调用中的this

再举个栗子:ui

var person = {
    "name": "shenfq",
    "showName": function () {
        console.log(this.name);
    } 
};

person.showName(); // 'shenfq'复制代码

此时,showName方法中的this指向的是对象person,由于调用showName的是person对象,因此showName方法中的 this.name 其实就是 person.name。this

可是若是咱们换个思路,把showName方法赋值给一个全局变量,而后在全局环境下调用。

var name = 'global',
    person = {
        "name": "shenfq",
        "showName": function () {
            console.log(this.name);
        } 
    },
    showGlobalName = person.showName;
showGlobalName(); // 'global'复制代码

能够看到,在全局环境中调用showName方法时,this就会指向window。

再换个思路,若是showName方法被其余对象调用呢?

var person = {
        "name": "shenfq",
        "showName": function () {
            console.log(this.name);
        } 
    },
    animal = {
        "name": "dog",
        "showName": person.showName
    };

animal.showName(); // 'dog'复制代码

此时的name又变成了animal对象下的name,再复杂一点,若是调用方法的是对象下的一个属性,而这个属性是另个对象。

function showName () {
    console.log(this.name);
}
var person = {
    "name": "shenfq",
    "bodyParts": {
        "name": "hand",
        "showName": showName
    },
    "showName": showName
};

person.showName(); // 'shenfq'
person.bodyParts.showName(); // 'hand'复制代码

虽然调用showName方法的最源头是person对象,可是最终调用的是person下的bodyParts,因此方法写在哪一个对象下其实不重要,重要的是这个方法最后被谁调用了,this指向的永远是最终调用它的那个对象。讲来说去,this也就那么回事,只要知道函数体的执行上下文就能知道this指向哪儿,这个规则在大多数状况下都适用,注意是大多数状况,少部分状况后面会讲。

最后一个思考题,当方法返回一个匿名函数,这个匿名函数里面的this指向哪里?

var name = 'global',
    person = {
        "name": "shenfq",
        "returnShowName": function () {
            return function () {
                console.log(this.name);
            }
        } 
    };

person.returnShowName()(); // 'global'复制代码

答案一目了然,匿名函数无论写在哪里,只要是被直接调用,它的this都是指向window,由于匿名函数的执行环境具备全局性。


3、new构造函数中的this

仍是先举个栗子:

function Person (name) {
    this.name = name;
}
var global = Peson('global'),
    xiaoming = new Person('xiaoming');

console.log(window.name); // 'global'
console.log(xiaoming.name); // 'xiaoming'复制代码

首先不使用new操做符,直接调用Person函数,这时的this任然指向window。当使用了new操做符时,这个函数就被称为构造函数。

所谓构造函数,就是用来构造一个对象的函数。构造函数老是与new操做符一块儿出现的,当没有new操做符时,该函数与普通函数无区别。

对构造函数进行new操做的过程被称为实例化。new操做会返回一个被实例化的对象,而构造函数中的this指向的就是那个被实例化的对象,好比上面例子中的xiaoming。

关于构造函数有几点须要注意:

  1. 实例化对象默认会有constructor属性,指向构造函数;
function Person (name) {
    this.name = name;
}
var xiaoming = new Person('xiaoming');

console.log(xiaoming.constructor); // Person复制代码
  1. 实例化对象会继承构造函数的原型,能够调用构造函数原型上的全部方法;
function Person (name) {
    this.name = name;
}
Person.prototype = {
    showName: function () {
        console.log(this.name);
    }
};
var xiaoming = new Person('xiaoming');

xiaoming.showName(); // 'xiaoming'复制代码
  1. 若是构造函数返回了一个对象,那么实例对象就是返回的对象,全部经过this赋值的属性都将不存在
function Person (name, age) {
    this.name = name;
    this.age  = age;
    return {
        name: 'innerName'
    };
}
Person.prototype = {
    showName: function () {
        console.log(this.name);
    }
};
var xiaoming = new Person('xiaoming', 18);

console.log(xiaoming); // {name: 'innerName'}复制代码

4、经过call、apply间接调用函数时的this

又一次举个栗子:

var obj = {
    "name": "object"
}

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

test.call(obj);   // 'object'
test.apply(obj);  // 'object'复制代码

callapply方法都是挂载在Function原型下的方法,全部的函数都能使用。

这两个函数既有相同之处也有不一样之处:

  • 相同的地方就是它们的第一个参数会绑定到函数体的this上,若是不传参数,this默认仍是绑定到window上。
  • 不一样之处在于,call的后续参数会传递给调用函数做为参数,而apply的第二个参数为一个数组,数组里的元素就是调用函数的参数。

语言很苍白,我只好写段代码:

var person = {
    "name": "shenfq"
};
function changeJob(company, work) {
    this.company = company;
    this.work    = work;
};

changeJob.call(person, 'NASA', 'spaceman');
console.log(person.work); // 'spaceman'

changeJob.apply(person, ['Temple', 'monk']);
console.log(person.work); // 'monk'复制代码

有一点值得注意,这两个方法会把传入的参数转成对象类型,无论传入的字符串仍是数字。

var number = 1, string = 'string';
function getThisType () {
    console.log(typeof this);
}

getThisType.call(number); //object
getThisType.apply(string); //object复制代码

5、经过bind改变函数的this指向

最后举个栗子:

var name = 'global',
    person = {
        "name": "shenfq"
    };
function test () {
    console.log(this.name);
}

test(); // global

var newTest = test.bind(person);
newTest(); // shenfq复制代码

bind方法是ES5中新增的,和call、apply同样都是Function对象原型下的方法-- Function.prototype.bind ,因此每一个函数都能直接调用。bind方法会返回一个与调用函数同样的函数,只是返回的函数内的this被永久绑定为bind方法的第一个参数,而且被bind绑定后的函数不能再被从新绑定。

function showName () {
    console.log(this.name);
}
var person = {"name": "shenfq"},
    animal = {"name": "dog"};

var showPersonName = showName.bind(person),
    showAnimalName = showPersonName.bind(animal);

showPersonName(); //'shenfq'
showAnimalName(); //'shenfq'复制代码

能够看到showPersonName方法先是对showName绑定了person对象,而后再对showPersonName从新绑定animal对象并无生效。

6、箭头函数中的this

真的是最后一个栗子:

var person = {
    "name": "shenfq",
    "returnArrow": function () {
        return () => {
            console.log(this.name);
        }
    }
};

person.returnArrow()(); // 'shenfq'复制代码

箭头函数是ES6中新增的一种语法糖,简单说就是匿名函数的简写,可是与匿名函数不一样的是箭头函数中的this表示的是外层执行上下文,也就是说箭头函数的this就是外层函数的this。

var person = {
    "name": "shenfq",
    "returnArrow": function () {
        let that = this;
        return () => {
            console.log(this == that);
        }
    }
};

person.returnArrow()(); // true复制代码

补充:

事件处理函数中的this:

var $btn = document.getElementById('btn');
function showThis () {
    console.log(this);
}
$btn.addEventListener('click', showThis, false);复制代码

点击按钮能够看到控制台打印出了元素节点。

事件结果
事件结果

其实事件函数中的this默认就是绑定事件的元素,调用事件函数时能够简单理解为

$btn.showThis()

只要单击了按钮就会已这种方式来触发事件函数,因此事件函数中的this表示元素节点,这也与以前定义的“谁调用的这个函数,this就是谁”相吻合。

eval中的this:

eval('console.log(this)'); //window
var obj = {
    name: 'object',
    showThis: function () {
        eval('console.log(this)');
    }
}
obj.showThis(); // obj复制代码

eval是一个能够动态执行js代码的函数,能将传入其中的字符串看成js代码执行。这个方法通常用得比较少,由于很危险,想一想动态执行代码,什么字符串都能执行,可是若是用得好也能带来很大的便利。

eval中的this与箭头函数比较相似,与外层函数的this一致。

固然这只针对现代浏览器,在一些低版本的浏览器上,好比ie七、低版本webkit,eval的this指向会有些不一样。

eval也能够在一些特殊状况下用来获取全局对象(window、global),使用 (1,eval)('this')


先写这么多,有须要再补充 ^ _ ^

参考:

  1. this - JavaScript | MDN
  2. Javascript的this用法
  3. (1,eval)('this') vs eval('this') in JavaScript?

原文连接

相关文章
相关标签/搜索