【面试必考点】这一次带你彻底学会this的指向问题

news/2024/7/4 12:54:18

文章目录

  • 前言
  • 一、this的指向问题
    • 1.1 全局中的this
    • 1.2 普通函数中的this
    • 1.3 定时器中的this
    • 1.4 事件处理函数中的this
    • 1.5 构造函数中的this
    • 1.6 构造函数静态方法中的this
    • 1.7 箭头函数中的this
  • 二、修改函数中的this指向
    • 2.1 call
    • 2.2 apply
    • 2.3 bind
  • 三、 this指向练习
    • 3.1 某小游戏公司笔试题
    • 3.2 大厂笔试题
  • 总结


前言

相信很多朋友和我一样,总是搞不清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省略了

    let obj = {
        name : '前端百草阁',
        fn: function(){
            console.log(this.name)
        }
    }
    obj.fn() // 输出结果: 前端百草阁

这里的fn调用者是obj这个对象,所以this.name等价于obj.name

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指向实例化对象

	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

  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指向

接下来介绍的这三种方法的调用者都必须是函数

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(34) // 正常调用函数,函数中的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(34)
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这个面试必考点呢!


http://www.niftyadmin.cn/n/3725570.html

相关文章

深入Windows NT/2000模块的组织(http://webcrazy.yeah.net)

深入Windows NT/2000模块的组织 WebCrazy( http://webcrazy.yeah.net/)在《 小议Windows NT/2000分页机制 》中我对x86平台Windows NT/2000的非分页内存内部机制有了较为详细的说明,从中也可以看出地址空间可以分为进程空间与系统空间,其中每个进程有各…

自行车运动水壶及水壶架的作用

自行车运动水壶的特点在于可以单手开启(徒步及登山用水壶则需要双手开启),这对于喜欢边骑边喝水的朋友比较实用,不过这时要记得放慢速度并注意前后来车哦。水壶架通常安装在车架上,水壶置于其上可以减轻背负重量。 【…

mysql导出数据1049_mysql怎么导入数据库 1049 42000 unknown database

前很多年, 使用mysqldump / mysql 进行数据库的备份和恢复, 都非常成功,但是, 这周换了windows2012 r2 和 mysql 5.6.15后, 并加一个discuz论坛是可以访问的 备份数据库是没有问题的, 但数据库恢复有两个错误:1 测试1命令行 .\mysql -uroot -p 数据库名错误提示内容…

剖析Windows NT/2000内核对象组织(http://webcrazy.yeah.net)

剖析Windows NT/2000内核对象组织 WebCrazy( http://webcrazy.yeah.net/)对象管理器在Windows NT/2000内核中占了极其重要的位置,其一个最主要职能是组织管理系统内核对象。在Windows NT/2000中,内核对象管理器大量引入了C面向对象的思想,即…

mysql 事务处理 存储过程_MySQL存储过程事务处理

Dev tdxDBTreeView可以快速的用tree展示层次结构,无需任何编码;对tree的操作会自动post到数据集:对数据集的操作会 在tree上表现 一.关键 设置 datasource displayField:节点的 ...《JavaScript DOM 编程艺术(第2版)》读书笔记阅读了本书第五章关于使用JavaScript的最佳实践,…

分析Windows NT/2000堆内存与虚拟内存组织(http://webcrazy.yeah.net)

分析Windows NT/2000堆内存与虚拟内存组织 WebCrazy( http://webcrazy.yeah.net/)在讨论今天这个主题时,我觉得是应该给大家重复推介以下两本书:Matt Pietrek<<Windows 95 System Programming Secrets>>Jeffrey Richter<<Programming Microsoft Windows,f…

mysql 三个关联_MySQL中三种关联查询方式的简单比较

看看下面三个关联查询的 SQL 语句有何区别&#xff1f;SELECT * FROM film JOIN film_actor ON (film.film_id film_actor.film_id)SELECT * FROM film JOIN film_actor USING (film_id)SELECT * FROM film, film_actor WHERE film.film_id film_actor.film_id最大的不同更多…

idea 里面的maven依赖下载不了,以及mabtis依赖包错误

目录前言一&#xff0c;下载maven依赖下载不了&#xff08;数据源问题&#xff09;二、之前用的MapperScan不能注入了&#xff08;mybatis包未注入&#xff09;三、mybatis版本问题&#xff08;mybatis&#xff09;后言前言 在进行了一天的奋战中&#xff0c;因为maven依赖而导…