JS高级使用1.0——this的使用以及函数apply()和call()的解释

创作场景

   因公司中大多数使用的是原生的js或者jquery写法,使用的UI框架大多数只是一些结构和一些组件(业务比较复杂),所以学习了js的高级教程,再根据公司现有的代码进行举证分析,准备对JS高级部分写一些博客做一下加深。

阅读前提

   最好是有一定工作经验(没有也不要紧,肯定会讲清楚),同时对js基础有一定了解,包括函数的定义和调用函数的作用域、什么是window对象等。如果对上述不了解的伙伴,此博客也会进行简单说明,帮助阅读者理解内容,并附上帮助内容的详细博客链接,以便后续了解。
   关于this的介绍可能会有些许繁杂和抽象,在apply()和call()函数是需要了解this的属性的,如果不懂可以适当跳过或只看一下总结内容。

开始正题

1. 什么是this,函数内的this和函数外的this有什么区别

   定义:JavaScript的this指的是调用某个函数的对象,官方语言:函数上下文
   解释:只要是一个函数,都有其调用者,函数无法脱离调用者主动执行,像setTimeOut执行函数和闭包执行函数只是调用方式不同,并不是说没有调用者。
   先用一个例子看一下怎么个事。

    <!-- 定义一个全局的函数-->
    function Fn() {
        console.log("函数内部的this", this)
    }
    // 执行全局函数
    Fn()
    // 输出外部的this
    console.log("函数外部的this", this)

   先看一下输出结果
在这里插入图片描述
   可以看到两个的输出都是window对象。

1.1 window对象

   在JavaScript中,一个浏览器窗口就是一个window对象。ECMAScript规定window对象是BOM对象,也就是浏览器对象,我们可以根据window进行浏览器的交互,比如说跳转页面:window.location.href = ‘跳转的路径’,而一般我们使用document.getElementById是用来获取页面上的某个标签,而标签<div></div>就是我们常说的DOM元素。

   如果详细了解window对象可以点击下面的链接。

JavaScript中的window对象详解

   OK,回归我们的this探索,先来看一下window对象中有什么?

在这里插入图片描述
   可以看到,我们当时定义的Fn函数已经挂载到了window对象上,这也是为什么函数内部的this是window对象的原因,因为就是window调用的这个函数,众所周知,函数的调用一定是对象点方法,也就是object.fun,一般我们在全局上下文中定义的函数直接就可以调用,是因为函数已经挂载到了window对象中,调用的时候会自动在window对象上找这个方法并且调用,最终就是这样执行的:

window.Fn()

   我们执行一下上面这个方法,其实输出结果和直接执行是一样的,这样就可以直观的看到this就是调用的对象这个定义了。
在这里插入图片描述

2. 对象内部的this怎么使用

   对象内部的this简单点来说就是指的对象本身,如下:

  var obj = {
    name: "张三",
    age: 18,
    // 等价于obj.name
    fun: function () {
      console.log("name:", this.name)
      console.log("age:", this.age)
    }
  }
  obj.fun()

   输出结果如下:
在这里插入图片描述
   稍作解释:这里用对象调用了对象内部的一个方法,所以函数fun中的this指向的就是obj对象,obj对象上有name和age两个属性,所以输出的就是对象上的属性值,接下来用两个稍微复杂的例子更进一步说明一下this。
   例1:

  console.log("-----------------------------例1中的this---------------------------")
  var obj1 = {
    name: "张三",
    age: 18,
    fun: function () {
      console.log("例1name:", name)
      console.log("例1age:", age)
    }
  }
  obj1.fun()

   输出结果:
在这里插入图片描述
   可以看到name输出了一个空值,而age显示的是未定义,这是怎么回事呢?
   解释一下,js在执行函数的时候,会在内存中开辟一块空间用于执行函数,执行结束后释放空间,那么这个空间就可以称之为函数的执行空间,一般函数中所用到的变量会优先在当前的执行空间中寻找,如果找不到,则继续向上找(但不包括对象,对象的属性都可以看为私有变量,只能通过对象访问),所以在这个例子中无法直接找到obj对象的name和age值。问题是name怎么是一个空值呢?
   按照刚才所说的,继续向上找的话只能是window,也就是全局上下文,这是最大也是最后一个上下文,先看下图:
