【JavaScript】this 指向由入门到精通

this 的概念

this 在JavaScript 及其其他面向对象的编程语言中,存在的目的是为了提供一种在对象方法中引用当前对象的方式。

它为方法提供了对当前实例的引用,使得方法能够访问或者修改实例的成员变量。

注意点:

  1. this 的绑定和定位的位置(编写的位置) 没有关系
  2. this 的绑定和调用方式以及调用的位置有关系
  3. this 是在运行时被绑定的

this 绑定规则

this 的绑定规则根据函数的调用方式分为四种主要规则。

默认绑定(Default Binding)

当函数在非严格模式下独立调用时,this默认绑定到全局对象(在浏览器中是window对象,在Node,js中是globaly对象)。
如果在严格模式下,this会被绑定为undefined。

开启严格模式:
脚本开启: ‘use strict’
函数内部开启:‘use strict’
注意:'use strict’写在代码顶端

隐式绑定(Implicit Binding)

当函数作为对象的方法被调用时,this 绑定到该对象。
此时,this的值就是调用方法的对象。

显式绑定(Explicit Binding)

通过 call 、apply或bind方法,可以显式地指定 this的绑定对象。
call 和 apply 会立即调用函数并传递this,而bind会返回一个新的函数并绑定指定的this。

new绑定(New Binding)

当通过new关键字调用构造函数时,this绑定到新创建的对象实例。
这个新对象由构造函数创建,并作为 this 的绑定对象。

除此之外还有箭头函数:

  1. 之所以设计箭头函数,仅仅是为了实现⼀种简洁的函数定义语法,⽆需考虑与函数(对象)相关的东⻄,所以箭头函数没有原型,即没有 prototype 属性,也没有相关的 super、new.target、this ,没有 arguments 对象 等。
  2. 箭头函数没有⾃⼰的 arguments 对象,所以⽆法通过它获取参数。如果要获取,可以⽤ rest 参数代替:let arguments = (…args) => args;。与 this、 super、new.target ⼀样,arguments 的值由最近外部作用域的非箭头函数决定
    在这里插入图片描述
  3. 不能通过 new 关键字调⽤:JS 的函数有两个内部⽅法:[[Call]] 和 [[Construct]]。当通过 new 调⽤普通函数时,执⾏ [[Construct]] ⽅法,创建⼀个实例对象,然后再执⾏函数体,将 this 绑定到实例上。当直接调⽤的时候,执⾏ [[Call]] ⽅法,直接执⾏函数体。⽽由于箭头函数并没有 [[Construct]] ⽅法,不能被⽤作构造函数,如果通过 new 的⽅式调⽤,会报错。

规则优先级

默认规则 < 隐式绑定 < 显示绑定

new绑定优先级高于隐式绑定
new绑定优先级高于bind
new绑定和 call、apply 是不允许同时使用的,所以不存在谁的优先级更高

// 如何确认this的值
// 1.全局执行环境
// 严格模式,非严格模式:全局对象(window)
// 2.函数内部
// 2.1 直接调用
// 严格模式下:undefined
// 非严格模式:全局对象(window)
// 2.2 对象方法调用
// 严格模式,非严格模式:调用者
// 3. 使用 new 方法调用构造函数,构造函数内的 this 会绑定到新创建的对象上。
// 4. 箭头函数,this 指向由外层(函数或者全局)作用域决定。
// 5. apply / bind / call 方法调用,函数体内的 this 绑定到指定参数的对象上。// ------------- 1.全局执行环境 -------------//  严格模式,非严格模式 全局对象(window)// 'use strict'// console.log(this)// ------------- 2.函数内部 -------------// 2.1 直接调用-非严格模式// function func() {//   console.log(this) // window// }// func()// 2.1 直接调用-严格模式// function func() {//   'use strict'//   console.log(this) // undefined// }// func()// 2.2 对象方法调用const food = {name: '猪脚饭',eat() {'use strict'console.log(this)}}// 非严格模式,严格模式food.eat() // 调用者

DOM 绑定事件中的 this

dom 元素绑定事件时,事件处理函数里面的 this 指向绑定了事件的元素。

此时和 target 不同,target 指向触发事件的元素。

<ul id="color-list"><li>red</li><li>yellow</li><li>blue</li><li>black</li><li>white</li>
</ul>
<script>let colorList = document.getElementById('color-list')colorList.addEventListener('click', function (e) {console.log('this:', this)console.log('target', e.target)console.log('srcElement:', e.srcElement)})
</script>

image.png

