this绑定规则

153 阅读6分钟

第十一章 this的指向

this绑定规则

一、默认绑定

1、全局环境下的this指向了window

 var a = 0;       
console.log(this);//window ​

2、函数独立调用,函数内部的this指向了window

var a = function(){          
  console.log(this);        
}        
a();

3、被嵌套的函数独立调用时,this默认指向window 函数当做对象的方法来调用 this指向了obj

 var a = 0;       
 var obj = {           
    a:2,            
    foo:function (){  
        var that = this;          
      function text(){              
      console.log(that.a);//2指向了object            
    }               
 text();                        
  }        
}      
obj.foo();

      var a = 0;
        var obj = {
            a:2,
            foo:function (){
            //    var that = this;
                function text(){
                    console.log(this.a);//0  指向了window
                }
                text();               
            }
        }
        obj.foo(); */

4.IIFE执行函数的this也指向了window

	var a = 10;
        function fn(){
            // console.log(this.a);//2
            (function(){
                console.log(this);//10
            })();
        }
        var obj = {
            a:2,
            foo:fn
        }
        obj.foo();

    var a = 10;
        function fn(){
            (function(that){
                console.log(that.a);//2
            })(this);
        }
        var obj = {
            a:2,
            foo:fn
        }
        obj.foo();

5、闭包的this默认指向了window

类似地,test()函数是独立调用,而不是方法调用,所以this默认绑定到window

注意:函数共有4中调用方法

var a = 0;
function foo() {
    function test() {
        console.log(this.a);
    }
    test();
}
var obj = {
    a: 2;
    foo: foo
}
obj.foo();//0

由于闭包的this默认绑定到window对象,但又常常需要访问嵌套函数的this,所以常常在嵌套函数中使用var that = this,然后在闭包中使用that替代this,使用作用域查找的方法来找到嵌套函数的this值

var a = 0;
function foo(){
    var that = this;
    function test(){
        console.log(that.a);
    }
    return test;
};
var obj = {
    a : 2,
    foo:foo
}
obj.foo()();//2

二、隐式绑定

一般地,被直接对象所包含的函数调用,也被称为方法地调用,this隐式绑定到该直接对象

function foo(){
    console.log(this.a);
}
var obj1 = {
    a : 1;
    foo: foo,
    obj2 : {
        a:2,
        foo:foo
    }
}
//foo()函数的直接对象是obj1,this隐式绑定到obj1
obj1.foo();//1
//foo()函数的直接对象是obj2,this隐式绑定到obj2
obj1.obj2.foo();//2

三、隐式丢失

隐式丢失是指被隐式绑定的函数丢失绑定对象,从而默认绑定到window。这种情况容易出错却又常见

1、函数别名

原本想要的代码,this指向obj

  var a = 10;
        var obj = {
            a: 3,
            foo: foo
        }
        function foo() {
            console.log(this.a);//3
        }
      obj.foo;

把obj.foo复制给了bar,造成隐式丢失的情况,因为只是把obj.foo赋值给了bar变量,而bar变量与obj对象没有关系

       var a = 10;
        var obj = {
            a: 3,
            foo: foo
        }
        function foo() {
            console.log(this.a);//10
        }
        var bar = obj.foo;
        bar();
2、参数传递
        var a = 0;
        function foo(){
            console.log(this.a);//0 打印值为0
        }
        function bar(fn){
            fn();
        }
        var obj = {
            a:3,
            foo:foo
        } 
        /*  把obj.foo当做参数传递给bar函数中的时候,有隐式的复杂fn = obj.bar,只是把foo函数赋值给了fn,
         而fn与obj对象没有直接的联系,所以当前foo函数内部的this指向了widow */
        bar(obj.foo);

上面代码也等价于

	    var a = 0;
        var bar = function foo() {
            console.log(this.a);
        }
        bar();//0
3、内置函数

setTimeout()和setInterval()第一个参数的回调函数中的this,默认指向了window,跟地位置情况类似

        //  setTimeout()和setInterval()第一个参数的回调函数中的this,默认指向了window,跟地位置情况类似
        var a = 10;
        function foo(){
            console.log(this.a);
        }
        var obj = {
            a:3,
            foo:foo
        }
        setTimeout(obj.foo, 1000);
4、间接调用
   function foo(){
            console.log(this.a);
        }
        var a = 2;
        var obj = {
            a:6,
            foo:foo
        };
        var p = {
            a:4
        };
		//隐式绑定 函数当做对象中的方法来使用,内部的this指向了对象
        obj.foo();//6
        //将obj.foo函数对象赋值给p.foo函数,然后立即执行。相当于仅仅是foo()函数的立即调用,函数内部的this就会指向window
        (p.foo = obj.foo)();//2

        //将obj.foo赋值给了p.foo函数。之后p.foo()函数再执行,其实是属于p对象的方法指向,this指向了当前的p对象
		 p.foo = obj.foo;
         p.foo();//4
    </script>
5、其他情况

