深入学习js之——this#6

815 阅读6分钟

深入学习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系列目录

欢迎添加我的个人微信讨论技术和个体成长。

欢迎关注我的个人微信公众号——指尖的宇宙,更多优质思考干货