【JavaScript】this关键字的指向问题(五千字详解)

简介: 【JavaScript】this关键字的指向问题(五千字详解)

前言

看了这篇文章,麻麻再也不用担心我不理解JS中this的指向问题了。本文以文章目录为顺序,层层递进。耐心的看完,会有很大的收获。

一、this的作用?我们为什么要用this,没它不行吗?

this关键字是JavaScript中最复杂的机制之一。它是一个很特别的关键字,被自动定义在所有函数的作用域中。但是即使是非常有经验的JavaScript开发者也很难说清它到底指向什么。
实际上,JavaScript中this的机制并没有那么先进,但是开发者往往会把理解过程复杂化。毫不夸张地说,不理解它的含义,大部分开发任务都无法完成。
this都有一个共同点,它总是返回一个对象。简单说,this就是属性或方法“当前”所在的对象。
从某些角度来说,开发中如果没有this,很多问题也有其他的解决办法。
但是没有this,会让我们编写代码变得非常不放便。

话不多说,举个栗子 看看就知道了,示例代码如下:

var obj = {
    name: '张三',
    eating () {
        console.log(obj.name + '在吃饭')
    },
    studying () {
        console.log(obj.name + '在学习')
    },
    running () {
        console.log(obj.name + '在跑步')
    }
}
obj.eating()
obj.studying()
obj.running()
上述示例代码,如果我们修改了变量obj名字为object,之后需要修改的代码有以下两处:
首先,需要修改的代码有方法的调用。
其次,就是在eating、studying、running三个方法中打印语句中的变量名
如果我们的obj对象中不止三个方法呢?如果是一百个方法呢?一个一个改,杀了我吧(裂开)。

如下示例代码,展示了使用this进行优化的效果:

var obj = {
    name: '张三',
    eating () {
        console.log(this.name + '在吃饭')
    },
    studying () {
        console.log(this.name + '在学习')
    },
    running () {
        console.log(this.name + '在跑步')
    }
}
obj.eating()

优化后的代码不管我们怎么改变变量名称 ,我们需要修改的地方只有一个,那就是方法调用时的对象名称。是不是简便了许多,为编程的效率提升的不止一点哦。

二、this在全局作用域中指向哪里?

其实在大多数情况下,this都是出现在函数中。但也有出现在全局作用域下的情况,我们就来举个栗子说一说。

示例代码如下:

var name = 'code'
console.log(this) 
console.log(this.name) // 输出 code 
console.log(Window.name) // 输出 code 

浏览器环境下输出结果如下图所示:

由图可知,全局作用域下,浏览器环境中:this指向的是全局对象Window。


这里需要补充一下的是,在全局作用域下,node环境中,this指向的是一个空对象{}。


三、同一个函数中this的不同指向

先看代码,再做结论。如下示例代码展示了不同的调用方法,得到的this的不同指向。示例代码如下:

// 定义一个函数
function foo () {
    console.log(this)
}
// 调用方式一:直接调用
foo() // this指向:Window
// 调用方式二:间接调用
var obj = {
    name: '张三',
    fn: foo
}
obj.fn() // this指向:obj对象
// 调用方式三:通过call/apply调用
foo.call('123') //this指向: String {‘123’}对象

浏览器输出结果如下所示:

三个不同的调用方法,得到了三个不同的this指向。


结论:

1.函数在调用时,JavaScript会默认给this绑定一个值;

2.this的绑定值与定义的位置没有关系;

3.this的绑定和调用方式以及调用的位置没有关系;

4.this是在运行时被绑定的,而不是在定义时。


四、this的四种绑定规则

1、默认绑定

在一个函数体中使用this,当该函数被独立调用时,就会被默认绑定一个对象。


在默认绑定情况下,只要是独立函数调用就是指向Window。


如下展示五个案例进行深入学习,层层深入。示例代码如下:

