JavaScript中关于this关键字的魔幻现实 一篇文章搞懂他

Stella981
• 阅读 550

不得不承认JS中存在一些“历史的遗产”,比如基于全局变量的编程模型。
如果写一个函数,如:

    function test(name){
        var namex = name
        console.log(this.namex)
    }
    test('jack')

方法调用之后,this.namex=undefined,为何呢?因为此处的this是全局对象window,这就令人比较头大。
不过别着急,慢慢往下看,关于this还有很多魔幻的现实,我们来一一解锁。

1、完整的函数调用

还是上面的代码,其实他的完整调用写法是这样的:

    function test(name){
        var namex = name
        console.log(this.namex)
    }
    test.call(undefined, 'jack')

test是一个函数对象--即Function对象,Function.prototypecall方法。call()会有两个参数,第一个参数是this上下文对象,第二个参数是函数入参列表。
如果call()传入的this上下文是undefinednull,那么window对象将成为默认的this上下文。这也就解释了开头例子中this为啥为window的原因了

2、对象中的this

const obj = {
    name: 'Jack',
    greet: function() {
        console.log(this.name)
    }
}
obj.greet()  //简写调用
obj.greet.call(obj) //完整调用

obj.greet()中的this无疑就是obj对象

3、对象方法中嵌套函数的this

对2中的代码进行修改:

const obj = {
    name: 'Jack',
    greet: function() {
        retufn function(){console.log(this.name)}
    }
}
obj.greet()()      //输出undefined

需要注意的是嵌套函数中的this依然是window,为什么呢?可以拆分来看:

var greet = obj.greet()
greet()        // = greet.call(undefined)

嵌套函数被调用的时候,真实的调用者上下文是undefined,也就是window

4、原型与this

    function Clt() {
    }

    Clt.prototype.x = 10
    Clt.prototype.test = function () {
        console.log(this)
        this.y = this.x + 1
    }
    let bean = new Clt()
    bean.test();
    console.log(bean.y)

test方法中输出的this是一个Clt对象。
这里需要引入一个新的概念:构造器函数。
new关键字就是构造器函数,它的作用是:

一旦函数被new来调用,就会创建一个链接到该函数的prototype属性的新对象,同时this会被绑定到那个新对象上

理解了构造器函数,我们就理解了原型与this的关系了,因为new会重新指定this上下文

5、箭头函数与this

ECMAScript6出现了箭头函数的用法,关于箭头函数中的this,需要先记住一句话:

函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。

看代码(引自阮一峰老师的教程:https://es6.ruanyifeng.com/#docs/function#%E7%AE%AD%E5%A4%B4%E5%87%BD%E6%95%B0):

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

var id = 21;

foo.call({ id: 42 });
// id: 42

此处的this指向了foo的this上下文,即定义时的this对象。

箭头函数与非箭头函数的this区别:

function Timer() {
  this.s1 = 0;
  this.s2 = 0;
  // 箭头函数
  setInterval(() => this.s1++, 1000);
  // 普通函数
  setInterval(function () {
    this.s2++;
  }, 1000);
}

var timer = new Timer();

setTimeout(() => console.log('s1: ', timer.s1), 3100);
setTimeout(() => console.log('s2: ', timer.s2), 3100);
// s1: 3
// s2: 0

箭头函数的this指向了定义时所在对象的this;非箭头函数的this指向了运行时的作用域,即全局域window

this的指向固定化,也算是解决了一些历史旧账,无疑带来的很大好处,比如封装调用:

var handler = {
  id: '123456',

  init: function() {
    document.addEventListener('click',
      event => this.doSomething(event.type), false);
  },

  doSomething: function(type) {
    console.log('Handling ' + type  + ' for ' + this.id);
  }
};

一个嵌套的例子:

function foo() {
  return () => {
    return () => {
      return () => {
        console.log('id:', this.id);
      };
    };
  };
}

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

var t1 = f.call({id: 2})()(); // id: 1
var t2 = f().call({id: 3})(); // id: 1
var t3 = f()().call({id: 4}); // id: 1

最里层的this也绑定到了定义时所在对象的—即foo的this

总结

ECMAScript6的箭头函数也算是解决了一些历史问题。不过正如HTML5离不开HTML4一样,因为只有保持了继承与兼容,新技术的推广才会更加的迅速。
享受语言新特性之余,也要搞清楚旧版本的一些细节,这有助于更全面的掌握知识。
希望这篇文章对您有用!

点赞
收藏
评论区
推荐文章
Wesley13 Wesley13
2年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
待兔 待兔
2个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
小嫌 小嫌
2年前
js中箭头函数在对象内部的继承
function函数functionPerson()this.name'Jack',this.age25,this.sayNamefunction()console.log(this.name)functioninner()console.log(this.n
Wesley13 Wesley13
2年前
IOS全局变量
IOS中的全局变量和JAVA中的全局变量定义和使用方法不一样,在Java中,只需要将变量定义为static就行了。而在IOS中这种方法不适合。IOS中定义全局变量有三种方法:1.使用extern关键字在AppDelegate.m或AppDelegate.h中写入你需要的全局变量名,例如:int name;注意定义全局变量时候不能初始化,否则报错
Stella981 Stella981
2年前
JS 对象数组Array 根据对象object key的值排序sort,很风骚哦
有个js对象数组varary\{id:1,name:"b"},{id:2,name:"b"}\需求是根据name或者id的值来排序,这里有个风骚的函数函数定义:function keysrt(key,desc) {  return function(a,b){    return desc ? ~~(ak
Wesley13 Wesley13
2年前
OC之description
打印对象用%@,比如打印字符串对象:NSString\name@”haha”;NSLog(@”%@”,name);输出结果为:haha比如:Person\p\\Personalloc\init\;p.age20;p.name@”jack”;NSLog(@”%@”,p);会打印出对象
Wesley13 Wesley13
2年前
ES6 简单整理
1.变量声明let和constlet与const都是块级作用域,letfunctionname(){letage12;//age只在name()函数中存在}constconstname'tom'name'jack'//
Stella981 Stella981
2年前
JavaScript面向对象编程的15种设计模式
在程序设计中有很多实用的设计模式,而其中大部分语言的实现都是基于“类”。在JavaScript中并没有类这种概念,面向对象编程不是基于类,而是基于原型去面向对象编程,JS中的函数属于一等对象,而基于JS中闭包与弱类型等特性,在实现一些设计模式的方式上与众不同。ps:本文之讲述面向对象编程的设计模式策略,JavaScript原型的基础请参考阮一峰面向
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Stella981 Stella981
2年前
Javascript中,实现类与继承的方法和优缺点分析
Javascript是一种弱类型语言,不存在类的概念,但在js中可以模仿类似于JAVA中的类,实现类与继承第一种方法:利用Javascript中的原型链1//首先定义一个父类23functionAnimal(name,age){4//定义父类的属性5thi