js函数中this的指向

本文详细阐述了JavaScript中this关键字在不同函数调用方式下的绑定规则,包括默认绑定、隐式绑定、new绑定和显式绑定,以及箭头函数的this行为。特别强调了显式绑定方法如.call()和.bind()的优先级。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JavaScript函数中this的指向

本文是学习coderwhy前端课程中js高阶的一些笔记总结,其中的例子是引用了老师上课的一些例子

一、函数的基本用法

// 定义函数
    function foo(name) {
      console.log("foo函数:", this)
    }

    // 1.方式一: 直接调用
    // foo()

    // 2.方式二: 通过对象调起
    var obj = { name: "why" }
    obj.aaa = foo
    
    obj.aaa()

二、this绑定规则

1. this绑定规则一:默认绑定

默认绑定规则描述

默认绑定是最基本的规则,当函数调用不符合其他绑定规则(如隐式绑定、显式绑定或者new绑定)时应用。在默认绑定下,函数的调用方式决定了this的值:

  • 非严格模式下,this默认绑定到全局对象(浏览器环境中是window对象)。
  • 严格模式下,this将绑定到undefined,防止不小心修改全局对象。

(1)普通的函数被独立调用

// "use strict"
// 定义函数
// 1. 普通的函数被独立调用
function foo() {
    console.log("foo:",this)
}

foo()   // 在非严格模式下,this指向全局对象window;在严格模式下,this为undefined。

当函数不在任何对象或类中直接调用时,this的值取决于是否在严格模式下运行。在非严格模式下,this会默认绑定到全局对象(浏览器中是window对象),在严格模式下,this会被绑定到undefined

(2) 函数定义在对象中,但独立调用

//2. 函数定义在对象中,但是独立调用
var obj = {
    name: "why",
    bar: function() {
        console.log("bar:",this)
    }
}

var baz = obj.bar
baz()

在这里,bar函数从其属于的对象obj中脱离出来,并被赋值给baz。当通过baz()独立调用时,this遵循默认绑定规则,即指向全局对象或在严格模式下为undefined

(3)高阶函数

// 3.高阶函数
var obj = {
    name: "why",
    bar: function() {
        console.log("bar:",this)
    }
}
function test(fn) {
    fn()
}

test(obj.bar)// 传递obj.bar给函数test并调用

即使bar函数最初是obj对象的方法,当它被作为参数传递并在test函数中调用时,this再次遵循默认绑定,指向全局对象或在严格模式下为undefined

(4)严格模式下的默认绑定

严格模式下,独立调用的任何函数中的this值将被设置为undefined。这是一种防止函数意外修改全局对象的安全措施。

2. this绑定规则二:隐式绑定

隐式绑定规则

  1. 调用位置:在确定this的绑定时,关键在于查找函数被调用的位置。具体来说,是查看函数调用时点符号.前面的对象是什么。
  2. 调用方式:如果一个函数是作为对象的方法被调用,那么this就指向这个对象。

在JavaScript中,隐式绑定是指当一个函数作为对象的属性被调用时,this自动指向这个对象。

 function foo() {
      console.log("foo函数:", this)
    }

    var obj = {
      bar: foo
    }

    obj.bar() // 调用方式决定了this绑定到obj

在这个例子中,函数foo最初被定义为一个普通的函数。之后,它被赋值给对象objbar属性。当通过obj.bar()调用foo时,this关键字自动绑定到obj对象。这是因为foo是作为obj对象的bar属性被调用的。

隐式绑定的失效情况

尽管隐式绑定似乎很直接,但在某些情况下,它可以失效,使this回到默认绑定(指向全局对象或undefined)。失效的常见情况包括:

  • 间接引用:如果将方法赋值给一个变量,并通过这个变量调用方法,隐式绑定将失效。

    var anotherFunc = obj.bar;
    anotherFunc();  // this不再指向obj,而是全局对象或在严格模式下为undefined
    
  • 传递给高阶函数:如果你将一个对象的方法作为回调函数传递给另一个函数,除非使用绑定工具(如.bind),否则原有的this绑定通常会丢失。

    setTimeout(obj.bar, 100);  // 当bar被调用时,this通常不会指向obj
    

3. this绑定规则三:new绑定

/*
   new绑定的步骤:
   1.创建新的空对象
   2.将this指向这个空对象
   3.执行函数体中的代码
   4.没有显示返回非空对象时, 默认返回这个对象
*/

function foo() {
    this.name = "why"
    console.log("foo函数:", this)
  }

new foo()

在这个示例中:

  • new foo()创建了一个新对象。
  • thisfoo函数中指向了这个新创建的对象。
  • foo函数内部,这个新对象被赋予了一个名为name的新属性,并设为"why"
  • 函数执行了console.log("foo函数:", this);语句,打印了这个新对象,显示它现在包含一个name属性。
  • 由于函数没有返回任何值,所以new foo()的结果是新创建的对象。

4. this绑定规则四:显示绑定

