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

原创
2020/08/25 17:21
阅读数 602

不得不承认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一样,因为只有保持了继承与兼容,新技术的推广才会更加的迅速。
享受语言新特性之余,也要搞清楚旧版本的一些细节,这有助于更全面的掌握知识。
希望这篇文章对您有用!

展开阅读全文
0
2 收藏
分享
加载中
更多评论
打赏
0 评论
2 收藏
0
分享
返回顶部
顶部