JavaScript类定义中方法应在构造函数内部还是外部声明?
JavaScript类方法声明位置问题解答
方法应该声明在constructor内部还是外部?
没有绝对标准答案,根据业务场景选择即可:
- 绝大多数通用业务场景,优先把方法声明为类的原型方法(也就是constructor外部直接写
方法名() {}的普通写法),所有实例共享同一个方法引用,内存开销最小 - 遇到需要固定this指向、访问构造函数私有变量、动态生成方法逻辑的场景,再选择在constructor内部挂载方法
- 注意:你示例中constructor外写
sound = () => {}的写法是ES2022的类公共字段语法,不属于原型方法,本质也是给每个实例自身挂载独立的方法属性。
构造函数内部声明方法的实际作用
- 永久绑定this指向:方法的this永远指向当前类实例,把方法单独抽离作为回调(比如事件监听、定时器回调)时,不会出现this丢失的问题,不需要额外写
.bind(this)做绑定 - 支持真正的私有状态封装:方法可以读取constructor内声明的、没有挂载到this上的局部变量,这部分变量外部无法直接访问,不会被随意篡改
- 灵活度更高:可以根据构造函数传入的参数,动态给实例挂载不同实现的方法,适配差异化的实例逻辑
构造函数外部声明方法的弊端
需要分两种常见的外部写法分别说明:
- 普通原型方法(
sound() {}形式)的弊端- this指向不固定,脱离实例上下文调用时(比如作为函数参数传递)会丢失this指向,需要手动绑定
- 无法访问构造函数内声明的局部私有变量
- 类公共字段箭头函数(
sound = () => {}形式)的弊端- 每个实例都会生成独立的方法副本,相比原型方法内存占用更高,当需要批量创建成千上万个实例时,内存开销差异会很明显
- 继承时存在初始化顺序陷阱,父类构造函数中调用该方法时,不会走到子类重写的方法实现,很容易写出不符合预期的逻辑
两种示例写法的易忽略差异
你提到的两种写法对应代码如下:
// 写法1:constructor内部挂载方法 class animal { constructor(name, type, color){ this.name = name; this.type = type; this.color = color; this.sound = () => {console.log(this.name,this.type,this.color)} } } // 写法2:类公共字段声明方法 class animal { constructor(name, type, color){ this.name = name; this.type = type; this.color = color; } sound = () => {console.log(this.name,this.type,this.color)} }
两种写法创建的实例日常调用时效果几乎一致,核心差异集中在3个容易忽略的细节:
- 属性初始化顺序不同:类公共字段的赋值发生在父类构造(
super())调用完成后、constructor内剩余代码执行前;构造函数内的方法赋值,只有代码执行到对应赋值行时才会生效。如果在constructor中赋值行之前访问方法,写法1会拿到undefined,写法2可以正常拿到方法引用。 - 属性可枚举性不同:构造函数内挂载的方法是实例的可枚举属性,能被
for...in遍历到、会出现在Object.keys()的返回结果里;类公共字段声明的方法默认不可枚举,上述遍历操作拿不到这个属性。 - 继承时的执行逻辑不同:子类继承父类时,父类的类字段会在父类构造阶段初始化,子类重写的同名字段要等父类构造执行完才会覆盖,如果父类构造函数中调用了这个方法,会执行父类版本的逻辑,不会触发子类重写的逻辑;构造函数内挂载的方法如果被子类重写,行为符合原型链继承的常规预期。
内容的提问来源于stack exchange,提问作者KawaiKx
相关产品推荐
相关产品推荐

