美文网首页
JavaScript浅析 -- this

JavaScript浅析 -- this

作者: Da_xiong | 来源:发表于2018-07-08 21:07 被阅读0次

要说js令很多初学者头痛的点,那么this肯定是其中之一,但如果不理解this,很多开发任务就会出错。本文先梳理了常见的几种this的情况,然后再揪其本质,最后讲解与this相关的一些方法,希望能对其他有困惑的小伙伴有帮助。

一、this的指向规则

1.全局环境下执行函数,其中的this指向window
var message = 'window';
function fn() { 
  // use 'strict';
  var message = 'fn';
  return this.message;
}
fn(); // window

如果该函数开启了严格模式(即函数第一句的use 'strict'),那么函数里面的this则指向undefined。因为严格模式规定了this不能指向window。

2.构造函数里的this指向实例对象
function Fun() {
  this.getThis = function() {
    return this;
  }
}
var fun = new Fun();
console.log(fun.getThis()===fun, fun.getThis()===window); // true false
3.对象的方法的this指向.前面的对象
var a = {
  b: function() { return this; },
  c: {
    d: function() { return this; }
  }
};
console.log(a.b()===a, a.c.d()===a.c); // true true

这里要注意的一点是,如果将函数赋值给一个变量,再通过变量调用,则此时已经相当于直接在全局环境执行函数,this指向的会是window。

var fun = a.b;
console.log(fun()===window, fun()===a); // true false

实际上也是将函数b的地址值给了fun,所以通过fun调用已找不到上一级的a。而通过a.b则是先找到a的地址再找到b的地址,此时执行函数a.b()仍保持this指向a。

4.回调函数的this指向window
[1, 2, 3].forEach(function(value) {
  console.log(this); // window
});

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

回调函数的this之所以指向window,实际上是因为回调函数会先放到任务队列里,等到主执行栈为空的时候才拿出来执行,而此时主执行栈的环境为全局环境(又回到第一点)。

5.dom监听事件里的this指向dom
// html code
<body>
  <button>click me</button>
</body>
// js code
document.querySelector('body').onclick = function(e) {
  console.log(this, e.target); // 点击按钮时,this是body而e.target是button
}

二、this的指向本质

小伙伴们在开发中大多遇到的this就是上面的几种情况,记住了基本都够用了。但还是简单讲讲this内在的本质。
实际上我们执行函数的时候都是在执行fn.call(调用者)。call就是绑定函数内部的this为第一个参数,当第一个参数为空对象{}、null、undefined时内部this指向window(后面还会讲call,此处知道这个就可以了)。

  1. 执行对象函数,调用者就是函数前面的东西,所以a.b.c()相当于c.call(a.b)
  2. 全局环境下执行函数,fn()前面没有调用者,实际上相当于fn.call(undefined),而当第一个参数为undefined时this就变成了window。

而由于第一个参数是调用者只有在调用的时候才知道,所以this其实是动态确定的,即没法定义时就确定,只有调用时才确定。

var name = "The Window";
var object = {
  name : "My Object",
  getNameFunc : function(){
    return function(){
      return this.name;
    };
  }
};
console.log(object.getNameFunc()()); //The Window

对于上面的例子同样分析,object.getNameFunc()返回的是一个函数,相当于a.b.c()中的c,而object.getNameFunc()前面没有对象,所以最后的调用相当于object.getNameFunc().call(undefined),所以自然指向window而不是object。若要指向object该如何操作呢?一是变成object.getNameFunc().call(object),二是固定内部的this(非常常用)。

var name = "The Window";
var object = {
  name : "My Object",
  getNameFunc : function(){
    var that = this;
    return function(){
      return that.name;
    };
  }
};
console.log(object.getNameFunc()()); //My Object

三、可以改变this的三个方法

1.call方法

fn.call(obj, a, b)是以第一个参数(若为空对象{}、null、undefined时默认传入window)的身份去调用fn,后面的a、b即函数的参数。类似平时看到的obj.fn(a, b)。而函数内的this就是传入的第一个参数。他的作用主要有以下三个:
(1) 借用原型方法:

// 获得编辑器中被选中的指定类型的节点
function getSelectorNodes (editor, type) {
  var selection = window.getSelection(),
      selectNodes = Array.prototype.filter.call(editor.querySelectorAll(type), function(item) {
        return selection.containsNode(item, true) && item.tagName===type;
      });
  return selectNodes;
};

上面的例子,节点对象本身没有filter方法,但通过call借用了数组原型上的filter来使用。另一个借用场景是实例中的原生方法被覆盖了,也可以用此法调用原型方法。

var obj = {};
obj.hasOwnProperty('toString') // false

// 覆盖掉继承的 hasOwnProperty 方法
obj.hasOwnProperty = function () {
  return true;
};
obj.hasOwnProperty('toString') // true

Object.prototype.hasOwnProperty.call(obj, 'toString') // false

(2) 将类数组对象转为数组:
类数组对象,指的是属性名为数字,并且有length的对象,但却不是真正的数组,没有数组的push等方法。如字符串、Dom元素集、函数的arguments等。

