首页 > 其他分享 >JS 中的函数 this 指向总结

JS 中的函数 this 指向总结

时间:2024-04-06 18:44:30浏览次数:32  
标签:function obj 函数 指向 对象 JS var fn

这个 js 语言中的 this 和其他面向对象的语言有本质的不同, 也更复杂, 它更多取决于函数在不同场景下的调用方式, 要理解它并总结出它的规律的话, 优先要从上下文 这个概念认知说起.

理解上下文

上下文 context 可理解为程序执行时的背景环境, 包含了在特定时刻程序所需要的所有信息. 包括变量的值, 函数的调用情况, 执行的位置等.

上下文的核心应用场景在于, 程序状态管理, 函数调用, 内存管理, 异常处理等. 本篇这里是以 JS 编程语言层面的上下文 this 指向总结, 就更多是一种规则梳理.

  • 函数中可以使用 this 关键字, 它表示函数的上下文
  • 与中文中的 类似, 函数中 this 指向必须通过 调用函数时 的 "前言后语" 来判断
  • 如果函数不调用, 则不能确定函数的上下文

规则01: 对象.方法(), this 指向该打点的对象

当出现 对象.方法() 的场景时, 方法里面的 this 就是这个对象.

// case 01
function fn() {
  console.log(this.a + this.b);
}

var obj = {
  a: 100,
  b: 200,
  fn: fn 
}

obj.fn()  // this 指向 obj

这里便是构成了 obj.fn() 的形式, 此时对象的方法 fn 中的 this 则指向该对象 obj 则最后输出 300.

// case 02
var obj1 = {
  a: 1,
  b: 2,
  fn: function () {
    console.log(this.a + this.b);
  }
}

var obj2 = {
  a: 3,
  b: 4,
  fn: obj1.fn 
}

obj2.fn() // this 指向 obj2

首先要注意对于 obj1 来说, 里面的 this 在方法没有被调用的时候, 是不知道具体指向的.

然后分析 obj2.fn() 是符合对象.方法()1的, 虽然这里的 fnobj1 的 fnthis 仍指向 obj1 则最后输出 7.

// case 03
function outer() {
  // 这里的 a, b 是内部变量, 其实是个干扰项
  var a = 1
  var b = 2
  // 外层函数返回一个对象
  return {
    a: 3,
    b: 4,
    fn: function () {
      console.log(this.a + this.b);
    }
  }
}

outer().fn() // this 指向 outer() 返回的对象

分析调用可知 outer() 返回的是一个对象, 对象再调用其方法 fn 所以还是适用于 对象.方法() 的形式, 因此这里的 this 便是指向其返回的对象, 则输出 7.

// case 04
function fn() {
  console.log(this.a + this.b);
}

var obj = {
  a: 1,
  b: 2,
  c: [{
    a: 3,
    b: 4,
    c: fn
  }]
}

var a = 5  // a 是全局变量
obj.c[0].c() // this 指向 c 里面的对象

分析调用可知, obj.c[0] 是一个对象, 然后再调用里面的 fn 方法, 则还是构成了 对象.方法() 形式, 则里面的 this 指向 c 里面的对象, 这个全局的 5 没有啥关系, 则最后输出 7.

规则02: 圆括号直接调用函数(), this 指向 window 对象

当出现普通 函数() 的场景时, 函数里面的 this 在浏览器中指向 window对象

nodejs 中则指向空对象 {} 本篇的所有分析均用浏览器哈, 不在 nodejs 中运行.

// case 01
var obj = {
  a: 1,
  b: 2,
  fn: function () {
    console.log(this.a + this.b);
  }
}

var a = 3
var b = 4

var fn = obj.fn
fn() 

从调用分析, 这里 fn 的调用首先是进行了一个函数的提取 obj.fn, 然后再调用则形成了 函数() 的形式, 则此时 this 在浏览器指向了 window 对象, 全局变量 a, b 都是其属性, 则最后输出7.

注意在 nodejs 里面上面的代码是不能运行的, 因为其没有 window 对象哦

