河南省网站建设_网站建设公司_自助建站_seo优化
2026/1/14 23:29:25 网站建设 项目流程


实例属性 vs 非实例属性:前端新人别再搞混了(附避坑指南)

  • 实例属性 vs 非实例属性:前端新人别再搞混了(附避坑指南)
    • 先甩结论:别硬背,先搞懂“谁的锅”
    • new 的一瞬间,内存里到底在忙啥
    • 私房钱 vs 班费:一张图看懂
    • 原型链上挂属性,到底算谁的?
    • 为什么一改全改?——引用类型大锅饭
    • static 方法里为啥不能 this. 属性?
    • 箭头函数当方法?this 绑定翻车现场
    • TypeScript 的 public private static 对运行时有影响吗?
    • 实战 1:表单校验工具类,正则放哪?
    • 实战 2:组件状态管理,data 必须实例化
    • 实战 3:工具函数集合,学 Math 就完事
    • 实战 4:缓存策略,全局 + 用户分离
    • 调试骚操作:一键打印属性归属
    • 命名冲突:同名惨案
    • 口诀背一下,下班不加班
    • 写在最后的碎碎念

实例属性 vs 非实例属性:前端新人别再搞混了(附避坑指南)

友情提示:本文全程碎碎念,代码量巨大,阅读时请自备咖啡和 debugger,否则容易在第五段原地睡着。


先甩结论:别硬背,先搞懂“谁的锅”

我第一次把this.name = 'Tom'static name = 'Jerry'写在一个类里,控制台直接给我打印出俩 name,当时我就裂开了——这他妈到底谁盖了谁?
后来我才悟了:实例属性≈你钱包里的私房钱,非实例属性≈班级公费
前者自己花自己的,后者全班一起薅。就这么简单粗暴。


new 的一瞬间,内存里到底在忙啥

先别急着背概念,咱们把new拆开看——这货其实就干了四件事:

