彻底搞懂JS中的this机制

2019-04-22  本文已影响0人  Camilia_yang

摘要:本文属于原创,欢迎转载,转载请保留出处:https://github.com/jasonGeng88/blog

this是什么

如果通过this的英文解释,很容易误导this的意思。它实际是在函数被调用时才绑定的,也就是说this具体指向什么,取决于你是怎么调用的函数。

this的四种绑定规则

this的四种绑定规则分别是:默认绑定,隐式绑定,显示绑定,New绑定。

默认绑定

没有其他绑定规则存在时的默认规则,这是函数调用中最常见的规则。

function foo() { 
}       console.log( this.a );

var a = 2; 
foo(); //打印的是什么?

打印的是:2;
因为foo是直接调用的,没有应用其他绑定规则,这里进行了默认绑定,将全局对象绑定在this上,所以this.a就解析成了全局变量(window)中的a
注意:在严格模式下,全局变量无法使用默认绑定,即执行上面代码会报undefined的错误。

function foo() { 
    "use strict";
   console.log( this.a );
}

var a = 2; 
foo(); // Uncaught TypeError: Cannot read property 'a' of undefined

隐式绑定

除了之间对函数进行调用外,有些函数的调用是在对象上触发的(即比如对象的方法),即调用位置上存在上下文对象。

function foo() { 
    console.log( this.a );
}

var a = 2;

var obj = { 
    a: 3,
    foo: foo 
};

obj.foo(); // ?

返回的结果是:3;
这里foo函数被当做引用属性,被添加到obj对象上。这里的调用过程是这样的:
获取obj.foo属性 --> 根据引用关系找到foo函数,执行调用
所以这里对foo的调用存在上下文对象obj,this进行了隐式绑定,即this绑定到了obj上,所以this.a被解析成了obj.a,即3。

多层调用链

function foo() { 
    console.log( this.a );
}

var a = 2;

var obj1 = { 
    a: 4,
    foo: foo 
};

var obj2 = { 
    a: 3,
    obj1: obj1
};

obj2.obj1.foo(); //?

返回的结果是:4;
同样,我们看下函数的调用过程:
先获取obj2.obj1 -> 通过引用获取到obj1对象,再访问 obj1.foo -> 最后执行foo函数调用.
这里调用链不只一层,存在obj1、obj2两个对象,那么隐式绑定具体会绑哪个对象呢?这里原则是获取最后一层调用的上下文对象,即obj2,所以结果是4(obj1.a)。

隐式丢失(函数别名)

注意:这里存在一个陷阱,大家在分析调用过程时,要特别小心。
先看个代码:

function foo() { 
    console.log( this.a );
}

var a = 2;

var obj = { 
    a: 3,
    foo: foo 
};

var bar = obj.foo;
bar(); //?

返回的结果是:2;
这里有个概念要理解清楚,obj.foo 是引用属性,赋值给bar的实际上就是foo函数(即:bar指向foo本身)。
foo并不是在obj这个对象上下文中调用的,而是把obj.foo的值,即foo函数的地址,赋值给bar。bar直接指向foo函数。
那么,实际的调用关系是:通过bar找到foo函数,进行调用。整个调用过程并没有obj的参数,所以是默认绑定,全局属性a。

隐式丢失(回调函数)

function foo() { 
    console.log( this.a );
}

var a = 2;

var obj = { 
    a: 3,
    foo: foo 
};

setTimeout( obj.foo, 100 ); // ?

返回的结果是:2;
和上面一样,回调传的也是函数的地址,所以没有绑定obj对象,进行的依然是默认绑定。(虽然参传是obj.foo,因为是引用关系,所以传参实际上传的就是foo对象本身的引用。)

显示绑定

相对隐式绑定,this值在调用过程中会动态变化,可是我们就想绑定指定的对象,这时就用到了显示绑定。

显示绑定主要是通过改变对象的prototype关联对象,这里不展开讲。具体使用上,可以通过这两个方法call(…)或apply(…)来实现(大多数函数及自己创建的函数默认都提供这两个方法)。
call与apply是同样的作用,区别只是其他参数的设置上

function foo() { 
    console.log( this.a );
}

var a = 2;

var obj1 = { 
    a: 3,
};

var obj2 = { 
    a: 4,
};
foo.call( obj1 ); // ?
foo.call( obj2 ); // ?

打印的结果是3, 4。
这里因为显示的申明了要绑定的对象,所以this就被绑定到了obj上,打印的结果自然就是obj1.a 和obj2.a。

硬绑定

function foo() { 
    console.log( this.a );
}

var a = 2;

var obj1 = { 
    a: 3,
};

var obj2 = { 
    a: 4,
};

var bar = function(){
    foo.call( obj1 );
}

setTimeout( bar, 100 ); // 3

bar.call( obj2 ); // 这是多少

前面两个(函数别名、回调函数)打印3,因为显示绑定了,没什么问题。

最后一个打印是3。

这里需要注意下,虽然bar被显示绑定到obj2上,对于bar,function(){…} 中的this确实被绑定到了obj2,而foo因为通过foo.call( obj1 )已经显示绑定了obj1,所以在foo函数内,this指向的是obj1,不会因为bar函数内指向obj2而改变自身。所以打印的是obj1.a(即3)。

new 绑定

js中的new操作符,和其他语言中(如JAVA)的new机制是不一样的。js中,它就是一个普通函数调用,只是被new修饰了而已。

使用new来调用函数,会自动执行如下操作:
如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
this指向的就是对象本身。

看个代码:

function foo(a) { 
    this.a = a;
}

var a = 2;

var bar1 = new foo(3);
console.log(bar1.a); // ?

var bar2 = new foo(4);
console.log(bar2.a); // ?

最后一个打印是3, 4。

因为每次调用生成的是全新的对象,该对象又会自动绑定到this上,所以答案显而易见。

箭头函数

箭头函数的this绑定只取决于外层(函数或全局)的作用域

上一篇下一篇

猜你喜欢

热点阅读