Javascript躲猫猫的this到底在哪里?

384 阅读4分钟

公众号:暮北林
Q Q 群 : 一起学前端
今天学习Javascript中的this指向

概述

Javascript中的this为函数执行时的执行环境,因此函数的调用方式决定了执行时的this指向,每次函数被执行所处的执行环境不同this的指向也不尽相同;本文从普通函数调用、对象方法、构造函数、call/apply/bind、箭头函数、Dom事件等来揭开this的红盖头.

为什么需要this

this为一个代词代指当前函数的执行环境,在真实世界中也是如此,比如 小辉辉自己说: 小辉辉身材高大英俊威猛足足有280斤 这样的描述着实让人郁闷,自己说自己不用人称代词(我/你)代指吗? 另一种说法 小辉辉自己说: 我身材高大英俊威猛足足有280斤 这里的就是一个代指不管在何种环境谁来谁这句话就和说话人有关系. 我 和 this 的作用都是一样的一个代指当前的环境(人)

普通的函数声明式调用

function sayName(name) {
    var name = name;
    console.log(name); // wangfpp
    console.log(this); // Window
    console.log(this.name) // ""
}
sayName('wangfpp');
// 此种调用方式可以看做window.sayName('name');

解析:

  • 上述函数在全局环境下执行this便是全局对象Window
  • 全局环境中无name属性所以第5行打印为空
  • 该函数执行会使Window上多了一个name属性
  • 函数在全局环境下调用 this指向window

对象方法中的函数调用

var name = "test fn";
var person = {
    name: 'wangfpp',
    sayName: function() {
        console.log(this.name);
    }
}
person.sayName();
var fn = person.sayName;
fn()
// 执行结果
// wangfpp
// index.html:48 test fn

解析:

  • 全局环境有name person两个变量, person有sayName的属性方法
  • 直接调用person.sayName() 函数的执行环境在person上自然打印person.name-wangfpp
  • 第9行将函数赋值给fn后在全局环境下执行fn() this指向Window则打印window.name-test fn
  • 在什么环境执行this就是环境的代指
看第二个对象中的方法
var name = "test fn";
var person = {
    name: 'wangfpp',
    sayName: function(name) {
        console.log(this.name);
        var printName = function(name) {
            this.name = name
            console.log(this.name);
        }
        console.log(this)
        printName(name);
    }
}
person.sayName('变更值');
console.log('goabl:' + name);
// 执行结果
/*
wangfpp
{name: "wangfpp", sayName: ƒ}
变更值
goabl:变更值
*/

解析:

  • 上述代码是对象中的方法执行环境sayName的执行环境在person中
  • 但是PrintName的执行环境变更为全局导致this === Window 而且修改了全局变量name
  • 聪明的你改写以下传递this进入printName进入函数内部就好了 这是JS的设计缺陷
  • 虽然是对象中的方法,但调用函数者仍是全局对象

构造函数中的函数调用

一:构造函数直接调用

var name = 'myName', age = 100;
function Person(name, age) {
    this.name = name;
    this.age = age;
    console.log(this.name);
    console.log(this.age);
    console.log(this)
}
Person('wangfpp', 18);
console.log(this.name, this.age, this);

解析:

  • 构造函数直接调用执行环境为Window全局对象
  • 函数传值后给this对象赋值 修改了全局的age和name属性
  • 构造函数直接调用和普通函数一样

二:new 关键字调用

var name = 'myName', age = 100;
function Person(name, age) {
    this.name = name;
    this.age = age;
    console.log(this.name);
    console.log(this.age);
    console.log(this)
    this.sayName = function() {
        console.log('myName:' + this.name);
    }
}
var person = new Person('aaa', 2);
person.sayName()
console.log(person);
/*
aaa
2
Person {name: "aaa", age: 2}
myName:aaa
Person {name: "aaa", age: 2, sayName: ƒ}
*/

解析:

  • new关键字会创建一个心得对象并将this指向这个对象

call/apply/bind调用函数改变this指向