在这里插入图片描述
   发现了吗?window上有个name属性是空值,这也是为什么没有报name未找到的错误,在全局上下文中这是一个变量,所以可以直接访问到,同时也可以看到两个obj对象也在window中。

  console.log("-----------------------------例2中的this---------------------------")
  var obj2 = {
    name: "张三",
    age: 18,
    fun: function () {
      var name = "李四";
      var age = 20;
      var height = 180;
      var objFn = {
        job: "程序猿",
        fun1: function (){
          console.log("fun1函数中的this", this)
        }
      }
      function fun2() {
        var name = "王五";
        var age = 30;
        objFn.fun1()
        console.log("fun2函数中的this", this)
        console.log("name:", name)
        console.log("age:", age)
        console.log("height:", height)
        console.log("this.name", this.name)
        console.log("this.age", this.age)
        console.log("this.height", this.height)
      }

      console.log("fun函数中的this", this)
      fun2()
    }
  }
  obj2.fun()

在这里插入图片描述

   这段代码其实涉及到了另一个闭包的知识点,这里不对闭包说明,解释一下这段程序:
   obj2对象中有一个函数是fun,fun函数中又定义了一个函数fun2,在fun2函数中分别使用this和不使用this输入了几个值,发现不用this的话,就是我们说的,首先在本身找,没有会依次向上找,直到找到为止,因为在fun函数中定义了name、age和height,所以输出的其实就是fun函数中的,那用this为啥就不行了呢?
   注意看哈,我们分别对fun2函数中的this和fun函数的this进行了打印,fun函数的this好理解,因为是obj2调用的,所以this就是obj2,那fun2函数中的this为啥是window对象,说白了,像这种定义的函数,因为没有直接调用的对象,obj2对象没有调用吧,那谁来调用呢?只能window老大哥了呀,你想啊,你是老三(fun2),你家老二(fun)不管你,那只能老大(window)管了啊,老大不管谁管,无论怎么嵌套定义,你就是在fun2函数在在定义一个fun3,它的this还是window,因为没人管呀。你看老二中有一个函数fun1,在对象objFn中,人家的this指向的就是自己objFn,因为就是人家自己调用的呀(可以这么理解,都在家里待着,你姐没钱了人家花自己的,你没钱了你就向你妈要,因为你姐不给你,虽然你两都在家里,都是兄妹,好惨)。

   总结:上面这段文字其实理解起来有些抽象,大家只需要记住,函数中的this不是指向的这个函数,这是很多人的误区,这个函数严格来说只是一个上下文,执行完就销毁了,this一定是一个可观测、持久存在的对象,如果函数执行前没有指定对象,那this指向的就是window。

场景this
obj.fun()obj对象
fun()window对象
在这里插入图片描述数组本身(数组中用法,一般正常人不这么用,了解即可)
setInterval、setTimeoutwindow对象
DOM元素的click或者其它事件DOM元素
IIFE(自执行函数)window对象

2.1 IIFE

   关于IIFE(自执行函数)可以看一下下面这篇博客,总结还是可以的

IIFE介绍

3. 实际和工作中this应该怎样使用

3.1 对象中定义多个函数,函数中出现互相调用的情况

  var funP = {
      fun1: function () {
          console.log("老大说:我出门上学了妈妈")
      },
      fun2: function () {
          console.log("老二说:我出门上学了妈妈")
      },
      fun3: function () {
          console.log("老三说:我出门上学了妈妈")
      },
      fun: function () {
          console.log("孩儿他爸说:我送三个孩子出门上学了老婆")
          this.fun1()
          this.fun2()
          this.fun3()
      }
  }
  // 老大自己去上学
  funP.fun1()
  // 老二自己去
  funP.fun2()
  // 老三自己去
  funP.fun3()
  // 她妈看不惯,决定让孩儿他爸出手
  funP.fun()

