博客
博客
文章目录
  1. 一、函数直接调用中的 this
  2. 二、对象方法调用中的 this
  3. 三、new 构造函数中的 this
  4. 四、通过 call、apply 间接调用函数时的 this
  5. 五、通过 bind 改变函数的 this 指向
  6. 六、箭头函数中的 this
  7. 补充:
  8. 参考:

JavaScript 中 this 关键字

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

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

我们来逐字解读这句话:

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

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


一、函数直接调用中的 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,因为匿名函数的执行环境具有全局性。

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

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

(function () {
'use strict';
console.log (this); //undefined
})();

二、对象方法调用中的 this

再举个栗子:

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

person.showName (); // 'shenfq'

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

但是如果我们换个思路,把 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,因为匿名函数的执行环境具有全局性。


三、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'}

四、通过 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

五、通过 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 对象并没有生效。

六、箭头函数中的 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 一致。

当然这只针对现代浏览器,在一些低版本的浏览器上,比如 ie7、低版本 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?
支持一下
扫一扫,支持一下
  • 微信扫一扫
  • 支付宝扫一扫