js小记:this 使用的八大情况

如果你还是对js的this一无所知或是一知半解,那这篇文章是你最好的选择,它对this几乎使用的所有情况都做了讲解,所以看完它以后,别再问我this怎么用。


一、漫谈

JavaScript中的this格外的不一样,比如Java语言中的this是在代码的执行阶段是不可更改,而JavaScript的this是在调用阶段进行绑定。因为这一性质所以给了this很大的发挥空间。

this的定义: this是在执行上下文创建时确定的一个在执行过程中不可更改的变量。

只在函数调用阶段确定: this只在函数调用阶段确定,也就是执行上下文创建的阶段进行赋值,保存在变量对象中。这个特性也导致了this的多变性:即当函数在不同的调用方式下都可能会导致this的值不同。


二、细谈

下面分别就下面几种this使用情况 展开讨论:

  • 在全局环境或普通函数中直接调用
  • 对象中调用
  • 作为构造函数
  • 使用apply和call
  • eval() 中的 this
  • bind() 中的 this
  • DOM 事件处理函数中的 this
  • 内联事件处理函数中的 this

1、直接调用(在全局环境或普通函数中)

this无论是在全局环境中,还是普通函数中直接调用; 它在不同的模式下,都会有不同的行为。

  • 非严格模式:this先指向undefined,然后再自动指向全局对象。
  • 严格模式:this指向undefined

以下的代码均是在 非严格模式下。

例1:

var a = 10;
console.log(this.a); ///10    //this先指向undefined再指向全局对象
console.log(this === window);//true

//若在严格模式下:this会指向undefined 

例2:

var a = 1;
function fun() {
    var a = 2;
    return this.a;
}
fun();//1

//若在严格模式下:运行fun()会报错

例3:

var a = 1;
var obj = {
    a:2,
    b:function(){
        function fun(){
            return this.a;
        }
        console.log(fun());
    }
};
obj.b();//1

fun函数虽然在obj.b方法中定义,但它还是一个普通函数,直接调用在非严格模式下指向undefined,又自动指向了全局对象,正如预料,严格模式会报错undefined.a不成立,a未定义。

例4:看下面闭包

var name = "The Window";  
var object = {    
    name: "My Object",
    getNameFunc: function() {      
        var that = this;      
        return function() {
            //that是在对象方法中调用的
            console.log(that.name); //My Object 
            //this是在普通函数中调用的   
            console.log(this.name); //The Window   
        };    
    }  
};  
object.getNameFunc()(); 

