我刚刚接触javaScript的时候,关于this指向一度是我害怕触碰的一个问题,但是其实this指向其实很简单也很好理解,一句话简述就是this取值是在函数执行时确认。
一、绑定规则
1、作为普通函数(window)— 默认绑定
function fn1() {
console.log(this.a)
}
var a = 1;
fn1() // 1
当调用fn1();的时候,this.a被解析成了全局变量a,因为这里的fn1();是直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定,this指向全局对象。
但是呢只有在非严格模式下,默认绑定才能绑定到全局对象。严格模式下与fn1()的调用位置无关
"use strict";
function fn1() {
console.log(this.a)
}
var a = 1;
fn1(); // Uncaught TypeError: Cannot read property 'a' of undefined
2、作为对象方法被调用(返回对象本身)— 隐式绑定
const name = '李四';
function sayHi() {
console.log(this.name);
}
const obj = {
name: '张三',
sayHi
}
obj.sayHi(); // 张三
首先,sayHi()无论是直接在obj中定义还是先定义再添加为引用属性,这个函数严格来说都不属于obj对象。当sayHi();被调用的时,它的落脚点确实指向obj对象。当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。因为调用sayHi()时this被绑定到obj,因此this.name和obj.name是一样的。
const name = '李四';
function sayHi() {
console.log(this.name);
}
const obj = {
name: '张三',
sayHi
}
const objOut = {
name: '王五',
obj
}
objOut.obj.sayHi(); // 张三
对象属性引用链中只有最后一层会影响调用位置
下面是一个最常见的this绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把this绑定到全局对象或者undefined上,取决于是否是严格模式。
const name = '李四';
function sayHi() {
console.log(this.name)
}
const obj = {
name: '张三',
sayHi
}
let sayHello = obj.sayHi;
sayHello(); // 李四
虽然sayHello是obj.sayHi的一个引用,但实际上它引用的是sayHi函数本身,因此此时的bar()其实是一个不带任何修饰的函数调用,因此应用了默认绑定
下面是一种更常见并且更出乎意料的情况发生在传入回调函数:
const name = '李四';
function sayHi() {
console.log(this.name)
}
function anotherSayHi(fn) {
fn();
}
const obj = {
name: '张三',
sayHi
}
let sayHello = obj.sayHi;
anotherSayHi(obj.sayHi) // 李四
参数传递其实就是一种隐式赋值,所以和上一个例子一样。回调函数丢失this绑定是非常常见的,调用回调函数的函数可能会修改this。
3、使用call,apply,bind(传入什么是什么)— 显示绑定
const a = 1;
function fn2() {
console.log(this.a)
}
const obj = {
a: 2
}
fn2.call(obj) // 2
通过fn2.call(…),我们可以在调用fn2时强制把它的this绑定到obj上。
硬绑定
const a = 1;
function fn2() {
console.log(this.a);
}
const obj = {
a: 2
}
let fn3 = function() {
fn2.call(obj);
}
fn3(); // 2
fn3.call(window) // 2
创建了fn3 => 在它内部手动调用fn2.call(obj),因此强制把fn2的this绑定到了obj => 之后再调用fn3 => 它总会手动在obj上调用foo => 这种就是显式的强制绑定,也叫做硬绑定。
4、在class方法中调用(当前实例本身)— new绑定
使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作:
创建一个新的对象 => 这个新的对象会被执行【原型】连接 => 这个新的对象会绑定到函数调用的this => 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象
const name = '张三';
class People {
constructor(name) {
this.name = name
this.age = 20
}
sayHi() {
console.log(this.name)
}
}
const lisi = new People('李四')
lisi.sayHi() // 李四
二、优先级
通过上面的例子,我们已经知道了this绑定的规则,需要做的就是找到函数的调用位置并判断应用哪条规则,但是某个调用位置可以应用多条规则该怎么办呢?那就需要知道一下他们的优先级啦!
隐式绑定和显式绑定哪个优先级更高?
const a = 1;
function fn1() {
console.log(this.a);
}
const obj1 = {
a: 2,
fn1
};
const obj2 = {
a: 3,
fn1
};
obj1.fn1(); // 2
obj2.fn1(); // 3
obj1.fn1.call(obj2); // 3
obj2.fn1.call(obj1); // 2
可以看到显示绑定优先级更高!
那么隐式绑定和new绑定哪个优先级更高?
function foo(something) {
this.a = something;
}
const obj1 = {
foo
};
const obj2 = {};
obj1.foo(2);
console.log(obj1.a); // 2
obj1.foo.call(obj2, 3)
console.log(obj2.a); // 3
const bar = new obj1.foo(4);
console.log(obj1.a) // 2
console.log(bar.a) // 4
可以看到new绑定优先级更高!
new 和 call/apply 无法一起使用, 因此无法通过 new foo.call(obj1) 来直接进行测试。 但是我们可以使用硬绑定来测试它俩的优先级。
function foo(something) {
this.a = something;
}
const obj1 = {};
const bar = foo.bind(obj1);
bar(2);
console.log(obj1.a); // 2
const baz = new bar(3);
console.log(obj1.a); // 2
console.log(baz.a); // 3
bar被硬绑定到obj1上,new修改了硬绑定(到obj1的)调用bar(…)中的this。因为使用了new绑定,得到了名字为baz的新对象,且baz.a的值是3
所以我们可以通过优先级来判断应该应用上述哪条规则:
- new绑定
- call,apply(显示绑定)
- 隐式绑定
- 默认绑定
三、特殊的ES6箭头函数
箭头函数并不会使用四条标准的绑定规则, 而是根据当前的词法作用域来决定this,箭头函数会继承外层函数调用的 this 绑定(无论 this 绑定到什么)。
const zhangsan = {
name: '张三',
sayHi() {
// this 即当前对象
console.log(this);
},
wait() {
// this 即当前对象
console.log(this);
setTimeout(function () {
// this === window
// setTimeout本身触发的这个函数执行,并不是zhangsan.sayHi()方式
console.log(this);
});
},
waitAgain() {
// this 即当前对象
console.log(this);
setTimeout(() => {
// this 即当前对象
// 箭头函数this永远取上级作用域this
console.log(this);
});
}
};
zhangsan.sayHi();
zhangsan.wait();
zhangsan.waitAgain();
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。