在JS中,this是让开发者经常困惑的一个概念,在学习this之前,我也是一头雾水。接下来,我将用简单易懂的方式和适当的代码示例来帮助我们理解它的原理和应用。
一、为什么要有this
我们首先来看一段代码:
function identify(context) {
return context.name.toUpperCase()
}
function speak(context){
var gretting ='Hello,I am '+ identify(context)
console.log(gretting);
}
var me ={
name: 'Tom'
}
speak(me) // 输出“hello,I am TOM”
这段代码中不难看出实参me被反复显示传递,如果这有多个函数都需要用到显示传递就会使得代码越来越混乱。我们怎么优化一下这段代码呢?
function identify() {
return this.name.toUpperCase()
}
function speak() {
var gretting = 'Hello, I am ' + identify.call(this)
console.log(gretting);
}
var me = {
name: 'Tom'
}
speak.call(me) //// 输出“hello,I am TOM”
this 让函数可以自动引用合适的上下文对象,避免了显式传递上下文参数的麻烦。(暂时不用纠结怎么实现的,相信大家看完下面的都会有新的理解。)
二、this代表着什么
1.this是一个代词,永远代指某一个域,且只在域中才有意义
2.this在全局下指向window(浏览器环境)/global(node环境)
三、this指向规则
1.默认绑定:函数独立调用,this指向window
函数只要是独立调用的,this就是指向window
function foo() {
console.log(this);
}
foo(); // 全局环境下指向 window
即使在bar中调用了foo的this,this的指向依旧是window。
function foo() {
console.log(this);
}
function bar() {
foo();
}
bar() //this 依然指向全局window
又或者,即使bar的this在foo中被调用,this指向的仍然是window。
function foo() {
function bar() {
console.log(this);
}
bar()
}
foo()
那大家现在应该有个疑惑了吧,那怎样才叫非独立调用呢?其实那就是this指向规则的第二点了。
2.隐式绑定:函数作为对象方法被调用
function foo() {
console.log(this.a)
}
const obj = {
a: 1,
foo: foo //可缩写,直接写成 foo
}
obj.foo() //this 指向 obj 对象
3.隐式丢失:
①链式调用时,this 指向最近的那个对象
function foo() {
console.log(this.a);
}
const obj = {
a: 1,
foo: foo
}
const obj2 = {
a: 2,
obj: obj
}
obj2.obj.foo() //输出 1, 此时this指向obj,就近原则
②对象方法赋值给变量后调用时,this指向全局(并非很严谨)
function foo() {
console.log(this.a)
}
const obj = {
a: 1,
foo
}
const fn = obj.foo()
fn() // 相当于普通函数调用:this -> window/undefined
fn 是独立函数调用,this 按默认规则指向window(非严格模式)或 undefined(严格模式)。
以我目前水平,暂时只能告诉大家这两种情况会造成隐式丢失,但其实不止。
4.显示绑定:使用call/apply/bind 来实现函数的this绑定到一个对象上
function foo(x, y){
console.log(this.a, x + y);
}
var obj = {
a: 2
}
// call 使得 foo 中的 this 指向 obj
foo.call(obj, 2 ,3);
// apply 需要接收数组 []
foo.apply(obj, [2, 3])
// bind 会返回一个函数体
const bar = foo.bind(obj, 2, 3)
bar() // 输出2, 5
//const baz = foo.bind(obj)
//baz(2, 3)
5.new绑定
其实在上一篇文章JavaScript原型与构造函数入门指南中有介绍到new操作符。
function Person() {
this.name = '张三';
}
const p = new Person();
console.log(p.name); // 张三
正常来说,this应该是指向全局的,但是因为new操作符会使得构造函数Person中的this指向对象 p。
注意:如果构造函数返回一个对象,则 new 表达式返回该对象而非新创建的对象:
function Person() {
this.name = '张三';
return { name: '李四' }; // 返回对象
}
const p = new Person();
console.log(p.name); // 李四
四、箭头函数特性
1.箭头函数的this继承自其外层作用域的this
function foo() {
let obj = { name: "Bar 的 this" };
let bar = function() {
let baz = () => {
console.log(this.name); // 继承 bar 的 this (obj)
};
baz();
};
bar.call(obj); // 强制 bar 的 this = obj
}
foo(); // 输出: "Bar 的 this"
2. 箭头函数不能作为构造函数
let Foo =() => {
this.name = "John";
}
let foo = new Foo(); // TypeError: Foo is not a constructor
手写 call 方法
在显示绑定那我们介绍了call方法,那我们能不能手搓一个自己的call呢?它深层是通过什么绑定来实现改变this指向的呢?
function foo(x, y) {
console.log(this.a, x);
return x + y
}
const obj = {
a: 10
}
foo.call(obj, 1, 2) // 10 3
其实实现call主要就是三个大步骤:
- 将 foo 引用到 obj 上
- 让 obj 调用 foo
- 移除 obj 上的 foo
Function.prototype.myCall = function(...arg) {
const context = arg[0] // obj
const args = arg.slice(1) // [1, 2]
context.fn = this // 将 foo 引用到 obj 上
const res = context.fn(...args) // 让 obj 调用 foo,需要把[1, 2]解构出来
delete context.fn // 移除 obj 上的 foo
return res
}
有没有同学不清楚为什么context.fn = this就能把foo引用到obj上的,接下来我来一个例子
// 定义函数
function demoFunction() {
console.log("我是 demoFunction");
}
// 添加 inspectThis 方法
demoFunction.inspectThis = function() {
return this;
};
// 调用 inspectThis 方法
const result = demoFunction.inspectThis();
// 检查结果
console.log(result === demoFunction); // true
调用 demoFunction.inspectThis() 的结果:
this 值: function demoFunction() {
console.log("我是 demoFunction"); }
this 就是 demoFunction 函数: ✅ true
总结
·this规则:默认绑定、隐式绑定、显示绑定、new绑定
·箭头函数:没有自己的this,不能作为构造函数
今天就和大家分享到这,如果文章什么地方有问题,还请各位大佬指出