有些时候我们会遇到一些困扰,比如在 div 节点的事件函数内部,有一个局部的 callback 方法,该方法被作为普通函数调用时, callback 内部的 this 是指向全局对象 window 的。

<div id="div1">我是一个 div</div>
<script>window.id = 'window'document.getElementById('div1').onclick = function () {console.log(this.id)  // div1const callback = function () {console.log(this.id)}callback() // window}
</script>

可以用一个变量保存 div 节点。

<div id="div1">我是一个 div</div>
<script>window.id = 'window'document.getElementById('div1').onclick = function () {console.log(this.id)  // div1const that = thisconst callback = function () {console.log(that.id)}callback() // div1}
</script>

指定 this

call

通常是为函数(方法)指定 this 指向(其他对象)。

call 方法可以改变 this 的指向,指定 this 指向对象 obj,然后在对象 obj 的作用域中运行函数。

call 方法的参数,应该是对象 obj,如果参数为空或 null,undefind,则默认传参全局对象。

如果 call 传参不是以上类型,则转换为对应的包装对象,然后传入方法。

let obj = {}
console.log(obj.hasOwnProperty('toString')) // false 查看本身是否有某方法
console.log(obj.toString()) // [object Object] 调用toString方法 是继承来的方法
// 重写hasOwnProperty方法
// obj.hasOwnProperty = function () {
//     return 'aaa'
// }
// console.log(obj.hasOwnProperty('toString')) // aaa// 可以使用 call 调用原生的方法
console.log(Object.prototype.hasOwnProperty.call(obj, 'toString'))  // false

apply

可以通过 apply 方法,利用 Array 构造函数将数组的空元素变成 undefined。

let a = ['1', , '2']
Array.apply(null, a).forEach((e, i) => {console.log(e, i) // 1 0   undefined 1   2 2
})

配合数组对象的 slice 方法,可以将一个类数组的对象(比如 arguments 对象)转成真正的数组。

Array.prototype.slice.apply({0: 1, length: 1}) // [1]
Array.prototype.slice.apply({0: 1}) // []
console.log(Array.prototype.slice.apply({0: 1, length: 2})) // [1, undefined]
console.log(Array.prototype.slice.apply({length: 2})) // [undefined, undefined]

这个方法起作用的前提是,被处理的对象必须有length属性,以及相对应的数字键。

bind

let d = new Date()
d.getTime()
// let print = d.getTime
// this 的指向改变 导致报错 可以使用 bind 改变 this 的指向
// print() //  this is not a Date object.let print = d.getTime.bind(d)
console.log(print()) // 1715074121540

bind 的一些注意点:

  • 每一次返回一个新函数

image.png

  • 结合回调函数使用

image.png

  • 某些数组方法可以可以接收一个函数当做参数

image.png

  • 结合 call
let slice = Function.prototype.call.bind(Array.prototype.slice)
// call 方法来源于 Function.prototype
// 这里是改写了 slice 方法
console.log(slice([1, 2, 3], 0, 1))  // 1function fn(){console.log(this.v)
}
let obj = {v:123
}
let func = Function.prototype.call.bind(Function.prototype.bind)
func(fn, obj)() // 123

可以通过两种方法指定this:

  1. 调用时指定:
    1. call方法
    2. apply方法
  2. 创建时指定:
    1. bind方法
    2. 箭头函数
    /*** 如何指定this的值:*  1. 调用时指定this:*  2. 创建时指定this:* */// ------------- 1. 调用时指定this: -------------function func(numA, numB) {console.log(this)console.log(numA, numB)}const person = {name: 'username'}// 1.1 call:挨个传入参数// func.call(person, 1, 2)  // this 为 {name: 'username'}// 1.2 apply:以数组的方式传入参数// func.apply(person, [3, 4])  // this 为 {name: 'username'}// ------------- 2. 创建时指定this: -------------// 2.1 bind方法// const bindFunc = func.bind(person, 666)  // this 为 {name: 'username'} ,Func函数的参数可以依次传入// bindFunc(888)  // numA 为 666,numB 为 888// 2.2 箭头函数const food = {name: '西兰花炒蛋',eat() {console.log(this)  // food// 箭头会从自己作用域链的上一层继承thissetTimeout(() => {console.log(this)  // food}, 1000);// setTimeout(function () {//   console.log(this)  // window// }, 1000)}}food.eat()

总结

如何确认this指向:

  1. 默认绑定:① 非严格模式:全局对象(window) ② 严格模式:undefined

  2. 隐式绑定:对象方法调用的this值:① 调用者

  3. 使用 new 方法调用构造函数,构造函数内的 this 会绑定到新创建的对象上。

  4. 箭头函数,this 指向由外层作用域决定。

  5. apply / bind / call 方法调用,函数体内的 this 绑定到指定参数的对象上。

如何开启严格模式:

// 为整个脚本开启严格模式
'use strict'
function func() {// 为函数开启严格模式'use strict'
}

如何改变this指向,有2类改变this指向的方法,分别是:

  1. 调用函数时并传入具体的this
    1. call:从第二个参数开始挨个传递参数
    2. apply:在第二个参数以数组的形式传递参数
  2. 创建函数时绑定this?
    1. bind:返回一个绑定了this以及参数(可选)的新函数
    2. 箭头函数:创建时会绑定上一级作用域中的this

例题实战:

const foo = {bar: 10,fn: function () {console.log(this)  console.log(this.bar)  }
}
let fn1 = foo.fn
fn1() // window or global 和 undefined
foo.fn() // { bar: 10, fn: [Function: fn] } 和 10

面试题

// 1.
var name = "window"
var person = {name: 'person',sayName: function () {console.log(this.name)}
}
function sayName() {var sss = person.sayNamesss()  // 独立调用 windowperson.sayName() // 隐式调用 person(person.sayName())() // 隐式调用 person(b = person.sayName)() // 独立调用 window
}
sayName()// 2.
var name = "window"
var person1 = {name: "person1",foo1: function () {console.log(this.name)},foo2: () => console.log(this.name),foo3: function () {return function () {console.log(this.name)}},foo4: function () {return () => console.log(this.name)}
}
var person2 = { name: "person2" }
person1.foo1() // 隐式绑定 person1
person1.foo1.call(person2) // 显示绑定优先级高于隐式绑定 person2person1.foo2() // 箭头函数没有 this 而外层是一个对象(不是作用域) 而非块级作用域 因此直接找到 window
person1.foo2.call(person2) // 本身就没有this 因此绑定不了this 因此也是 windowperson1.foo3()()  // 独立调用 window
// 相当于
// const bar = person1.foo3()
// bar()
person1.foo3.call(person2)() // 和上面同理 因此也是 window
// 相当于
// const bar = person1.foo3().call(person2)
// bar()
person1.foo3().call(person2) // 这个是先拿到 foo3 的返回值函数,再调用这个函数并绑定this,因此是 person2person1.foo4()() // foo4 是普通函数 调用后this 为person1对象,相当于闭包 返回的箭头函数this 绑定了 foo4 中的 this,即指向 person1。
person1.foo4.call(person2)() // 箭头函数绑定的this为person1 外层作用域显示绑定了person2 所以最终为 person2
person1.foo4().call(person2) // 先调用foo4 返回的箭头函数无法绑定this 因此最初返回值箭头函数外部作用域的 person1// 3.
var name = "window"
function Person(name) {this.name = namethis.foo1 = function () {console.log(this.name)}this.foo2 = () => console.log(this.name)this.foo3 = function () {return function () {console.log(this.name)}}this.foo4 = function () {return () => console.log(this.name)}
}
var person1 = new Person("person1")
var person2 = new Person("person2")
person1.foo1() // 隐式绑定 person1
person1.foo1.call(person2) // 显示绑定优先级高于隐式绑定 person2person1.foo2() // 箭头函数没有 this 而外层是一个函数(作用域)new绑定 因此为 person1
person1.foo2.call(person2) // 箭头函数还是无法绑定 因此还是 person1person1.foo3()() // 独立调用 window
person1.foo3.call(person2)() // 和上面同理独立调用 因此也是 window
person1.foo3().call(person2) // 这个是先拿到 foo3 的返回值函数,再调用这个函数并绑定this,因此是 person2person1.foo4()() // 箭头函数不绑定this 使用外层作用域 person1
person1.foo4.call(person2)() // 箭头函数不绑定this 使用外层作用域的显示绑定 person2
person1.foo4().call(person2) // 箭头函数不绑定this 使用外层作用域 person1// 4. 对象没有作用域 外层普通函数有作用域
var name = "window"
function Person(name) {this.name = namethis.obj = {name: "obj",foo1: function () {console.log(this.name)},foo2: function () {return () => console.log(this.name)}}
}
var person1 = new Person("person1")
var person2 = new Person("person2")person1.obj.foo1()() // 独立调用 window
person1.obj.foo1.call(person2)() // 独立调用 window
person1.obj.foo1().call(person2) // 返回值普通函数的显示绑定 person2person1.obj.foo2()() // 闭包 箭头函数的this指向外层作用域 obj
person1.obj.foo2.call(person2)() // 闭包 箭头函数不绑定 外层作用域的显示绑定 person2
person1.obj.foo2().call(person2) // 闭包 箭头函数不绑定 外层作用域的隐式绑定 obj

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/70642.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JavaScript完整版知识体系(持续更新~~)

一、Variables变量 (1)变量的声明方式。 在 JavaScript 中&#xff0c;let、const 和 var 是用于声明变量的关键字&#xff0c;但它们在作用域、可变性、以及提升&#xff08;Hoisting&#xff09;行为上有所不同。下面是对这三者的详细解释&#xff1a; 1. var: 作用域&…

OpenStack-Train版-Allinone自动化部署脚本

一、环境准备 操作系统&#xff1a;CentOS 7 或以上版本 建议配置&#xff1a; CPU&#xff1a;8 核或以上 内存&#xff1a;16 GB 或以上 磁盘&#xff1a;500 GB 或以上 网络配置&#xff1a; 确保虚拟机已配置静态 IP 地址 确保虚拟机可以正常访问外部网络 二、自动…

【0403】Postgres内核 检查(procArray )给定 db 是否有其他 backend process 正在运行

文章目录 1. 给定 db 是否有其他 backend 正在运行1.1 获取 allPgXact[] 索引1.1.1 MyProc 中 databaseId 初始化实现1.2 allProcs[] 中各 databaseId 判断1. 给定 db 是否有其他 backend 正在运行 CREATE DATABASE 语句创建用户指定 数据库名(database-name)时候, 会通过 …

git fetch和git pull 的区别

git pull 实际上就是 fetch merge 的缩写, git pull 唯一关注的是提交最终合并到哪里&#xff08;也就是为 git fetch 所提供的 destination 参数&#xff09; git fetch 从远程仓库下载本地仓库中缺失的提交记录,并更新远程分支指针 git pull抓取更新再合并到本地分支,相当于…

详解在Pytest中忽略测试目录的三种方法

关注开源优测不迷路 大数据测试过程、策略及挑战 测试框架原理&#xff0c;构建成功的基石 在自动化测试工作之前&#xff0c;你应该知道的10条建议 在自动化测试中&#xff0c;重要的不是工具 你是否曾因无关或过时的代码导致测试失败&#xff1f; 这可能会增加调试和故障排除…

SQL Server查询计划操作符(7.3)——查询计划相关操作符(6)

7.3. 查询计划相关操作符 48)Key Lookup:该操作符对一个有簇索引的表进行书签查找。参数列包含簇索引的名字和用于查找簇索引中数据行的簇键。该操作符总是伴随一个Nested Loops操作符。如果其参数列中出现WITH PREFETCH子句,则查询处理器已决定使用异步预取(预读,read-ah…

Python Pandas(5):Pandas Excel 文件操作

Pandas 提供了丰富的 Excel 文件操作功能&#xff0c;帮助我们方便地读取和写入 .xls 和 .xlsx 文件&#xff0c;支持多表单、索引、列选择等复杂操作&#xff0c;是数据分析中必备的工具。 操作方法说明读取 Excel 文件pd.read_excel()读取 Excel 文件&#xff0c;返回 DataF…

基于钉钉API的连接器实现:企业数据集成与自动化管理

文章目录 概要背景与需求钉钉API概述连接器实现小结 概要 在当今数字化时代&#xff0c;企业面临着海量数据的管理与整合挑战。钉钉作为国内广泛使用的办公协作平台&#xff0c;提供了丰富的API接口&#xff0c;支持企业进行数据集成与自动化管理。本文将介绍如何通过钉钉API实…

第六届MathorCup高校数学建模挑战赛-A题:淡水养殖池塘水华发生及池水自净化研究

目录 摘要 1 问题的重述 2 问题的分析 2.1 问题一的分析 2.2 问题二的分析 2.3 问题三的分析 2.4 问题四的分析 2.5 问题五的分析 3. 问题的假设 4. 符号说明 5. 模型的建立与求解 5.1 问题一的建模与求解 5.1.1 分析对象与指标的选取 5.1.2 折线图分析 5.1.3 相关性分析 5.1.4…

方舟字节码原理剖析:架构、特性与实践应用

方舟字节码原理剖析&#xff1a;架构、特性与实践应用 一、引言 在当今软件行业高速发展的大背景下&#xff0c;应用程序的性能、开发效率以及跨平台兼容性成为了开发者们关注的核心要素。编译器作为软件开发流程中的关键工具&#xff0c;其性能和特性直接影响着软件的质量和…

如何在Android Studio中开发一个简单的Android应用?

Android Studio是开发Android应用的官方集成开发环境&#xff08;IDE&#xff09;&#xff0c;它提供了许多强大的功能&#xff0c;使得开发者能够高效地创建Android应用。如果你是Android开发的初学者&#xff0c;本文将引导你如何在Android Studio中开发一个简单的Android应用…

使用 JFreeChart 创建动态图表:从入门到实战

文章目录 前言一、JFreeChart 简介二、环境准备三、 创建第一个折线图四、自定义图表样式4.1 设置背景色4.2 设置折线颜色4.3 设置字体&#xff08;解决中文乱码&#xff09;4.4 设置横坐标的标签宽度和方向 五、导出图表六、实战&#xff1a;动态生成日报图表总结 前言 在数据…

vue.js v-model实现原理

在 vue.js 3中&#xff0c;通过 v-model 指令可以方便实现表单元素数据双向绑定。实现 v-model 指令元素并不神奇&#xff0c;本质上是一种语法糖。实现原理其实是 v-bind 和 v-on 这两个指令。 v-bind 指令会将表单元素的 value 属性与一个变量绑定&#xff0c;简写为 :属性名…

Formality:探针(Probe Point)的设置与使用

相关阅读 Formalityhttps://blog.csdn.net/weixin_45791458/category_12841971.html?spm1001.2014.3001.5482 一般情况下&#xff0c;verify命令会对参考设计和实现设计所有匹配的比较点各自进行验证&#xff0c;但有些时候为了调试&#xff0c;可能需要验证参考设计和实现设…

idea如何使用AI编程提升效率-在IntelliJ IDEA 中安装 GitHub Copilot 插件的步骤-卓伊凡

idea如何使用AI编程提升效率-在IntelliJ IDEA 中安装 GitHub Copilot 插件的步骤-卓伊凡 问题 idea编译器 安装copilot AI工具 实际操作 在 IntelliJ IDEA 中安装 GitHub Copilot 插件的步骤如下&#xff1a; 打开 IntelliJ IDEA&#xff1a; 打开你的 IntelliJ IDEA 应用…

【计算机网络】TCP/IP 网络模型有哪几层?

目录 应用层 传输层 网络层 网络接口层 总结 为什么要有 TCP/IP 网络模型&#xff1f; 对于同一台设备上的进程间通信&#xff0c;有很多种方式&#xff0c;比如有管道、消息队列、共享内存、信号等方式&#xff0c;而对于不同设备上的进程间通信&#xff0c;就需要网络通…

Spring Boot: 使用 @Transactional 和 TransactionSynchronization 在事务提交后发送消息到 MQ

Spring Boot: 使用 Transactional 和 TransactionSynchronization 在事务提交后发送消息到 MQ 在微服务架构中&#xff0c;确保消息的可靠性和一致性非常重要&#xff0c;尤其是在涉及到分布式事务的场景中。本文将演示如何使用 Spring Boot 的事务机制和 TransactionSynchron…

c/c++蓝桥杯经典编程题100道(14)矩阵转置

矩阵转置 ->返回c/c蓝桥杯经典编程题100道-目录 目录 矩阵转置 一、题型解释 二、例题问题描述 三、C语言实现 解法1&#xff1a;使用额外空间&#xff08;难度★&#xff09; 解法2&#xff1a;原地转置&#xff08;仅限方阵&#xff0c;难度★★&#xff09; 四、…

整合 Redis 分布式锁:从数据结构到缓存问题解决方案

引言 在现代分布式系统中&#xff0c;Redis 作为高性能的键值存储系统&#xff0c;广泛应用于缓存、消息队列、实时计数器等多种场景。然而&#xff0c;在高并发和分布式环境下&#xff0c;如何有效地管理和控制资源访问成为一个关键问题。Redis 分布式锁正是为了解决这一问题…

(done) openMP学习 (Day10: Tasks 原语)

url: https://dazuozcy.github.io/posts/introdution-to-openmp-intel/#19-%E6%8A%80%E8%83%BD%E8%AE%AD%E7%BB%83%E9%93%BE%E8%A1%A8%E5%92%8Copenmp 本章节内容仅提供引入&#xff0c;关于 task 更详细的细节请看 openMP 手册或者源材料 Day9 介绍了一个优化链表遍历的粗糙方…