【博学谷学习记录】超强总结,用心分享 | this/call/apply/bind
【博学谷IT技术支持】
this的指向问题
在绝大多数情况下,函数的调用方式决定了 this
的值(运行时绑定)。this
不能在执行期间被赋值,并且在每次函数被调用时 this
的值也可能会不同。
简单例子
var a = 1
const demo = {
a: 2,
func: function() {
return this.a
}
}
console.log(demo.func()) // 2
console.log(this.a) // 1
以上简单例子,demo.func()中this的指向是对象demo, 而this.a的指向是window
全局上下文
在函数内部,this
的值取决于函数被调用的方式。例如上面的this.a,声明var a,就是在全局环境下声明了a,在浏览器中,全局对象是window,this.a就是读取了window对象下a的值
在非严格模式下
this === window // true
function b() {
return this
}
b() === window // true
两个效果是一致的
在严格模式下,我们调用b()函数的时候是直接调用的,并没有通过window来调用,this就会保持为undefined
function b() {
return this // undefined
}
console.log(b() === undefined) // true
类上下文
在类的构造函数中,this
是一个常规对象。类中所有非静态的方法都会被添加到 this
的原型中
class Example {
constructor() {
const proto = Object.getPrototypeOf(this)
console.log(Object.getOwnPropertyNames(proto))
}
a(){}
b(){}
static c() {}
}
new Example() // ['constructor', 'a', 'b']
派生类
派生类的构造函数没有初始的 this
绑定。在构造函数中调用 [super()
]会生成一个 this
绑定,并相当于执行如下代码,Base为基类:
this = new Base()
派生类不能在调用 super()
之前返回,除非其构造函数返回的是一个对象,或者根本没有构造函数。
class A {}
class B extends A {}
class C extends A {
constructor() {
return { a: 5}
}
}
class D extends A {
constructor() {}
}
new B()
new C()
new D() // ReferenceError
例子:
var a = { b: 1}
var b = 2
function whatsThis() {
return this.b
}
这里有个有趣的现象,this.b输出的值取决于函数的调用方式,在我们正常调用的情况下,得到的值就是2,取到的是window下的a的属性,但是通过call或者apply绑定,我们获得得就是1了
whatsThis.call(a) // 1
whatsThis.apply(a) // 1
在非严格模式下使用 call
和 apply
时,如果用作 this
的值不是对象,则会被尝试转换为对象。null
和 undefined
被转换为全局对象。
如下
function getType() {
console.log(Object.prototype.toString.call(this));
}
getType.call(1); // [object Number]
getType.call('1'); // [object String]
getType.call(undefined); // [object global]
getType.call(true); // [object Boolean]
this和对象的转换
function add(c, d) {
console.log(this.a + this.b + c + d)
}
var o = {a: 1, b: 3};
// 第一个参数是用作“this”的对象
// 其余参数用作函数的参数
add.call(o, 5, 7); // 16
// 第一个参数是用作“this”的对象
// 第二个参数是一个数组,数组中的两个成员用作函数参数
add.apply(o, [10, 20]); // 34
bind
调用bind(someObject)
会创建一个与函数具有相同函数体和作用域的函数,但是在这个新函数中,this
将永久地被绑定到了bind
的第一个参数,无论这个函数是如何被调用的。
function b() {
return this.a
}
let a1 = b.bind({a: 1})
console.log(a1()) // 1
let a2 = a1.bind({a: 2})
console.log(a2()) // 2
let e = {a:3, b:b, a1:a1, a2:a2}
console.log(e.a, e.b(), e.a1(), e.a2()) // 3 3 1 1
箭头函数
在[箭头函数]中,this
与封闭词法环境的this
保持一致。在全局代码中,它将被设置为全局对象
var globalThis = this
var foo = (() => this)
console.log(foo() === globalThis) // true
var obj = { foo: foo}
console.log(obj.foo() === globalThis) // true
console.log(foo.call(obj) === globalThis) // true
foo = foo.bind(obj)
console.log(foo() === globalThis)// true
作为对象的方法
当函数作为对象里的方法被调用时,this
被设置为调用该函数的对象。
var obj = {
data: 1,
f: function() {
return this.data
}
}
console.log(obj.f()) // 1
换一种方式
var obj = { data: 1 }
function independent() {
return this.data
}
obj.f = independent;
console.log(obj.f()) // 1
这里我们输出的还是1,原因是,f函数本身是没有data的,但是f绑定的是obj, this指向的是obj,所有拿到的data为1
this
的绑定只受最接近的成员引用的影响
obj.b = {g: independent, data: 2};
console.log(obj.b.g()); // 2
原型链中的 this
对于在对象原型链上某处定义的方法,同样的概念也适用。如果该方法存在于一个对象的原型链上,那么 this
指向的是调用这个方法的对象,就像该方法就在这个对象上一样。
var obj = {
f: function() {
return this.a + this.b;
}
};
var p = Object.create(obj);
p.a = 1;
p.b = 4;
console.log(p.f()); // 5
函数p本身是没有f函数的,f函数继承自它的原型。在p.f的引用中,函数的this指向了p.
getter 与 setter 中的 this
再次,相同的概念也适用于当函数在一个 getter
或者 setter
中被调用。用作 getter
或 setter
的函数都会把 this
绑定到设置或获取属性的对象。
function sum () {
return this.a + this.b + this.c
}
var obj = {
a: 1,
b: 2,
c: 3,
get average() {
return (this.a + this.b + this.c)/3
}
}
Object.defineProperty(obj, 'sum', {
get: sum,
enumerable: true,
configurable: true
})
console.log(obj.average, obj.sum)
构造函数
当一个函数用作构造函数时(使用new关键字),它的this
被绑定到正在构造的新对象。
function obj(){
this.a = 37;
}
var data1 = new obj();
console.log(data1.a); // 37
function obj2(){
this.a = 37;
return {a:38};
}
data1 = new obj2();
console.log(data1.a); // 38