JavaScript之例题中完全理解this

本文共 2025 字,看完只需 8 分钟javascript

概述

前面的文章讲解了 JavaScript 中的执行上下文,做用域,变量对象,this 的相关原理,可是我后来在网上看到一些例题的时候,依然没能全作对,说明本身有些细节还没能掌握,本文就结合例题进行深刻实践,讨论函数在不一样的调用方式 this 的指向问题。java

老规矩,先给结论 1 和 结论2:git

this 始终指向最后调用它的对象es6

“箭头函数”的this,老是指向定义时所在的对象,而不是运行时所在的对象。github

特别提示:
本文的例子,最好本身在浏览器控制台中去试一遍,看完过两天就会忘的,必定要实践。浏览器

1、隐式绑定

// 例 1
var name = "window";

function foo() {
    var name = "inner";

    console.log(this.name);
}

foo();  // ?
复制代码

输出:闭包

windowapp

例 1 中,非严格模式,因为 foo 函数是在全局环境中被调用,this 会被默认指向全局对象 window;函数

因此符合了咱们的结论一:post

this 始终指向最后调用它的对象

2、通常函数和箭头函数的对象调用

// 例 2
var name = "window";

var person = {
    name: "inner",
    show1: function () {
        console.log(this.name);
    },
    show2: () => {
        console.log(this.name);
    }
}

person.show1();  // ?
person.show2();  // ?
复制代码

输出:

inner
window

person.show1() 输出 inner 没毛病,person.show2() 箭头函数为何会输出 window 呢。MDN 中对 this 的定义是:

箭头函数不绑定 this, 箭头函数不会建立本身的this,它只会从本身的做用域链的上一层继承this。

再看本文前面给的结论:

“箭头函数”的this,老是指向定义时所在的对象,而不是运行时所在的对象。

因为 JS 中只有全局做用域和函数做用域,箭头函数在定义时的上一层做用域是全局环境,全局环境中的 this 指向全局对象自己,即 window。

3、call

// 例 3
var name = 'window'

var person1 = {
  name: 'person1',
  show1: function () {
    console.log(this.name)
  },
  show2: () => console.log(this.name),
  show3: function () {
    return function () {
      console.log(this.name)
    }
  },
  show4: function () {
    return () => console.log(this.name)
  }
}
var person2 = { name: 'person2' }

person1.show1()  // ?
person1.show1.call(person2)  // ?

person1.show2()  // ?
person1.show2.call(person2)  // ?

person1.show3()()  // ?
person1.show3().call(person2)  // ?
person1.show3.call(person2)()  // ?

person1.show4()()  // ?
person1.show4().call(person2)  // ?
person1.show4.call(person2)()  // ?
复制代码

输出:

person1
person2

window
window

window
person2
window

person1
person1
person2

上面 10 行打印,你对了几个呢?

首先:
person1.show1()person1.show1.call(person2) 输出结果应该没问题,call 的做用就是改变了调用的对象 为 person2

其次:
person1.show2()person1.show2.call(person2),因为调用的是箭头函数,和本文例 2 中是同样的,箭头函数定义时 this 指向的是上一层,也就是全局对象, 而且 箭头函数不绑定本身的 this, 因此经过 call()apply() 方法调用箭头函数时,只能传递参数,不能传递新的对象进行绑定。故打印的值都是 window。

进而:

function foo () {
    return function () {
      console.log(this.name)
    }
  }

foo()();
复制代码

博客前面的文章有讲过闭包,上面这段代码也是典型的闭包运用,能够看做:

function foo () {
    return function () {
      console.log(this.name)
    }
  }

var bar = foo();
bar();
复制代码

因此,很明显,被返回的内部函数实际上是在全局环境下被调用的。回到前面看咱们的结论 1,this 始终指向最后调用函数的对象,这句话的关键词应该是什么?我以为应该是 调用,何时调用,谁调用。

再回过头来看:
person1.show3()() 输出 window,由于内部函数在全局环境中被调用。

person1.show3().call(person2) 输出 person2, 由于内部函数被 person2 对象调用了。

person1.show3.call(person2)() 输出 window,也是由于内部函数在全局环境中被调用。

最后:
重点理解结论 2:

“箭头函数”的this,老是指向定义时所在的对象,而不是运行时所在的对象。

show4: function () {
    return () => console.log(this.name)
  }
复制代码

这段代码中,箭头函数是在 外层函数 show4 执行后才被定义的。为何?能够翻看我前面关于做用域链,执行上下文,变量对象的文章,函数在进入执行阶段时,会先查找内部的变量和函数声明,将他们做为变量对象的属性,关联做用域链,并绑定 this 指向。

因此:
person1.show4()() 输出 person1,由于外部函数在执行时的 this 为 person1, 此时定义了内部函数,而内部函数为外部函数的 this。

