【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)  // div1
        const 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)  // div1
        const that = this
        const 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))  // 1

function 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
        // 箭头会从自己作用域链的上一层继承this
        setTimeout(() => {
          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.sayName
    sss()  // 独立调用 window
    person.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) // 显示绑定优先级高于隐式绑定 person2

person1.foo2() // 箭头函数没有 this 而外层是一个对象(不是作用域) 而非块级作用域 因此直接找到 window
person1.foo2.call(person2) // 本身就没有this 因此绑定不了this 因此也是 window

person1.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,因此是 person2

person1.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 = name
    this.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) // 显示绑定优先级高于隐式绑定 person2

person1.foo2() // 箭头函数没有 this 而外层是一个函数(作用域)new绑定 因此为 person1
person1.foo2.call(person2) // 箭头函数还是无法绑定 因此还是 person1

person1.foo3()() // 独立调用 window
person1.foo3.call(person2)() // 和上面同理独立调用 因此也是 window
person1.foo3().call(person2) // 这个是先拿到 foo3 的返回值函数,再调用这个函数并绑定this,因此是 person2

person1.foo4()() // 箭头函数不绑定this 使用外层作用域 person1
person1.foo4.call(person2)() // 箭头函数不绑定this 使用外层作用域的显示绑定 person2
person1.foo4().call(person2) // 箭头函数不绑定this 使用外层作用域 person1

// 4. 对象没有作用域 外层普通函数有作用域
var name = "window"
function Person(name) {
    this.name = name
    this.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) // 返回值普通函数的显示绑定 person2

person1.obj.foo2()() // 闭包 箭头函数的this指向外层作用域 obj
person1.obj.foo2.call(person2)() // 闭包 箭头函数不绑定 外层作用域的显示绑定 person2
person1.obj.foo2().call(person2) // 闭包 箭头函数不绑定 外层作用域的隐式绑定 obj

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

javaEE-10.CSS入门

目录 一.什么是CSS ​编辑二.语法规则: 三.使用方式 1.行内样式: 2.内部样式: 3.外部样式: 空格规范 : 四.CSS选择器类型 1.标签选择器 2.类选择器 3.ID选择器 4.通配符选择器 5.复合选择器 五.常用的CSS样式 1.color:设置字体颜色 2.font-size:设置字体大小 3…

数据中台是什么?:架构演进、业务整合、方向演进

文章目录 1. 引言2. 数据中台的概念与沿革2.1 概念定义2.2 历史沿革 3. 数据中台的架构组成与关键技术要素解析3.1 架构组成3.2 关键技术要素 4. 数据中台与其他平台的对比详细解析 5. 综合案例&#xff1a;金融行业数据中台落地实践5.1 背景5.2 解决方案5.3 成果与价值 6. 方向…

Linux磁盘空间使用率100%(解决删除文件后还是显示100%)

本文适用于&#xff0c;删除过了对应的数据文件&#xff0c;查看还是显示使用率100%的情况 首先使用df -h命令查看各个扇区所占用的情况 一、先对系统盘下所有文件大小进行统计&#xff0c;是否真的是数据存储以达到了磁盘空间 在对应的扇区路径下使用du -sh * | sort -hr 命…

DeepSeek--教师备课效能100%

关键功能深度解析 深度思考&#xff08;R1&#xff09; 开启这个功能&#xff0c;就如同为 DeepSeek 赋予了深度思考的 “大脑”。当你向它咨询备课问题时&#xff0c;它会像经验丰富的教师一样&#xff0c;在 “脑海” 中梳理思路&#xff0c;不仅给出答案&#xff0c;还会展…

基于Java的自助多张图片合成拼接实战

目录 前言 一、图片合成需求描述 二、图片合成设计与实现 1、编程语言 2、基础数据准备 3、图片合成流程 4、图片合成实现 三、总结 前言 在当今数字化时代&#xff0c;图像处理技术在各个领域都发挥着至关重要的作用。从社交媒体到电子商务&#xff0c;从在线教育到虚拟…

大模型基本原理(四)——如何武装ChatGPT

传统的LLM存在几个短板&#xff1a;编造事实、计算不准确、数据过时等&#xff0c;为了应对这几个问题&#xff0c;可以借助一些外部工具或数据把AI武装起来。 实现这一思路的框架包括RAG、PAL、ReAct。 1、RAG&#xff08;检索增强生成&#xff09; LLM生成的内容会受到训练…

电控--PWM

理论知识 脉宽调制&#xff08;Pulse Width Modulation&#xff0c;PWM&#xff09; 对脉冲信号的宽度改变并输出出来高频的PWM波可以让设备进行频繁开关、通断 PWM波形的参数 周期(T)&#xff1a;完整脉冲循环时间&#xff08;单位&#xff1a;秒&#xff09;频率(f)&…

