前面我们学习 JavaScript 基础的对象章节时,曾经接触过 this 关键字。对象里面的 this 关键字指向当前对象。在学习 DOM 编程中的事件章节时,也遇到过 this 关键字,指向绑定事件的元素。
在本小节中,我们就一起来总结一下 JavaScript 中 this 指向的几种情况,同时来看一下如何对 this 的指向进行修改。
this 指向的默认情况
1. this 指向当前对象
如果是在对象里面使用 this,则指向当前对象。this 可以用在方法内,获取对对象属性的访问。示例如下:
const person = {
name: 'xiejie',
age: 18,
intro: function () {
console.log(this); // { name: 'xiejie', age: 18, intro: [Function: intro] }
console.log(`My name is ${this.name},I'm ${this.age} years old`); // My name is xiejie,I'm 18 years old
}
}
person.intro();
再来看一个构造函数中的 this 指向,也是同样指向当前对象,示例如下:
const Computer = function (name, price) {
this.name = name;
this.price = price;
}
Computer.prototype.showSth = function () {
console.log(`这是一台${this.name}电脑`);
}
let apple = new Computer('苹果', 15000);
let asus = new Computer('华硕', 4500);
apple.showSth(); // 这是一台苹果电脑
asus.showSth(); // 这是一台华硕电脑
2. 普通函数中的 this 指向
如果是普通调用的函数,this 则指向全局对象,如下:
const test = function () {
console.log(this); // global 对象
// 在 node 里面,全局对象就是 global 对象
// 如果是在浏览器里面,那么全局对象代表 window 对象
}
test(); // 普通函数调用,this 将会指向全局对象
需要说明的是,以普通函数的方式调用的时候,无论嵌套多少层函数,this 都始终指向全局对象,如下:
const test = function () {
const test2 = function () {
const test3 = function () {
console.log(this); // global 对象
}
test3();
}
test2();
}
test();
接下来我们再来看一个例子,如下:
var name = "PHP";
const obj = {
name: "JavaScript"
}
const show = function () {
console.log("Hello," + this.name);
}
obj.fn = show; // 将 show() 方法赋值给 obj 对象的 fn 属性
obj.fn(); // Hello,JavaScript
show(); // Hello,undefined 如果是浏览器环境则是 Hello,PHP
这里,我们将show()
方法赋值给 obj 对象的 fn 属性,所以在执行obj.fn()
时是以对象的形式来调用的,this 将会指向 obj 对象。而后面的show()
方法则是单纯的以普通函数的形式来进行调用的。所以 this 指向全局对象。
3. this 指向绑定事件的元素
关于这一种 this 的指向,我们在学习事件章节时也接触过。DOM 元素绑定事件时,事件处理函数里面的 this 指向绑定了事件的元素。这个地方一定要注意它和 target 的区别,target 是指向触发事件的元素。
示例如下:
<body>
<ul id="color-list">
<li>red</li>
<li>yellow</li>
<li>blue</li>
<li>green</li>
<li>black</li>
<li>white</li>
</ul>
<script>
// this 是绑定事件的元素
// target 是触发事件的元素 和 srcElememnt 等价
let colorList = document.getElementById("color-list");
colorList.addEventListener("click", function (event) {
console.log('this:', this);
console.log('target:', event.target);
console.log('srcElement:', event.srcElement);
})
</script>
</body>
当我点击如下位置时打印出来的信息如下:
有些时候我们会遇到一些困扰,比如在 div 节点的事件函数内部,有一个局部的 callback 方法,该方法被作为普通函数调用时,callback 内部的 this 是指向全局对象 window 的,示例如下:
<body>
<div id="div1">我是一个div</div>
<script>
window.id = 'window';
document.getElementById('div1').onclick = function(){
console.log(this.id); // div1
const callback = function(){
console.log(this.id); // 因为是普通函数调用,所以 this 指向 window
}
callback();
}
</script>
</body>
此时有一种简单的解决方案,可以用一个变量保存 div 节点的引用,如下:
<body>
<div id="div1">我是一个div</div>
<script>
window.id = 'window';
document.getElementById('div1').onclick = function(){
console.log(this.id); // div1
const that = this; // 保存当前 this 的指向
const callback = function(){
console.log(that.id); // div1
}
callback();
}
</script>
</body>
在 ECMAScript 5 的严格模式下,这种情况下的 this 已经被规定为不会指向全局对象,而是 undefined,如下:
<body>
<div id="div1">我是一个div</div>
<script>
window.id = 'window';
document.getElementById('div1').onclick = function(){
console.log(this); // <div id="div1">我是一个div</div>
const callback = function(){
"use strict"
console.log(this); // undefined
}
callback();
}
</script>
</body>
改变 this 的指向
在前面的小节中,我们已经总结出了在 JavaScript 中关于 this 指向的几种情况。但是,这个 this 的指向,我们是可以进行修改的。
接下来,我们就来看一看几种修改 this 指向的方法。
1. 方法借用函数修改 this 指向
前面我们有介绍过 JavaScript 中的方法借用模式,使用call()
或者apply()
可以借用其他对象的方法而不用通过继承。
在借用的同时,call()
和apply()
也算是间接修改了 this 的指向。示例如下:
const Test = function () {
this.name = "JavaScript";
this.say = function () {
console.log(`这是${this.name}`);
}
}
const test = new Test();
test.say(); // 这是JavaScript
const a = {
name: "PHP"
};
test.say.call(a); // 这是PHP
这里我们借用了 test 对象的say()
方法。本来对于 test 对象的say()
方法来讲,this 是指向 test 对象的,但是现在 a 对象借用了say()
方法以后,this 就指向了 a 对象,所以打印出了"这是PHP"。
2. bind 方法绑定 this 指向
第二种方式是可以通过bind()
方法来绑定 this 的指向。bind()
方法的语法如下:
fun.bind(thisArg[, arg1[, arg2[,
参数:
thisArg:当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。
arg1,arg2,...:当绑定函数被调用时,这些参数将置于实参之前传递给被绑定的方法。
具体的示例如下:
const a = {
name: "PHP"
};
const Test = function () {
this.name = "JavaScript";
this.say = function () {
console.log(`这是${this.name}`);
}.bind(a);
}
const test = new Test();
test.say(); // 这是 PHP
这里我们在say()
方法后面使用了bind()
方法,并将 a 对象作为参数传递进去。这个时候,当我们以 test 对象的身份来调用say()
方法时,打印出来的仍然是"这是PHP",这是因为bind()
方法在这里将 this 的指向绑定到了 a 对象上。
3. 箭头函数的 this 指向
当我们的 this 是以函数的形式调用时,this 指向的是全局对象。不过对于箭头函数来讲,却比较特殊。箭头函数的 this 指向始终为外层的作用域。
先来看一个普通函数作为对象的一个方法被调用时,this 的指向,如下:
const obj = {
x : 10,
test : function(){
console.log(this); // 指向 obj 对象
console.log(this.x); // 10
}
}
obj.test();
// { x: 10, test: [Function: test] }
// 10
可以看到,普通函数作为对象的一个方法被调用时,this 指向当前对象。在上面的例子中,就是 obj 这个对象,this.x 的值为 10。
接下来是箭头函数以对象的方式被调用的时候的 this 的指向,如下:
var x = 20;
const obj = {
x: 10,
test: () => {
console.log(this); // {}
console.log(this.x); // undefined
}
}
obj.test();
// {}
// undefined
这里的结果和上面不一样,this 打印出来为{}
,而 this.x 的值为 undefined。
为什么呢?实际上刚才我们有讲过,箭头函数的 this 指向与普通函数不一样,它的 this 指向始终是指向的外层作用域。所以这里的 this 实际上是指向的全局对象。
如果证明呢?方法很简单,将这段代码放入浏览器运行,在浏览器中用 var 所声明的变量会成为全局对象 window 的一个属性,如下:
接下来我们再来看一个例子,来证明箭头函数的this
指向始终是指向的外层作用域。
var name = "JavaScript";
const obj = {
name: "PHP",
test: function () {
const i = function () {
console.log(this.name);
// i 是以函数的形式被调用的,所以 this 指向全局
// 在浏览器环境中打印出 JavaScript,node 里面为 undeifned
}
i();
}
}
obj.test(); // JavaScript
接下来我们将 i 函数修改为箭头函数,如下:
var name = "JavaScript";
const obj = {
name : "PHP",
test : function(){
const i = ()=>{
console.log(this.name);
// 由于 i 为一个箭头函数,所以 this 是指向外层的
// 所以 this.name 将会打印出 PHP
}
i();
}
}
obj.test();// PHP
最后需要说一点的就是,箭头函数不能作为构造函数,如下:
const Test = (name, age) => {
this.name = name;
this.age = age;
};
const test = new Test("xiejie", 18);
// TypeError: Test is not a constructor
总结
-
所谓编程范式,指的是计算机编程的基本风格或典范模式。大致可以分为命令式编程和声明式编程。
-
面向对象的程序设计是站在哲学的角度上,将人类思维融入到了程序里面的一种编程范式。
-
描述对象的时候可以通过两个方面来进行描述,分别是对象的外观和功能。在程序中与之对象的就是属性和方法。
-
JavaScript 中的对象都是基于原型的,从一个原型对象中可以克隆出一个新的对象。
-
在 JavaScript 中每个对象都有一个原型对象。可以通过proto属性来找到一个对象的原型对象。
-
在其他语言中,对象从类中产生。而在 JavaScript 中,通过构造函数来模拟其他语言中的类。
-
类与对象的关系可以总结为,类是对对象的一种概括,而对象是类的一种具体实现。
-
面向对象的 3 大特征为封装,继承和多态。
-
封装是指对象的属性或者方法隐藏于内部,不暴露给外部。
-
继承是指一个子类继承一个父类。在继承了父类以后,子类就拥有了父类所有的属性和方法。
-
多态是指不同的对象可以拥有相同的方法,不过是以不同的方式来实现它。
-
this 的指向根据使用的地方不同分为好几种情况,但是我们可以通过一些方式来修改 this 的指向。
-