3.2 将this存储到变量,模拟修改this指向,达到使用this的目的

  console.log("------------------------------------------------")
  var obj1111111111111111 = {
      name: "张三",
      age: 18,
      fun1: function () {
          var that = this;
          function fun2() {
              console.log("that.name", that.name)
          }
          fun2()
      }
  }
  obj1111111111111111.fun1()	// 输出“张三”

   这种比较常见,因为实际中对象的定义都是见名知意的,一般都比较长,所以这种写法还是比较实用。

3.3 DOM对象调用函数中使用this

在这里插入图片描述
   这种可能看起来this的优势没有那么明显,如果页面上有多个DOM对象都需要绑定一个函数,函数中需要获取自己的文本,总不能给所有对象都像上面一样绑死一个函数,就是下面这样。

    $("#h1").bind('click', function () {
        console.log($("#h1").text())
    })
    $("#h2").bind('click', function () {
        console.log($("#h2").text())
    })
    。。。

   如果使用this,就可以改造为

    var domFun = function () {
        console.log(this.innerText)
    }
    $("#h1").bind('click', domFun)
    $("#h2").bind('click', domFun)

   这样就达到了函数复用的效果,维护起来也更好维护。

4. 为啥花精力研究this

   有的人就说了,我只需要记住这东西简单使用就行了,了解this这么深干嘛,因为apply和call要用啊,重头戏在这两函数,这两函数有大用啊!!!

   apply()和call()各有优势,最明显的就是可以修改this的指向,且听我简单唠一下,为什么要执意修改this的指向,有的人可能就说了,我传个参数不就行了吗,没毛病的,这么做在参数少的情况下可以,但是如果参数过多呢,开发中我们不建议将一个函数的参数定义过多,太多了不好维护,同时还有一个就是代码量压缩的概念,你看哈,搞个参数userPostInfo对象,那之后使用这个参数中的属性必须要用userPostInfo来访问,久而久之,代码量不就上去了吗,this是不是相对少一些呢?不要小瞧这几个字符串,积少成多也不少,程序讲究的是效率,如果两个产品实现了同样的功能,一个执行速度快,一个慢,肯定优先使用快的。

   其实这也是变向要求代码的精简度,也是一个好的习惯,毕竟代码要写的越来越少不是吗。

5. apply介绍及使用

   首先看一下官方的语法:

apply(thisArg)
apply(thisArg, argsArray)

   参数介绍:

thisArg: 需要改变this的值,可以是null和undefined,如果是null和undefined则默认会赋值window对象

argsArray:一个类数组对象,用于指定调用 func 时的参数,或者如果不需要向函数提供参数,则为 null 或 undefined。

   返回值:

函数处理的结果

   解释一下,因为函数中的this不是你调用函数的对象就是window,如果你想改变this的值,我们在上面用到过一种假的实现,就是用一个变量存起来,但是那种写法还是有些浪费内存,而apply函数就可以很好的解决这个问题。
   而第二个参数也是比较重要的,了解函数的知道,每个函数都有一个arguments对象,也就是参数数组,而argsArray实际上就是把你需要传递的参数放到一个数组给arguments,然后函数就可以接收到,不过稍微有点限制条件,之后说明一下。
   用两个例子说明一下
   例1:

  console.log("apply例1------------------------------------")
  function testApply1(name, age, height) {
    console.log("我的姓名是", name, ",我的年龄是", age, "我的身高是", height)
  }
  testApply1.apply(null, ["张三", 18, 180])

   例2:

  console.log("apply例2------------------------------------")
  function testApply2(name, age, height) {
    var obj = {
      name: name,
      age: age,
      height: height
    }
    return obj
  }
  var apply2Res = testApply2.apply(null, ["张三", 18, 180])
  console.log("apply例2的返回值", apply2Res)

   例3:

  console.log("apply例3------------------------------------")
  var array = ["a", "b"];
  var elements = [0, 1, 2];
  // 等价于:array.push(... elements),但是这种写法只支持ES6
  array.push.apply(array, elements);
  console.info("apply例3", array);

   例5:

  console.log("apply例5------------------------------------")
  // 数组中的最小/最大值
  var numbers = [5, 6, 2, 3, 7];
  // 用 apply 调用 Math.min/Math.max
  var max = Math.max.apply(null, numbers);
  var min = Math.min.apply(null, numbers);
  console.log("apply5最大值", max, "apply5最小值", min)