person1.show4().call(person2) 输出 person1,箭头函数不会绑定 this, 因此 call 传入 this 指向无效。

person1.show4.call(person2)() 输出 person2,由于外部函数在执行时的 this 为 person2,此时定义了内部函数,而内部函数为外部函数的 this。

4、构造函数中的 this

// 例 4
var name = 'window'

function Person (name) {
  this.name = name;
  this.show1 = function () {
    console.log(this.name)
  }
  this.show2 = () => console.log(this.name)
  this.show3 = function () {
    return function () {
      console.log(this.name)
    }
  }
  this.show4 = function () {
    return () => console.log(this.name)
  }
}

var personA = new Person('personA')
var personB = new Person('personB')

personA.show1()  // 
personA.show1.call(personB)  // 

personA.show2()  // 
personA.show2.call(personB)  // 

personA.show3()()  // 
personA.show3().call(personB)  // 
personA.show3.call(personB)()  // 

personA.show4()()  // 
personA.show4().call(personB)  // 
personA.show4.call(personB)()  //
复制代码

输出:

personA
personB

personA
personA

window
personB
window

personA
personA
personB

例 4 和 例 3 大体同样,惟一的区别在于两点:

  1. 构造函数中 this 指向被建立的实例
  2. 构造函数,也是函数,因此存在做用域,因此里面的箭头函数,它们的 this 指向,来自于上一层,就再也不是全局环境 window, 而是构造函数 的 this。

5、setTimeout 函数

// 例 5
function foo(){
  setTimeout(() =>{
    console.log("id:", this.id)
      setTimeout(() =>{
        console.log("id:", this.id)
      }, 100);
  }, 100);
}

foo.call({id: 111});  // 
复制代码

输出:

111
111

注意一点:
setTimeout 函数是在全局环境被 window 对象执行的,可是 foo 函数在执行时,setTimtout 委托的匿名箭头函数被定义,箭头函数的 this 来自于上层函数 foo 的调用对象, 因此打印结果才为 111;

6、setTimeout 函数 2

// 例 6
function foo1(){
  setTimeout(() =>{
    console.log("id:", this.id)
      setTimeout(function (){
        console.log("id:", this.id)
      }, 100);
  }, 100);
}


function foo2(){
  setTimeout(function() {
    console.log("id:", this.id)
      setTimeout(() => {
        console.log("id:", this.id)
      }, 100);
  }, 100);
}

foo1.call({ id: 111 });  // ?
foo2.call({ id: 222 });  // ?
复制代码

输出:

111
undefined

undefined
undefined

例 5 中已经提到,setTimeout函数被 window 对象调用,若是 是普通函数,内部的 this 天然指向了全局对象下的 id, 因此为 undefined,若是是箭头函数,this 指向的就是外部函数的 this。

7、嵌套箭头函数

// 例 7
function foo() {
  return () => {
    return () => {
      return () => {
        console.log("id:", this.id);
      };
    };
  };
}

var f = foo.call({id: 1});

var t1 = f.call({id: 2})()();  // 
var t2 = f().call({id: 3})();  // 
var t3 = f()().call({id: 4});  // 
复制代码

输出:

1
1
1

这段代码是为了巩固咱们的结论2:

“箭头函数”的this,老是指向定义时所在的对象,而不是运行时所在的对象。

  1. foo.call({}) 在执行时,内部的第一层箭头函数才被定义
  2. 箭头函数没法绑定 this, 因此 call 函数指定 this 无效
  3. 箭头函数的 this 来自于上一层做用域(非箭头函数做用域)的 this

总结

有本书中有提到,当理解 JavaScript 中的 this 以后,JavaScript 才算入门,我深觉得然。

缘由是,要完全理解 this, 应该是创建在已经大体理解了 JS 中的执行上下文,做用域、做用域链,闭包,变量对象,函数执行过程的基础上。

有兴趣深刻了解上下文,做用域,闭包相关内容的同窗能够翻看我以前的文章。

参考连接:

1:this、apply、call、bind
2: 从这两套题,从新认识JS的this、做用域、闭包、对象
3: 关于箭头函数this的理解几乎彻底是错误的
4: 深刻JS系列

欢迎关注个人我的公众号“谢南波”,专一分享原创文章。

掘金专栏 JavaScript 系列文章

  1. JavaScript之变量及做用域
  2. JavaScript之声明提高
  3. JavaScript之执行上下文
  4. JavaScript之变量对象
  5. JavaScript原型与原型链
  6. JavaScript之做用域链
  7. JavaScript之闭包
  8. JavaScript之this
  9. JavaScript之arguments
  10. JavaScript之按值传递
  11. JavaScript之例题中完全理解this
  12. JavaScript专题之模拟实现call和apply
相关文章
相关标签/搜索