this
概述
简单地说,this 就是属性和方法当前所在的对象。函数可以在不同的上下文里运行,为了在运行时能够知道当前的运行环境,所以设计了 this
function f() {
console.log(this.x);
}
var x = 1;
var obj = {
f: f,
x: 2,
};
// 单独执行
f() // 1
// obj 环境执行
obj.f() // 2使用场合
- 全局调用: this -> 全局对象
- 作为对象方法调用: this -> 调用该方法的对象
- 作为构造函数调用 (new): this -> 新创建的实例对象
- call、apply、bind 调用: this -> 明确指定的对象
- DOM 事件处理: this -> 触发事件的 DOM 元素
- 箭头函数: this -> 继承其父级作用域的 this
加强this指向理解的一些案例
案例一:将对象的函数赋值给新变量会发生什么
var obj = {
name: 'a',
foo: function () {
console.log(this);
}
};
obj.foo() // {name: 'a', foo: ƒ}
const f = obj.foo
f() // window解析如下: 设: function () {console.log(this);} 存在堆中的一个位置(0xaaaa) {name: 'a', foo: 0xaaaa} 存在堆中的一个位置(0xbbbb)
我们用obj.foo()相当于在(0xbbbb)这块内存找到了(0xaaaa),然后才调用函数,也就是我们是通过 obj 去调的 foo ,那么函数的运行上下文就是(0xbbbb)这块内存,也就是 obj 对象。
const f = obj.foo 就相当于,让变量f指向(0xbbbb),之后我们再调用的时候,就是直接调用函数,直接就在全局上下文上调用了。
案例二:多层对象嵌套下的this
所谓多层对象,实际上不是嵌套关系,而是指向关系
var a = {
p: 'Hello',
b: {
name: 'b',
m: function () {
console.log(this, this.p);
}
}
};
a.b.m() // {name: 'b', m: ƒ} undefined
const b = a.b
b.m() // {name: 'b', m: ƒ} undefined
const m = b.m
m() // window undefined解析同上,注意到:
function () {console.log(this, this.p);}是内存中一块独立区, 设地址为'0x1111'{ name: 'b', m: '0x1111' }同样是内存中的一块独立区,设地址为'0x2222'{ p: 'Hello', b: '0x2222' }同样是内存中的一块独立区 如果调用函数时,是通过某对象去找到函数再去调用的,那内部的 this 就指向这个对象。如果是把函数取出来赋值给新变量的,那直接调用,this 指向全局
案例三:多层函数嵌套下的this
无论何时何地,声明函数,也是在一个内存中独立区域存放了函数,函数是独立的,函数的this仍然是取决于运行时!
即便是在一个函数内部定义函数,这个内部函数和外部函数一样,在内存中是独立地占据一块空间的,和在外侧定义大差不差,唯一区别就在于内部定义的这个函数,外部没有他的引用罢了。
const o = {
a: function () {
console.log(this)
function b() {
console.log(this)
}
b()
}
}
o.a() // {a: ƒ}, window
const a = o.a
a() // window, window注意,区分于闭包
const o = {
a: function () {
console.log(this)
const that = this
function b() {
console.log(that)
}
b()
}
}
o.a() // {a: ƒ}, {a: ƒ}
const a = o.a
a() // window, window案例三:定时器回调函数中的this
var x = 1;
var obj = {
x: 2,
y: function () {
console.log(this.x);
}
};
setTimeout(obj.y, 500); // 1
setTimeout(function () {
obj.y(); // 2
}, 1000);
setTimeout(() => {
obj.y() // 2
}, 1500);解析如下:setTimeout的第一个参数函数不是立即执行的,如果在执行时是通过实例调用的形式,那么this指向该实例。
第一种情况,在执行时并不是通过实例调用的, 可理解为在传参的时候直接把函数地址给传进去了,这个情况就相当于: const fy = obj.y 然后 fy()
第二、第三种情况,执行时是执行的匿名函数,匿名函数内部通过实例调用的形式执行函数。
注意事项
- 避免多层嵌套 this, 提升可读性
- 避免数组
forEachmap等函数中使用this - 避免回调函数中使用 this
绑定 this
call
应用:有时实例对象上的方法被重写了,用call来调用对象的原型上的原生方法
// 手撕 call
Function.prototype.myCall = function(context, ...args){
const fn = this
const ctx = context || window
// 1. 处理 null
if (ctx === null || ctx === undefined) {
ctx = window;
}
// 2. 处理原始类型, 使用Object()包装原始类型
else if (typeof ctx !== 'object' && typeof ctx !== 'function') {
ctx = Object(ctx);
}
const p = Symbol()
ctx[p] = fn
const res = context[p](...args)
delete ctx[p]
return res
}apply
不再多说,和call唯一区别就是参数是以数组形式传入
bind
bind相当于是把内部的this锁死到某个context。 常见的错误是,将包含this的方法直接当作回调函数,一般是先bind拿到新函数,再绑定给回调事件。
// 手撕bind,合理利用call
Function.prototype.bind = function(context, ...args){
const fn = this
return function(...params){
return fn.call(context, [...args, ...params])
}
}