一、this指向规则
在JavaScript中this的绑定不是创建时确定的,是由调用时决定的。
this的绑定有四个规则。
- 默认绑定
- 隐式绑定
- 显示绑定
- new绑定
1.默认绑定
当一个函数是直接被调用,而不是通过对象的方式调用,this指向全局对象window。
<script>
// 案例1
function foo() {
console.log(this); // window
}
foo();
// 案例2
function foo1() {
console.log(this); // window
foo2();
}
function foo2() {
console.log(this); // window
foo3();
}
function foo3() {
console.log(this); // window
}
foo1();
// 案例3
function foo4(func) {
func()
}
var obj = {
name: 'why',
bar: function() {
console.log(this);
}
}
foo(obj.bar); // window 这里还是直接调用的函数
</script>
2.隐式绑定
通过某个对象调用函数,函数的this指向为调用这个函数的这个对象(案例2)。
<script>
// 案例1
function foo() {
console.log(this);
}
var obj = {
name: 'tjx',
foo: foo
}
obj.foo(); // 指向obj对象
// 案例2
function foo1() {
console.log(this);
}
var obj1 = {
name: 'tjx1',
foo: foo
}
var obj2 = {
name: 'tjx2',
obj1: obj1
}
obj2.obj1.foo(); // this指向obj1 (指向调用这个函数的那个对象)
// 案例3
function foo2() {
console.log(this);
}
var obj3 = {
name: 'obj3',
foo: foo2
}
var bar = obj3.foo;
bar(); // this指向window,默认绑定,直接调用的foo2函数,因为var bar = obj3.foo,bar只是个函数了
</script>
3.显示绑定
通过call、apply、bind方法直接绑定this的指向。
这三个函数的第一个参数都要求是一个对象,在调用这个函数时,会将this绑定到这个传入的对象上。
<script>
// 案例1 通过call或者apply绑定this对象
function foo() {
console.log(this);
}
foo.call(window) // window
foo.call({name: 'tjx'}); // object
foo.call(123); // number
// 案例2
function foo1() {
console.log(this);
}
var obj = {
name: 'obj'
}
var bar = foo1.bind(obj);
bar(); // this绑定obj对象
</script>
4.new绑定
注: JavaScript中的函数可以当做一个类的构造函数来使用,也就是使用new关键字。
使用new关键字来调用函数是,会执行如下的操作:
- 创建一个全新的对象;
- 这个新对象会被执行prototype连接;
- 这个新对象会绑定到函数调用的this上(this的绑定在这个步骤完成);
- 如果函数没有返回其他对象,表达式会返回这个新对象;
<script>
function Person(name) {
console.log(this);
this.name = name
}
var p = new Person('tjx'); // 这里要输出一个Person对象(this绑定的对象)
console.log(p); // 这里再一次输出Person对象
</script>
5.内置函数的this绑定
有些时候,我们会调用一些JavaScript的内置函数,或者一些第三方库中的内置函数。
- 这些内置函数会要求我们传入另外一个函数;
- 我们自己并不会显示的调用这些函数,而且JavaScript内部或者第三方库内部会帮助我们执行;
- 这些函数中的this又是如何绑定的呢?
<script>
// setTimeout
setTimeout(function() {
console.log(this); // this指向window
}, 1000)
// foeEach
var num = [1,2,3,4,5]
var obj = {name: 'tjx'}
num.forEach(function(item) {
console.log(this); // 所以几次的循环绑定都为obj对象
},obj) // forEach的第二个参数为绑定的this
num.forEach(function(item) {
console.log(this); // 这里几次的循环绑定都为window
})
// 点击事件
var box = document.querySelector(".box")
box.onclick = function() {
console.log(this); // this指向box对象
}
</script>
二、规则优先级
new绑定 > 显示绑定、bind > 隐式绑定 > 默认绑定
注:
- new绑定和call、apply是不允许同时使用的,所以不存在谁的优先级更高
- new绑定可以和bind一起使用,new绑定优先级更高
三、特别的this绑定
1.忽略显示绑定
在显示绑定中传入一个null或undefined,这个显示绑定会被忽略,使用默认规则。
<script>
function foo() {
console.log(this);
}
var obj = {
name: 'tjx'
}
foo.call(obj); // obj对象
foo.call(null); // window
foo.call(undefined); // window
var bar = foo.bind(null); // window
bar(); // window
</script>
2.间接函数引用
创建一个函数的 间接引用,这种情况使用默认绑定规则:
赋值(obj2.foo = obj1.foo)的结果是foo函数;
foo函数被直接调用,那么是默认绑定;
<script>
function foo() {
console.log(this);
}
var obj1 = {
name: 'obj1',
foo: foo
}
var obj2 = {
name: 'obj2',
}
obj1.foo(); // obj1
(obj2.foo = obj1.foo)(); // window
</script>
3.ES6箭头函数
箭头函数不使用this的四种标准规则(也就是不绑定this),而是根据外层作用域来决定this;
箭头函数并不绑定this对象,那么this引用就会从上层作用于中找到对应的this;
注意setTimeout中箭头函数的this指向,指向的是外层作用域中对象的this。
四、面试题
1.面试题一
<script>
var name = 'window'
var person = {
name: 'person',
sayName: function() {
console.log(this.name);
}
};
function sayName() {
var sss = person.sayName;
sss(); // window
person.sayName(); // person对象
(person.sayName)(); // person对象
(b=person.sayName)(); // window
}
sayName();
</script>
2.面试题二
- JavaScript对象没用作用域;
- 箭头函数不进行隐式绑定,直接从上层作用域找this;
- 由于箭头函数没有作用域,call、bind对this不起作用;
<script>
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
person1.foo1.call(person2) // person2
person1.foo2() // window foo2为箭头函数,所以没有进行隐式绑定,所以去上层作用域中寻找对应this,foo2()的上层作用域是全局,不是person1对象(因为对象里的函数的作用域为全局,js没有对象作用域)
person1.foo2.call(person2) // window 由于箭头函数没有作用域,call、bind等是不生效的,所以还是从全局作用域中寻找对应this
person1.foo3()() // window 原因是person1.foo3()()的意思是直接调用的foo3()里的函数,属于独立调用函数,默认绑定为window
person1.foo3.call(person2)() // window 这里只是改变了person1.foo3()的this指向为person2,但是后面还有个(),所以属于独立调用,依然为window
person1.foo3().call(person2) // person2 显示绑定
person1.foo4()() // person1 这里箭头函数的上层作用域为foo4,foo4的this绑定为person1,所以为Person1
person1.foo4.call(person2)() // person2 箭头函数的上层作用域foo4被显示绑定为person2,所以为person2
person1.foo4().call(person2) // person1
</script>
3.面试题三
<script>
var name = 'window'
function Person(name) {
this.name = name
this.foo1 = function () {
console.log(this.name);
}
this.foo2 = () => console.log(this.name);
this.foo3 = function () {
return function () {
console.log(this.name);
}
}
this.foo4 = function () {
return () => {
console.log(this.name);
}
}
}
var person1 = new Person('person1')
var person2 = new Person('person2')
person1.foo1() // person1
person1.foo1.call(person2) // person2
person1.foo2() // person1
person1.foo2.call(person2) // person1
person1.foo3()() // window
person1.foo3.call(person2)() // window 直接调用函数
person1.foo3().call(person2) // person2
person1.foo4()() // person1
person1.foo4.call(person2)() // person2
person1.foo4().call(person2) // person1
</script>
4.面试题四
<script>
var name = 'window'
function Person(name) {
this.name = name
this.obj = {
name: 'obj',
foo1: function () {
return function () {
console.log(this.name);
}
},
foo2: function () {
return () => {
console.log(this.name);
}
}
}
}
var person1 = new Person('person1')
var person2 = new Person('person2')
person1.obj.foo1()() // window
person1.obj.foo1.call(person2)() // window
person1.obj.foo1().call(person2) // person2
person1.obj.foo2()() // obj对象
person1.obj.foo2.call(person2)() // person2
person1.obj.foo2().call(person2) // obj对象
</script>