JS 函数的 this 指向
1. this 在全局作用域下的指向
在浏览器中,this在全局作用域下指向 window
console.log(this) // window
var title = "hello"
console.log(this.title) // hello
console.log(window.title) // hello
2. this 的四个绑定规则
2.1 规则一:默认绑定
在独立函数调用的情况下会使用默认绑定
独立函数调用简单来说就是它没有绑定到某个对象上进行调用,简单看几个例子
// 例1
function foo() {
console.log(this)
}
foo() // window
// 例2
function foo1() {
console.log(this);
}
function foo2() {
foo1()
}
function foo3() {
foo2()
}
foo3() // window
// 例3
var obj = {
name: 'hello',
foo: function() {
console.log(this);
}
}
var bar = obj.foo
bar() // window
以上的函数调用的时候,都是没有被绑定到对象中调用的,所以 this 都指向 window
2.2 规则二:隐式绑定
函数通过某个对象进行调用的时候,这个对象会被 js 引擎绑定到函数中的 this 里
下面看几个例子:
// 1.
function foo() {
console.log(this);
}
var obj = {
name: 'hello',
fn: foo
}
obj.fn() // obj 对象
// 2.
var obj1 = {
foo: function() {
console.log(this);
}
}
var obj2 = {
title: 'hello',
bar: obj1.foo
}
obj2.bar() // obj2对象
2.3 显式绑定
利用 call,apply,bind手动指定 this 的绑定,这种明确的绑定称为显示绑定
(1) call,apply
call,apply 都是可以调用函数的
fn.call(自定的this指向,参数1,参数2) fn.apply(this指向,[参数1,参数2])
两者的区别在于传参的方式,下面例子可以看出来 call 传入参数列表,apply是数组
function sum(num1, num2, num3) {
console.log(num1 + num2 + num3, this);
}
var obj = {
title: "hello"
}
sum.call("123", 20,30,40) // 手动让this指向"123"
sum.apply(obj, [20,30,40]) // 手动让this指向obj
(2) bind
bind不会调用函数,而是返回一个新的对象。
function sum(num1, num2, num3) {
console.log(num1 + num2 + num3, this);
}
// var obj = {
// title: "hello"
// }
// sum.call("123", 20,30,40) // 手动让this指向"123"
// sum.apply(obj, [20,30,40]) // 手动让this指向obj
var newSum = sum.bind('aaa',10,20,30)
newSum() // 60 String {'aaa'}
咦?newSum 不也是独立函数调用吗,怎么不指向 window 了
这就是默认绑定和显示绑定bind的冲突,显示绑定的优先级更高!
2.4 new绑定
function Person(name, age) {
this.name = name
this.age = age
// 实际上这里是会把 this 返回出去的,即return this(默认)
// this = 创建出来的对象
}
// new 会创建一个全新的对象
var p1 = new Person("jenny", 12)
var p2 = new Person("tony", 15)
3. 一些内置函数的 this 绑定
有时候我们会调用一些 JS 的内置函数,这些函数要求我们传入另外一个函数,并且我们不会自己去调用这些函数,而是 JS 内部会帮助我们执行。
这个时候,this 的绑定就跟内部帮我们指向函数的方式有关系了。
看几个例子吧
3.1 setTimeout
内部通过 apply 调用函数,并绑定了this对象,是window
setTimeout(function() {
console.log(this); // window
}, 2000)
3.2 数组的内置函数 forEach
默认情况下传入的函数是自动调用函数(默认绑定)
var names = ['aaa', 'bbb','ccc']
names.forEach(function(item) {
console.log(this); // 三次 window
})
也可以通过传入第二个参数,改变this指向
var names = ['aaa', 'bbb','ccc']
names.forEach(function(item) {
console.log(this); // 三次 'obj'
}, 'obj')
3.3 点击事件
在发生点击时,回调函数被调用,会将 this 绑定到该函数中
<div class="box"> </div>
var box = document.querySelector(".box")
box.onclick = function() {
console.log(this); // box对象
}
所以说,某些内置的函数,我们很难确定它内部是如何调用传入的回调函数的,可以自己测试一下,记一下,看源码当然更好啦
4. 几个规则的优先级
从高到低:new绑定 > 显式绑定(bind) > 隐式绑定 > 默认绑定
new绑定和call,apply是不允许同时使用的,(毕竟都会调用函数),所以不存在谁的优先级更高
-
显示绑定高于隐式绑定
// 显式绑定和隐式绑定 function foo() { console.log(this); } var obj = { name: 'obj', foo: foo } obj.foo() // obj 对象 obj.foo.call("aaa") // 'aaa' -
new 绑定高于隐式绑定
function foo() { console.log(this); } var obj = { name: 'hello', foo: foo } new obj.foo() // 输出 foo 对象而不是 obj -
new 绑定高于bind
function foo() { console.log(this, title); } var obj = { name: 'hello' } var bar = foo.bind(obj) // 显式绑定obj new bar() // 打印 foo对象 而不是 obj
5. 两种特殊情况
5.1 在显示绑定中传入 null 或 undefined
这时候,显示绑定会被忽略,使用默认规则
function foo() {
console.log(this);
}
var obj = {
name: "hello",
foo: foo
}
foo.call(obj) // obj
foo.call(null) // window
foo.call(undefined) // window
5.2 间接函数引用
function foo() {
console.log(this);
}
var obj1 = {
name: 'obj1',
foo: foo
}
var obj2 = {
name: 'obj2',
}
// 先来看一下这个赋值的结果, 是 foo 函数
console.log((obj2.foo = obj1.foo)); // foo函数
// 这种叫做间接函数引用,是会把赋值语句右边的结果返回,然后直接调用,是独立函数调用
(obj2.foo = obj1.foo)() // 相当于直接调用 foo 函数,所以是默认绑定,输出window
6. 箭头函数的this
箭头函数不绑定 this,也就是说它没有自己的 this,而是根据外层作用域来决定this
var name = "hello"
var foo = () => {
console.log(this); // 在这里this的外层作用域就是 window
}
var obj = {
foo: foo
}
obj.foo() // window
obj.foo.call("aaa") // window
再来看一个案例:
使用setTimeout模拟一个网络请求,如果我们想把返回来的数据保存到 data 中,该怎么做
var obj = {
data: '',
getData: function() {
setTimeout(() => {
console.log(this); // obj 对象
// 这里的 this 不是应该指向window吗?
// 如果我们这里使用的是普通的函数 function() {console.log(this)}
// 那么这个函数在内部调用的时候,会绑定this,就是window
// 但是我们使用了箭头函数,它不绑定this,会往上层作用域中找到对应的this
// 所以找到了 obj
this.data = 'hello' // --> obj.data = "hello"
}, 2000);
}
}
obj.getData()
7. this 的面试题
如果每题都会了,那么 this 绑定的问题,就是你的送分题而不是送命题了哈哈哈
建议把我的答案跟注释都删掉,自己一点点理清哦
题一
var name = "window"; // window.name = "window"
var person = {
name: "person",
sayName: function () {
console.log(this.name);
}
};
function sayName() {
var sss = person.sayName;
sss(); // window 很明显是独立函数调用,没有与任何对象关联
person.sayName(); // person 隐式绑定,与person关联
(person.sayName)(); // person 同上(加括号只是代表这是一个整体)
// console.log((b = person.sayName)); // 这里实际上就是sayName这个函数
(b = person.sayName)(); // 间接函数引用,是独立函数调用, 输出 window
}
sayName();
题二
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对象
person1.foo1(); // person1
// 显式绑定person2
person1.foo1.call(person2); // person2
// 箭头函数不适用任何规则, 向上层作用域中找this
person1.foo2(); // window
person1.foo2.call(person2); // window
// person1.foo3()返回了一个函数,然后独立调用
person1.foo3()(); // window
// person1.foo3.call(person2) 返回的是一个函数,然后独立调用
person1.foo3.call(person2)(); // window
// person1.foo3()返回一个函数,然后显式绑定到 person2
person1.foo3().call(person2); // person2
// person1.foo4()返回一个箭头函数,往上层作用域找,找到foo4中绑定的this,是 person1
person1.foo4()(); // person1
// foo4显示绑定到 person2
person1.foo4.call(person2)(); // person2
// person1.foo4()返回箭头函数,往上层作用域找
person1.foo4().call(person2); // person1
题三
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() 返回一个函数,在全局调用
person1.foo3()() // window
person1.foo3.call(person2)() // window // 同理
// person1.foo3() 返回的函数使用 .call 显式绑定 person2
person1.foo3().call(person2) //person2
// person1.foo4() 返回一个箭头函数,再调用,向上层作用域找
person1.foo4()() // person1
// person1.foo4.call(person2) 返回箭头函数,并且foo4显式绑定this为person2
// 再调用这个箭头函数,向上找就找到 foo4 的this 为person2
person1.foo4.call(person2)() // person2
// person1.foo4() 返回箭头函数,不适用显式绑定,向上找到 person1
// 注意这里跟上面的区别,这里的foo4调用不是.call调用的,而是.foo4()这样调用的
// call是来调用箭头函数的,而箭头函数不适用显式绑定,向上找到的是person1
person1.foo4().call(person2) // person1
题四
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()返回一个函数,在全局中调用
person1.obj.foo1()() // window
// person1.obj.foo1.call(person2) 返回一个函数 在全局中调用
person1.obj.foo1.call(person2)() // window
// person1.obj.foo1() 返回一个函数,显式绑定person2
person1.obj.foo1().call(person2) // person2
// 箭头函数调用,向上找到 foo2 中的this是obj
person1.obj.foo2()() // obj
// foo2调用的时候显式绑定person2,箭头函数向上层找到的就是person2
person1.obj.foo2.call(person2)() // person2
// 箭头函数不适用 显式绑定,向上找找到 obj
person1.obj.foo2().call(person2) // obj