在JavaScript中,显式绑定是一种通过特定的函数方法,如 .call(), .apply(), 和 .bind(), 来设定函数执行时this的值的技术。通过显式绑定,我们可以直接指定函数调用的上下文对象,不论函数在何处被声明或被调用。

显式绑定的机制

显式绑定主要通过三种函数方法实现:

  1. .call(thisArg, arg1, arg2, ...):调用一个函数,其this值被显式设置为call的第一个参数thisArg,其余参数依次传递给函数。
  2. .apply(thisArg, [argsArray]):与.call()类似,但接受一个参数数组作为函数执行时的参数。
  3. .bind(thisArg[, arg1[, arg2[, ...]]]):创建一个新函数,当这个新函数被调用时,其this值永久地被绑定为.bind()的第一个参数thisArg,其余参数将作为新函数的预设参数。
var obj = {
  name: "why"
};

function foo() {
  console.log("foo函数:", this);
}

// 使用.call()方法,将this显式绑定到obj对象
foo.call(obj);  // 输出 "foo函数: { name: 'why' }"

// 将this显式绑定到数字123
foo.call(123);  // 输出 "foo函数: Number {123}"

// 将this显式绑定到字符串"abc"
foo.call("abc");  // 输出 "foo函数: String {'abc'}"
  • foo.call(obj):在这个调用中,this被显式设置为指向obj对象。因此,当foo执行时,它打印出this指向的对象,显示对象中的name属性。

  • foo.call(123)foo.call("abc"):在这两个调用中,this不指向一个常规的对象。JavaScript将这些原始值(数值和字符串)转换为它们对应的包装对象(NumberString对象)。因此,函数内部的this实际上是一个对象,而这个对象是原始值的一个包装形式。

三、this绑定规则优先级

1. 绑定优先级概述:

JavaScript中this的绑定规则有四种主要形式,且它们具有不同的优先级:

  1. new绑定
  2. 显式绑定 (call, apply, bind)
  3. 隐式绑定 (作为对象的方法调用)
  4. 默认绑定 (独立函数调用)

2. 显式绑定高于隐式绑定:

function foo() {
   console.log("foo:", this)
}

// 测试一: apply/call高于默认绑定
var obj = { foo: foo };
obj.foo.apply("abc");  // this 绑定到 "abc"
obj.foo.call("abc");   // this 绑定到 "abc"

// 测试二: bind高于默认绑定
var bar = foo.bind("aaa");
var obj = {
  name: "why",
  baz: bar
};
obj.baz();  // this 仍然绑定到 "aaa", 即使是作为 obj 的方法调用

无论是.apply(), .call()还是.bind(),显式绑定的this值都会覆盖隐式绑定。

3. new 绑定高于隐式绑定

function foo() {
   console.log("foo:", this)
}

var obj = {
  name: "why",
  foo: function() {
    console.log("foo:", this);
    console.log("foo:", this === obj);
  }
};
new obj.foo();  // this 指向新创建的对象,而不是 obj

即使foo函数作为obj的方法被定义和调用,new操作符创建了一个新的对象,并且this被绑定到这个新对象上。

4. new绑定高于bind

function foo() {
  console.log("foo:", this);
}
var bindFn = foo.bind("aaa");
new bindFn();  // this 指向新创建的对象,而不是绑定的 "aaa"

尽管foo.bind()方法绑定到"aaa",但new bindFn()的调用创建了一个新的对象,this指向这个对象,显示new绑定优先级高于.bind()

5. bind和apply/call的比较

var bindFn = foo.bind("aaa");
bindFn.call("bbb");  // this 仍然绑定到 "aaa"

这个例子中,尽管.call()试图将this绑定到"bbb"bindFnthis已被永久绑定到"aaa",表明.bind()的效果是不可被后续的.call().apply()覆盖的。

6. 总结

this绑定的优先级从高到低依次是:new绑定、显式绑定(bind优先于call/apply)、隐式绑定、默认绑定。

四、this绑定之外的情况

1. 显式绑定nullundefined

当使用.call(), .apply(), 或 .bind()进行显式绑定时,如果第一个参数是nullundefined,则这些方法不会将this绑定到任何特定值上,而是采用默认绑定规则(非严格模式下绑定到全局对象,严格模式下绑定到undefined)。

function foo() {
  console.log("foo:", this)
}

foo.apply("abc");         // "foo:", String {'abc'}
foo.apply(null);          // "foo:", Window(浏览器环境下的全局对象)或 global(Node.js)
foo.apply(undefined);     // "foo:", Window 或 global

这里,.apply("abc")this绑定到字符串"abc"的包装对象。但是,.apply(null).apply(undefined)则回落到默认绑定规则。在严格模式下,这些调用将导致this绑定到undefined

2.情况二:间接函数引用

间接引用发生时,函数虽然是从某个对象上取得,但由于赋值或其它操作,函数的调用已经与原对象分离。

var obj1 = {
  name: "obj1",
  foo: function() {
    console.log("foo:", this)
  }
};
var obj2 = {
    name: "obj2"
};

// obj2.foo = obj1.foo
// obj2.foo()   // 正常隐式绑定,"foo:", obj2

(obj2.foo = obj1.foo)();  // "foo:", Window 或 global