在javascript引擎内部,obj和obj.foo储存在两个内存地址,简称为M1和M2。只有obj.foo()这样调用时,是从M1调用M2,因此this指向obj。但是,下面三种情况,都是直接取出M2进行运算,然后就在全局环境执行运算结果(还是M2),因此this指向全局环境

var a = 0;
var obj = {
    a:2,
    foo:foo
}
function foo(){
    console.log(this.a);
}
(obj.foo = obj.foo)();//0
(false || obj.foo)();//0
(1,obj.foo)();//0

四、显示绑定

1、call()和aplay()、bind()

通过call()、apply()、bind()方法把对象绑定到this上,叫做显示绑定。对于被调用的函数来说,叫做间接调用

var a = 0;
function foo(){
    console.log(this.a);
}
var obj = {
    a : 2
};
foo();//0
foo.call(obj);//2

普通的显示绑定无法解决隐式丢失问题

var a = 0;
function foo(){
    console.log(this.a);
}
var obj1 = {
    a : 1
};
var obj2 = {
    a : 2
}
foo.call(obj1);//1
foo.call(obj2);//2
2、硬绑定

硬绑定是显式绑定的一个变种,使this不能再被修改

var a = 0;
function foo(){
    console.log(this.a);
}
var obj = {
    a:2
};
var bar = function (){
    foo.call(obj);
}
//在bar函数内部手动调用foo.call(obj)。因此,无论之后如何调用函数bar,它总会手动会在obj上调用foo
bar();//2
setTimeout(bar,2000);//2
bar.call(window);//2
3、数组的forEach(fn,对象)map() filter() some() every()

javascript中新增了许多内置函数,具有显式绑定的功能,如数组的5个迭代方法:map()forEach()filter()some()every()

var id = 'window'
function fn(el){
	console.log(el,this.id);
}
var obj = {
    id = 'fn'
}
var arr = [1,3,5,6];
arr.forEach(fn);

等价于上面的代码

var id = 'window'
var obj = {
    id = 'fn'
}
var arr = [1,3,5,6];
arr.forEach(function fn(el,index){
	console.log(el,index,this);//el是元素顺序  index是索引
},obj);

五、new绑定

如果函数或者方法调用之前带有关键字new,它就构成构造函数调用。对于this绑定来说,称为new绑定

【1】构造函数通常不适用return关键字,他们通常初始化新对象,当构造函数的函数体执行完毕时,它会显式返回。在这种情况下,构造函数调用表达式的计算结果就是这个新对象的值

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

        function fn(){
            //1、如果是new关键字来执行函数,相当于构造函数来实例化对象,那么内部的this指向了当前实例化的对象
            console.log(this);//fn{}
        }
        var fn = new fn();
        console.log(fn);//fn{}

function fn(){
    this.a = 2;
}
var test = new fn();
console.log(test);//{a:2}

【2】如果构造函数使用return语句但没有指定返回值,或者返回一个原始值,那么这时将忽略返回值,同时使用这个新对象作为调用结果

function fn(){
    this.a = 2;
    return;
}
var test = new fn();
console.log(test);//{a:2}

【3】使用构造函数显示地使用return语句返回一个对象,那么调用表达式的值就是这个对象

        function fn2(){
            //this还是指向了当前对象
            console.log(this);//fm2{}
            //使用return关键字来返回对象的时候实例化出来的对象是当前返回的对象
            return {
                name:'mjj'
            }
        }
        var fn2 = new fn2;
        console.log(fn2);//{name:'mjj'}

var obj = {a:1};
function fn(){
    this.a = 2;
    return obj;
}
var test = new fn();
console.log(test);//{a:1}

注意:尽管有时候构造函数看起来像一个方法调用,它依然会使用这个新对象作为this。也就是说,在表达式new o.m()中,this并不是o

var o = {
    m:function(){
        return this;
    }
}
var obj = new o.m();
console.log(obj,obj === o);//{} false
console.log(obj.contructor === o.m);//true

实例化出来的对象内部的属性construct属性指向了当前的构造函数

  var person = {
            fav:function(){
                return;
            }
        }
        var p = new person.fav();
        console.log(p,p===person);//fav{},false

        //实例化出来的对象内部的属性construct属性指向了当前的构造函数
        console.log(p.constructor === person.fav);

六、严格模式下this的指向

【1】严格模式下,独立调用的函数的this指向undefined

function fn(){
    'use strict';
    console.log(this);//undefined
}
fn();
function fn(){
    console.log(this);//window
}
fn();

【2】在非严格模式下,使用函数的call()或apply()方法时,null或undefined值会被转换成全局对象。而在严格模式下,函数的this值始终是指定的值

var color = 'red';
function displayColor(){
    console.log(this.color);
}
displayColor.call(null);//red
var color = 'red';
function displayColor(){
    'use strict';
    console.log(this.color);
}
displayColor.call(null);//TypeError: Cannot read property 'color' of null

总结: this的四种绑定规则:默认绑定、隐式绑定、显式绑定和new绑定,分别对应函数的四种调用方式:独立调用、方法调用、间接调用和构造函数调用。