JavaScript中的this指向问题

96 阅读5分钟

JavaScript中的this指向是否一直困扰着你?全局调用、隐式绑定、new绑定、显式绑定及其关于箭头函数与this指向你都清楚吗?

this 是javascript中的一个关键字并非是一个变量

this 关键字指向的是一个对象,而指向哪个对象,或者是这个对象的值是什么取决于使用(调用)的方式和环境

this 指向的值是可以通过手动方式去改变的,比如call bind apply方法。

this 在严格模式和非严格模式下也会有差别

注意:谈this的指向是指在一个函数里面的this的指向,如果不在函数里面或在全局函数中,那就要看具体环境:

  • 浏览器: this指向window (严格模式下为undefined )
  • node环境中: this指向 {}

函数中的this指向谁完全取决于是如何 调用 这个函数的(只有在调用时才能确定this的指向)

绑定规则优先级: new绑定 > 显式绑定 > 隐式绑定 > 默认绑定

全局调用(默认绑定)

  • 浏览器: this指向window (严格模式下为undefined )
  • node环境中: this指向 {}
let a = 0; // 相当于window.a = 0
function testWindow() {
  this.a = 1; //相当于window.a 
  console.log(this.a); //1
  console.log(window.a); // 1
  console.log(this); //Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
}
testWindow();
let a = 1;
function testStrict() {
  "use strict";
  console.log(this); //undefined
  console.log(this.a); // Uncaught TypeError: Cannot read property 'a' of undefined
}
testStrict();
console.log(this);
// 输出:
// {}

隐式绑定

谁调用就指向谁。

当函数作为对象的方法调用时,this 通常指向该对象

const person = {
    name: 'Alice',
    greet() {
        console.log('Hello, ' + this.name);
    }
};

// 调用,`this` 指向 `person`
person.greet();  // 输出: Hello, Alice

修改一下:

let obj = {
  name: 'obj',
  foo: function () {
    console.log(this);    //obj
    function test() {
      console.log(this);  //window  为什么?  因为test独立调用
    }
    test()
  }
}
obj.foo()
let obj = {
  name: 'obj',
  foo: function () {
    console.log(this);  //window   为什么不是obj?  
    // bar拿到obj.foo的引用,然后在全局下独立调用
  }
}
let bar = obj.foo
bar()
function foo() {
  console.log(this);  //window  obj.foo在bar函数内独立调用  
}
function bar(fn) {
  fn()
}
let obj = {
  name: 'obj',
  foo: foo
}
bar(obj.foo)

函数作为参数时,参数函数叫做子函数,外面的函数叫父函数,子函数也叫回调函数,像这样的函数有很多,比如forEachsetimeout,这些函数里的参数函数也叫内置参数

记住:父函数有能力决定子函数this的指向,例如forEach里第一个参数是一个函数,第二个参数就是this绑定的对象,不写默认绑定window

new绑定

不管是严格模式还是非严格模式,通过new 构造函数 this都是指向构造函数创建的对象实例

function Test(a) {
  this.a = a;
  console.log(this.a); //1
}

Test.prototype.say = function () {
  console.log(this); //test {a: 1}
  console.log(this.a); //1
};

const t = new test(1);
console.log(t); //test {a: 1}

t.say();

显式绑定

在 JavaScript 中,显式绑定是通过 call() apply() 或者 bind() 方法来手动指定函数执行时的 this 上下文。这种绑定方式允许我们明确地指定函数执行时想要使用的对象,而不依赖于函数的调用方式。

call

call() 方法允许我们调用一个函数并指定函数执行时的 this 上下文,传入函数的参数为零散接收即传入的参数用" , " 隔开

function greet() {
    console.log('Hello, ' + this.name);
}

const person = { name: 'John' };

// 使用 call() 显式绑定函数执行上下文为 person 对象
greet.call(person); // 输出: Hello, John

apply

apply() 方法与 call() 类似,但是传入的参数为数组而不是一系列的零散参数。

function greet(greeting) {
    console.log(greeting + ', ' + this.name);
}

const person = { name: 'John' };
const args = ['Hello']; // 参数数组