// case 02
function fn() {
  return this.a + this.b
}

// 全局变量
var a = 1
var b = 2

var obj = {
  a: 3,
  b: fn(), // 函数调用()
  fn: fn 
}

var result = obj.fn() // 对象.方法()
console.log(result);

先执行 obj 的定义, 里面的 b 直接调用了函数 fnthis 指向 window 全局对象, 此时 b 的值为 1 + 2 = 3

然后从调用分析, obj.fn() 的形式是 对象.方法()this 指向 obj

// 此时的 obj
obj = {
    a: 3,
    b: 6,
    fn: fn
}

最后形成了 对象.方法 形式, 则最后输出6.

// case 03
var c = 1
var obj = {
  a: function () {
    var c = 3
    return this.b 
  },
  b: function () {
    var c = 4
    document.write(this.c)
  },
  c: 2
}

var obj1 = obj.a()
obj1()

从调用分析, obj.a() 形式为 对象.方法()this 指向 obj 对象

则此时 a 方法里面的 return this.b 的值为 objb 是个方法.

再进行调用 obj1 则形如 函数()this 指向了全局 window 则此时的 c 是1, 而非函数里面的变量 4,

因此最后输出为1.

规则03: 类数组对象 数组[下标] (), this 指向该类数组

当类数组里面的元素是 function 时,里面的 this 指向该类数组.

// case 01
var arr = ['a', 'b', 'c', function () {
  console.log(this[0]);
}]

arr[3]()

从调用分析, arr[3] () 满足形如 数组[下标] () 的形式, 则 this 指向该数组 arr, 最终输出 'a`.

对于类数组对象, 即所有键名为自然数序列 (从 0 开始), 且有 length 的对象, 最常见的是 arguments .

// case 02
function fn() {
  arguments[3]()
}

fn('a', 'b', 'c', function () {
  console.log(this[1]);
})

从调用分析, arguments[3] () 满足形如 数组[下标] () 的形式, 则 this 指向 arguments 即 fn 在调用时传递的实参数组, 下标1则输出 'b'.

// case 03
var a = 6
var obj = {
  a: 5,
  b: 2,
  c: [ 1, a, function () { document.write(this[1])} ]
}

obj.c[2]()

从调用分析, obj.c 是一个数组, 然后再进行下标调用, 即形如 数组[下标] () 的形式, this 指向数组本身.

下标为1 则指向了数组里面的 a , 这里指向了全局变量 a , 则最后输出了 6.

规则04: IIFE中的函数, this 指向 window 对象

IIFE 表示立即执行函数, 定义后立即调用. 这在项目中经常用于在页面加载完后, 立即执行获取后端接收的数据方法, 定义 + 调用 的方式来渲染页面.

// 写法1: 将函数用 () 包裹起来再调用 ()
(function () {
   console.log(123)
})();


// 写法2: 函数前面加 void, 最后再调用
void function fn() {
  console.log(123)
}()

// case 01
var a = 1
var obj = {
  a: 2,
  fn: (function () {
    var a = this.a
    return function () {
      console.log(a + this.a);
    }
  })() // IIFE, this 指向 window
}

obj.fn() // 对象.方法 this 指向 obj

从调用分析,

obj.fn 是一个立即执行函数, 会先执行, 此时的 this 指向全局 window, 则闭包里面的 this.a 为外面的 1.

obj.fn() 形如 对象.方法() ,此 this 指向 obj, 则 fn 返回的函数里面的 this.a 的值为 obj.a 的值为 2,

因此最后输出了3.

规则05: 用定时器, 延时器调用函数, this 指向 window 对象

通常在做一些异步任务 如想后端请求数据啥的, 就容易改变 this 指向, 通常的操作是可以用一个别的变量如叫 that 或者 self 来先指向 this 以保证 this 的指向不会改变. 当然这些前提是, 咱们能识别问题.

  • setInterval (函数, 时间)
  • setTimeout(函数, 时间)
// case 01
var obj = {
  a: 1,
  b: 2,
  fn: function () {
    console.log(this.a + this.b);
  }
}

var a = 3
var b = 4

