本文已参与「新人创作礼」活动,一起开启掘金创作之路。 this是javascript中一个比较特别的关键字,this是在代码执行过程中被动态绑定的,不同的执行条件下很可能会被绑定不同的对象,关于this的绑定其中也有很多让人迷惑的地方,这次,一起来看一下this的绑定规则吧。
this的绑定方式
this共有四种绑定方式
- 默认绑定
- 隐式绑定
- 显示绑定
- new绑定
默认绑定
隐式绑定表现为一个独立函数的直接调用,比如下列代码:
function foo(){
console.log(this);
}
foo();//window
这种调用方式就算作独立函数的直接调用,this执行的就是默认绑定。this会被绑定到window对象上(严格模式下会绑定undefined) 接下来看一些案例(非严格模式下): 1.函数的链式调用
function test1() {
console.log(this);
test2();
}
function test2() {
console.log(this);
}
test1();
答案:
test1和test2打印的this都指向window
虽然test2是在test1内部执行的,但是在js中this并不会继承父级的this,this的指向主要取决于其调用方式,而非调用位置。所以虽然在内部被调用,但仍然是一个独立函数的调用,所以两个打印的this都是指向window对象的。
隐式绑定
隐式绑定往往需要一个调用主体,比较常见的有对象内方法的调用,如下述代码:
var obj={
name:"echo",
foo:function(){
console.log(this);
}
}
obj.foo();//obj
像这种调用方式,foo函数前面有一个调用主体obj,this执行的就是隐式的绑定,this绑定的就是调用主体。 看一个上述案例的变形:
var obj={
name:"echo",
foo:function(){
console.log(this);
}
}
obj.foo();
var bar=obj.foo;
bar();
答案:
obj.foo();//obj
bar();//window
obj.foo()指向obj这个不用多说,因为前面有个调用主体
看一下执行bar()的时候为何会执行window
首先先是执行var bar=obj.foo,这一步将obj对象下的foo函数赋给了变量bar,然后对bar执行调用,bar指向obj里的foo函数,但其最终的调用方式其实还是一个独立函数调用,所以最终打印结果还是指向window的。
显示绑定
显示绑定就是通过call/apply/bind三种方式进行this的绑定。 举个例子:
function foo(){
console.log(this);
}
var obj={name:"li"};
foo.call(obj);
foo.apply(obj);
foo.bind(obj)();
答案:
foo.call(obj);//obj
foo.apply(obj);//obj
foo.bind(obj)();//obj
new绑定
new绑定就是通过new一个构造函数的方式进行绑定,this会指向被new出来的那个对象。比如下述代码:
function Person(name){
this.name=name;
}
var person1=new Person();//person1
这里的this就是指向person1的。
this的规则优先级
- 独立调用优先级最低
- 显示调用优先级高于隐式调用
- new绑定优先级高于隐式调用
- new绑定优先级高于显示调用 第一条就不用多说了,先来验证一下2,3,4
//测试显示绑定优先级高于隐式绑定
var obj = {
name: "echo",
foo: function () {
console.log(this);//{name:"jango"}
}
}
obj.foo.call({name: "jango"});
obj.foo.call()同时使用了隐式绑定和显示绑定,最后打印的this指向的是{name:"jango"},说明最终生效的是call函数的绑定,因此可证明显示绑定的优先级是高于隐式绑定的。
//验证new绑定优先级高于隐式绑定
var obj = {
name: "echo",
foo: function () {
console.log(this);
}
}
new obj.foo();//foo
这里的this指向是foo,说明new绑定的优先级高于隐式绑定。
//验证new绑定优先级高于显示绑定
function foo() {
console.log(this);
}
var bar = foo.bind("abc");
new bar();//foo
new无法和call,apply一起使用,因为使用call,apply的话都会直接执行,只有bind是返回一个函数。所以这里用bind进行测试。
箭头函数的this绑定
箭头函数是不会绑定this的,因此以上的四个规则对箭头函数并不适用,箭头函数的this取决于其父级作用域。 看个例子验证一下箭头函数不绑定this
var obj = {
name: "echo",
foo: function () {
setTimeout(function () {
console.log(this);
},1000)
}
}
obj.foo();
答案:
obj.foo();//window
setTimeout内传递的参数是独立调用的,所以其打印结果的this是指向window对象的。可以再来看一下settimeout里传入箭头函数的情况
var obj = {
name: "echo",
foo: function () {
setTimeout(()=> {
console.log(this);
})
}
}
obj.foo();
答案:
obj.foo();//obj
这里打印的this指向是obj,这是因为箭头函数并不绑定this,所以它会去箭头函数的父级作用域查找this,它的父级作用域是foo。故指向foo;
一波面试题
var name = 'window'
var person1 = {
name: 'person1',
foo1: function () {
console.log(this.name)
},
foo2: () => console.log(this.name),
foo3: function () {
return function () {
console.log(this.name)
}
},
foo4: function () {
return () => {
console.log(this.name)
}
}
}
var person2 = {
name: 'person2'
}
person1.foo1();
person1.foo1.call(person2);
person1.foo2();
person1.foo2.call(person2);
person1.foo3()();
person1.foo3.call(person2)();
person1.foo3().call(person2);
person1.foo4()();
person1.foo4.call(person2)();
person1.foo4().call(person2);
答案:
1.person1.foo1(); //person1
2.person1.foo1.call(person2); //person2
3.person1.foo2(); //window
4.person1.foo2.call(person2); //window
5.person1.foo3()(); //window
6.person1.foo3.call(person2)(); //window
7.person1.foo3().call(person2); //person2
8.person1.foo4()(); //person1
9.person1.foo4.call(person2)(); //person2
10.person1.foo4().call(person2); //person1
- 隐式调用 指向调用它的主体
- 显式调用优先级高于隐式调用优先级,故为person2
- 箭头函数不绑定this,打印this的时候会去它的父级作用域查找,它的父级作用域为window,故打印window
- 同样的箭头函数不绑定this
- person1.foo3()()中的person1.foo()部分是将foo3.this绑定到了person1,并且返回一个函数,然后又在后面通过()调用了这个函数。这个函数实质上是一个独立调用,所以打印的仍旧是window
- 与5类似,person1.foo3.call(person2)是将foo3的this绑定到了person2,并返回了一个函数,再通过()调用,本质上还是一个独立调用,故为window
- 这里是person1.foo3()返回了一个函数,然后对这个返回的函数执行.call(person2).因此打印的是person2
- person1.foo4()绑定foo4的this为person1,然后返回一个箭头函数并在后面使用()调用,因为箭头函数不绑定this,所以会打印父级作用域也就是foo4的this,故为person1
- 同样是打印foo4的this,但是这里foo4被优先级更高的call绑定到了person2的this
- person1.foo4()将foo4的this绑定为person1,并返回了一个箭头函数,然后为这个箭头函数通过call绑定person2,但是箭头函数是无法绑定this的,所以最终打印的还是foo4里面的this-person1