(obj2.foo = obj1.foo)()的调用中,函数通过赋值操作被取得,并立即调用。这种调用方式使得函数的执行脱离了任何对象的上下文(默认绑定),因此在非严格模式下,this指向全局对象。这是因为赋值表达式(obj2.foo = obj1.foo)返回的是函数本身,并且这个返回值不持有原始的对象引用。

五、箭头函数

箭头函数在ECMAScript 6中引入,提供了一种更简洁的函数写法,主要用于匿名函数表达式。与传统的函数表达式相比,箭头函数具有几个特点,包括更短的语法、没有自己的thisargumentssupernew.target,这些值由外围最近一层非箭头函数决定。

箭头函数的注意事项:

  • 不适合用作方法:由于箭头函数没有自己的this,如果将箭头函数作为对象的方法,this将不会指向该对象。
  • 不能用作构造函数:箭头函数不能使用new关键字,因为它们没有[[Construct]]方法,不具备构造对象的能力。
  • 没有arguments对象:不能直接访问命名参数之外的参数,除非使用剩余参数语法。

1. 箭头函数基本使用

(1) 传统函数和箭头函数的对比:

// 1.函数传统写法
    function foo1() {}
    var foo2 = function(name, age) {
      console.log("函数体代码", this, arguments)
      console.log(name, age)
    }

    // 2.箭头函数完整写法
    var foo3 = (name, age) => {
      console.log("箭头函数的函数体")
      console.log(name, age)
    }

(2)forEach中使用箭头函数

    // 3.箭头函数的练习
    // 3.1. forEach
    var names = ["abc", "cba", "nba"]
    names.forEach((item, index, arr) => {
      console.log(item, index, arr)
    })
  • 箭头函数作为回调函数传递给forEach方法,可以直接访问item, index, 和 arr
  • 这种场景下箭头函数非常方便,因为它可以继承外围作用域的this

(3)setTimeout中使用箭头函数

    // 3.2. setTimeout
    setTimeout(() => {
      console.log("setTimeout")
    }, 3000)
  • setTimeout调用中使用箭头函数,无需担心this的指向问题,因为箭头函数不绑定this,可以安全地用在定时器和延时调用中。

2. 箭头函数的简写

    var names = ["abc", "cba", "nba"]
    var nums = [20, 30, 11, 15, 111]

    // 1.优化一: 如果箭头函数只有一个参数, 那么()可以省略
    names.forEach(item => {
      console.log(item)
    })
    var newNums = nums.filter(item => {
      return item % 2 === 0
    })

    // 2.优化二: 如果函数体中只有一行执行代码, 那么{}可以省略
    names.forEach(item => console.log(item))

    // 一行代码中不能带return关键字, 如果省略, 需要带return一起省略(下一条规则)
    var newNums = nums.filter(item => {
      return item % 2 === 0
    })

    // 3.优化三: 只有一行代码时, 这行代码的表达式结果会作为函数的返回值默认返回的
    var newNums = nums.filter(item => item % 2 === 0)
    var newNums = nums.filter(item => item % 2 === 0)


    // 4.优化四: 如果默认返回值是一个对象, 那么这个对象必须加()
    // 注意: 在react中我会经常使用 redux

    var arrFn = () => ["abc", "cba"]
    var arrFn = () => {} // 注意: 这里是{}执行体
    var arrFn = () => ({ name: "why" })
    console.log(arrFn())

    // 箭头函数实现nums的所有偶数平方的和
    var nums = [20, 30, 11, 15, 111]
    var result = nums.filter(item => item % 2 === 0)
                     .map(item => item * item)
                     .reduce((prevValue, item) => prevValue + item)
    console.log(result)

3. 箭头函数中this的使用

// 1.普通函数中是有this的标识符
function foo() {
  console.log("foo", this);
}
foo();              // 在非严格模式下,this指向全局对象(浏览器中的window,Node.js中的global)
foo.apply("aaa");   // 显式绑定this到字符串"aaa"的包装对象



// 2. 箭头函数中不存在this
var bar = () => {
  console.log("bar:", this);
}
bar();               // 箭头函数中的this继承自创建时的上下文
bar.apply("aaaa");   // .apply()和.call()无法改变箭头函数的this,它仍然指向创建时的上下文

箭头函数bar被定义在全局作用域中,因此其this指向全局对象(在浏览器中是window,在Node.js中可能是global或模块的exports对象),即便使用.apply().call()也无法改变this的指向。

4. this查找规则

var obj = {
  name: "obj",
  foo: () => {
    var bar = () => {
      console.log("bar:", this);
    }
    return bar;
  }
}
var fn = obj.foo();
fn.apply("bbb");  // 嵌套的箭头函数中的this仍然指向创建时的上下文,不会被.apply()改变

在这个例子中,即使foo是作为obj的属性定义的,由于foo是箭头函数,其this并不指向obj,而是继承自其创建时的上下文(全局作用域)。因此,嵌套的箭头函数bar中的this同样指向全局作用域,而非obj或通过.apply()尝试指定的任何对象。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值