// 使用 apply() 显式绑定函数执行上下文为 person 对象,并传入参数数组
greet.apply(person, args); // 输出: Hello, John

bind

bind() 方法创建并返回一个新函数将原始函数绑定到指定的对象并可选地预先设定部分参数。(参数匹配遵从就近原则)

bind的伪码:

function greet() {
    console.log('Hello, ' + this.name);
}

const person = { name: 'John' };

// 使用 bind() 创建一个新函数,将 greet 函数绑定到 person 对象
const greetPerson = greet.bind(person);

// 调用新函数
greetPerson(); // 输出: Hello, John
// 虽然说greetPerson()函数是直接在全局调用的,this指window
// 但是greet是在greetPerson内部调用的,相当于是在greetPerson内部:greet.call(person)
// 所以greet函数的this指向person

注意与new的优先级问题: new优先级更高:

function func() {
  console.log(this, this.__proto__ === func.prototype)
}

boundFunc = func.bind(1)
boundFunc()
// 输出:
// [Number: 1] false
function func() {
  console.log(this, this.__proto__ === func.prototype)
}

boundFunc = func.bind(1)
// boundFunc()
new boundFunc() // 口诀 2 优先
// 输出:
// func {} true

多次bind只认第一次的bind

function func() {
  console.log(this)
}

func.bind(1).bind(2)() // 1

箭头函数

箭头函数没有 this

箭头函数的this指向谁取决于该箭头函数定义的位置,而不是运行的位置 。因为它是基于闭包的,而闭包是基于词法作用域的

技巧: 箭头函数的this看外层是否有函数

  • 如果有,外层函数的this就是内部箭头函数的this
  • 如果没有, this就是window

箭头函数的 this 是在创建它时外层 this 的指向。这里的重点有两个:

  1. 创建箭头函数时,就已经确定了它的 this 指向
  2. 箭头函数内的 this 指向外层的 this
func = () => {
  // 这里 this 指向取决于外层 this
  console.log(this)
}

func.bind(1)() // Window
func = () => {
  // 这里 this 指向取决于外层 this
  console.log(this)
}

func.apply(1) // Window

独立调用对箭头函数无效

let a = 0
function foo() {
  let test = () => {
    console.log(this)
  }
  return test
}
let obj = { a: 1, foo: foo }
obj.foo()()
//  obj.foo()返回test  obj.foo()()调用test  而且是独立调用  但是this还是指向obj

隐式绑定对箭头函数无效

let a = 0
let obj1 = {
  a: 1,
  foo: () => {
    console.log(this);
  }
}
obj1.foo() //指向window

显式绑定对箭头函数无效

let a = 0
function foo() {
  let test = () => {
    console.log(this)
  }
  return test
}
let obj1 = {
  a: 1,
  foo: foo
}
let obj2 = {
  a: 2,
  foo: foo
}
obj1.foo().call(obj2)
​
 //obj1.foo()返回test   obj1.foo.call(obj2)把test的指向绑定到obj2上,无效,this依然指向obj1

事件处理函数

在事件处理函数中,this指向被绑定的目标对象

<body>
  <button id="btn">click</button>
  <button id="btn1">click1</button>
  <script>
    /**
     * this指向被绑定的目标对象
     * */
    const btn = document.getElementById("btn");
    btn.onclick = function () {
      console.log(this); // <button id="btn">click</button>
      this.innerHTML = "loading..";
      this.disabled = true;
    };


    const btn1 = document.getElementById("btn1");
    btn1.onclick = () => {
      console.log(this); //window
    };
  </script>
</body>

内联函数

以下代码包含了严格模式和非严格模式不同的情况:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>

    <button onclick=" alert(this);">内联事件处理0</button> //元素本身即button

    <button onclick="alert((function(){'use strict'; return this})());">内联事件处理1</button> 

    //undefined

    <button onclick="alert((function(){ return this})());">内联事件处理2</button> 

    //window

    <button onclick="'use strict'; alert(this.tagName.toLowerCase());">内联事件处理3</button> 

    //button

  </body>
</html>