// 案例一:
function foo1 () {
    console.log(this)
}
foo1()
// 案例二: 三个方法被调用的时候都是独立的,所以this的指向都是Window
function one () {
    console.log(this)
}
function two () {
    console.log(this)
    one()
}
function three () {
    console.log(this)
    two()
}
three()
// 案例三:
var obj = {
    name: '张三',
    foo3 () {
        console.log(this)
    }
}
var bar3 = obj.foo3
bar3() // Window
// 案例四:
function foo4 () {
    console.log(this)
}
var obj = {
    name: '张三',
    fn4: foo4
}
var bar4 = obj.fn4
bar4() // Window
// 案例五:
function foo5 () {
    function bar () {
        console.log(this)
    }
    return bar
}
var fn = foo5()
fn()

上述代码中五个案例,全部都是独立调用,所以显而易见,输出结果this都是指向Window对象


2、隐式绑定

通过某个对象进行调用,也就是它的调用位置中,是通过某个对象发起的函数调用。

隐式绑定有一个前提条件:


必须在调用的对象内部有一个对函数的引用(比如一个属性)

如果没有这样的引用,在进行调用时,会报找不到该函数的错误

正式通过这个引用,间接的讲this绑定到了这个对象上

如下通过三个案例,进行隐式绑定的详细了解,示例代码如下:

// 案例一:通过隐式绑定将fn函数中的this指向obj对象
function fn () {
    console.log(this)
}
var obj = {
    name: '张三',
    studying: fn
}
obj.studying() // {name: '张三', studying: ƒ}
// 案例二:
var obj2 = {
    name: '张三',
    eating () {
        console.log(this.name + '正在吃饭')
    },
    running () {
        console.log(obj.name + '正在跑步')
    }
}
// 隐式绑定进行调用
obj2.eating()  // 张三正在吃饭
obj2.running() // 张三正在跑步
var fn = obj.eating
fn() // 独立函数调用 指向window,name为window中的name的值为空    输出结果为: 正在吃饭
// 案例三
var obj3 = {
    name: 'foo3',
    foo () {
        console.log(this)
    }
}
var obj4 = {
    name: 'foo4',
    bar: obj3.foo
}
obj4.bar() // {name: 'foo4', bar: ƒ}

上述三个案例中,三个this都被隐式绑定到了调用此方法的对象上。


3、显示绑定 (call/apply/bind )

显示绑定就是指使用js中的原型方法call()、apply()、bind(),对this进行显示绑定。

首先我们先对三个方法的概念下手,如下是参考的相关文档整理的:


①、call()

定义:

call()方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。


语法:

fun.call(thisArg, arg1, arg2, ...)

参数:


thisArg:在 fun 函数运行时指定的 this 值。if(thisArg == undefined|null) this = window,if(thisArg == number|boolean|string) this == new Number()|new Boolean()| new String()

arg1, arg2, …: 指定的参数列表

返回值: 使用调用者提供的 this 值和参数调用该函数的返回值。若该方法没有返回值,则返回 undefined。


②、apply()

定义:

apply()方法调用一个具有给定this值的函数,以及作为一个数组(或类似数组对象)提供的参数。


语法:

func.apply(thisArg, [argsArray])

参数:


thisArg:可选的。在 func 函数运行时使用的 this 值。请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装。

argsArray: 可选的。一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。

返回值: 调用有指定this值和参数的函数的结果


③、bind()

定义:

bind()方法创建一个新的函数,在bind()被调用时,这个新函数的this被bind的第一个参数指定,其余的参数将作为新函数的参数供调用时使用。


语法:

function.bind(thisArg[,arg1[,arg2[, ...]]])

参数:


thisArg:调用绑定函数时作为this参数传递给目标函数的值。如果bind函数的参数列表为空,执行作用域的this将被视为新函数的thisArg。

arg1, arg2, …: 当目标函数被调用时,预先添加到绑定函数的参数列表中的参数。

返回值: 返回一个原函数的拷贝,并拥有指定的this值和初始参数。


概念介绍完了,上代码:

