你不知道的JS上卷this

此文为学习笔记。

每个函数的this是在调用时被绑定的,完全取决于函数的调用位置(也就是函数的调用方法)

this是怎么绑定的呢?js中的this是根据调用位置绑定的。具体一点的位置就是在当前执行函数的前一个调用函数中。

js this的绑定方法有:

  1. 默认绑定

    首先要介绍的是最常用的函数调用类型:独立函数调用。
    可以把这条规则看作是无法应用其他规则时的默认规则。
    
    function foo(){
      console.log(this.a);
    } 
    var a=2;
    foo();//2 因为this指向了全局对象
    //在代码中,foo()是直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定,无法应用其他规则。
    
    
    //如果使用严格模式(strictmode),那么全局对象将无法使用默认绑定,因此this会绑定到undefined:
    function foo(){
    	"use strict";
    	console.log(this.a);
    }
    var a=2;
    foo(); //TypeError : this is undefined
    //注意!:如果只是调用在严格模式,而this没有运行在严格模式的话,也会正常运行。   
    //严格模式下与foo()的调用位置无关
    
  2. 隐式绑定

    调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含
    function foo(){
      console.log(this.a);
    }
    var obj2 = {
      a:42,
      foo:foo
    };
    var obj1 = {
      a:2, 
      obj2:obj2
    };
    obj1.obj2.foo();//42
    

    该绑定方法需要注意的是:隐式绑定丢失

    function foo(){
      console.log(this.a);
    }
    var obj={
      a:2,
      foo:foo
    };
    var bar = obj.foo;//函数别名!
    var a="oops,global";//a是全局对象的属性
    bar();//"oops,global"	
    

    虽然bar是obj.foo的一个引用,但是实际上,它引用的是foo函数本身,因此此时的bar()其实是一个不带任何修饰的函数调用,因此应用了默认绑定。

    	function foo(){
          console.log(this.a);
        }
        function doFoo(fn){
          //fn其实引用的是foo
          fn();//<--调用位置!
        }
        var obj = {
          a:2,
          foo:foo
        };
        var a = "oops,global";//a是全局对象的属性
        doFoo(obj.foo);//"oops,global"
    

    参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值,所以结果和上一个例子一样。
    函数传入语言内置也是一样
    除此之外,还有一种情况this的行为会出乎我们意料:调用回调函数的函数可能会修改this。

  3. 显示绑定
    可以使用函数的call(…)和apply(…)方法。
    这两个方法是如何工作的呢?它们的第一个参数是一个对象,它们会把这个对象绑定到this,接着在调用函数时指定这个this。因为你可以直接指定this的绑定对象,因此我们称之为显式绑定。

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

    通过foo.call(…),我们可以在调用foo时强制把它的this绑定到obj上。
    如果你传入了一个原始值(字符串类型、布尔类型或者数字类型)来当作this的绑定对象,这个原始值会被转换成它的对象形式(也就是newString(…)、newBoolean(…)或者newNumber(…))。这通常被称为“装箱”。

可惜,显式绑定仍然无法解决我们之前提出的丢失绑定问题。

1.硬绑定
但是显式绑定的一个变种可以解决这个问题。

```javascript
function foo(){
	console.log(this.a);
}
var obj = {
	a:2
};
var bar=function(){
	foo.call(obj);
};
bar();//2
setTimeout(bar,100);//2
//硬绑定的bar不可能再修改它的this
bar.call(window);//2
```

无论之后如何调用函数bar,它总会手动在obj上调用foo。这种绑定是一种显式的强制绑定,因此我们称之为硬绑定。

  1. new绑定
    就是new绑定

绑定的优先级:
默认绑定<隐式绑定<显示绑定(<硬绑定)<new绑定

绑定例外:

当出现绑定例外的时候不管当前是什么绑定都变为默认绑定

情况1: 被忽略的this
如果你把null或者undefined作为this的绑定对象传入call、apply或者bind,这些值在调用时会被忽略,实际应用的是默认绑定规则

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

情况2: 间接引用
另一个需要注意的是,你有可能(有意或者无意地)创建一个函数的“间接引用”,在这种情况下,调用这个函数会应用默认绑定规则。

function foo(){
	console.log(this.a);
}
var a=2;
var o={
	a:3,
	foo:foo
};
var p={
	a:4
};
o.foo();//3
(p.foo=o.foo)();//2

情况3: 软绑定

可以通过一种被称为软绑定的方法来实现我们想要的效果:

if(!Function.prototype.softBind){
	Function.prototype.softBind=function(obj){
	var fn=this;//捕获所有curried参数
	var curried=[].slice.call(arguments,1);
	var bound=function(){
		return fn.apply(
			(!this||this===(window||global))?
				obj:this
			curried.concat.apply(curried,arguments)
		);
	};
	bound.prototype=Object.create(fn.prototype);
	returnbound;
	};
}

除了软绑定之外,softBind(…)的其他原理和ES5内置的bind(…)类似。它会对指定的函数进行封装,首先检查调用时的this,如果this绑定到全局对象或者undefined,那就把指定的默认对象obj绑定到this,否则不会修改this。此外,这段代码还支持可选的柯里化

下面我们看看softBind是否实现了软绑定功能:

function foo(){
	console.log("name:"+this.name);
}

var obj={name:"obj"},
  obj2={name:"obj2"},
  obj3={name:"obj3"};
  
  var fooOBJ=foo.softBind(obj);
  
  fooOBJ();//name:obj
  
  obj2.foo=foo.softBind(obj);
  obj2.foo();//name:obj2<----看!!!
  
  fooOBJ.call(obj3);//name:obj3<----看!
  
  setTimeout(obj2.foo,10);
  //name:obj<----应用了软绑定

可以看到,软绑定版本的foo()可以手动将this绑定到obj2或者obj3上,但如果应用默认绑定,则会将this绑定到obj。

小结

如果要判断一个运行中函数的this绑定,就需要找到这个函数的直接调用位置。找到之后就可以顺序应用下面这四条规则来判断this的绑定对象。

1.由new调用?绑定到新创建的对象。
2.由call或者apply(或者bind)调用?绑定到指定的对象。
3.由上下文对象调用?绑定到那个上下文对象。
4.默认:在严格模式下绑定到undefined,否则绑定到全局对象。

一定要注意,有些调用可能在无意中使用默认绑定规则。如果想“更安全”地忽略this绑定,你可以使用一个DMZ对象,比如ø=Object.create(null),以保护全局对象。

ES6中的箭头函数并不会使用四条标准的绑定规则,而是根据当前的词法作用域来决定this,具体来说,箭头函数会继承外层函数调用的this绑定(无论this绑定到什么)。这其实和ES6之前代码中的self=this机制一样。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值