在这里插入图片描述
   上面几个例子分别是几种用法,说实在的,目前我使用过的和最实用的也就是第三种方式,拼接数组,因为apply最大的好处就是可以将数组中的参数一个一个传递给函数,类似循环的效果,而且不用担心浏览器兼容性(因为有的公司确实是要兼容部分IE浏览器,ES6不能用,比如我们公司)。

6. call介绍及使用

   语法:

call(thisArg)
call(thisArg, arg1)
call(thisArg, arg1, arg2)

   参数介绍

thisArg: 改变this的指向对象,和apply一致(不传递则默认为window)

arg1等等,依次传递的参数(可选,可以不传递参数)

   返回值

使用指定的 this 值和参数调用函数后的结果。和apply保持一致

   继续搞几个例子看一下
   例1:

  console.log("call例1------------------------------------")
  function testCall1(name, age, height) {
    console.log("我的姓名是", name, ",我的年龄是", age, "我的身高是", height)
  }
  testCall1.call(null, "张三", 18, 180)

   例2:

  console.log("call例2------------------------------------")
  function testCall2() {
    console.log("我的姓名是", this.name, ",我的年龄是", this.age, "我的身高是", this.height)
  }
  testCall2.call({
    name: "张三",
    age: 18,
    height: 180
  })

   例3:

  console.log("call例3------------------------------------")
  var obj111 = {
    name: "李四",
    age: 68,
    money: "100亿"
  }
  var obj222 = {
    name: "王五",
    age: 18,
    money: "1块",
    getMoney: function () {
      return this.money
    },
    objFun: function () {
      // 如果只是this.getMoney,那this就是obj222这个对象,所以最后的钱就是1块
      var money = this.getMoney.call(obj111)
      console.log("我叫", this.name, "今年", this.age, "岁,拥有了李四的", money)
    }
  }
  obj222.objFun()

在这里插入图片描述
   call函数目前使用的我觉得较apply多一些,上面的例3中是比较常用的,改变this的指向,让我的方法中可以访问别的对象的属性,那么有人就说了,何必这么费劲呢,直接访问对象的属性不香吗?其实来说这是一种比较少见的概念,继承,A对象继承了B对象的属性,但是这会造成一定的内存损耗,因为B对象的属性是有的,没必要复制一份,造成浪费,而call函数可以有效的避免这个问题,我直接使用即可,不用继承你这个属性,然后挂载到我的对象上(这部分解释稍微有点抽象,且为个人理解,还需一定举证)。

7. apply和call的相同点和不同点

相同点:都可以改变this的指向,官方语言就是:扩充函数赖以运行的作用域
最大不同点

  1. apply传递参数是以数组传递的,使用时必须将所有参数包装为一个数组,但是调用时会依次进行调用,类似for循环。
  2. call传递参数只能一个一个传递。

同时我们需要注意一点,call和apply传递参数都不能超过函数定义的数量,即使你传多了,也是没用滴,如果你传递少了呢,那就是undefined喽。上面说过和arguments有点相似,但是arguments包含你所有的参数,也就是说你给多少,我就有多少,这是一个注意点。

  function test111(name) {
    console.log("arguments", arguments)
    console.log("name", name)
  }
  test111(1, 2, 3)

在这里插入图片描述

OK,本次就讲到这里,希望对大家有帮助,如果后续发现了更好的用处也会继续修正此文章。
如果有大佬发现问题,麻烦评论说一下,可能会存在部分描述不正确。

还是建议大家先面向官方学习,了解定义后在根据博客学习怎么使用或者不懂的地方。

  • 9
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值