likes
comments
collection
share

JavaScript中this的那些事儿

作者站长头像
站长
· 阅读数 23

前言

在js中,this关键字是十分重要但很容易混淆的概念。它的值取决于函数的调用上下文,具体来说,this 的值在执行时绑定,并且可以指向不同的对象,具体有:默认绑定,隐式绑定,隐式丢失,显示绑定以及new绑定等,废话不多说,直接开始。

THIS

哪里可以写this

  1. 全局。有以下几种情况:
  • 浏览器环境中:全局的 this 通常指向 window 对象。这意味着在全局作用域中或者非严格模式下的普通函数中使用 this,它将引用 window 对象。例如,在控制台直接输入 this 并执行,会输出窗口对象的详细信息。
  • Node.js环境中:全局的 this 指向 global 对象。global 对象是Node.js中的全局命名空间,类似于浏览器中的 window,包含了所有的全局变量和函数。
  • 严格模式('use strict';) :在严格模式下,全局作用域中的 this 不会自动指向全局对象。在非函数体的顶层代码中(比如脚本文件的最外层或模块顶级),this 会是 undefined,而在函数内部的严格模式下,未明确绑定的 this 同样是 undefined 而不是全局对象。
  1. 函数体内。这就来到了今天的重点,也是让很多前端小白都头疼的函数内的this指向。

函数内的this绑定

1.默认绑定

非严格模式下的普通函数调用: 当一个函数并非作为对象的方法被调用,也没有使用 new 关键字,且不是箭头函数时,this 会被绑定到全局对象(在非严格模式下)。同样地,与全局中的this类似,在严格模式下,这种情况下的 this 会是 undefined,而这种情况加上全局中的this指向,就组成了this的默认绑定。

2.隐式绑定

首先来看这段代码

let obj = {
    myname:'ace',
    age:18,
    say:function(){
        console.log(this.myname);
    }
}
obj.say()

这段代码首先是定义了obj这个对象,其内部有mynameage以及一个say,分别存放了字符串数字以及一个函数体。最后是调用了obj内的函数体say,要输出this.myname,而明显只有同样作为对象属性的myname是正解,而this能找到吗?是可以的:

JavaScript中this的那些事儿 再来看这段代码:

而这就是this的隐式绑定:当一个函数被某个对象所拥有或者函数被某个上下文对象调用时该函数中的this指向该上下文对象。而这是我们“看不到”的,所以称为隐式绑定。

3.隐式丢失

这种严格来说是隐式绑定的特殊情况,this 的隐式丢失是指原本通过隐式绑定绑定到特定对象的 this,在某些情况下失去了对该对象的绑定,转而指向了其他对象或默认值(在非严格模式下是全局对象,在严格模式下是 undefined)。这种情况通常发生在以下几种情形中:

  1. 赋值给另一个变量或作为参数传递: 如果将一个对象的方法赋值给一个变量,然后通过这个变量来调用函数,那么原本的隐式绑定就会丢失。
const obj = {
    method: function() {
        console.log(this);
      }
    };
    const func = obj.method; // 方法赋值给变量
    func(); // 这里 `this` 不再指向 `obj`,而是全局对象(非严格模式)或 `undefined`(严格模式)
  1. 间接引用方法调用: 当通过一个表达式来间接调用一个对象的方法时,如果该表达式的结果不是一个对象属性引用,this 的绑定也可能丢失。
const obj = {
  method: function() {
       console.log(this);
   }
};
let funcName = 'method';
obj[funcName](); // 这里 `this` 仍指向 `obj`,因为是间接但正确的对象属性引用
(obj[funcName] = obj[funcName])(); // 但这样调用会导致 `this` 的隐式丢失
  1. 数组的 .map(), .forEach() 等迭代方法: 当在这些迭代方法中使用对象的方法时,如果不采取措施,this 可能不会按预期绑定。
const array = [1, 2, 3];
const obj = {
  process: function(value) {
    console.log(this, value);
    }
};
array.forEach(obj.process); // 这里 `this` 不指向 `obj`

4.显示绑定

显示绑定即通过特定的方法明确this的指向,而并不通过js引擎来自动指定,在js中主要有以下几种方法来完成显示绑定:

  1. .call() 方法

    • func.call(context, arg1, arg2, ...) 允许你显式地设置函数执行时的this值为context,同时可以传递额外的参数给函数。
  2. .apply() 方法

    • func.apply(context, [argsArray]) 与.call()相似,也是用来改变函数调用的this上下文,但它接受一个数组或类数组对象作为参数列表传递给被调用的函数。
  3. .bind() 方法

    • var boundFunc = func.bind(context, arg1, arg2, ...) 返回一个新的函数,该函数的this值永久绑定到context上。还可以预先传递部分参数给函数。与.call().apply()立即调用函数不同,.bind()返回的函数需要你手动调用。

