【3期】javascript的this指向-完整场景(少看几本书不香吗)

默认绑定

全局环境中,this默认绑定到全局对象(严格模式:undefined)

在控制台中分别执行以下代码,注意,全局变量相当于window对象的属性

注意:对于默认绑定,决定this绑定对象的并不是调用位置是否处于严格模式,而是函数体是否处于严格模式。

<1> console.log(this==window) // true
<2> var a={
foo:"123",
bar:"456",
that:this
}
a.that // window
<3> this // window

函数独立调用或者被嵌套函数独立调用时,this默认绑定到window 

虽然test()函数被嵌套在obj.foo()函数中,但test()函数是独立调用,而不是方法调用。所以this默认绑定到window

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

立即执行函数实际上是函数声明后直接调用执行

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

闭包中被返回到外部的函数也是独立调用

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

隐式绑定

函数做为对象方法调用时(即方法调用),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>函数别名

var a = 0;
function foo(){
   console.log(this.a);
};
var obj = {
   a : 2,
   foo:foo
}
//把obj.foo赋予别名bar,造成了隐式丢失
var bar = obj.foo;
bar();//0

<2>参数传递

var a = 0;
function foo(){
   console.log(this.a);
};
function bar(fn){
   fn();
}
var obj = {
   a : 2,
   foo:foo
}
//把obj.foo当作参数传递给bar函数时,有隐式的函数赋值fn=obj.foo
bar(obj.foo);//0

<3>作为内置函数的回调

var a = 0;
function foo(){
   console.log(this.a);
};
var obj = {
   a : 2,
   foo:foo
}
//如果没有隐式丢失,this应该指向obj
setTimeout(obj.foo,100);//0
//结果为0,证明函数作为作为内置函数的回调时发生了隐式丢失

<4>其他特殊情况

function foo() {
   console.log( this.a );
}
var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 };
o.foo(); // 3
//将o.foo函数赋值给p.foo函数,然后立即执行。相当于仅仅是foo()函数的立即执行
(p.foo = o.foo)(); // 2
tips:
p.foo = o.foo;
p.foo(); //4

引用与调用的区别 

调用:用原函数名调用函数,如foo()

引用:通过别名调用函数,比如var a=foo;a()

显式绑定

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

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

显式绑定不能解决隐式丢失问题

function foo() {
    setTimeout(function (){        
        console.log('id:',this.id);     
    }, 100);
}
var id = 21;
foo.call({ id: 42 });//21

tip: 由于this在箭头函数中已经按照词法作用域绑定了,所以,用call()或者apply()调用箭头函数时,无法对this进行绑定,即传入的第一个参数被忽略.

new绑定

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

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

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

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

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

优先级

默认绑定<隐式绑定<显式绑定<new绑定

判断this通用方法

  1. 函数是否存在new中调用(new绑定)?是的话this绑定的是新创建的对象。
  2. 函数是否有call\apply\bind等显式绑定,有的话this绑定的是指定的对象
  3. 函数是否存在某个上下文对象中调用(隐式绑定)有的话this绑定那个上下文对象。
  4. 若以上都没有,则是默认绑定。严格模式-undefined,非严格模式-全局对象

例外

被忽略的this

如果把null,undefined作为this的绑定对象传入call,apply,bind,这些值在调用时会被忽略,实际会应用默认绑定规则。

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

需要注意这种情况的副作用,会把this绑定到全局对象。

如果你希望忽略this,可以使用Object.create(null)创建一个空对象。

function foo(a,b) {
	console.log(a,b)
}
var m = Object.create(null);
foo.apply(m,[2,3]); // 2,3

var bar = foo.bind(m,4)
bar(3) // 4,3

间接引用

你可能会无意间创建一个函数的间接引用,这时候调用这个函数会应用默认绑定规则。

 
//比如赋值时
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

// 解析:赋值表达式p.foo = o.foo的返回值是目标函数的引用,
//因此调用位置是foo()而不是p.foo() || o.foo(),根据之前说的,
//此处会使用默认绑定。

软绑定

硬绑定:显式的强制绑定,会大大降低函数灵活性,硬绑定之后无法使用隐式绑定活着显式绑定去修改this

我们创建了函数bar(),并在其内部手动调用foo.call(obj),强制把foo的this绑定到obj,无论之后如何调用函数bar,它总会在obj上调用foo。

function foo(){
  	console.log(this.a)
}
var obj = {a:2}
var bar=function(){
	foo.call(obj)
}
bar() // 2
setTimeout(bar,10000) //2

//硬绑定的bar不可能再修改它的this
bar.call(window) // 2

典型应用场景是场景是创建一个包裹函数,负责接受参数并返回值

function foo(sth){
  	console.log(this.a, sth)
    return this.a + sth
}
var obj = {a:2}
var bar=function(){
	return foo.apply(obj,arguments)
}
var b = bar(3) // 2 3
console.log(b) //5

另一种方法是创建一个可以重复使用的辅助函数:

function foo(sth){
  	console.log(this.a, sth)
    return this.a + sth
}

//辅助绑定函数
function bind(fn,obj){
	return function(){
		return fn.apply(obj, arguments)
	}
}
var obj = {a:2}
var bar = bind(foo, obj)
var b = bar(3) // 2 3
console.log(b) //5

软绑定:给默认绑定指定一个全局对象和undefined以外的值,就可以实现和硬绑定相同的效果,同时保留隐式绑定和显示绑定修改this的能力

// 首先检查调用时的this,如果绑定到全局对象或者undefined,那就把指定的默认对象obj绑定到this,否则不修改this
//支持柯里化
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);
    return bound;
  }
}

//example
function foo(){
	console.log(this.name)
}
var obj = {name: 'obj'},obj2 = {name: 'obj2'},obj3 = {name: 'obj3'};
var fooOBJ = foo.softBind(obj)
fooPBJ() // obj
obj2.foo = foo.softBind(obj)
obj2.foo() // obj2
fooOBJ.call(obj3) //obj3
setTimeout(obj2.foo, 10000) //obj ---应用了软绑定

箭头函数(ES6 新增)

箭头函数并不使用this的四种标准规则,而是根据外层(函数或者全局)作用域来决定this。

function foo(){
	return (a) => {
  	// this继承自foo()
    console.log(this.a)
  }
}
var obj1 = {a:2}
var obj2 = {a:3}
var bar = foo.call(obj1)
bar.call(oj2) //2!

解析:箭头函数的绑定无法被修改(new也不行)


总结

判断一个运行中函数的this绑定,需要找到这个函数的直接调用位置,找到之后顺序应用四条规则判断this的绑定对象。箭头函数根据当前的词法作用域决定this,简答来说,箭头函数继承外层函数的this绑定。

猜你喜欢

转载自blog.csdn.net/m0_38073011/article/details/108472695