setTimeout(obj.fn, 2000)

从调用分析, obj.fn 是一个函数, 然后外面被延时器调用, 2秒后执行, 则此时的 this 指向全局 window

则最后输出为7.

这里可以进行一个调用变化.

// case 02
var obj = {
    a: 1,
    b: 2,
    fn: function () {
      console.log(this.a + this.b);
    }
  }

  var a = 3
  var b = 4

  setTimeout(function () {
    obj.fn() // 这里真正调用
  }, 2000);

从调用分析, 这里的 setTimeout 并不是调用了函数, 只是将整体延迟了 2秒.

而真正调用函数的是 obj.fn() 形如 对象.方法 , 则 this 指向的是 obj, 则最后输出的是 3.

规则06: 事件处理函数, this 指向绑定事件的 DOM

Dom元素.onclick = function () { }

比如要实现一个效果, 当我们点击哪个盒子, 哪个盒子就变红, 要求使用同一个事件函数处理实现, 但不能用事件委托的方式.

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    div {
      float: left;
      margin-right: 10px;
      width: 200px;
      height: 200px;
      border: 1px solid #000;
    }
  </style>
</head>

<body>
  <div id="box1">box1</div>
  <div id="box2">box2</div>
  <div id="box3">box3</div>

  <script>
    function setColorRed() {
      // 这里的 this 指向前面绑定的 DOM 
      this.style.backgroundColor = 'red'
    }

    var box1 = document.getElementById('box1')
    var box2 = document.getElementById('box2')
    var box3 = document.getElementById('box3')

    box1.onclick = setColorRed
    box2.onclick = setColorRed
    box3.onclick = setColorRed

  </script>
</body>

</html>

注意这里的 this 是指向当前绑定的元素, 而 e.target 指的是内层触发的元素, 这俩不一样哦.

再对上面的案例做一个升级: 点击哪个盒子, 哪个盒子在 2秒 后就变红, 也是要求用一个事件函数来实现哦.

这咋一看似乎蛮简单:

function setColorRed() {
      // 直接放延迟函数是不行的, 
      // 因为它的 this 指向从当前 dom 变成了 window
      setTimeout(function () {
        this.style.backgroundColor = 'red'
      })
    }

这样其实是不行的, 因为又之前的规则5所知, 在延时器调用函数时, 里面的 this 指向的是 window对象.

这里最常用的一个巧妙办法是:

  • 先用一个变量比如叫 self 来保存原来的 this 指向的是 Dom, 进行备份
  • 然后在延时器中, 用 self 来替代 this 即可, 这样还是满足规则6的
function setColorRed() {
    // 这里的 this 指向当前 dom
    var self = this 
    setTimeout(function () {
    // 用 self 替换 this, 因为这里的 this 会指向 window 
    self.style.backgroundColor = 'red'
    }, 2000)
}

函数的 call 和 apply 方法能指定 this

在 js 中数组和函数都是对象 object , 既然是对象, 那就会有一些原型上的方法, 这里的 call / apply 作为函数对象的方法, 其功能是能指定函数的上下文 this

比如要统计语数英成绩, 对每个小朋友, 比如油哥:

var youge = {
  chinese: 80,
  math: 70,
  english: 60
}

有一个统计成绩的函数 sum

function sum() {
  console.log(this.chinese + this.math + this.english);
}

这两个怎么进行关联呢, 简单的方式将 sum 写进 youge 对象, 然后构造出 对象.方法() 的方式, 则 this 指向该对象.

var youge = {
  chinese: 80,
  math: 70,
  english: 60,
  sum: function () {
      console.log(this.chinese + this.math + this.english);
  }
}

youge.sum() // 对象.方法() this 指向 对象, 输出210

但 js 提供了更简单的方法, 即可通过 call 或者 apply 方法直接指定函数对象的 this

function sum() {
  console.log(this.chinese + this.math + this.english);
}

var youge = {
  chinese: 80,
  math: 70,
  english: 60
}

sum.call(youge)

