文章目录
前言
相信很多朋友和我一样,总是搞不清this的指向关系,由于没有硬性要求,所以又总是不想学习,经过两次面试后,笔者发现这个考点真是面试官常考的点,大家静下心来一起学习这篇this的指向吧!
文章主体内容分为两块:this的指向以及如何改变this的指向
一、this的指向问题
1.1 全局中的this
全局的this指向window
// 1. 全局中的this
console.log(this) // window
1.2 普通函数中的this
普通函数中的this指向调用者
function fn() {
console.log(this) // window
}
fn() // window.fn(),window调用了fn函数,所以fn中的this表示window
这里的fn(),调用者实际上是window,只不过是window.fn(),window省略了
默认绑定 (指向window的情况) 函数调用模式 fn()
let obj = {
name : '前端百草阁',
fn: function(){
console.log(this.name)
}
}
obj.fn() // 输出结果: 前端百草阁
这里的fn调用者是obj这个对象,所以this.name等价于obj.name
隐式绑定 (谁调用, this指向谁) 方法调用模式 obj.fn()
1.3 定时器中的this
定时器中的this指向全局对象window
function sayHello() {
console.log(this);
}
setTimeout(sayHello, 1000); // window
1.4 事件处理函数中的this
事件处理函数中的this指向事件源
document.body.addEventListener('click', function () {
console.log(this) // body
})
当触发点击事件时,打印 body,此例中事件源为body,所以this指向事件源(body)
1.5 构造函数中的this
构造函数中的this指向实例化对象
new 绑定(构造函数模式)
在使用
new
创建对象时也会进行this
绑定
new 实例化对象的四步:
1.创建一个新对象
2.让构造函数里的this指向新对象
3.执行构造函数
4.返回实例
function Dog(age) {
this.age = age
this.say = function () {
console.log(this)
console.log(this.age)
}
}
//利用构造函数创建实例化对象dog
let dog = new Dog()
dog.say() // 输出结果:
// Dog {age: 3, say: ƒ}
// 3
这里的dog为实例化对象,所以构造函数中的this等价于dog这个实例化对象
有的同学可能会有点迷糊,这里大家切记this指向是在运行时确定的,而不是在定义时确定的
代码运行时实例化出了一个dog对象,这时this指向这个实例化对象,并不是说在定义构造函数时就确定this了
值得一提的是,若你在构造函数的原型对象上再添加一个方法,this依然指向实例化对象
function Dog(age) {
this.age = age
this.say = function () {
console.log(this)
console.log(this.age)
}
}
Dog.prototype.eat = function () {
console.log(this)
}
//利用构造函数创建实例化对象dog
let dog = new Dog()
dog.say() // 输出结果:
// Dog {age: 3, say: ƒ}
// 3
dog.eat() // 输出结果: Dog {age: 3, say: ƒ}
1.6 构造函数静态方法中的this
构造函数的静态方法中,this表示构造函数
// 构造函数Pig
function Pig() {
}
// 给构造函数,直接添加的方法,叫做静态方法
Pig.eat = function () {
console.log(this)
}
// 调用的时候,只能使用构造函数调用
Pig.eat() // 输出结果: ƒ Pig() { }
1.7 箭头函数中的this
箭头函数没有自己的this值,会继承外部作用域的this
箭头函数不同于传统函数,它其实没有属于⾃⼰的 this
,
它所谓的 this
是, 捕获其外层 上下⽂
的 this
值作为⾃⼰的 this
值。
并且由于箭头函数没有属于⾃⼰的 this
,它是不能被 new
调⽤的。
我们可以通过 Babel 转换前后的代码来更清晰的理解箭头函数:
// 转换前的 ES6 代码
const obj = {
test() {
return () => {
console.log(this === obj)
}
}
}
// 转换后的 ES5 代码
var obj = {
test: function getArrow() {
var that = this
return function () {
console.log(that === obj)
}
}
}
通过语法降级我们看到,箭头函数中的 this
就是它上层上下文函数中的 this
。
var age = 10
let obj = {
age: 20,
say: () => {
console.log(this.age) // 10
}
}
obj.say()
这里大家觉得this.age是10 还是 20呢?
这里的箭头函数没有this,但是它会继承外部作用域的this,这里箭头函数作用域外,就是全局作用域window了,可能会有很多人觉得为什么不是obj的局部作用域呢? 因为!!对象的大括号不能当做一个作用域
所以这里的this.age 等价于 window.age 即为 10
再来一个例子
var age = 10
let obj = {
age: 20,
eat: function () {
let fn = () => {
console.log(this.age) // 箭头函数中没有this,所以这里的this指向eat方法中的this,即obj
}
fn()
}
}
obj.eat()
这里箭头函数中的this指向eat方法中的this,eat中的this又指向obj对象,所以这里的输出结果为20
二、修改函数中的this指向
接下来介绍的这三种方法的调用者都必须是函数
显式绑定 (又叫做硬绑定) 上下文调用模式, 想让this指向谁, this就指向谁
2.1 call
call的语法:函数.call(新的this,3,4),其中3,4代表传递的参数,34方便理解
let obj = { age: 20 }
function fn(x, y) {
console.log(this)
console.log(x + y)
}
fn(3,4) // 正常调用函数,函数中的this指向 window
fn.call(obj, 3, 4)
// 总结:
// 1. 函数.call() 表示调用函数,原函数fn得以调用了
// 2. 修改了原函数中的this,改成call方法的第一个参数
// 3. 如果原函数有形参,可以通过call方法为原函数传递实参
fn.call(obj,3,4) ,call函数一调用,就把原先fn里的this(指向window)改成了一个新的this(指向obj),3,4分别代表x,y要传递的参数
2.2 apply
apply的语法:函数.apply(新的this, [3, 4]),其中3,4代表传递的参数,34方便理解
let obj = { age: 20 }
function fn(x, y) {
console.log(this)
console.log(x + y)
}
fn(3,4)
fn.apply(obj, [3, 4])
// 总结:
// 1. 函数.apply() 表示调用函数,原函数fn得以调用
// 2. 修改了原函数中的this,改成 apply 方法的第一个参数
// 3. 如果原函数有形参,可以通过 apply 方法为原函数传递实参,但是必须使用数组格式(这也是与call方法的区别)
2.3 bind
bind的用法就比较不一样了,我们先看看bind函数的特点
1. 函数.bind() 表示创建了一个新的函数,并且不会调用任何函数
2. 修改了新函数中的this,改成 bind 方法的第一个参数了
3. 如果原函数有形参,可以通过 bind 方法为新函数传递实参
所以接下来直接给大家演示bind的使用
let obj = { age: 20 }
function fn(x, y) {
console.log(this)
console.log(x + y)
}
let a = fn.bind(obj, 3, 4)
a()
这里因为fn.bind()不会调用任何函数,所以要自己调用一遍
也可以这么写
let obj = { age: 20 }
function fn(x, y) {
console.log(this)
console.log(x + y)
}
fn.bind(obj, 3, 4)()
三、 this指向练习
3.1 某小游戏公司笔试题
请大家先想想答案是多少,再看讲解
let obj = {
stringName : "我是abc",
getName(){
return function(){
return this.stringName
}
}
}
console.log(obj.getName()()); // undefined
找普通函数的this指向一定要知道调用者是谁,这道笔试题很多人会被误导,以为调用者全都是obj,其实不然
我换种写法,大家就一目了然了
let obj = {
stringName : "我是abc",
getName(){
return function(){
console.log(obj.getName);
return this.stringName
}
}
}
let fn = obj.getName()
console.log(fn()) // undefined
这两种方式是一模一样的,这样大家是不是一眼就辨别出了呢?调用对象的方法的返回值,this指向的是全局对象window! 。这是因为返回的函数是作为全局函数被调用的,而不是作为 obj的方法被调用的
接下来,我们再看看如何才能读取到obj中的srtingName呢?
let obj = {
stringName : "我是abc",
getName(){
let that = this
return function(){
return that.stringName
}
}
}
console.log(obj.getName()());
这里和上面不一样的点:这里利用一个变量that,存储了getName中的this,getName的调用者又是obj,相当于他利用一个that存储了一个this并且指向obj,所以运行结果是:
3.2 大厂笔试题
手写实现call函数
Function.prototype.myCall = function(context, ...args) {
// 判断是否传入了context,如果没有则默认为全局对象
context = context || window;
// 将当前函数设置为context的一个属性,以便调用时可以通过context调用
context.fn = this;
// 调用函数并传入参数
const result = context.fn(...args);
// 删除context的fn属性
delete context.fn;
// 返回函数的执行结果
return result;
};
function greeting(name) {
console.log(`Hello, ${this.name + name}!`);
}
let a = {
name: "前端"
}
greeting.myCall(a,'百草阁');
};
实现要点:1.函数要调用 2.要改变this指向 3.要传参
如何实现改变this的指向呢? 大家注意context.fn = this这一行 ,this指向的其实就是greeting这个函数,相当于原本是window.greeting()调用,现在把函数作为context的一个属性调用,把this指向了这个context,改变了原greeting函数的调用方式,从而改变了this的指向
总结
本文重点讲解了,各种this的使用场景、如何改变函数中this的指向以及this的练习题
其实很多时候我们都会在各种场景下碰到各类this的问题,但是我们都选择了得过且过,不想花时间去了解,何不在这一次和笔者一起全面的学习this这个面试必考点呢!