之前对this的四种绑定不太理解,好在浏览了https://www.cnblogs.com/xiaohuochai/p/5735901.html这篇博文,才得以清晰思路,接下来我再次总结this的四种绑定机制。
1 this的四种绑定机制
在JavaScript中this共有四种绑定机制:分别是
- new绑定
- 显式绑定(包括硬绑定)
- 隐式绑定
- 默认绑定
绑定优先级从上到下
2 默认绑定
- 在非严格模式下,默认绑定指的是,函数独立调用,不添加修饰的函数调用,它绑定的是全局对象
- 严格模式下,this绑定"undefined"
接下来我们将默认绑定分为几种情况,在这几种情况下发生的一般都是默认绑定
2.1全局作用域中使用this
我们不在任何函数中使用this,直接在全局作用域中使用this,那么这个this指向的是全局对象
var a=2;
console.log(this.a); //绑定全局对象属性a=2
2.2 函数独立调用时
我们在全局作用域中单独调用某一个函数,这个函数的this绑定的是全局对象
function foo() {
var a=3;
console.log(this.a);
}
var a=2;
foo(); //值为2,而不是3,代表绑定的是全局对象
2.3 被嵌套的独立函数调用时
被嵌套的函数独立调用时,this默认绑定的是window对象
var a=2;
var obj={
a:3,
foo:function () {
function bar() {
console.log(this.a);
}
bar();
}
};
obj.foo(); //2
虽然bar(...)是嵌套在obj的foo(...)函数中,由于bar(...)是独立调用,而不是方法调用,因此this被绑定到全局对象window
2.4 立即执行函数表达式
在立即执行函数表达式中的this也会默认绑定全局对象。
var a=2;
function foo() {
(function bar() {
console.log(this.a);
})();
}
var obj={
a:3,
foo:foo()
};
obj.foo; //2
foo(...)是obj的属性函数,应该是属于上下文调用,this的绑定是隐式绑定,但是在foo(...)函数中我们执行的程序是IIFE,那么this就会默认绑定全局对象。
2.5 闭包
我们先回忆一下闭包,闭包是外部函数内部定义了内部函数,在全局作用域(或者是其他的作用域,只要不是外部函数本身的作用域)中调用内部函数,使得可以访问外部函数作用域的功能。
var a=2;
function foo() { //外部函数
function bar() { //内部函数
console.log(this.a)
}
return bar(); //返回内部函数
}
var obj={
a:3,
foo:foo()
};
obj.foo; //2
那么在闭包中,我们需要访问到外部函数的作用域,这时我们该怎么做呢?答案是使用 var self=this,在内部函数中使用self代替this。
var a=2;
function foo() { //外部函数
var self=this; //self代替this
function bar() { //内部函数
console.log(self.a) //self代替this
}
return bar; //返回内部函数
}
var obj={
a:3,
foo:foo
};
obj.foo()(); //3
2.6 隐式丢失
在this的隐式绑定过程中,由于失去了绑定对象,从而应用默认绑定规则。
2.6.1 函数别名的隐式丢失
在this的隐式绑定过程中,给带有上下文的this函数值起一个别名(创建一个新的对象)
var a=2;
function foo() {
console.log(this.a);
}
var obj={
a:3,
foo:foo
};
obj.foo; //3
var bar=obj.foo; //函数别名
bar(); //2 隐式绑定的过程中发生了丢失
原本在obj中的foo属性:foo(...)中的this绑定上下文obj对象,但是我们创建一个bar()作为obj.foo的引用,实际上bar()引用的是foo(...)函数的本身,bar()是一个不带任何装饰的函数调用,因此默认函数调用
2.6.2 传入回调函数时,参数的隐式赋值
在传入回调函数时,参数之间的隐式赋值也会导致this对象丢失从而应用默认绑定
var a=2;
function foo() {
console.log(this.a);
}
var obj={
a:3,
foo:foo
};
function doo(fn) { //传入obj.foo,传入的实际上只有foo(...),这算是一个独立的函数调用
fn();
}
doo(obj.foo); //2 obj.foo传值给foo,就跟之前的var fn=obj.foo;
2.6.3 内置函数
当我们使用Javascript时内置函数时,也会发生this对象的改变,从而应用默认绑定
var a=2;
function foo() {
console.log(this.a);
}
var obj={
a:3,
foo:foo
};
setTimeout(obj.foo,100);
我们应用了"setTimeout(...)"内置函数,原本foo(...)中的this应该绑定到obj上,但是我们引用了javascript内置函数,“obj.foo”作为参数,导致this应用了默认绑定。
3 隐式绑定
当调用有上下文对象的函数时,便是隐式绑定。通俗点来说,就是被直接对象所包含的函数调用时,也称为该直接对象的方法调用。
function foo() {
console.log(this.a);
}
var obj={
a:2,
foo:foo //foo成为obj中的方法
}
obj.foo(); //obj对象的foo方法----方法调用,
函数foo(...)被保存在obj对象中,成为obj对象的一个属性,这时,我们称这个函数(foo)为这个对象(obj)的方法,在最后一行,我们对这个函数进行调用(对象.属性或者说是对象.方法),这就是方法的调用。而这个对象我们称之为上下文对象。
3.1 多个嵌套的上下文对象调用
什么是多个嵌套的上下文对象调用呢,就是在一个对象中引用另一个对象的方法,此时的this会绑定第一的对象还是最后的对象呢?
function foo() {
console.log(this.a);
}
var obj2={
a:2,
foo:foo
};
var obj1={
a:3,
obj2:obj2,
foo:foo
};
obj1.foo(); //3
obj2.foo(); //2
obj1.obj2.foo(); //2 嵌套的上下文调用
从结果可以看出,当发生嵌套的上下文调用时,首先this传入的值是obj2的值,那么就是说发生嵌套的上下文调用时,this的绑定对象是离this最近的那一个。
注意!!!在这里,var obj2的定义一定要比 var obj1的定义位置靠前,因为之前讲了函数的提升,变量(对象)的提升优先级是一样的,当先声明obj1后声明obj2时,会发生错误。
3.2 间接引用
在一般的程序设计中,函数的间接引用是最容易导致隐式丢失的
ar a=2;
function foo() {
console.log(this.a);
}
var obj1={
a:3,
foo:foo
};
var obj2={
a:4,
};
obj1.foo(); //3
obj2.foo=obj1.foo; //foo不存在obj2中,this绑定的对象应该是obj1
obj2.foo(); //4
var a=2;
function foo() {
console.log(this.a);
}
var obj1={
a:3,
foo:foo
};
var obj2={
a:4,
};
obj1.foo(); //3
(obj2.foo=obj1.foo)(); //2 IIFE中this默认绑定
3.3 其他情况
在JavaScript内部,obj对象单独有一个内存地址M1,obj的属性foo函数单独有一个内存地址M2,当"obj.foo"时,引擎首先找到M1地址,再从M1地址处调用M2地址,这样foo的this就会绑定到obj上。
如果单独引用M2地址,就会发生隐式丢失
4 显示绑定
显示绑定是利用JavaScript中的方法".call(...)、.apply(...)"强制this绑定到指定对象上。对于被调用的函数来说这是间接引用
4.1 call()方法
call方法是JavaScript中,强制使对象绑定到指定对象上。
var a=3;
function foo(b) {
console.log(this.a);
}
var obj={
a:2
};
foo(1); //4
foo.call(obj,1); //3 强制foo绑定到obj上
4.2 apply()方法
apply()方法与call()方法作用相同,不同的是两者除了第一个参数是对象其余的参数不同。具体可看这一篇文章
https://www.cnblogs.com/lengyuehuahun/p/5643625.html
var a=3;
function foo(b,c) {
console.log(this.a+b+c);
}
var obj={
a:2
};
foo(1,2); //3
foo.apply(obj,[1,2]); //apply()传入的参数必须要放到一个数组中
尽管显式绑定很厉害,但是还是解决不了隐式丢失的问题。
4.3 硬绑定
尽管显式绑定无法解决隐式丢失的问题,但是显式绑定的变种-----硬绑定,可以。
硬绑定是,在一个函数中通过call(...)或者是apply(...)方法将带有this的函数绑定到指定的对象中。
简而言之就是,将call()或者是apply()方法强制使this指向一个对象这个操作封装起来,调用这个封装函数就行了。
var a=3;
function foo() {
console.log(this.a);
}
var obj={
a:2
};
function bind() { //在bind中this与obj对象绑定到了一起,所以只要调用bing函数,this肯定指向obj
foo.call(obj);
};
bind(); //2
setTimeout(bind,100); //2
bind.call(wind); //2
硬绑定是如何解决隐式丢失的呢?答案是通过封装call()、apply()方法将this绑定到指定的对象上,等到调用时,用这个封装函数就行了。如此一来,无论何时,在封装函数中,this永远指向对象。
在JavaScript中由于对硬绑定的需求是非常大,所以JavaScript中定义了一个内置函数"bind(...)"来代表硬绑定
function foo(something) {
console.log(this.a,something);
return this.a+something;
}
var obj={
a:2
};
var bar=foo.bind(obj);
var b=bar(3);
console.log(b); //5
4.4 API调用
JavaScript中不止有"bind(...)"这一个函数,还有许多内置函数具有显式绑定功能,如数组迭代的五个方法:map(...)、forEach(...)、filter(...)、some(...)、every(...)
var id='window';
function foo(el) {
console.log(el,this.id);
}
var obj={
id:'fn'
};
[1,2,3].forEach(foo); //1|2|3 window
[1,2,3].forEach(foo,obj); //1|2|3 fn
5 new绑定
new绑定有太多细节要讲,具体的可以看我之前的文章,现在是简单介绍下new绑定的概念。
new绑定:函数或者方法调用之前带有关键字"new",它就是构造函数调用,这也就是this绑定
5.1 不带return的new绑定
当不带return的new构造函数调用时,它会初始化新对象,当构造函数的函数体执行完毕以后,它会显式返回。在这种情况下 ,构造函数的计算值便是"new"创建的新对象的值
function foo() {
this.a=2;
};
var obj=new foo();
console.log(obj); //a:2
5.2 带有return 但是却不返回值的new绑定
当带有return的函数,发生,构造函数调用(new绑定)时,它同上面类型的new绑定是一致的,初始化新对象,执行构造函数体,显示返回,构造函数的计算值是"new"创建的新对象的值。
function foo() {
this.a=2;
return;
};
var obj=new foo();
console.log(obj); //a:2
5.3 带有return返回一个对象
当带有return的函数,发生,构造函数调用时,并且显式返回一个对象时,新创建的对象的值等于这个对象。
var obj1={
a:3
}
function foo() {
this.a=2;
return obj1;
};
var obj=new foo();
console.log(obj); //a:3
5.5 new绑定发生的那些事
new绑定到底发生了什么导致this对象的改变呢?
- 创建一个全新的对象
- 这个对象被执行原型链接
- 这个对象会被绑定到指定的this
- 如果函数中没有返回对象,那么new表达式中的函数会自动返回这个对象
5.6 new绑定的用法
使用new绑定非常之简单,只需要创建指定的对象,new一个this函数。
var a=3;
function foo(a) {
this.a=a;
}
foo(a);
var bar=new foo(2); //创建的bar绑定到foo中的this上
console.log(bar.a); //2
注意!!!尽管构造函数看起来很像一个方法调用,它依然会使用这个新对象作为this。也就是说,在表达式new o.m() 中,this不是o而是m()
6 优先级
在优先级上:new>显式绑定>隐式绑定>默认绑定