这三种方法都是用来改变this的指向的,但他们的返回值有所不同:

  • .call() 方法会立即调用函数,并返回该函数的执行结果。这意味着如果被调用的函数有返回值,.call() 就会返回这个值。如果没有明确的返回值(或者函数返回 undefined),.call() 也会返回 undefined
  • 类似于 .call().apply() 也会立即调用函数,并返回该函数的执行结果。
  • 与前两者不同,.bind() 方法并不会立即调用函数,而是返回一个新的函数,这个新函数的this值被永久绑定到了.bind()的第一个参数所指定的上下文。这意味着.bind()的直接返回值是一个函数,你可以稍后调用这个新函数。被绑定的函数如果有返回值,将在你调用这个新函数时返回,而不是在.bind()调用时。

这就是this的显示绑定:通过call,apply,bind,将函数的this掰弯到一个对象中。

5.new绑定

new绑定是一种特殊的函数调用模式,用于创建并初始化一个由构造函数定义的新对象。当使用new关键字调用一个函数时,会发生以下几件事,从而形成了thisnew绑定:

  1. 新对象创建:首先,JavaScript会创建一个新的空对象(即实例)。
  2. 原型链链接:接着,这个新对象的[[Prototype]](也就是内部的__proto__属性)被链接到构造函数的prototype属性所指向的对象上。这意味着新对象可以从构造函数的原型链上继承属性和方法。
  3. this绑定:在构造函数内部,this被绑定到新创建的对象上。这意味着在构造函数中使用this时,它指的是新创建的实例。
  4. 执行构造函数:构造函数的代码被执行,其中可以使用this来初始化新对象的属性和方法。
  5. 自动返回:如果构造函数没有显式返回一个对象(或者返回nullundefined),那么new调用会自动返回新创建的对象。如果构造函数返回一个对象,那么这个对象将替代默认的返回值。

例如:

function Person(name) {
  this.name = name;
}

Person.prototype.sayHello = function() {
  console.log('Hello, my name is ' + this.name);
};
const alice = new Person('Alice');
alice.sayHello(); // 输出 "Hello, my name is Alice"

在这个例子中,new Person('Alice') 使用new关键字调用了Person构造函数,创建了一个新的Person实例,并将name属性初始化为"Alice"。之后,我们可以通过这个实例调用sayHello方法。

这就是this的几种绑定,但还有一个例外

例外

箭头函数

箭头函数中没有this这个机制,写在箭头函数中的this那也是外层非箭头函数的。 这意味着箭头函数内部的this是词法绑定(lexical binding),在定义时就已经确定,不会像普通函数那样在运行时动态改变。具体来说:

  1. 词法作用域绑定:箭头函数不生成自己的this上下文,它没有自己的this。它会捕获其所在上下文的this值,并将其作为自己的this值使用。这个上下文通常是包含箭头函数的最近一层非箭头函数的上下文。
  2. 不变性:一旦箭头函数捕获了this,无论箭头函数如何被调用或在哪里被调用,其内部的this2不会改变。
  3. 适合场景:箭头函数非常适合那些需要访问父作用域this的场景,比如在事件处理程序、回调函数或定时器中,可以避免使用.bind()方法或额外的变量来保存this。 看以下代码:
const obj = { 
    name: 'Object', 
    regularMethod: function() { 
    console.log(this.name); // 输出 "Object" 
    }, 
    arrowMethod: () => { 
    console.log(this.name); // 输出 "Window" 或 "global",取决于环境,因为这里的this继承自全局作用域 
    } 
 }; 
 obj.regularMethod(); // 正常绑定到obj 
 obj.arrowMethod(); // 箭头函数的this继承自外部作用域 
 
 // 使用箭头函数保持外部this 
 const obj2 = { 
     name: 'Another Object', 
     method: function() { 
         setTimeout(() => { 
             console.log(this.name); // 输出 "Another Object",因为箭头函数继承了method的this 
         }, 100); 
     }
}; 
obj2.method();

在这个例子中,regularMethod内的this按照常规函数的规则绑定到obj上,而arrowMethod内的this则继承了定义时的上下文,即局对象。在obj2method中,箭头函数保证了在setTimeout回调里this依然指向obj2

总结

js中的this关键字十分重要,又容易混淆视听,希望我的分享对你有所帮助,我们下次再见!!!

转载自:https://juejin.cn/post/7392194854862372902
评论
请登录