标签:function,obj,函数,指向,对象,JS,var,fn
From: https://www.cnblogs.com/chenjieyouge/p/18117732

相关文章

  • 基于SSM+Jsp+Mysql的个性化影片推荐系统
    开发语言:Java框架:ssm技术:JSPJDK版本:JDK1.8服务器:tomcat7数据库:mysql5.7(一定要5.7版本)数据库工具:Navicat11开发软件:eclipse/myeclipse/ideaMaven包:Maven3.3.9系统展示系统首页用户注册用户登录热门电影个人中心我的收藏新闻资讯管理员登录管理员首页用户管......
  • 函数及其表达方式
    函数及其表达方式哎,真是没想到上上周的数学课的总结会拖得那么久。表达方式:自变量&因变量在函数\(f(x)=x+1\)中,\(f(x)\)是因变量,\(x\)是自变量。定义域定义域的意思若有集合\(J\)使\(\forallx\inJ\),则称\(J\)是\(f(x)\)的定义域。求定义域的方法1.分式函数(分母不能......
  • JsonCpp 笔记: 读写 Json 文件
    JsonCpp笔记:读写Json文件完成时间:2024-04-06本文主要介绍使用JsonCpp读写Json文件,JsonCpp是C++上的一个Json处理库Json的语法如果熟悉Json语法,此部分可以跳过Json包含两种结构:对象(object),它是键值对的集合(key:value)有序数组(array)......
  • JS——webAPIs(6)
    一、知识点1.正则表达式的使用//正则表达式:用于匹配字符串中字符组合的模式conststr='学习前端'//定义规则constreg=/前端///进行查找-两个方法//用于判断是否有符合规则的字符串,返回布尔值console.log(reg.test(str));//用于......
  • C++中拷贝构造函数调用时机——学习记录
    拷贝构造函数调用时机:C++中拷贝构造函数调用时机通常有三种情况使用一个已经创建完毕的对象来初始化一个新对象值传递的方式给函数参数传值以值方式返回局部对象问题描述在黑马C++课程上学习时发现,第三种情况:以值方式返回局部对象时会不会调用构造函数。对比后发现,黑......
  • c语言字符串函数(strlen strcpy strcat strcmp等使用及模拟)
    在编程的过程中,我们经常要处理字符和字符串,为了方便操作字符和字符串,C语⾔标准库中提供了一系列库函数,接下来我们就学习一下这些函数。目录1、strlen的使用及模拟实现。2、strcpy的使用及模拟实现。3、strcat的使用及模拟实现。4、strcmp的使用及模拟实现。5、strncpy的......
  • 蓝旭工作室第三周预习:JS入门
    一、JavaScript概述    JavaScript是一种运行于JavaScript解释器/引擎中的解释型脚本语言。    解释型:运行之前不需要编译;运行之前不会检查错误,直到碰到错误为止。    编译型:对源码进行编译,还能检查语法错误。如C、C++。运行环境    1、......
  • 【代码分享】基于最小二乘支持向量机(LSSVM)+自适应带宽核函数密度估计(ABKDE)的多变量回
    专题推荐:论文推荐,代码分享,视角(点击即可跳转)所有链接建议使用电脑端打开,手机端打开较慢 关注公X众X号:NewPowerSystem预测和优化理论分享新型电力系统预测和优化领域的理论研究成果,包括优秀论文、工程应用、仿真代码等文章阅读推荐和代ma获取链接:......
  • day11 基础函数(二)
    知识回顾```python#函数:封装具有某种功能的代码块函数的定义def函数名():  代码函数名()#函数调用实参:相当于变量值(演员)形参:相当于变量名(角色) 必须参数(位置参数)就是必须按照正确的顺序将实参传入到函数中,实参和形参个数必须一一对应 默认......
  • pandas中var() 函数的应用
    var()函数用于计算DataFrame或Series中数值型数据的方差。方差是衡量数据分散程度的一种统计量,它是各个数据与整个数据集平均值之差的平方的平均值。下面是一个示例,说明如何使用var()函数:importpandasaspd#创建一个DataFramedata={'A':[1,2,3,4,5......