构造函数继承常见问题答疑:规范写法与避坑指南
构造函数继承常见问题解答
1. 判断对象是否为Parent实例的几种方式是否等价?
首先指出代码中第四个判断是笔误,Object.getPrototypeOf(obj) === Object.getPrototypeOf(obj) 恒为true,和判断是否为Parent实例无关,推测你想写的是 Object.getPrototypeOf(obj) === Parent.prototype。下面分析前三种(修正后的第四种)的差异:
obj.constructor === Parent:默认情况下结果为true,但不可靠。因为constructor属性挂载在Parent.prototype上,一旦修改Parent.prototype(比如重新赋值对象),或手动修改obj原型链上的constructor,这个判断就会失效。obj.__proto__ === Parent.prototype:结果和修正后的第四种一致,但__proto__是非标准属性(仅部分浏览器实现),不推荐在生产代码中直接使用。它直接对比实例的原型对象和父类的原型对象,默认情况下相等,但若原型被修改则结果改变。obj instanceof Parent:这是最可靠的判断方式之一。它检查obj的整条原型链上是否存在Parent.prototype,只要原型链上有匹配就返回true,不受constructor属性修改的影响(除非父类的prototype被完全替换)。Object.getPrototypeOf(obj) === Parent.prototype:这是__proto__对比的标准替代写法,Object.getPrototypeOf是ES5规范方法,兼容性更好,推荐使用。
总结:默认场景下前三种(含修正后的第四种)结果一致,但可靠性和规范度不同。优先使用instanceof或Object.getPrototypeOf的对比方式,避免依赖constructor或__proto__。
2. 两段示例代码存在差异吗?
两段代码的输出结果虽然一致,但内部实现机制、规范性有明显差异:
第一段代码(this.__proto__ = new Shape())
function Shape() { this.checkedPoints = [] } function Rectangle(a){ this.__proto__ = new Shape() this.a = a; } let m = new Rectangle("first") let n = new Rectangle("second") m.checkedPoints.push(1) n.checkedPoints.push(2); console.log(m.checkedPoints) // Array [ 1 ] console.log(n.checkedPoints) // Array [ 2 ]
- 机制:每个
Rectangle实例的__proto__指向一个独立的Shape实例,checkedPoints是挂载在实例的原型(Shape实例)上的属性。因为每个Rectangle实例的原型都是新的Shape实例,所以各自的checkedPoints互不干扰。 - 问题:使用非标准的
__proto__属性,兼容性和规范性差;给每个实例单独设置原型会带来性能损耗;若Shape有原型方法,虽然能继承,但这种写法不是构造函数继承的标准实现。
第二段代码(Shape.apply(this))
function Shape() { this.checkedPoints = []; } function Rectangle(a) { Shape.apply(this); this.a = a; } let m = new Rectangle("first"); let n = new Rectangle("second"); m.checkedPoints.push(1); n.checkedPoints.push(2); console.log(m.checkedPoints); // Array [ 1 ] console.log(n.checkedPoints); // Array [ 2 ]
- 机制:这是**盗用构造函数(构造函数继承)**的标准写法。通过
Shape.apply(this),将Shape构造函数内的this指向当前Rectangle实例,直接在Rectangle实例上创建checkedPoints属性,每个实例的checkedPoints都是独立的。 - 优势:使用标准API,兼容性好;属性直接挂载在子类实例上,避免原型共享引用类型的问题;如果需要给父类构造函数传参,也能通过
apply/call传递。
差异总结:
- 属性位置:第一段的
checkedPoints在实例的原型上,第二段在实例自身。 - 继承方式:第一段是原型链继承的变体,第二段是标准构造函数继承。
- 规范性:第一段不推荐,第二段是构造函数继承的推荐写法。
- 扩展性:第一段能继承父类原型上的方法,第二段不能(若要继承原型方法,需结合原型链继承)。
3. 为何m是Shape的实例却无法访问Shape的属性?
function Shape() { this.checkedPoints = []; } function Rectangle(a) { this.a = a; Object.setPrototypeOf(this.__proto__, Shape.prototype) } let m = new Rectangle("first"); console.log(m instanceof Shape, m instanceof Rectangle) // true true console.log(m.a) //first m.checkedPoints.push(1) // m.checkedPoints is undefined
原因分两点:
instanceof的判断逻辑:instanceof检查对象的原型链是否包含目标构造函数的prototype。代码中Object.setPrototypeOf(this.__proto__, Shape.prototype)相当于把Rectangle.prototype的原型设为Shape.prototype,所以m的原型链是:m -> Rectangle.prototype -> Shape.prototype -> Object.prototype,因此m instanceof Shape返回true。checkedPoints的挂载逻辑:Shape构造函数中的this.checkedPoints = []是给Shape的实例添加属性,而非挂载到Shape.prototype上。而你的Rectangle构造函数中没有调用Shape构造函数(比如Shape.call(this)),所以m实例上不会创建checkedPoints属性;同时Shape.prototype上也没有这个属性,自然访问不到。
修正写法:
如果要同时继承Shape的实例属性和原型方法,需要结合构造函数继承和原型链继承:
function Shape() { this.checkedPoints = []; } function Rectangle(a) { Shape.call(this); // 调用父类构造函数,给子类实例添加属性 this.a = a; } // 设置原型链,继承父类原型方法 Object.setPrototypeOf(Rectangle.prototype, Shape.prototype); let m = new Rectangle("first"); m.checkedPoints.push(1); console.log(m.checkedPoints); // [1] console.log(m instanceof Shape); // true
内容的提问来源于stack exchange,提问作者Boris Borais
相关产品推荐
相关产品推荐

