this 在 javaScript 中是个基础,也容易搞不懂的知识点。有的小伙伴总是搞不懂 this 在什么时候调用指向哪里,这篇文章带着大家梳理一下所有会出现 this 的情况,归类总结并拿些面试题来看看。
有两点前置知识(箭头函数特性,new修饰符的工作原理),我放到了最后,需要了解的可以先去看看。
我们进入正题:
this是什么
this是对象内部的关键词。在箭头函数出现前,this的指向是在调用时产生的,也可以说是谁调用就指向于谁,通过 this 可以访问指向对象中的属性。说说this有哪几种绑定方式吧:
- 默认绑定
- 隐式绑定
- 显示绑定
- 强制绑定
new实例化绑定
默认绑定
没有绑定规则时,就用到默认绑定。也是比较常见的。
var a = 1;
function fn(){
console.log(this.a);
}
fn() // 1
this 默认指向调用他的对象,那fn在执行时,其实是window在调用。回头可以聊聊在js脚本中初始化的GO和在调用栈运行中的AO及预编译,就会明白为什么在window调用了。
注意:1.如果用 let const 声明的变量,由于它没有挂在 window 上,所以无法用 this 访问。2.当使用严格模式时,this 是 undefined。
function fn(){
"use strict"
console.log(this);
}
fn() // undefined
隐式绑定
如果函数的调用是在某个对象上发生的,那么this也就指向这个对象。
var a = 2;
var obj = {
a: 3,
foo() {
console.log( this.a );
}
};
obj.foo(); // 3
当使用obj调用foo方法时,foo 的调用在obj的上下文中,this进行了隐式绑定,this 绑定到 obj 中,this.a为3。
如果再嵌套,多层调用呢?
function foo() {
console.log( this.a );
}
var a = 2;
var obj1 = {
a: 4,
foo: foo
};
var obj2 = {
a: 3,
obj1: obj1
};
obj2.obj1.foo(); //4
obj2.obj1 调用 obj1=>obj1.foo 调用 foo,foo 执行,this 还是指向最终调用的对象obj上。
function fn() {
console.log( this.a );
}
var a = 2;
var obj1 = {
a: 4,
foo: fn
};
let bar = obj1.foo();
bar() //2
但要注意赋值过程,由于fn是引用赋值给obj.foo。内存地址指向的同一个地方,所以也相当与使用 window 去调用 bar 这时 this.a 为2。
显示绑定
显示绑定是改变函数内部的 prototype 关联对象来更改this上下文,由两个函数 call() 和 apply() 来实现,call() 和 apply() 只是内部传参不同,关于call()、apply()和bind()可以拿一篇文章探讨。网上也有许多相关文章,不展开解释
function foo() {
console.log( this.a );
}
var a = 2;
var obj1 = {
a: 3,
};
var obj2 = {
a: 4,
};
foo.call( obj1 ); // 3
foo.call( obj2 ); // 4
可以理解成吧 foo 放在 obj1 ,由 obj1 调用 foo 执行,这时this指向也就在 obj 上下文中。
强制绑定
如果一个函数开发者不希望其他人员对 this 进行更改,也可以再封装一层函数进行调用。
function foo() {
console.log( this.a );
}
var a = 2;
var obj1 = {
a: 3,
};
var obj2 = {
a: 4,
};
var bar = function(){
foo.call( obj1 );
}
bar.call( obj2 ); // 3
bar()其实已经将 this 指向了 obj2 的上下文了,可内部函数绑定的是 obj1 的上下文,所以函数输出还是3。
new 实例化绑定
new 修饰符最终会生成一个对象,把函数中的 this 指向新的对象上,不会受全局变量的影响。
function Foo(a) {
this.a = a;
}
var a = 2;
var bar = new Foo(3);
console.log(bar.a); // 3
但如果该函数有返回值,那么 this 会指向函数的返回值。
function Foo(a) {
this.a = a;
return {
b:a
}
}
var a = 2;
var bar = new Foo(3);
console.log(bar.a); // undefined
console.log(bar.b); // 3
以上绑定方式优先级依次递增:
- 是否在new中调用(new绑定)?如果是的话this绑定的是新创建的对象。
- 是否通过call、apply(显式绑定)或者硬绑定调用?如果是的话,this绑定的是 指定的对象。
- 是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是那个上下文对象。
- 都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到 全局对象。
前置知识:
- 箭头函数特性。
new修饰符的工作原理。 这里会简单介绍。毕竟不是讨论这个的专题。
箭头函数特性
箭头函数跟普通函数的区别:
- 书写方式,简化return。
- 不能作为构造函数,不能使用
new。因为箭头函数没有ptototype - 箭头函数没有
arguments,可以用rest参数接收。 - 箭头函数不绑定
this,而是从上下文捕获this作为函数内部的this。 - 箭头函数通过
call()apply()方法调用一个函数时,只传入了一个参数,对 this 并没有影响。 - 箭头函数不能当做Generator函数,不能使用yield关键字。
// 一、简化return
let fn = (value)=> {
return value
}
// 等价于
let fn = (value)=> value
// 二、不能使用new
let FunConstructor = () => {
console.log('lll');
}
let fc = new FunConstructor(); //Uncaught TpyeError 报错
new修饰符的工作原理
关键字new在调用构造函数的时候实际上进行了如下的几个步骤:
- 创建一个新的对象
- 将构造函数的作用域赋值给这个新的对象(因此this指向了这个新的对象)
- 执行构造函数中的代码(为这个新对象添加属性)
- 返回新对象
// 一个简单的new实现
function myNew() {
const obj = {},
Constructor = [].shift.call(arguments)
obj.__proto__ = Constructor.prototype
const res = Constructor.apply(obj, arguments);
return typeof res === 'object' ? res : obj;
}
内部创建空对象,将构造函数的 prototype 赋值给新对象的__proto__,将this指向新创建的对象。如果构造函数有返回非空的对象,则返回该对象,否则返回第一步中创建的对象。
如果此文章对您有帮助或启发,那便是我的荣幸