深入浅出js中的this(一)

时间:2022-11-03 16:19:38

Q:this是什么?

A:this是Javascript语言的一个关键字,它代表函数运行时,自动生成的一个内部对象,在每个 function 中自动根据作用域(scope) 确定, 指向的是此次调用者。

 

Q:this的使用场景?

A:  1.普通函数调用。

  2.作为对象的方法来调用。

  3.作为构造函数调用。

  4.函数被call,apply,bind调用的时候。

 

栗子:

普通函数调用

function test1(){
  console.log(this);
}
test1();   // window

在非严格模式下,由于this必须是一个对象,所以就默认为指向全局对象window。在严格模式下,上面的代码则会出现不同的结果,如下:

function test1(){
  "use strict";
  console.log(this);
}
test1(); // undefined

再看看下面这段代码:

function test2(){
    this.x=2;
}
test2();
console.log(window.x);   // 2

由于普通函数的调用,this的值指向window。所以执行this.x的时候相当于执行了window.x,故window.x的值为2。

 

作为对象的方法来调用

var message = {
  content: "I'm a message!",
  showContent: function() {
    console.log(this.content);
  }
};

message.showContent();   // I'm a message!

  上面栗子是将函数保存为对象的属性, 这样就转化为一个方法, 可以通过对象调用这个方法。而当函数被当成对象的方法来调用时, 里面的 this 值就被设置为调用方法的对象。其实,不管被调用函数在声明时是属于方法,还是函数,当最终作为对象的方法来调用时,this都是指向方法的调用者,即母体对象。看看下面的栗子你就明白了。

var obj = {
    x: 1,
    showX: function() {
        console.log(this.x)
    }
}
obj.showX();  // 1

var obj1={x:11};
obj1.showX=obj.showX;
obj1.showX();  // 11


show=function(){
    console.log('show'+this.x);    
}

obj1.showX=show;
obj1.showX();   // show11

  上面栗子中,在obj对象中,匿名函数在声明时就作为obj对象的一个属性,this直接指向obj对象。函数show在声明时是一个全局函数,函数里面的this指向window,但当最后作为对象obj1的一个属性时,其this便指向了对象obj1。但是,请注意,并不是所有函数作为对象的方法来调用时,this都会指向调用者,如下:

var obj = {
    x : 100,
    y : function(){
        setTimeout(
            function(){ console.log(this.x); }    
         , 1000);
    }
};

obj.y();  // undefined

上面栗子中的this指向的是window对象,并不是我们期待的obj,所以会弹出undefined。所以在书写这类代码时,尽量避免这种写法,当然,你也可以在调用的时候将当时的this所指向的对象存在一个变量里,等到定时器运行时再用所存的变量去调用x,如下:

var obj = {
    x : 100,
    y : function(){
        var that = this;
        setTimeout(
            function(){ console.log(that.x); }
         , 1000);
    }
};

obj.y();   // 100

 

作为构造函数调用

当一个函数作为构造器使用时(通过new关键字),它的this值绑定到新创建的那个对象。

function Message(content){
   this.content = content;
   this.showContent = function(){
       console.log(this.content);
   };
}

var message = new Message("I'm a message!");
message.showContent();   // I'm a message!

 再看看下面这个栗子。

function obj(){
    this.a=666;
    return 'abc';
}

var obj=new obj();
console.log(obj);  // obj{a: 666} 

有木有看出什么?当一个带有return返回值的函数作为构造器去new一个新的对象时,return的值是被忽略的。是不是很神奇!

 

函数被call,apply,bind调用的时候

所有的函数都有apply()和call()这两个方法。我们可以通过这两个方法来改变函数的上下文, 在任何时候都有效, 用来显式地设置this的值。

apply()方法接收两个参数: 第一个是要设置为this的那个对象,第二个参数是可选的,如果要传入参数,则封装为数组作为apply()的第二个参数即可。

call()方法和apply()基本上是一样的,除了后面的参数不是数组,而是分散开一个一个地附加在后面。

function warrior(speed, strength){
   console.log(
      "Warrior: " + this.kind +
      ", weapon: " + this.weapon +
      ", speed: " + speed +
      ", strength: " + strength
  );
}

var warrior1 = {
   kind: "ninja",
   weapon: "shuriken"
};

var warrior2 = {
   kind: "samurai",
   weapon: "katana"
};

warrior.call(warrior1, 9, 5);   // Warrior: ninja, weapon: shuriken, speed: 9, strength: 5
warrior.apply(warrior2, [6, 10]);   // Warrior: samurai, weapon: katana, speed: 6, strength: 10

  在上面,我们通过对构造器warrior()传入不同的参数创建不同类型的对象, this将指向我们通过call() 和/或 apply()传入的对象。

  在第一个函数调用中,我们使用call() 方法来将this设置为warrior1对象, 并传入需要的其他参数, 参数间用逗号分隔。在第二个函数调用中, 其实都差不多, 只是传入的是warrior2对象, 并将必要参数封装为一个数组。

  除了call()和apply()以外,ECMAScript 5还增加了bind()方法,在调用一个函数或方法时也可以通过bind方法来绑定this对象。让我们看下面的栗子:

function warrior(kind){
   console.log(
      "Warrior: " + kind +
      ". Favorite weapon: " + this.weapon +
      ". Main mission: " + this.mission
   );
}

var attributes = {
   weapon: "shuriken",
   mission: "espionage"
};

var ninja = warrior.bind(attributes, "ninja");

ninja();   // Warrior: ninja. Favorite weapon: shuriken. Main mission: espionage

在这个栗子中, bind()方法的使用方式还是类似的, 但warrior.bind()创建了一个新的函数(方法体和作用域跟warrior()一样),并没有改动原来的warrior()函数。新函数的功能和老的一样, 只是绑定到了attributes对象。

注:bind方法和call,apply的区别在于,bind() 之后函数并没有执行,可以传给其他函数,在某个适当的时机再调用。