深入学习js系列是自己阶段性成长的见证,希望通过文章的形式更加严谨、客观地梳理js的相关知识,也希望能够帮助更多的前端开发的朋友解决问题,期待我们的共同进步。
如果觉得本系列不错,欢迎点赞、评论、转发,您的支持就是我坚持的最大动力。
开篇
this关键字是JavaScript中最复杂的机制之一,它是一个很特别的关键字,被自动定义在所有函数的作用域 中。
跟别的语言大相径庭的是:js的this总是指向一个对象,而具体指向哪个对象是在运行时基于函数的执行环境动态绑定的,而非函数被声明时候的环境。
为什么要使用this
如果学习this的代价很大,但是对于我们平时工作并不大,我们干嘛要付出这么大的代价学习呢?的确,在介绍怎么做之前我们需要先明白为什么。
除去不常用的with和eval的情况,具体到实际应用中,this的指向大致可以分为以下四种:
- 1.作为对象的方法调用。
- 2.作为普通函数调用。
- 3.构造器调用。
- 4.Function.prototype.call 或者 Function.prototype.apply 下面分为这四种情况分别进行调用:
1.作为对象的方法调用:
当函数作为对象的方法被调用的时候,this指向该对象:
var obj = {
a: 1,
getA: function () {
console.log(this === obj); // true
console.log(this.a); // 1;
}
}
obj.getA();
2.作为普通函数被调用:
当函数不作为对象的属性被调用时候,也就是我们所说的普通函数方式,此时的this总是指向全局的对象。在浏览器的js里面,这个全局对象是window对象。
// 创建全局的name对象 挂载在window上面
window.name = "globalName";
var getName = function () {
return this.name;
}
console.log(getName()); // 输出的是 globalName
或者
window.name = "globalName";
var myObject = {
name: "louis",
getName: function () {
return this.name;
}
}
var getName = myObject.getName;
console.log(getName()); // "globalName"
有时候我们会遇到一些困扰,比如在事件节点的div函数内部,有一个局部的callback方法,callback被作为普通的函数被调用时,callback内部的this指向了window,但我们往往想让它的指向div节点.
<div id = "div1">我是一个div</div>
window.id = "window";
document.getElementById('div1').onclick = function () {
alert(this.id); // 输出:'div1'
var callback = function () {
alert(this.id); // 输出:'window'
}
callback();
};
此时有一种简单的解决方案,可以用一个变量保存div节点的引用:
document.getElementById('div1').onclick = function () {
var that = this; // 保存div的引用
var callback = function () {
alert(that.id); // 输出:'div1'
}
callback();
}
在ECMAScript 2015 中的严格模式下,这种情况下的this指向已经被规定为不会指向全局对象,而是undefined:
function func() {
"use strict"
alert(this); // undefined
}
func();
3.构造器的调用:
js中没有类,但是可以从构造器中创建对象,同时也提供了new运算符,使得构造器看起来像是一个类,
除了宿主提供的一些内置函数,大部分js函数都可以当成构造器使用,构造器的外表看起来和普通的函数没有什么区别,他们的区别在于调用方式,当使用new运算符调用函数的时候,该函数总是返回一个对象,通常情况下,构造器里面的this就是指向返回的这个对象。
var MyClass = function () {
this.name = "louis";
}
var obj = new MyClass();
console.log(obj.name);
但是new调用构造器时候,还要注意一个问题,如果构造器显式的返回了一个object对象那么此次运算结果最终会返回这个对象,而不是我们之前期待的this:
var MyClass = function () {
this.name = 'sven';
return { // 显式地返回一个对象
name: 'anne'
}
};
var obj = new MyClass();
alert(obj.name); // 输出:anne”
如果构造器不显式的返回任何数据,或是返回一个非对象类型的数据,就不会造成上述问题。
var MyClass = function () {
this.name = 'sven'
return 'anne'; // 返回string类型
};
var obj = new MyClass();
alert(obj.name); // 输出:sven”
4.Function.prototype.call 或者 Function.prototype.apply调用
跟普通函数调用相比,用 Function.prototype.call 或者 Function.prototype.apply可以动态的改变传入函数的this:
var obj1 = {
name: "louis",
getName: function () {
return this.name;
}
}
var obj2 = {
name: "kerry";
}
console.log(obj1.getName()); // 输出: louis
console.log(obj1.getName.call(obj2)); // 输出: kerry
call 和 apply 方法能够很好的体现 js的函数式语言特性 在js中几乎每一次编写函数式语言风格的代码都离不开call和apply
5.丢失的this
下面看一个经常遇到的问题:
var obj = {
myName: "louis",
getName: function () {
return this.name;
}
}
console.log(obj.getName()); // louis;
var getName2 = obj.getName;
console.log(getName2()) // undefined
当调用obj.getName时,getName方法是作为obj对象的属性被调用的,根据上文提到的规律,此时的this指向obj对象,所以obj.getName()输出'louis'。
当用另外一个变量getName2来引用obj.getName,并且调用getName2时, 此时是普通函数调用方式,this是指向全局window的,window上面并没有挂载任何属性所以程序的执行结果是undefined。
再看另一个例子,document.getElementById这个方法名实在有点过长,我们大概尝试过用一个短的函数来代替它,如同prototype.js等一些框架所做过的事情:
var getId = function (id) {
return document.getElementById(id);
};
getId('div1');
我们也许思考过为什么不能用下面这种更简单的方式
var getId = document.getElementById;
getId( 'div1' );
现在不妨花1分钟时间,让这段代码在浏览器中运行一次
<div id="div1">我是一个div</div>
var getId = document.getElementById;
getId( 'div1' );
在chrome friefox IE10 中执行过后就会发现,这段代码抛出一个异常,这是因为很多引擎的document.getElementById 方法的内部实现中需要用到this,这个this本来被期望指向document,当getElementById方法作为document对象的属性被调用时,方法内部的this确实是指向document的。
但是当getId来引用document,getElementById之后,再调用getId,此时就成了普通的函数调用了,函数内部的this指向了window,而不是原来的document。
我们可以尝试利用apply把document当做this传递给getId函数,修正 this指向问题。
document.getElementById = (function(func){
return function(){
return func.apply(document,arguments);
}
})(document.getElementById);
var getId = document.getElementById;
var div = getId('div1');
alert(div.id);
深入学习JavaScript系列目录
- #1 【深入学习js之——原型和原型链】
- #2 【深入学习js之——词法作用域和动态作用域】
- #3 【深入学习js之——执行山下文栈】
- #4 【深入学习js之——变量对象】
- #5 【深入学习js之——作用域链】
- #6 【深入学习js之——实际开发场景中的this指向】
- #7 【深入学习js之——执行上下文】
- #8 【深入学习js之——闭包】
- #9 【深入学习js之——参数按值传递】
- #10 【深入学习js之——call和apply】
- #11 【深入学习js之——类数组对象与arguments】
- #12 【深入学习js之——创建对象的各种方式以及优缺点】
欢迎添加我的个人微信讨论技术和个体成长。
欢迎关注我的个人微信公众号——指尖的宇宙,更多优质思考干货