// call 与 apply
function foo () {
    console.log(this + 'foo函数被调用了')
}
var obj = {
    name: 'obj'
}
// 调用方法一:直接调用 指向的是全局对象(window)
foo()
// 调用方法二:  call和apply与直接调用不同的在于this的绑定不同 ,可以直接指定this的绑定对象为obj
foo.call(obj) //{name: 'obj'} 
foo.apply(obj) //{name: 'obj'}
// bind
function bar () {
    console.log(this)
}
var newBar = bar.bind('aaa')
newBar() // String {'aaa'}   解释一下 newBar()句话 很明显是直接调用应该指向全局对象window 但是bind显示绑定的优先级更高 (优先级会在下一篇文章更新)

4、new绑定

在JavaScript中,构造函数只是一些使用new操作符时被调用的函数。包括内置对象函数在内的所有函数都可以用new来调用,这种函数调用称为构造函数调用。

我们通过一个new关键字调用一个函数时(构造器),这个时候this是在调用这个构造器时创建出来的对象。 this 等于 创建出来的对象 ,这个过程就是 new 绑定。


示例代码如下:

function Person (name, sex) {
    this.name = name
    this.sex = sex
}
var p1 = new Person('张三', '男')
console.log(p1.name, p1.sex) // 张三 男
// 每次都会创建新对象赋值给this
var p2 = new Person('李红', '女')
console.log(p2.name, p2.sex) // 李红 女

5、call和apply的区别

根据上面的概念我们可以很明显的看出来,两个方法接收的参数不一样,其实作用基本一致。

// call 与 apply的区别是什么?
function sum (num1, num2, num3) {
    console.log(num1 + num2 + num3)
}
sum.call('call', 10, 20, 30) // 60
sum.apply('apply', [10, 20, 30]) // 60
// call参数是单个的值,而apply的参数是一个数组,就这点差别
目录
相关文章
|
1月前
|
JavaScript 前端开发 安全
ECMAScript 6(以下简称 ES6)的出现为 JavaScript 带来了许多新的特性和改进,其中 let 和 const 是两个非常重要的关键字。
ES6 引入了 `let` 和 `const` 关键字,为 JavaScript 的变量管理带来了革新。`let` 提供了块级作用域和暂存死区特性,避免变量污染,增强代码可读性和安全性;`const` 用于声明不可重新赋值的常量,但允许对象和数组的内部修改。两者在循环、函数内部及复杂项目中广泛应用,有助于实现不可变数据结构,提升代码质量。
27 5
|
1月前
|
前端开发 JavaScript 开发者
除了 async/await 关键字,还有哪些方式可以在 JavaScript 中实现异步编程?
【10月更文挑战第30天】这些异步编程方式在不同的场景和需求下各有优劣,开发者可以根据具体的项目情况选择合适的方式来实现异步编程,以达到高效、可读和易于维护的代码效果。
|
3月前
|
JavaScript 前端开发 Java
JavaScript 保留关键字
JavaScript 保留关键字
24 2
|
3月前
|
JavaScript 前端开发
JavaScript this 关键字
JavaScript this 关键字
17 1
|
4月前
|
JavaScript 前端开发
JavaScript 语句标识符(关键字)
【8月更文挑战第29天】
29 5
|
4月前
|
JavaScript 前端开发
|
6月前
|
JavaScript 前端开发 开发者
JavaScript中的const关键字解析
JavaScript中的const关键字解析
|
6月前
|
JavaScript 前端开发
JavaScript变量命名规则及关键字详解
JavaScript变量命名规则及关键字详解
|
6月前
|
自然语言处理 JavaScript 前端开发
在JavaScript中,this关键字的行为可能会因函数的调用方式而异
【6月更文挑战第15天】JavaScript的`this`根据调用方式变化:非严格模式下直接调用时指向全局对象(浏览器为window),严格模式下为undefined。作为对象方法时,`this`指对象本身。用`new`调用构造函数时,`this`指新实例。`call`,`apply`,`bind`可显式设定`this`值。箭头函数和绑定方法有助于管理复杂场景中的`this`行为。
62 3
|
5月前
|
JavaScript
js 【详解】函数中的 this 指向
js 【详解】函数中的 this 指向
44 0