你可能很困惑,其实上面程序 this是在普通函数中调用的,而that是在对象方法中调用的。详见下面(2、对象中调用


特殊:
在new Function()里面的this,不论它是在构造函数中,还是函数调用中,
this都指向 全局对象。

例5:

(function(){
    var f = new Function('alert(this)');
    f();
})();

//或
function Foo(){
    this.bar = function(){
        var f = new Function('alert(this)');
        f();
    }
}
var foo = new Foo();
foo.bar();

2、对象中调用

(1)作为对象的方法 调用

例1:

var a = 1;
var obj = {
    a: 2,
    b: function() {
       return this.a;
    }
}
console.log(obj.b())//2

b所引用的匿名函数作为obj的一个方法调用,这时候this指向调用它的对象。这里也就是obj。那么如果b方法不作为对象方法调用呢?看下面

(2)不作为对象的方法 调用

例2:

var a = 1;
var obj = {
    a: 2,
    b: function() {
       return this.a;
    }
}
var t = obj.b;
console.log(t());//1

如上,t函数执行结果竟然是全局变量1,为啥呢?这就涉及Javascript的内存空间了,就是说,obj对象的b属性存储的是对该匿名函数的一个引用,可以理解为一个指针。当赋值给t的时候,并没有单独开辟内存空间存储新的函数,而是让t存储了一个指针,该指针指向这个函数。

例3:
当obj在全局声明的时候,obj内部属性中的this指向全局对象,当obj在一个函数中声明的时候,严格模式下this会指向undefined,非严格模式自动转为指向全局对象。

var a = 1000;
var obj = {
    a: 1,
    b: this.a + 1
}
function fun() {
    var obj = {
        a: 1,
        c: this.a + 2 //严格模式下这块报错 Cannot read property 'a' of undefined
    }
    return obj.c;
}
console.log(fun());//1002
console.log(obj.b);//1001

3、作为构造函数

何为构造函数?所谓构造函数就是用来new对象的函数,像Function、Object、Array、Date等都是全局定义的构造函数。其实每一个函数都可以new对象,那些批量生产我们需要的对象的函数就叫它构造函数罢了。注意,构造函数首字母记得大写。

例:
构造函数中的this:指向新创建的对象(红圈部分)
这里写图片描述
另外还有一点,prototype对象的方法的this指向实例对象,因为实例对象的
_ proto _已经指向了原型函数的prototype。这就涉及原型链的知识了,即方法会沿着对象的原型链进行查找。实际上不仅仅是构造函数的prototype,即便是在整个原型链中,this代表的也都是当前对象的值。


4、使用apply和call

例1: apply和call实际只是将 obj.b里的this指针的指向做了改变。

var a = 1;
var obj = {
  a: 2,
  b: function() {
    return this.a;
  }
}
console.log(obj.b());   ///2
console.log(obj.b.apply(window));  //1
console.log(obj.b.call(window));   //1

//说明非严格模式下,this先指向undefined,然后再自动指向全局对象
console.log(obj.b.call(undefined)); //1

例2: apply和call实际只是将 Point里的this指针的指向做了改变,point.move.call后,move方法开始指向circle对象。

function Point(x,y){
    this.x = x;
    this.y = y;
    this.move = function(x,y){
        this.x += x;
        this.y += y;
    }
}
var point = new Point(0,0);
point.move(1,1);//移动到(1,1)

var circle = {x:0, y:0, r:1};//以(0,0)为圆心,1为半径的圆

//移动圆至(1,2)
point.move.apply(circle,[1,1]);
//或point.move.call(circle,1,1);

通过下面两行代码是等价的:
point.move.apply(circle,[1,1]);
point.move.call(circle,1,1);

call与apply区别:
两者区别仅在参数上。apply第二个参数必须是个数组(把函数调用的值依次传入),而call把参数依次传入时用的不是数组,直接用逗号隔开而已,当然call第二个参数也可以是数组.

注:箭头函数是一个不可以用call和apply改变this的典型。


5、eval() 中的 this

eval()中的this:指向调用上下文中的this

eval() 方法可以将字符串转换为 JavaScript 代码,使用 eval() 方法时,this 指向哪里呢?答案很简单,看谁在调用 eval() 方法,调用者的执行环境中的 this 就被 eval() 方法继承下来了。

例:

// 全局上下文
function f1(){
    return eval("this");
}
console.log(f1() === window); // true

// 函数上下文
var o = {
    name: "stone",
    f: function() {
        return eval("this.name");
    }
};
console.log(o.f()); // "stone"

6、bind() 中的 this

调用 f.bind(someObject) 会创建一个与 f 具有相同函数体和作用域的函数,但是在这个新函数中,this 将永久地被绑定到了 bind 的第一个参数,无论这个函数是如何被调用的(即使调用call或apply也不能改变this的指向)

例:

function f() {
    return this.a;
}

var g = f.bind({
    a: "stone"
});
console.log(g()); // stone

var o = {
    a: 28,
    f: f,
    g: g
};
console.log(o.f(), o.g()); // 28, stone
g.call(o);                 //即使调用call或apply也不能改变this的指向
console.log(o.f(), o.g()); // 28, stone

7、DOM 事件处理函数中的 this

一般来讲,当函数使用 addEventListener,被用作事件处理函数时,它的 this 指向触发事件的元素。如下代码所示:

<button id="btn" type="button">click</button>

<script>
   var btn = document.getElementById("btn");
   btn.addEventListener("click", function(){
          this.style.backgroundColor = "#A5D9F3";
   }, false);
</script>

但在 IE 浏览器中,当函数使用attachEvent ,被用作事件处理函数时,它的 this 却指向 window。如下代码所示:

 <button id="btn" type="button">click</button>

 <script>
     var btn = document.getElementById("btn");
     btn.attachEvent("onclick", function(){
          console.log(this === window);  // true
     });
 </script>

8、内联事件处理函数中的 this

当代码被内联处理函数调用时,它的 this 指向监听器所在的 DOM 元素。如下代码所示:

<button onclick="alert(this.tagName.toLowerCase());">
  Show this
</button>

上面的 alert 会显示 button,注意只有外层代码中的 this 是这样设置的。如果 this 被包含在匿名函数中,则又是另外一种情况了。如下代码所示:

<button onclick="alert((function(){return this})());">
  Show inner this
</button>

在这种情况下,this 被包含在匿名函数中,相当于处于全局上下文中,所以它指向 window 对象。


三、箭头函数

箭头函数是一个不可以用call和apply改变this的典型。

例1:

var a = 1;
var obj = {
  a: 2
};
var fun = () => console.log(this.a);
fun();          //1
fun.call(obj)   //1

以上,两次调用都是1。

那么箭头函数的this是怎么确定的呢?箭头函数会捕获其所在上下文的 this 值,作为自己的 this 值,也就是说箭头函数的this在词法层面就完成了绑定。apply,call方法只是传入参数,却改不了this。

例2:

var a = 1;
var obj = {
  a: 2
};
function fun() {
    var a = 3;
    let f = () => console.log(this.a);
    f();
};
fun();//1
fun.call(obj);//2

如上,fun直接调用,fun的上下文中的this值为window,注意,这个地方有点绕。fun的上下文就是此箭头函数所在的上下文,因此此时f的this为fun的this也就是window。当fun.call(obj)再次调用的时候,新的上下文创建,fun此时的this为obj,也就是箭头函数的this值。

例3:

function Fun() {
    this.name = 'Damonare';
}
Fun.prototype.say = () => {
    console.log(this);
}
var f = new Fun();
f.say();//window

你可能会感觉this应该指向f这个实例对象啊。不是的,此时的箭头函数所在的上下文是_ proto _所在的上下文也就是Object函数的上下文,而Object的this值就是全局对象。

例4:

function Fun() {
    this.name = 'Damonare';
    this.say = () => {
        console.log(this);
    }
}
var f = new Fun();
f.say();   //Fun的实例对象

如上,this.say所在的上下文,此时箭头函数所在的上下文就变成了Fun的上下文环境,而因为上面说过当函数作为构造函数调用的时候(也就是new的作用)上下文环境的this指向实例对象。

猜你喜欢

转载自blog.csdn.net/b954960630/article/details/81136542