var obj = {
	name: 'wangfpp',
	age: 19,
	sayName: function(hobby) {
		console.log(`${this.name} ${this.age}, ${hobby}`);
	}
}
let other = {name: 'xiaohuihui', age: 32};
let another = {name: 'boyang', age: 33};
obj.sayName('study');  // 直接调用
obj.sayName.call(other, 'women'); // call改变this指向
obj.sayName.apply(another, ['yanan']); // apply 改变this指向
let fn = obj.sayName.bind({name: 'bind', age: 'inify'}); // bind返回一个新的函数
fn('Javascript');
/* 执行结果
wangfpp 19, study
xiaohuihui 32, women
boyang 33, yanan
bind inify, Javascript
*/

解析:

  • 10行函数以对象方法调用this指向obj
  • 11行和12行分别以call apply传入this 和参数 this指向传入的this
  • call和apply的区别在于 call参数逐个传入 apply传入的是一个参数列表Arguments Array
  • 13行使用bind方法传入一个this bind不会立即执行而是返回一个函数
  • 以上三种都是改变函数this的方法 只是各有不同

箭头函数 () => {}

var obj = {
	name: 'wangfpp',
	age: 19,
	sayName: function() {
		console.log(`${this.name} 'sayname' ${this.age}`);
	},
	sayMe: () => {
		console.log(this);
	},
	main: function() {
		console.log(this);
		this.sayName();
		this.sayMe();
		setTimeout(() => {
			console.log(this);
		}, 0)
	}
}
obj.main();
obj.sayMe();
/* 执行结果
11: {name: "wangfpp", age: 19, sayName: ƒ, sayMe: ƒ, main: ƒ}
5: wangfpp 'sayname' 19
8: Window {parent: Window, opener: null, top: Window, length: 0, frames: Window, …}
8: Window {parent: Window, opener: null, top: Window, length: 0, frames: Window, …}
15: {name: "wangfpp", age: 19, sayName: ƒ, sayMe: ƒ, main: ƒ}
*/

解析:

  • 箭头函数不会绑定this this指向最外层函数的执行环境
  • 对象方法通过obj.sayName()不是箭头函数 调用this指向obj
  • 对象方法是箭头函数的 无外层函数则this指向Window
  • main函数中的setTimeout的外层函数为main()函数 main函数的this指向obj

Dom事件

<body>
	<button id="btn" onClick="btnClick()">按钮1</button>
	<button id="btn2">按钮2</button>
</body>
<script>
function btnClick() {
	console.log(this);
}
let node = document.querySelector('#btn2');
node.onclick = function() {
	console.log(this);
}
/*
Window {parent: Window, opener: null, top: Window, length: 0,...}
<button id="btn2">按钮2</button>
</script>

解析:

  • DOM事件的this和常规函数一样谁调用指向谁

其他的this指向

一: 数组中的方法

function fn() {
	console.log(this);
}
var arr = [fn, 1, 2];
arr[0]();
// (3) [ƒ, 1, 2] 指向arr数组

二:闭包中的this

function fn() {
    return (function() {
	console.log(this)
    }());
}
fn();
// Window {parent: Window, opener: null, top: Window …}指向window

三: 匿名函数中的this

function fn() {
    let test = function() {
	console.log(this);
    }
test();
}
fn();
// Window {parent: Window, opener: null, top: Window …}指向window

四: 函数参数传递

var x = 1;
var obj = {
    x: 2,
    printX: function(){
        console.log(this.x);
    }
}
setInterval(obj.printX, 1000);
// 一直打印1 调用printX类似于函数体赋值
var PrintX = function () {};
setInterval(printX, 1000)

写在最后(总结)

可能还有其他的this指向的案例 但是判断条件是不变的,谁调用执行环境就是谁this指向的就是执行环境对象, 除非你使用call/apply/bind或者var that = this变量赋值来修改或者传递this, 要么就是利用箭头函数来继承外层函数的执行环境

我的公众号和QQ群

公众号