this指向问题
es5 中this指向
参考资料: 嗨,你真的懂this吗?
- 默认绑定
a. 浏览器中
- 非严格模式下 顶层代码、函数内部 this指向 window 或 self
- 严格模式下 this指向undefined
b. nodejs 中
- 顶层代码this指向CJS模块,
- 函数内部this 非严格模式下指向global,严格模式下指向 undefined
c. 作为一个DOM事件处理函数 .addEventListener 方式 this指向 MouseEvent
d. class中的this
-
在普通方法和构造函数中中this指向实例
-
在静态方法中指向类
-
继承后
- 在父类普通方法和构造函数中指向子类实例
- 父类静态方法中指向子类
- 因私有属性不被继承,在父类方法中访问私有属性,拿到的是父类的私有属性
Tips: 普通函数做为参数传递的情况, 比如setTimeout, setInterval, 非严格模式下的this指向全局对象
var name = 'lubai';
var person = {
name: 'hahahahahah',
sayHi: sayHi
}
function sayHi(){
console.log(this); // { name: hahahahhah, sayHi: Fn }
setTimeout(function(){
console.log('Hello,', this.name); // Hello, lubai
})
}
person.sayHi();
- 隐式绑定
a. 即通常说说的方法调用,谁调用指向谁
b. 这里有个坑, 解构赋值会丢失隐式绑定
- 有些隐式绑定比较坑 如
setTimeout(obj.fn, 0)
c. getter 与 setter 及 原型链中的this指向当前对象
Tips: 那如果有链式调用的情况呢? this会绑定到哪个对象上?
function sayHi(){
console.log('Hello,', this.name);
}
var person2 = {
name: 'lubai',
sayHi: sayHi
}
var person1 = {
name: 'hahhahaahh',
friend: person2
}
person1.friend.sayHi(); // Hello, lubai
- 显示绑定
a. call apply bind 可以改变this指向
- bind只有第一次绑定有效
// bind第一次绑定有效
var obj = {a:1}
function fn(a, ...args) {
console.log(obj, ...args)
this.b = 2
}
var nFn = fn.bind({}, 'a', 'b');
var bFn = nFn.bind(obj, 'c', 'd');
bFn() // {a:1} a b c d
- new绑定 指向新创建的对象
- 优先级 new绑定 > 显示绑定 call/apply/bind > 隐式绑定 > 默认绑定
- 箭头函数中的this值是定义它所在环境中的this
var name = 'outer'
var obj = {
name: 'inner',
say() {
console.log(this.name);
return () => {
console.log(this.name);
}
}
}
obj.say()(); // inner inner
var say = obj.say;
say()(); // outer outer
var bSay = say.bind({name: 'bind'});
bSay()(); // bind bind
- generator中的this
- g()返回的是遍历器对象,不是this对象
- 想用this的话
g.call(g.prototype)
顶层对象
- 浏览器里面,顶层对象是window,但 Node 和 Web Worker 没有window
- 浏览器和 Web Worker 里面,self也指向顶层对象,但是 Node 没有self
- Node 里面,顶层对象是global,但其他环境都不支持。
- 同一段代码为了能够在各种环境,都能取到顶层对象
- 全局环境中,this会返回顶层对象。但是,Node.js 模块中this返回的是当前模块,ES6 模块中this返回的是undefined。
- 函数里面的this,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this会指向顶层对象。但是,严格模式下,这时this会返回undefined。
- 不管是严格模式,还是普通模式,new Function('return this')(),总是会返回全局对象。但是,如果浏览器用了 CSP(Content Security Policy,内容安全策略),那么eval、new Function这些方法都可能无法使用。
// 综上所述,很难找到一种方法,可以在所有情况下,都取到顶层对象。下面是两种勉强可以使用的方法。
// 方法一
(typeof window !== 'undefined'
? window
: (typeof process === 'object' &&
typeof require === 'function' &&
typeof global === 'object')
? global
: this);
// 方法二
var getGlobal = function () {
if (typeof self !== 'undefined') { return self; }
if (typeof window !== 'undefined') { return window; }
if (typeof global !== 'undefined') { return global; }
throw new Error('unable to locate global object');
};
- globalThis
ES2020 在语言标准的层面,引入globalThis作为顶层对象。也就是说,任何环境下,globalThis都是存在的,都可以从它拿到顶层对象,指向全局环境下的this。
垫片库global-this模拟了这个提案,可以在所有环境拿到globalThis。
super
- 作为函数时,super()只能用在子类的构造函数之中
- super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
- 在子类普通方法中通过super调用父类的方法时,方法内部的this指向当前的子类实例。
- super.x= 3 修改的是子类实例x
- 注意,使用super的时候,必须显式指定是作为函数、还是作为对象使用
练习
var name = '123';
var obj = {
name: '456',
print: function() {
function a() {
console.log(this.name);
}
a();
}
}
obj.print(); // 123
function Foo(){
Foo.a = function(){
console.log(1);
}
this.a = function(){
console.log(2)
}
}
Foo.prototype.a = function(){
console.log(3);
}
Foo.a = function(){
console.log(4);
}
Foo.a(); // 4
let obj = new Foo();
obj.a(); // 2
Foo.a(); // 1
var length = 10;
function fn() {
console.log(this.length);
}
var obj = {
length: 5,
method: function(fn) {
fn(); // 10
arguments[0](); // 2, arguments: { 0: fn, 1: 1, length: 2 }
}
};
obj.method(fn, 1);