Maui 实践:让 JavaScript 的 this 怪物如同邻居家(强类型)的乖孩子

news/2025/10/20 12:00:46/文章来源:https://www.cnblogs.com/zhally/p/19152352

Maui 实践:让 JavaScript 的 this 怪物如同邻居家(强类型)的乖孩子

原创 夏群林 2025.10.20

MAUI,不能不说,好。也不得不说,好——多坑。

指望一个 Windows 平台当家的大厂,把自家与 Windows 平台深度绑定的 .Net 运行时,和 .Net 的天生语言 C#,如同支持 Windows 平台一样支持其他平台,那是想多了。因为喜欢 C# 的优美,还有 Visual Studio 的近乎完美,我选择性相信了当初他们宣称的跨平台支持能力。事已至此,得失不论。没必要质疑他们的努力,也没必要纠结他们的动机,反正结果是:现实不由人。

于是,我尝试引入 HTML 5,作为前端。而且是纯粹的那种,不要框架,只依赖原生。我本喜欢强类型语言,但这次,JavaScript 绕不过。好在语言相互借鉴,大概念趋同。本文专门谈谈 this, 似同非同的处理。

一、C# 中的 this:乖孩子

在 C# 这类强类型语言中,this 是让开发者省心的乖孩子,行为稳定、规则清晰,从定义到运行始终如一。

  1. 编译期绑定指向this 在写代码时锁死了关联当前类的实例,无论方法是直接调用、作为委托传递,还是通过其他方式触发,this 都不会“跑偏”。

    public class Person {public string Name { get; set; }public void SayHi() => Console.WriteLine($"Hi, {this.Name}");
    }var person = new Person { Name = "张三" };
    Action say = person.SayHi; 		  // 提取方法引用
    say(); 							// 输出 "Hi, 张三"(this 仍指向 person 实例)
    
  2. 继承的先父后子,无论是隐式调用父类无参构造,还是显式调用有参构造,父类的构造函数总是先执行,父类成员先行完成初始化,不会出现子类访问未就绪的父类属性的情况。

    // 父类:仅定义有参构造,无无参构造
    public class Parent {protected string Name;public Parent(string name) { // 有参构造this.Name = name;}
    }// 子类:必须显式调用父类的 Parent(string) 构造,否则报错
    public class Child : Parent {public int Age;// 正确:显式调用父类有参构造public Child(string name, int age) : base(name) { this.Age = age;Console.WriteLine($"子类初始化:{this.Name},{this.Age}"); // 正常(Name 已由父类初始化)}// 错误示例(无法编译):未显式调用 base(name)// 父类:有隐式无参构造(未定义任何构造时,编译器自动生成)// public class Parent {//     protected string Name; // 父类成员// }// public Child(string name, int age) { //     this.Age = age; // }// 编译报错:“Parent”不包含采用 0 个参数的构造函数
    }
    
  3. 事件回调中的身份自觉,在 UI 事件(如按钮点击)中,this 始终指向当前组件/页面实例,绝不会指向触发事件的控件(如按钮本身)。

    // 按钮点击事件中,this 指向当前页面,而非按钮
    button.Click += (s, e) => Console.WriteLine(this.Title);
    

C# 的 this 之所以“乖”,核心是静态绑定——行为在编译期就确定,运行时无需额外判断。这个核心特性,恰恰是 JavaScript 早期 this 所缺乏的。

二、JavaScript 的 this:似是而非

早期 JavaScript 没有类的概念,通过 “构造函数+原型链” 模拟面向对象,this 因“动态绑定”特性,从强类型语言的角度看,其表现堪比怪物,this 指向完全依赖调用方式,稍不注意就出错。

// 早期模拟类的方式,this 容易失控
function Person(name) {this.name = name; // 构造函数中 this 指向实例(需用 new 调用)
}
Person.prototype.sayHi = function() {console.log(`Hi, ${this.name}`); // 原型方法中 this 依赖调用者
};const person = new Person("张三");
person.sayHi(); // 正常(this 指向 person)
const say = person.sayHi;
say(); // 报错(this 指向全局)

2015 年 ES6 引入 classextends 等特性,明显吸收了 C#、Java 等强类型语言的设计思想,让 JavaScript 的面向对象更贴近开发者直觉。

// 类似 C# 的类定义,结构更清晰
class Person {constructor(name) {this.name = name; // 构造函数中 this 指向实例}sayHi() {console.log(`Hi, ${this.name}`); // 类方法中的 this}
}

这种借鉴并非复制粘贴,JavaScript 仍保留动态语言特性,但 class 语法,降低了理解成本。

class 本质是“语法糖”,底层仍基于原型链(prototype),只是包装后更像 C# 的类:

  1. 实例初始化 。constructor 对应 C# 的构造函数,new 调用时,this 指向新创建的实例,用于初始化实例属性(this.xxx)。

    class Person {constructor(name) {this.name = name; // name 是实例属性(每个实例单独拥有)}
    }
    
  2. 类方法默认挂载在原型上。类中定义的方法(如 sayHi)会被挂载到类的原型(Person.prototype)上,所有实例共享该方法。这和 C# 中”方法在类中,实例共享方法定义“的逻辑一致,但底层实现不同,C# 基于类,JavaScript 基于原型链。

    const p1 = new Person("张三");
    const p2 = new Person("李四");
    p1.sayHi === p2.sayHi; // true(共享原型上的方法)
    
  3. static 方法挂载在类本身,而非原型,this 指向类本身。C# 静态方法中无 this,但逻辑类似:不依赖实例。

    class Person {static createDefault() {return new Person("默认名称"); // this 指向 Person 类}
    }
    const defaultPerson = Person.createDefault();
    
  4. 基于原型链封装的 extends 继承,class Child extends Parent 本质是让 Child.prototype.__proto__ 指向 Parent.prototype,但语法上模拟了 C# 的继承。super 对应 C# 的 base,用于调用父类的构造函数或方法。

三、让 JavaScript 的 this 怪物变成乖孩子

JavaScript 的 this 像怪物,核心是指向由函数调用时的方式决定,属于动态绑定,而非定义时的静态绑定。 动态绑定规则决定 this 指向:

绑定类型 调用方式示例 this 指向 与 C# 的对比
默认绑定 fn() 全局对象(非严格模式)/undefined(严格模式) 无对应(C# 无全局 this
隐式绑定 obj.fn() 调用方法的对象 obj 类似 C# 实例调用方法(this 指向实例)
显式绑定 fn.call(obj)/fn.apply(obj)/fn.bind(obj) 被指定的对象 obj 无对应(C# this 不可改)
new 绑定 new Fn() 新创建的实例对象 类似 C# new 实例化(this 指向实例)

示例:

// 同个函数,不同调用方式,this 指向不同**  
function showThis() {console.log(this);
}const obj = { name: "测试对象", showThis };showThis(); // 默认绑定 → 全局对象
obj.showThis(); // 隐式绑定 → obj
showThis.call({ custom: "自定义对象" }); // 显式绑定 → 自定义对象
new showThis(); // new 绑定 → 新实例

但 JavaScript 提供了显式绑定工具,call / apply / bind ,可以手动控制this 指向,让它像 C# 的 this 那样驯服。

方法 作用 调用时机 适用场景
call 强制 this 指向第一个参数,立即执行函数 立即执行 明确参数数量时调用函数
apply 强制 this 指向第一个参数,立即执行函数 立即执行 参数以数组形式存在时
bind 强制 this 指向第一个参数,返回新函数(延迟执行) 延迟执行 固定事件回调、方法提取后调用

1)bind 优先级最高,绑定后不可修改,类似 C# 中 this 的不可变性:

function sayHi() {console.log(`Hi, ${this.name}`);
}const person = { name: "张三" };
const boundSayHi = sayHi.bind(person); // 绑定 this 到 personboundSayHi(); // 输出 "Hi, 张三"
boundSayHi.call({ name: "李四" }); // 仍输出 "Hi, 张三"(bind 不可覆盖)

2)箭头函数,天生继承外层 this,规避动态陷阱

ES6 箭头函数没有自己的 this,其 this 继承自外层作用域(定义时的上下文),行为类似 C# 匿名方法捕获当前 this 的特性。这是让 this 变“乖”的更简洁方式。对比普通函数与箭头函数:

class Timer {constructor() {this.seconds = 0;// 普通函数:this 指向调用者(setTimeout 的全局环境)setInterval(function() {this.seconds++; // 错误:this.seconds 未定义}, 1000);// 箭头函数:this 继承自 constructor(Timer 实例)setInterval(() => {this.seconds++; // 正确:this 指向 Timer 实例}, 1000);}
}

适用场景:事件回调、定时器、嵌套函数中,需要保留外层 this 时优先使用。

3) 内存泄漏:this 引发的暗坑

C# 有自动垃圾回收机制,但 JavaScript 中若 this 关联的事件回调未正确解绑,会导致对象无法被回收,引发内存泄漏。

错误示例:动态生成的函数无法解绑

class Component {constructor() {this.name = "组件";// 错误:每次 bind 生成新函数,后续无法解绑document.querySelector('button').addEventListener('click', this.handleClick.bind(this));}handleClick() { console.log(this.name); }destroy() {// 失败:解绑的函数与绑定的不是同一个引用document.querySelector('button').removeEventListener('click', this.handleClick.bind(this));}
}

正确做法:保存绑定后的函数引用

class Component {constructor() {this.name = "组件";// 提前绑定并保存引用this.boundHandleClick = this.handleClick.bind(this);document.querySelector('button').addEventListener('click', this.boundHandleClick);}handleClick() { console.log(this.name); }destroy() {// 用同一引用解绑document.querySelector('button').removeEventListener('click', this.boundHandleClick);this.boundHandleClick = null; // 释放引用}
}

四、HTML 5 自定义 UI 组件中 this 规范化

在 HTML5 开发自定义 UI 组件(如按钮、表单控件)时,this 的坑会集中爆发。结合类与继承,我们可以用规范化技术解决。

场景 1:自定义按钮组件(基础类)

问题:事件回调中 this 指向 DOM 元素(而非组件实例)。

class CustomButton {constructor(label) {this.label = label; // 组件属性this.btn = document.createElement('button');this.btn.textContent = label;// 坑:点击时 this 指向 btn(DOM 元素)this.btn.addEventListener('click', this.onClick);document.body.appendChild(this.btn);}onClick() {console.log(`点击了 ${this.label}`); // 报错:this.label 不存在}
}

解决:用 bind 或箭头函数固定 this 指向组件实例。

class CustomButton {constructor(label) {this.label = label;this.btn = document.createElement('button');this.btn.textContent = label;// 方案 1:bind 绑定this.btn.addEventListener('click', this.onClick.bind(this));// 方案 2:箭头函数回调(更简洁)// this.btn.addEventListener('click', () => this.onClick());document.body.appendChild(this.btn);}onClick() {console.log(`点击了 ${this.label}`); // 正确:this 指向组件实例}
}

场景 2:带图标的按钮(子类继承)

问题:子类构造函数未调用 super() 就使用 this,直接报错。

class IconButton extends CustomButton {constructor(label, icon) {this.icon = icon; // 报错:必须先调用 super()super(label);}
}

解决:严格遵循“先 super()this”,对齐 C# 的 base() 逻辑。

class IconButton extends CustomButton {constructor(label, icon) {super(label); // 先调用父类构造this.icon = icon; // 再初始化子类属性this.btn.innerHTML = `<i>${icon}</i> ${label}`; // 扩展父类 DOM}// 重写父类方法onClick() {console.log(`点击了带 ${this.icon} 图标的 ${this.label}`);}
}

场景 3:组件移除/销毁与资源清理

问题:事件未解绑导致内存泄漏。
解决:提供 destroy 方法,手动解绑事件并释放引用。

class CustomButton {// ... 其他代码 ...destroy() {// 解绑事件(用绑定时期的引用)this.btn.removeEventListener('click', this.boundOnClick || this.onClick);this.btn.remove(); // 移除 DOM 元素// 释放属性引用this.btn = null;this.label = null;}
}

五、几点心得

如果您像我一样,熟悉 C# 或者 Java 这样的强类型语言, 只是偶尔使用 JavaScript 配置前端,我的建议,与其花时间通透掌握 JavaScript 本身,不如改造它适合自己的思维习惯。用强类型思维顺服 this,更顺手。具体来说:

  1. 直接用 ES+,忽略传统 JavaScript 语法,尽管语言本身是向后兼容的。
  2. 用 class 对齐结构,借助 classextends,让 JavaScript 类的写法贴近 C# ,降低认知成本;
  3. bind 或箭头函数固定 this,抵消动态性,模拟 C# 中“方法与实例强绑定”的特性;
  4. 子类构造函数先 super()this,对齐 C# 的 base() 调用逻辑;
  5. 主动将事件回调的 this 指向组件实例,避免指向 DOM 元素;
  6. 显式保留 connectedCallback() / disconnectedCallback() 方法,只要可能,统一在 connectedCallback 中注册事件,在 disconnectedCallback 移除事件。
  7. 确保组件移除/销毁时解绑事件,释放 this 关联的引用,类似 C# 的 Dispose

做到这些,JavaScript 的 this 就会像邻居家强类型语言的 this 一样,成为可靠、可控的乖孩子。

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

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

相关文章

[251020 699mAh] 模拟赛破防有感 2.0

我会 T1 了。 我会 T2 了。 但是是个看起来细节非常多需要各种分奇偶性讨论的用 set 平衡树状物维护的东西。 我会 T3 了。 我不会 T4。 写一下 T3 看看。样例过不去。好像假了。 我刷新了。 题目不存在。 我草 T3 T4 …

2025 年速冻机源头厂家最新推荐榜单:涵盖隧道式、大型、全自动、螺旋、箱式柜式小型等多类型设备,助力食品加工企业选优质供应商

当前食品加工行业对速冻机的需求日益多元化,从隧道式、大型液氮速冻机到全自动、螺旋及箱式柜式小型速冻机,不同规模企业有着差异化采购需求。但市场上供应商鱼龙混杂,部分非源头厂家加价倒卖、技术实力薄弱的企业产…

2025 年最新钙片厂家推荐榜单:聚焦四期临床实证与蓝帽认证,为中老年骨健康精选优质品牌指南

引言当前人口老龄化进程加快,中老年群体对骨健康重视程度与日俱增,钙片市场需求随之攀升,但市场乱象却让消费者陷入选择困境。众多品牌混杂,部分产品成分含量不达标、配方缺乏科学性,且无权威临床数据支撑,仅靠宣…

基于瑞萨R7F0C807的无线充电发送器设计

一、硬件架构设计 1. 主控模块MCU选型:R7F0C807(RL78内核,20MHz主频,8KB Flash) 核心功能: 定时器阵列(TAU)生成PWM驱动信号(131kHz5%) 12位ADC实时监测电流/电压(精度1.5%) 内置看门狗定时器(WDT)防止程…

2025 年冷却塔源头厂家最新推荐排行榜:无风机无填料节能型设备领衔,优质品牌深度解析

引言当前工业领域对冷却设备的节能性、稳定性与运维成本要求日益严苛,传统风机填料冷却塔能耗高、维修频繁、更换成本高的问题愈发凸显,且国家低碳节能政策持续推进,企业亟需适配的高效冷却解决方案。为帮助工业企业…

AtCoder Beginner Contest 428 D - 183184

该算法高效解决了在给定范围[1, D]内寻找所有满足条件的x,使得数字C与C+x字符串拼接后形成的数字为完全平方数的问题。其核心创新在于通过数学建模将字符串拼接问题转化为平方数区间计数问题,避免了暴力枚举的低效做…

2025 年广州装修公司最新推荐排行榜:涵盖花都、黄埔、天河等十区,精选全品类商业空间装修优质品牌从化/越秀/荔湾/番禺/白云/增城装修公司推荐

当前广州装修市场鱼龙混杂,不少装修需求者在选择时屡屡碰壁 —— 设计方案缺乏个性且不贴合实际使用场景、施工质量差导致后期问题频发、工期延误影响开业或入住、合规性不足面临整改等问题层出不穷。为帮助广州花都、…

【Docker项目实战】启用Docker部署WikiDocs文档管理工具

【Docker项目实战】启用Docker部署WikiDocs文档管理工具pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas&…

人狗大战:面向对象关系详解

人狗大战:面向对象关系详解 好的,让我们用一个有趣的"人狗大战"例子来详细说明各种面向对象关系。 类与类之间的关系继承/泛化 (Inheritance)class Animal:"""动物基类"""d…

2025年10月超声波清洗机厂家推荐榜:十强对比评测与选购全攻略分析

一、引言 在精密制造、半导体、光学镜片、珠宝首饰乃至汽车零部件等行业,超声波清洗已成为保障良品率与表面洁净度的关键工序。对于产线升级或新建工厂的采购负责人、工艺工程师以及质量经理而言,如何在预算可控的前…

2025年10月超声波清洗机厂家推荐榜:十强对比评测与选购全攻略。

一、引言 在精密制造、半导体、光学镜片、珠宝首饰乃至汽车航空等领域,清洗环节直接决定后续工艺良率与产品寿命。对于产线采购负责人、工艺工程师以及计划升级清洗工序的中小企业主而言,如何在保证洁净度的同时控制…

微服务,Spring Cloud 和 Eureka:服务发现工具 - 教程

微服务,Spring Cloud 和 Eureka:服务发现工具 - 教程2025-10-20 11:50 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; d…

2025年10月超声波清洗机厂家推荐榜:十强对比评测与选购指南。

一、引言 超声波清洗技术凭借高效、无损、环保的优势,已成为电子、光学、半导体、精密五金等行业提升良率、降低人工成本的必备工艺。对于计划升级产线、替换老旧设备或首次采购的创业者、工艺工程师、采购经理而言,…

2025年10月中国数据库排行榜:PolarDB重回榜眼,TDSQL跃进前五

10月墨天轮排行榜解读已出炉!本月共有170款数据库参与排名,榜单前十竞争激烈,OceanBase再次领跑、PolarDB重回榜眼、TDSQL 跃进前五,此外还有一些在产品技术/生态/产业落地上持续发力,欢迎一起盘点~10月墨天轮社区…

docker镜像搬运命令

save命令: 将mysql这个image保存到目录/home/data,保存为mysql.tar docker save -o /home/data/mysql.tar mysql load命令: 将/home/data/mysql.tar 镜像拉到本地。 docker load -i /home/data/mysql.tar

本土化DevOps平台崛起:Gitee如何重塑企业研发效能新范式

本土化DevOps平台崛起:Gitee如何重塑企业研发效能新范式 在数字化转型浪潮席卷全球的当下,企业研发效能已成为决定商业竞争力的关键因素。Gitee DevOps平台凭借其独特的本土化优势,正在成为中国企业提升研发效率的战…

2025 年国内集装箱拖车供应厂家最新推荐权威榜单:全方位解析优质厂家实力,助力企业精准选合作商

在集装箱运输行业蓬勃发展的当下,诸多问题逐渐显现,给行业的稳健前行带来了挑战。市场上集装箱拖车供应厂家数量众多,资质参差不齐,部分厂家缺乏规范的管理体系,导致服务质量难以保证。运输过程中的安全问题也时有…

windows 查询exe文件版本

windows 查询exe文件版本文件路径: F:\test\test.execmd查询 wmic datafile where name="F:\test\test.exe" get Version /value 路径使用双反斜杠powershell (Get-Item -path "F:\test\test.exe"…

【大模型评估】大模型评估框架 HELM(Holistic Evaluation of Language Models)全解析:原理、应用与实践

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

MyEMS:开启智能化能源管理新时代

在全球能源供需矛盾加剧、“双碳” 目标持续推进的背景下,传统能源管理模式因数据滞后、分析粗放、调控被动等问题,已难以满足企业与个人对高效用能、降本减排的需求。MyEMS(My Energy Management System,我的能源…