CNN卷积神经网络多变量多步预测,光伏功率预测(Matlab完整源码和数据)

代码地址&#xff1a;CNN卷积神经网络多变量多步预测&#xff0c;光伏功率预测&#xff08;Matlab完整源码和数据) 标题&#xff1a;CNN卷积神经网络多变量多步预测&#xff0c;光伏功率预测 一、引言 1.1 研究背景及意义 随着全球能源危机的加剧和环保意识的提升&#xff…

在clion中对linux的工程进行远程调试

本地主机&#xff1a;windows 远程主机:ubuntu 0. 建立一个用于同步远程工程代码的文件夹 在windows上新建了一个iot_frame_0210文件夹&#xff0c;用于远程调试&#xff0c;远程的代码会被下载到这个本地目录。 调试的时候&#xff0c;如果修改文件&#xff0c;则不会直接…

使用sunshine和moonlight串流时的音频输出问题

设备&#xff1a;电脑和平板串流&#xff0c;把平板当副屏使用 1.如果启用安装steam音频驱动程序&#xff0c;则平板有声&#xff0c;电脑无声&#xff0c;在moonlight端可以设置平板和电脑同时发声&#xff0c;但是有点卡 2.只想电脑发声&#xff0c;平板无声 禁用安装steam…

postgresql 游标(cursor)的使用

概述 PostgreSQL游标可以封装查询并对其中每一行记录进行单独处理。当我们想对大量结果集进行分批处理时可以使用游标&#xff0c;因为一次性处理可能造成内存溢出。 另外我们可以定义函数返回游标类型变量&#xff0c;这是函数返回大数据集的有效方式&#xff0c;函数调用者…

深入探索人工智能的未来:DeepSeek R1与蓝耘智算平台的完美结合

在当今数字化时代&#xff0c;人工智能&#xff08;AI&#xff09;和机器学习&#xff08;ML&#xff09;正以前所未有的速度改变着我们的生活和工作方式。从智能语音助手到自动驾驶汽车&#xff0c;从精准医疗到金融风险预测&#xff0c;AI的应用无处不在。深度学习作为AI的核…

树和二叉树_9

树和二叉树_9 一、leetcode-107二、题解1.引库2.代码 一、leetcode-107 二叉树的层序遍历Ⅱ 给你二叉树的根节点 root &#xff0c;返回其节点值 自底向上的层序遍历 。 &#xff08;即按从叶子节点所在层到根节点所在的层&#xff0c;逐层从左向右遍历&#xff09;。 样例输…

【安当产品应用案例100集】037-强化OpenVPN安全防线的卓越之选——安当ASP身份认证系统

在当前数字化时代&#xff0c;网络安全已成为企业发展的重要组成部分。对于使用OpenVPN的企业而言&#xff0c;确保远程访问的安全性尤为重要。安当ASP身份认证系统凭借其强大的功能和便捷的集成方式&#xff0c;为OpenVPN的二次登录认证提供了理想的解决方案&#xff0c;特别是…

Blazor-<select>

今天我们来说说<select>标签的用法&#xff0c;我们还是从一个示例代码开始 page "/demoPage" rendermode InteractiveAuto inject ILogger<InjectPage> logger; <h3>demoPage</h3> <select multiple>foreach (var item in list){<…

基于微信小程序的博物馆预约系统的设计与实现

hello hello~ &#xff0c;这里是 code袁~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f981;作者简介&#xff1a;一名喜欢分享和记录学习的在校大学生…

鸿蒙NEXT开发-发布三方库

开发一个三方库 如需发布一个 har 包&#xff0c;必须包含 oh-package.json5、README.md&#xff0c;CHANGELOG.md 和 LICENSE 四个文件&#xff0c;若文件缺失&#xff0c;会导致上架至中心仓失败。 HAR&#xff08;Harmony Archive&#xff09;是静态共享包&#xff0c;可以…

【深度学习】Java DL4J 2024年度技术总结

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…

【翻译+论文阅读】DeepSeek-R1评测:粉碎GPT-4和Claude 3.5的开源AI革命

目录 一、DeepSeek-R1 势不可挡二、DeepSeek-R1 卓越之处三、DeepSeek-R1 创新设计四、DeepSeek-R1 进化之路1. 强化学习RL代替监督微调学习SFL2. Aha Moment “啊哈”时刻3. 蒸馏版本仅采用SFT4. 未来研究计划 部分内容有拓展&#xff0c;部分内容有删除&#xff0c;与原文会有…

关于 IoT DC3 中设备(Device)的理解

在物联网系统中&#xff0c;设备&#xff08;Device&#xff09;是一个非常宽泛的概念&#xff0c;它可以指代任何能够接入系统并进行数据交互的实体。包括但不限于手机、电脑、服务器、网关、硬件设备甚至是某些软件程序等所有能接入到该平台的媒介。 内容 定义 目的 示例 …