JavaScript之例题中彻底理解this

栏目: JavaScript · 发布时间: 6年前

内容简介:前面的文章讲解了 JavaScript 中的执行上下文,作用域,变量对象,this 的相关原理,但是我后来在网上看到一些例题的时候,依然没能全做对,说明自己有些细节还没能掌握,本文就结合例题进行深入实践,讨论函数在不同的调用方式 this 的指向问题。老规矩,先给结论 1 和 结论2:this 始终指向最后调用它的对象

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

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

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

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

特别提示:

本文的例子,最好自己在浏览器控制台中去试一遍,看完过两天就会忘的,一定要实践。

一、隐式绑定

// 例 1
var name = "window";

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

    console.log(this.name);
}

foo();  // ?
复制代码

输出:

window

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

所以符合了我们的结论一:

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

二、一般函数和箭头函数的对象调用

// 例 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。

三、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。

四、构造函数中的 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。

五、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;

六、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
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 中的执行上下文,作用域、作用域链,闭包,变量对象,函数执行过程的基础上。

有兴趣深入了解上下文,作用域,闭包相关内容的同学可以翻看我之前的文章。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

2小时品牌素养

2小时品牌素养

邓德隆 / 2009-1 / 38.00元

《2小时品牌素养(第2版)》第一次系统发布有关中国企业的品牌竞争力分析报告,揭示了中国一流企业在品牌战略上面临的深重危机,提出了定位突围之道和实践方法。全书分上下两篇,上篇详细分析了定位的原理,给出定位的三种方法,并特别为中国企业走向世界指出了三条出路;下篇以王老吉品牌战略历程为例,细致论述了一个品牌打造的完整过程,并就品牌实践中的许多关键问题进行了阐述和研讨。作为兼顾理论和实践的第2版,《2小时......一起来看看 《2小时品牌素养》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

html转js在线工具
html转js在线工具

html转js在线工具