彻底搞懂this指向
在 JavaScript 中,普通函数内部的 this
默认情况下会指向全局对象(非严格模式下),或者是 undefined
(在严格模式下),而不会自动继承外部函数的 this
。
一、为什么使用this
使用this有什么意义呢?
javascript
var obj = {
name: "why",
running: function() {
console.log(obj.name + " running");
},
eating: function() {
console.log(obj.name + " eating");
},
studying: function() {
console.log(obj.name + " studying");
}
}
如果没有this:
- 在该方法中,为了能够获取name名称,必须通过obj的引用(变量名称)来获取
- 这样有一个很大的弊端:如果将obj的名称换成info,那么所有的方法中obj都需要换成info
在实际开发中,便需要使用this来进行优化
javascript
var obj = {
name: "why",
running: function() {
console.log(this.name + " running");
},
eating: function() {
console.log(this.name + " eating");
},
studying: function() {
console.log(this.name + " studying");
}
}
二、 this 绑定规则
this无非就是在函数调用时被绑定的一个对象,我们只需知道它在不同场景下的绑定规则即可
2.1 默认绑定
什么情况下使用默认绑定呢?
独立函数调用。我们可以理解成没有被绑定到某个对象上进行调用
如:
- 普通函数调用
- 函数调用链(一个函数又调用另外一个函数)
所有的函数调用都没有被绑定到某个对象上
scss
// 2.案例二:
function test1() {
console.log(this);
test2();
}
function test2() {
console.log(this);
test3()
}
function test3() {
console.log(this);
}
test1();
- 将函数作为参数,传入到另一个函数中
scss
function foo(func) {
func()
}
function bar() {
console.log(this); // window
}
foo(bar);
2.2 隐式绑定
隐式绑定是通过某个对象进行调用的;也就是它的调用位置中,是通过某个对象发起的函数调用。
2.3.2 bind函数
bind可以将一个函数一直显式的绑定到一个对象上
scss
function foo() {
console.log(this);
}
var obj = {
name: "why"
}
var bar = foo.bind(obj);
bar(); // obj对象
bar(); // obj对象
bar(); // obj对象
2.3.3 内置函数
一些内置函数会要求我们传入另外一个函数,JavaScript内部会帮助我们执行;
- setTimeout
setTimeout中会传入一个函数,这个函数中的this通常是window。
javascript
setTimeout(function() {
console.log(this); // window
}, 1000);
setTimeout内部是通过apply进行绑定的this对象,并且绑定的是全局对象
- 数组的forEach
默认也是window,因为默认情况下传入的函数是自动调用函数(默认绑定)
我们也可以改变该函数的this指向,forEach函数的第二个参数可以改变指向
ini
var names = ["abc", "cba", "nba"];
var obj = {name: "why"};
names.forEach(function(item) {
console.log(this); // 三次obj对象
}, obj);
拓展:forEach我们知道是遍历,那么这个遍历可以停下来吗?怎么让它停下来
答:不可以,我们可以通过try...catch的方法捕获错误让它停下来,但是不能使用return终止它,因为forEach可以看作是一个一个的函数,你只return一个,让一个停下来,这无济于事
2.4 new 绑定
JavaScript中的函数可以当作一个类的构造函数来使用,也就是使用new关键字;
使用new关键字来调用函数时,会执行如下的操作:
- 创建一个全新的对象
- 这个新对象会被执行Prototype连接
- 这个新对象会绑定到函数调用的this上
javascript
// 创建Person
function Person(name) {
console.log(this); // Person {}
this.name = name; // Person {name: "why"}
}
var p = new Person("why");
console.log(p);
2.5 规则优先级
new绑定 > 显示绑定(bind)> 隐式绑定 > 默认绑定
2.6 箭头函数
箭头函数捕获最近的非箭头函数父级作用域的 this 值
当谈到箭头函数的 this 时,它与传统的 JavaScript 函数行为有所不同。在箭头函数中,this 的值是在创建函数时确定的,而不是在调用函数时确定的。具体来说,箭头函数的 this 值是从封闭(定义)它时所在的词法作用域中继承而来的,而不是动态绑定的。
这意味着,无论在什么上下文中调用箭头函数,this 的值都保持不变,始终与箭头函数定义时所在的作用域中的 this 值相同。
也就是说:箭头函数的 this 值是由定义箭头函数时的词法作用域决定的,而不是由调用时的对象决定的
javascript
const obj1 = {
fn: () => {
return this
}
}
const obj2 = {
fn: function(){
return this
}
}
console.log(obj1.fn());
console.log(obj2.fn());
输出结果:
- window || undefined
- obj2
原因:在箭头函数 fn 中的 this 关键字指向的是定义该函数的上下文,而不是调用该函数的对象。因此,当 obj1.fn() 被调用时,由于箭头函数没有它自己的this,当你调用fn()函数时,会捕获最近的非箭头函数父级作用域的 this 值,因此箭头函数中的 this 指向的是全局对象(在浏览器环境下通常是 window 对象),因此返回的是 undefined。
面试题一
ini
var name = "window";
var person = {
name: "person",
sayName: function () {
console.log(this.name);
}
};
function sayName() {
var sss = person.sayName;
sss();
person.sayName();
(person.sayName)();
(b = person.sayName)();
}
sayName();
这道面试题非常简单,无非就是绕一下,希望把面试者绕晕:
scss
function sayName() {
var sss = person.sayName;
// 独立函数调用,没有和任何对象关联
sss(); // window
// 关联
person.sayName(); // person
(person.sayName)(); // person
(b = person.sayName)(); // window
}
面试题二
javascript
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);
下面是代码解析:
scss
// 隐式绑定,肯定是person1
person1.foo1(); // person1
// 隐式绑定和显示绑定的结合,显示绑定生效,所以是person2
person1.foo1.call(person2); // person2
// foo2()是一个箭头函数,不适用所有的规则
person1.foo2() // window
// foo2依然是箭头函数,不适用于显示绑定的规则
person1.foo2.call(person2) // window
person1.foo3()() // window
// foo3显示绑定到person2中
// 但是拿到的返回函数依然是在全局下调用,所以依然是window
person1.foo3.call(person2)() // window
// 拿到foo3返回的函数,通过显示绑定到person2中,所以是person2
person1.foo3().call(person2) // person2
// foo4()的函数返回的是一个箭头函数
// 箭头函数的执行找上层作用域,是person1
person1.foo4()() // person1
// foo4()显示绑定到person2中,并且返回一个箭头函数
// 箭头函数找上层作用域,是person2
person1.foo4.call(person2)() // person2
// foo4返回的是箭头函数,箭头函数只看上层作用域
person1.foo4().call(person2) // person1
在person1.foo3()()
这个调用中,foo3
函数返回了一个新的函数,然后我们立即调用了这个新的函数。这个新的函数并没有被一个对象调用,而是直接被调用的,所以它的this
值默认指向全局对象(在浏览器中是window
)。这就是为什么person1.foo3()()
打印的是"window"。
面试题三
javascript
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.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)
下面是代码解析:
scss
// 隐式绑定
person1.foo1() // peron1
// 显示绑定优先级大于隐式绑定
person1.foo1.call(person2) // person2
// foo是一个箭头函数,会找上层作用域中的this,那么就是person1
person1.foo2() // person1
// foo是一个箭头函数,使用call调用不会影响this的绑定,和上面一样向上层查找
person1.foo2.call(person2) // person1
// 调用位置是全局直接调用,所以依然是window(默认绑定)
person1.foo3()() // window
// 最终还是拿到了foo3返回的函数,在全局直接调用(默认绑定)
person1.foo3.call(person2)() // window
// 拿到foo3返回的函数后,通过call绑定到person2中进行了调用
person1.foo3().call(person2) // person2
// foo4返回了箭头函数,和自身绑定没有关系,上层找到person1
person1.foo4()() // person1
// foo4调用时绑定了person2,返回的函数是箭头函数,调用时,找到了上层绑定的person2
person1.foo4.call(person2)() // person2
// foo4调用返回的箭头函数,和call调用没有关系,找到上层的person1
person1.foo4().call(person2) // person1