news 2026/3/11 21:25:40

04.深入闭包和js函数的this指向跟规则

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
04.深入闭包和js函数的this指向跟规则

闭包的内存泄漏测试

<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>Document</title></head><body><scriptsrc="./01_js闭包内存泄漏案例.js"></script></html>
functioncreateFnArray(){// 整数占据 4 个字节// arr 占据内存大小:1024 * 1024 * 4 = 4Mvararr=newArray(1024*1024).fill(1)returnfunction(){console.log(arr.length)}}vararrayFns=[]for(vari=0;i<100;i++){setTimeout(()=>{arrayFns.push(createFnArray())},i*100)}setTimeout(()=>{for(vari=0;i<50;i++){setTimeout(()=>{arrayFns.pop()},i*100)}},100*100)

打开浏览器的开发者工具,找到性能模块然后点击录制,我这里录制了40s,由于浏览器的一些干扰因素影响可能不太准确,可自行多录制几次,大致的内存变化就是1-10s 开始慢慢增加内存到 4M,然后 10-15s 过程中数组断开一半的引用,然后由 GC 算法统一回收,内存降到 2M。

闭包引用的自由变量销毁

functionfoo(){varname='why'varage=18// js引擎会将没有使用的变量销毁functionbar(){debuggerconsole.log(name)}returnbar}varfn=foo()fn()

打了 debugger 之后,我们可以在控制台输入 name,以及 age,可以看到 age 是访问报错的

为什么需要 this

在常见的编程语言中,几乎都有 this 这个关键字(Objective-C 中使用的是 self), 但是 JavaScript 中的 this 和常见的面向对象语言中的 this 不太一样

  • 常见面向对象的编程语言中,比如 Java、C++、Swift、Dart 等等一系列语言中,this 通常只会出现在类的方法中
  • 也就是你需要有一个类,类中的方法(特别是实例方法)中,this 代表的是当前调用的对象
  • 但是 JavaScript 中的 this 更加灵活,无论是它出现的位置还是它代表的含义

编写一个 obj 的对象,有 this 和没有 this 的区别

// 从某种角度来说,开发中如果没有 this,很多问题我们也是有解决方案// 但是没有 this,会让我们编写代码变得非常的不方便varobj={name:'why',eating:function(){console.log(this.name+'在吃东西')console.log(obj.name+'在吃东西')// obj 一改动,里面用到 obj 的都要改},running:function(){console.log(this.name+'在跑步')console.log(obj.name+'在跑步')},studying:function(){console.log(this.name+'在学习')console.log(obj.name+'在学习')}}

this 指向什么

在大多数情况下,this 都是出现在函数中

在全局作用域下 this 的指向:

  • 浏览器:window
  • Node 环境:{}

node 环境里面的全局作用域的 this 为什么是{}

在 Node.js 的“全局作用域”里看到的 this 并不是“真正的全局对象”,而是模块作用域里的 this。Node.js 在启动你的文件时,会把文件内容包进一个函数里执行,大致过程:module -> 加载 -> 编译 -> 放到一个函数 -> 执行这个函数.call({}),因此,在“文件顶层”打印 this 得到{},只是 Node 的模块封装机制带来的副作用,而不是浏览器里那种指向全局 window 的行为。

https://registry.npmmirror.com/binary.html?path=node/v14.9.0/

开发中直接在全局作用域下去使用 this,通常都是在函数中使用

  • 所有的函数在被调用时,都会创建一个执行上下文
  • 这个上文中记录着函数的调用栈、AO 对象等
  • this 也是其中的一条记录

this 到底指向什么

下面定义了一个函数,采用三种不同的方式对它进行调用,产生了三种不同的结果

// this 指向什么,跟函数所处的位置没有关系// 跟函数被调用的方式有关系functionfoo(){console.log(this)}// 1. 直接调用这个函数foo()// window// 2. 创建一个对象,对象中的函数指向 foovarobj={name:'why',foo:foo}obj.foo()// obj 对象// 3. 通过 apply 调用foo.apply('abc')// String {'abc'} 对象

这个案例给我们的启示:

  • 函数在调用的时,JavaScript 会默认给这个 this 绑定一个值
  • this 的绑定和定义的位置(编写的位置)没有关系
  • this 的绑定和调用方式以及调用的位置有关系
  • this 是在运行时被绑定的

this 到底是怎么样的绑定规则

绑定规则一:默认绑定

什么情况下使用默认绑定?独立函数调用。

独立的函数调用我们可以理解成函数没有被绑定到某个对象上进行调用。

// 案例1functionfoo(){console.log(this)}foo()// 案例2functionfoo1(){console.log(this)}functionfoo2(){console.log(this)foo1()}functionfoo3(){console.log(this)foo2()}foo3()// 案例3varobj3={name:'why',foo:function(){console.log(this)}}varbar3=obj3.foobar3()// 案例4functionfoo4(){console.log(this)}varobj4={name:'obj4',foo:foo4}varbar4=obj4.foobar4()// 案例5functionfoo5(){functionbar(){console.log(this)}returnbar}varfn=foo5()fn()

绑定规则二:隐式绑定

通过某个对象进行调用的:也就是它的调用位置中,是通过某个对象发起的函数的调用。

隐式绑定有一个前提条件:

  • 必须在调用的对象内部有一个对函数的引用(比如一个属性)
  • 如果没有这样的引用,在进行调用时,会报找不到该函数的错误
  • 正是通过这个引用,间接的将 this 绑定到了这个对象上
// 隐式绑定:object.fn()// object 对象会被 js 引擎绑定到 fn 函数中的 this 里面// 案例1functionfoo(){console.log(this)}varobj={name:'why',foo:foo}obj.foo()// 案例2varobj2={name:'why',eating:function(){console.log(this.name+'在吃东西')},running:function(){console.log(this.name+'在跑步')},studying:function(){console.log(this.name+'在学习')}}obj2.eating()obj2.running()obj2.studying()// 案例3varobj3={name:'obj3',foo:function(){console.log(this)}}varobj4={name:'obj4',bar:obj3.foo}obj4.bar()

绑定规则三:显示绑定

如果我们不希望在对象内部包含这个函数的引用,同时又希望在这个对象上进行强制调用,该怎么做?

  • JavaScript 所有的函数都可以使用 call 和 apply 方法(这个和 Prototype 有关)
  • 这两个函数的第一个参数都要求是一个对象,这个对象的作用是什么?就是给 this 准备的
  • 在调用这个函数时,会将 this 绑定到这个传入的对象上

通过 call 或者 apply 绑定 this 对象,显示绑定后,this 就会明确的指向绑定的对象

functionfoo(){console.log(this)}// foo 直接调用和 call/apply 调用的不同在于 this 绑定的不同// foo 直接调用指向的是全局对象 windowfoo()varobj={name:'why'}// call/apply 是可以指定 this 的绑定对象foo.apply(obj)foo.call(obj)// call/apply 在传参上有所区别functionsum(num1,num2){console.log(num1+num2,this)}sum.apply(obj,[10,20])sum.call(obj,10,20)// call 和 apply 在执行函数时,是可以明确的绑定 this,这个绑定规则称之为显示绑定。

如果希望一个函数总是显示的绑定到一个对象上可以使用 bind

functionfoo(){console.log(this)}// foo.call('kaimo')// foo.call('kaimo')// foo.call('kaimo')// foo.call('kaimo')// foo.call('kaimo')// 默认绑定和显示绑定 bind 冲突:显示绑定优先级更高varnewFoo=foo.bind('kaimo')newFoo()newFoo()newFoo()newFoo()newFoo()

绑定规则四:new 绑定

JavaScript 中的函数可以当做一个类的构造函数来使用,也就是使用 new 关键字

使用 new 关键字来调用函数时,会执行如下的操作:

  • 1.创建一个全新的对象
  • 2.这个新对象会被执行 prototype 链接
  • 3.这个新对象会绑定到函数调用的 this 上(this 的绑定在这个步骤完成)
  • 4.如果函数没有返回其他对象,表达式会返回这个新对象
// 通过一个 new 关键字调用一个函数时(构造器),这个时候 this 是在调用这个构造器时创建出来的对象// this = 创建出来的对象// 这个绑定过程就是 new 绑定functionPerson(name,age){this.name=namethis.age=age console.log(this)}varp1=newPerson('why',18)console.log('p1---->',p1)varp2=newPerson('kaimo',313)console.log('p2---->',p2)
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/9 1:01:07

UVM-phase中的object机制

在class uvm_phase extends uvm_object中1. 类的作用和结构这个类是 uvm_phase&#xff0c;它管理测试平台中阶段的 objection 机制。UVM 使用 objection 机制来控制仿真的执行时间&#xff0c;防止测试提前结束。核心成员&#xff1a;systemveriloguvm_objection phase_done; …

作者头像 李华
网站建设 2026/3/10 19:31:04

FeignRequestInterceptor 原理详解

FeignRequestInterceptor是 OpenFeign 的请求拦截器机制&#xff0c;其工作原理如下&#xff1a;1. 核心设计模式责任链模式&#xff1a;Feign 通过拦截器链在请求发送前和接收后执行自定义逻辑。// 拦截器接口定义 public interface RequestInterceptor {void apply(RequestTe…

作者头像 李华
网站建设 2026/3/5 13:05:58

AI 写论文哪个软件最好?虎贲等考 AI 凭 “学术闭环” 登顶首选

毕业季的论文攻坚战&#xff0c;“AI 写论文哪个软件最好” 成了学子圈的终极拷问。有的软件文献引用虚拟无据&#xff0c;有的 AI 痕迹明显被导师打回&#xff0c;有的功能碎片化需反复切换工具 —— 真正靠谱的 AI 写作软件&#xff0c;不仅要 “写得快”&#xff0c;更要 “…

作者头像 李华
网站建设 2026/3/6 2:49:42

网络安全技术核心框架:一文理清从原理到实践的知识体系

1.网络安全的概念 网络安全的定义 ISO对网络安全的定义&#xff1a;网络系统的软件、硬件以及系统中存储和传输的数据受到保护&#xff0c;不因偶然的或者恶意的原因而遭到破坏、更改、泄露&#xff0c;网络系统连续可靠正常地运行&#xff0c;网络服务不中断。 网络安全的属…

作者头像 李华
网站建设 2026/3/11 13:43:03

Aurix TC387 Can配置记录

一、MCMCAN介绍fSYN is supplied from fMCANH and fASYN is supplied from fMCAN from CCU. fSYN is used as the clock source for Register and RAM interface,fASYN is used to generate the nominal and fast CAN FD baudrates. It is recommended to use fASYN as 80, 40,…

作者头像 李华
网站建设 2026/3/10 23:54:13

原理:XinServer 是如何实现开箱即用的后端服务的?

原理&#xff1a;XinServer 是如何实现开箱即用的后端服务的&#xff1f; 不知道你有没有过这种经历&#xff1a;产品经理或者客户拿着一个原型图过来&#xff0c;说“咱们这个App/小程序/管理后台&#xff0c;下个月能上线吗&#xff1f;”你一看&#xff0c;好家伙&#xff0…

作者头像 李华