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)
函数作为参数时,参数函数叫做子函数,外面的函数叫父函数,子函数也叫回调函数,像这样的函数有很多,比如forEach 、setimeout,这些函数里的参数函数也叫内置参数
记住:父函数有能力决定子函数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 的指向。这里的重点有两个:
- 创建箭头函数时,就已经确定了它的
this指向。 - 箭头函数内的
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>