functionmyNew(constructor,...args){// 1. 先整一个空对象,原型指向构造函数constobj=Object.create(constructor.prototype);// 2. 把构造函数里的 this 绑到这个空对象constret=constructor.apply(obj,args);// 3. 如果构造函数手贱 return 了个对象,就用它的;否则用咱们新建的returnretinstanceofObject?ret:obj;}

看见没?只有构造函数里用 this.xxx 写的属性,才会被塞进这个新对象
你在类外面写的Foo.bar = 123或者类里写的static bar = 123,跟这一步半毛钱关系都没有——它们老早就趴在构造函数(类)自己身上了。


私房钱 vs 班费:一张图看懂

classWallet{// 班费——全班共用statictreasury=0;constructor(name){// 私房钱——每人一份this.name=name;this.cash=50;}// 实例方法:花自己的钱buy(price){this.cash-=price;}// 静态方法:花班费staticclassParty(cost){Wallet.treasury-=cost;}}consta=newWallet('张三');constb=newWallet('李四');console.log(a.cash);// 50console.log(b.cash);// 50console.log(Wallet.treasury);// 0// 张三偷偷把班费薅走 100Wallet.classParty(100);console.log(Wallet.treasury);// -100// 李四的私房钱纹丝不动console.log(b.cash);// 50

一句话总结

  • 实例属性 = 挂在this上 = 每个对象单独一份。
  • 静态属性 = 挂在上 = 全局唯一,一改全变。

原型链上挂属性,到底算谁的?

很多教程喜欢把原型链画成九转大肠,看得人云里雾里。咱们直接撸代码:

functionDog(name){this.name=name;}// 把技能挂在原型上——共享Dog.prototype.say=function(){console.log(`${this.name}汪汪`);};constd1=newDog('旺财');constd2=newDog('来福');d1.say();// 旺财 汪汪d2.say();// 来福 汪汪// 原型属性是共享的,但 this 会动态指向调用者console.log(d1.say===d2.say);// true,同一个函数

所以:

  • 原型上的属性不算实例自己的(Object.getOwnPropertyNames(d1)找不到say)。
  • 但实例可以来用,借的时候this指向借的那个人。
    四舍五入:原型属性是“公共图书馆”,谁都能借书,但借回家写名字就是你的

为什么一改全改?——引用类型大锅饭

classConfig{staticcache={};// 班费constructor(){this.local={};// 私房钱}}constc1=newConfig();constc2=newConfig();// 1. 改班费Config.cache.hit=1;console.log(c1.local.hit);// undefinedconsole.log(Config.cache.hit);// 1// 2. 不小心把班费地址递给私房钱c1.local=Config.cache;c1.local.hit=999;console.log(Config.cache.hit);// 999 —— 全班遭殃

血泪教训

  • 基本类型(number、string、boolean)复制值;
  • 引用类型(对象、数组、函数)复制地址。
    把引用类型当静态属性,就是全班共用一个钱包,谁往里扔屎,全班一起闻。

static 方法里为啥不能 this. 属性?

classTool{staticapi='https://api.xxx.com';getUrl(){// 实例方法里可以随便 thisreturnthis.api;// undefined,因为 this 指向实例,实例没有 api}staticgetUrl(){// 静态方法里 this 指向类本身returnthis.api;// 正确,相当于 Tool.api}}

底层原理
静态方法会被 babel 直接挂到构造函数上,压根不会进原型链
babel 转译长这样:

varTool=function(){functionTool(){_classCallCheck(this,Tool);}Tool.getUrl=functiongetUrl(){returnTool.api;};returnTool;}();

看见没?getUrl直接是Tool自己的属性,跟实例半毛钱关系都没有,所以你在 static 方法里写this.xxxthis 指向的是 Tool 本身,不是某个实例。


箭头函数当方法?this 绑定翻车现场

classCounter{count=0;// 普通方法——this 动态绑定inc(){this.count++;}// 箭头函数——this lexical 绑定incArrow=()=>{this.count++;};}constc=newCounter();const{inc,incArrow}=c;inc();// 报错:Cannot read property 'count' of undefinedincArrow();// 正常 +1

解释

  • 普通方法被“扒”下来当裸函数后,this 丢失;
  • 箭头函数在构造函数执行时就已经把 this 绑死为当前实例,扒下来也不怕
    所以:
  • 写 React 类组件时,直接用箭头函数当回调——省得写bind(this)
  • 写纯逻辑库时,别乱用箭头函数——原型上根本不会出现,每个实例都 copy 一份,内存爆炸

TypeScript 的 public private static 对运行时有影响吗?

classFoo{publicinstance=1;private_secret=2;staticpublicstaticProp=3;}

编译后(target ES5):

varFoo=(function(){functionFoo(){this.instance=1;this._secret=2;// private 只是名字上加下划线,照样能访问}Foo.staticProp=3;returnFoo;}());

真相

  • public/private/protected都是编译时检查,运行时毛线都没有;
  • static会实打实挂到构造函数上,运行时依旧存在
    所以:别把 private 当安全锁,只是给编译器看的“君子协定”

实战 1:表单校验工具类,正则放哪?

// ❌ 每个实例都 copy 一份,浪费内存classValidator{constructor(){this.emailReg=/^[^\s@]+@[^\s@]+\.[^\s@]+$/;}validateEmail(v){returnthis.emailReg.test(v);}}// ✅ 全班共用,内存友好classValidator{staticemailReg=/^[^\s@]+@[^\s@]+\.[^\s@]+$/;validateEmail(v){returnValidator.emailReg.test(v);}}

结论

  • 正则、配置、常量绝不需要实例状态—— 直接 static;
  • 如果正则巨大且需要运行时动态拼接,再考虑放实例。

实战 2:组件状态管理,data 必须实例化

classReactLikeComponent{// 状态放实例,防止串台state={count:0};// 方法挂原型,共享引用setState(patch){Object.assign(this.state,patch);}}constA=newReactLikeComponent();constB=newReactLikeComponent();A.setState({count:1});console.log(B.state.count);// 0,互不影响

套路

  • 需要各自独立的数据 → 实例属性;
  • 纯函数逻辑 → 原型方法;
  • 全局通信 → 单例 or 事件总线,别用 static 变量乱塞。

实战 3:工具函数集合,学 Math 就完事

// 像 Math 一样,无状态,直接静态classUUID{staticgenerate(){return'xxx-xyxx'.replace(/[xy]/g,c=>{constr=(Math.random()*16)|0;return(c==='x'?r:(r&0x3)|0x8).toString(16);});}}// 使用console.log(UUID.generate());

要点

  • 不需要new,无内部状态 —— 直接 static 方法;
  • 防止被误new可以加一手保险:
constructor(){thrownewError('UUID is static, do not new it');}

实战 4:缓存策略,全局 + 用户分离

classRequest{// 全局缓存池staticglobalCache=newMap();constructor(userId){// 用户级缓存this.userCache=newMap();this.userId=userId;}asyncfetch(url){constkey=`${this.userId}:${url}`;// 先读用户缓存if(this.userCache.has(key))returnthis.userCache.get(key);// 再读全局if(Request.globalCache.has(url))returnRequest.globalCache.get(url);constdata=awaitfetch(url).then(r=>r.json());// 写用户缓存this.userCache.set(key,data);// 写全局缓存Request.globalCache.set(url,data);returndata;}}

套路总结

  • 全局唯一的放 static;
  • 用户隔离的放实例;
  • 两层缓存,读先私有后公共,写先私有后公共,减少内存重复。

调试骚操作:一键打印属性归属

functiondebugProps(obj){console.log('【own】',Object.getOwnPropertyNames(obj));console.log('【proto】',Object.getOwnPropertyNames(Object.getPrototypeOf(obj)));console.log('【static】',Object.getOwnPropertyNames(obj.constructor));}classFoo{statics=1;instance=2;method(){}}debugProps(newFoo());// 【own】 ["instance"]// 【proto】 ["constructor", "method"]// 【static】 ["s", "length", "name", "prototype"]

好处

  • 一眼看出属性到底挂哪
  • 排查“我明明写了却 undefined” 的终极利器。

命名冲突:同名惨案

classConf{staticname='Class';constructor(){this.name='Instance';}}constc=newConf();console.log(c.name);// Instanceconsole.log(Conf.name);// Classconsole.log(c.constructor.name);// Conf(函数名)

规则

  • 实例属性屏蔽原型/静态同名属性;
  • 静态属性永远不会被实例访问到(除非手动类名.静态);
  • 多人协作时,静态统一加 S_ 前缀或者全大写,避免手滑覆盖。

口诀背一下,下班不加班

“每个对象都要有的放实例,全班共用的放 static;
箭头函数别乱写,内存爆炸自己背;
调试先分清 own/proto/static,undefined 先找 constructor。”


写在最后的碎碎念

其实吧,静态和实例这事儿,写错一次坑一次,坑多了自然就记住了
今天给你把内存图、babel 转译、TypeScript 擦除、原型链图书馆、私房钱班费全掰开揉碎聊了一遍,再分不清就只能——
打印出来贴工位,每天拜一拜,bug 退散!

代码copy一时爽,一直copy一直爽,但记得把变量名改一改,别让后人考古时骂你。

欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。

推荐:DTcode7的博客首页。
一个做过前端开发的产品经理,经历过睿智产品的折磨导致脱发之后,励志要翻身农奴把歌唱,一边打入敌人内部一边持续提升自己,为我们广大开发同胞谋福祉,坚决抵制睿智产品折磨我们码农兄弟!


专栏系列(点击解锁)学习路线(点击解锁)知识定位
《微信小程序相关博客》持续更新中~结合微信官方原生框架、uniapp等小程序框架,记录请求、封装、tabbar、UI组件的学习记录和使用技巧等
《AIGC相关博客》持续更新中~AIGC、AI生产力工具的介绍,例如stable diffusion这种的AI绘画工具安装、使用、技巧等总结
《HTML网站开发相关》《前端基础入门三大核心之html相关博客》前端基础入门三大核心之html板块的内容,入坑前端或者辅助学习的必看知识
《前端基础入门三大核心之JS相关博客》前端JS是JavaScript语言在网页开发中的应用,负责实现交互效果和动态内容。它与HTML和CSS并称前端三剑客,共同构建用户界面。
通过操作DOM元素、响应事件、发起网络请求等,JS使页面能够响应用户行为,实现数据动态展示和页面流畅跳转,是现代Web开发的核心
《前端基础入门三大核心之CSS相关博客》介绍前端开发中遇到的CSS疑问和各种奇妙的CSS语法,同时收集精美的CSS效果代码,用来丰富你的web网页
《canvas绘图相关博客》Canvas是HTML5中用于绘制图形的元素,通过JavaScript及其提供的绘图API,开发者可以在网页上绘制出各种复杂的图形、动画和图像效果。Canvas提供了高度的灵活性和控制力,使得前端绘图技术更加丰富和多样化
《Vue实战相关博客》持续更新中~详细总结了常用UI库elementUI的使用技巧以及Vue的学习之旅
《python相关博客》持续更新中~Python,简洁易学的编程语言,强大到足以应对各种应用场景,是编程新手的理想选择,也是专业人士的得力工具
《sql数据库相关博客》持续更新中~SQL数据库:高效管理数据的利器,学会SQL,轻松驾驭结构化数据,解锁数据分析与挖掘的无限可能
《算法系列相关博客》持续更新中~算法与数据结构学习总结,通过JS来编写处理复杂有趣的算法问题,提升你的技术思维
《IT信息技术相关博客》持续更新中~作为信息化人员所需要掌握的底层技术,涉及软件开发、网络建设、系统维护等领域的知识
《信息化人员基础技能知识相关博客》无论你是开发、产品、实施、经理,只要是从事信息化相关行业的人员,都应该掌握这些信息化的基础知识,可以不精通但是一定要了解,避免日常工作中贻笑大方
《信息化技能面试宝典相关博客》涉及信息化相关工作基础知识和面试技巧,提升自我能力与面试通过率,扩展知识面
《前端开发习惯与小技巧相关博客》持续更新中~罗列常用的开发工具使用技巧,如 Vscode快捷键操作、Git、CMD、游览器控制台等
《photoshop相关博客》持续更新中~基础的PS学习记录,含括PPI与DPI、物理像素dp、逻辑像素dip、矢量图和位图以及帧动画等的学习总结
日常开发&办公&生产【实用工具】分享相关博客》持续更新中~分享介绍各种开发中、工作中、个人生产以及学习上的工具,丰富阅历,给大家提供处理事情的更多角度,学习了解更多的便利工具,如Fiddler抓包、办公快捷键、虚拟机VMware等工具

吾辈才疏学浅,摹写之作,恐有瑕疵。望诸君海涵赐教。望轻喷,嘤嘤嘤

非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。愿斯文对汝有所裨益,纵其简陋未及渊博,亦足以略尽绵薄之力。倘若尚存阙漏,敬请不吝斧正,俾便精进!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询