var str = 'hello world';
console.log(str instanceof Array); // false

如果要将这些类数组对象转成真正的数组,从而拥有数组的方法,可以通过slice来实现转换,具体代码如下:

var str = 'hello world';
var arr = Array.prototype.slice.call(str);
console.log(arr instanceof Array); // true

通过上面的代码可以将字符串转成真正的数组,从而拥有数组的push、forEach等方法。这里可能有人要问了,如果只是要用数组的方法,直接通过第一点说的call借用不可以吗?如Array.prototype.forEach.call(str, function(){})。答案是肯定可以,只是此法处理的效率会比真正的数组处理要慢,所以还是推荐先转成数组再调用forEach。
(3) 绑定回调函数中的this:
如上面我们讲到的Dom监听事件里面的this,若要让他其中的this指向我们想要的对象而不是Dom对象本身,可以通过call来实现。

var obj = {};
var fn = function() {
  console.log(this);
};
document.querySelector('body').onclick = function(e) {
  fn.call(obj); // 如果不绑定obj,则默认this为body节点对象
};
2.apply方法

fn.apply(obj, [a, b])大致与call相似,不同的是apply函数的参数是以数组的形式传进去的。而利用这点特性我们经常用apply来处理数组,其用处大概有以下四点:
(1) 借用其他方法处理数组:

var arr = [5, 15, 8, 3];
Math.max.apply(null, arr);  // 借用max寻找数组的最大元素

对于函数的多个参数储存在数组里的情况,以往我们可能需要一个个取出来,但借用此法可以直接将数组作为参数传入进去,得到想要的结果。
(2) 将数组的空元素变为undefined:
由于函数参数传空的时候,函数内部读到该参数为视为undefined。所以通过apply方法,利用Array构造函数将数组的空元素变成undefined。

Array.apply(null, ['a', ,'b']); // [ 'a', undefined, 'b' ]

空元素与undefined的差别在于,数组的forEach、for...in、Object.keys()方法会跳过空元素,但是不会跳过undefined。因此,遍历内部元素的时候,会得到不同的结果。
(3) 将类数组对象转为数组。
(4) 绑定回调函数中的this。

3.bind方法

fn.bind(obj, a, b)与call的传参很类似,但效果却有很大差别,call和apply调用是立即执行,而bind调用则是每次返回一个新的函数,这个函数里面的this被改成obj,且函数的参数绑定了默认初始值a, b。他的主要用处就是改变函数的this。
如上面的绑定回调函数的this,最好的实现使用bind。因为apply和call都会立即执行,所以上面使用call需要在外面嵌套一层使得函数在调用时才执行,而bind直接返回的就是个函数不执行,具体执行时机依据回调函数的规则。将上面代码改写如下:

var obj = {};
var fn = function() {
  console.log(this);
};
document.querySelector('body').onclick = fn.bind(obj);

四、总结

最后依然来一个总结:

  1. 代码调用者.函数()调用实际上执行的是函数.call(调用者),函数中的this就是传入的调用者,当为{}、null、undefined时调用者就是window。
  2. 改变函数中的this有三个相关方法,当需要返回新函数时用bind,当需要立即执行时用call和apply;call常用于借用原型方法,apply常用于处理数组。

相关文章

  • 2018-04-11

    JavaScript作用域链浅析 1、作用域 作用域指的是变量存在的范围。在ES5规范中,JavaScript只有...

  • JavaScript浅析 -- this

    要说js令很多初学者头痛的点,那么this肯定是其中之一,但如果不理解this,很多开发任务就会出错。本文先梳理了...

  • JavaScript面向对象

    JS本身为我们提供了Array、Date、Math等不少对象(见《浅析JavaScript的对象系统》),但在实际...

  • JavaScript面向对象程序设计—创建对象的模式

    JS本身为我们提供了Array、Date、Math等不少对象(见《浅析JavaScript的对象系统》),但在实际...

  • javaScript原型浅析

    在本文中主要探讨JavaScript中原型的相关知识。 JavaScript中所有的函数默认都有一个名为proto...

  • 浅析javascript递归

    浅析递归 一、写递归的思路 1、先写主体2、假设函数具备该功能3、补充边界 二、实例 1、先来个简单的递归 // ...

  • JavaScript浅析 -- 闭包

    一、什么是闭包 关于闭包的解释有很多,而我个人理解的闭包,就是引用了局部作用域变量的函数。他由以下两部分构成: 可...

  • 浅析设计模式-JavaScript

    一、工厂模式 目的: 创建功能类似的对象,创建后对其进行一些初始化的操作和处理; 提供给工厂客户一个创建对象的接口...

  • JavaScript系列:浅析Array

    前言 本文是结合JavaScript30,对Array的回顾与总结,主要结合问题总结一些API的用法。部分语法使用...

  • 浅析JavaScript ES 6

    本文主要讲的是与ES5的区别,笔者也只是初次接触ES6,更多的只是了解一下ES6的新特性。 1.let命令 1.1...

网友评论

      本文标题:JavaScript浅析 -- this

      本文链接:https://www.haomeiwen.com/subject/okdssftx.html