BubblyPoker's Blog

其实js中的this没那么诡异

记得初学js的时候,就是被标题中的this搞得头大,一会儿this指向父级Object,一会儿又蛋疼地指向window了

自己也是查阅了一些资料,总结了一些关于this的判断的方法,想分享一下


预热知识

先说一下相关知识点吧

函数调用一般分为四种方式:

  • 简单调用:最为基本的函数调用,函数名前没有任何引导内容

    1
    func()
  • 方法调用:函数作为一个对象的属性时

    1
    obj.func()
  • call/apply:利用call/apply方法改变函数的运行上下文(Context)

    1
    2
    3
    //将func函数里的this指向obj
    func.apply(obj)
    func.call(obj)

    至于call和apply的区别,其实就是接受的参数不一样

  • 构造调用:函数被当作构造器,使用new关键字来创建对象

    1
    var obj = new Func()

How

那么怎么去判断this的值呢?

其实很简单

  1. 如果函数调用为方法调用,则函数内的this为调用该方法的对象

  2. 如果为简单调用,则this为全局对象(global object)

    strict mode下this为undefined
    browser环境下this为window对象,node环境下为global对象

  3. 如果是call.apply方式调用,this为call/apply的第一个参数(eg: obj)

  4. 如果是构造调用,则原构造函数内的this变更为new出的对象

    其实利用构造函数新建对象可以分解为以下三个步骤

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var obj = new Func()
    //等同于
    var obj = {}
    //将obj的原型链上游赋值为Func的原型,这是为了obj可以访问到Func的原型上的方法以及属性
    obj.__proto__ = Func.prototype
    //利用call/apply改变this,将Func里的this指向obj
    //这样做是目的是如果Func内给this定义了一些属性
    //通过将this指向obj,使得obj也具有与Func相同的属性
    Func.call(obj) //或者Func.apply(obj)

    但是如果构造函数renturn了一个对象,那么new Func()和普通的调用没区别了,例如

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var Func = function(){
    this.name = "BubblyPoker";
    return {
    name: "zhuzhiyang"
    }
    }
    Func.prototype.getName = function(){
    return this.name;
    }
    var func = new Func()
    func.name // zhuzhiyang
    func.getName() //TypeError: func.getName is not a function

    可以看到func的name并不是BubblyPoker,这和func = Func()的结果是一样的
    既然是简单调用,那么func自然就没有继承自Func了,那么func的原型链上游就不是Func的原型了,而是Object的原型

通过这四步,可以很轻松地应对有关this的场景。

哦对了,还有一点很重要

this是在函数调用时才设定的,而不是在写代码时就YY出来的

有趣的例子

对于上面提到的all this一文,很全面的从多个方面列出了this的应用场景

  • global
  • function
  • prototype
  • object
  • dom事件
  • html中的事件
  • eval
  • width
  • jquery

其中,有两个例子比较有趣,可以拿来说道一番

  1. prototype this中有这么一段code

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function Thing() {
    }
    Thing.prototype.foo = "bar";
    Thing.prototype.logFoo = function () {
    var info = "attempting to log this.foo:";
    function doIt() {
    console.log(info, this.foo);
    }
    doIt();
    }
    var thing = new Thing();
    thing.logFoo(); //logs "attempting to log this.foo: undefined"
  2. prototype this中的另一段code

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    function Thing() {
    }
    Thing.prototype.foo = "bar";
    Thing.prototype.logFoo = function () {
    var self = this;
    function doIt() {
    console.log(self.foo);
    }
    doIt();
    }
    function doItIndirectly(method) {
    method();
    }
    var thing = new Thing();
    thing.logFoo(); //logs "bar"
    doItIndirectly(thing.logFoo); //logs undefined

我第一眼看到这两个例子时,没有考虑就得出两个code的结果都是logs bar,但是稍加一考虑就发现问题了。

两段代码中的函数调用都是简单调用,所以函数内的this指向的是window(strict mode下是undefined)

对于第一段来讲是很容易判别的,那么对于第二段,你可能有疑问了

doItIndirectly函数传入的参数不是thing.logFoo吗?logFoo应该是是方法调用呀,为什么是简单调用

其实仔细思考一下的话,原因是这样的

函数传参都是值传递,而thing.logFoo的值就是logFoo这个方法在内存中的地址,所以形参method其实
是logFoo方法的地址,所以method()调用时,并没有给这个方法指定上下文,依旧是其调用时简单调用,
所以它内部的this还是指向window或者undefined(strict mode)的

总结

好吧,说了这么多,可以看出其实js中this也是很好判别的嘛,掌握了上述的四种,在绝大部分情况下都可以说得通,对于复杂一些的代码,唯一需要的就是细心+耐心地站在函数调用的角度去分析

以上就是我个人查阅了一些资料后对于js中this的使用判别的总结,这些都是基于我所见过的情况,对于在那些我暂未遇见过的场景中不能使用上述方法判断出this的情况,可以在下方留言或者直接联系我,希望这篇文章能帮助到你。

参考资料

  1. this | MDN
  2. all this
  3. Some of this
坚持原创技术分享,您的支持将鼓励我继续创作!