深入理解原型、原型链、继承
浏览:79
评论:0
在JavaScript中,原型(prototype)、原型链(prototype chain)以及基于原型的继承机制是理解和掌握面向对象编程模式的重要概念。以下是对这三个概念的深入解析:
原型(prototype)
原型是什么
在javascript中,每一个函数都有一个 prototype 属性,该属性指向一个对象。这个对象,就是我们说的原型。这个原型对象有一个constructor属性,指向这个函数本身。
__proto__访问器
在创建对象时,可以选择性的将一些属性和方法通过prototype
属性,挂载在原型对象上。而每一个new
出来的实例,都有一个__proto__([[Prototype]]
)属性,该属性指向构造函数的原型对象,通过这个属性,让实例对象也能够访问原型对象上的方法。因此,当所有的实例都能够通过__proto__([[Prototype]]
)访问到原型对象时,原型对象的方法与属性就变成了共有方法与属性。
// 构造函数 首字母一般需要大写
function Vscing(name) {
this.name = name;
}
// 构造函数有一个默认的原型对象
console.dir(Vscing.prototype); // 旧版:{constructor: ƒ, __proto__: Object} 新版:{constructor: ƒ, [[Prototype]]: Object}
// new 实例出来 vscing
var vscing = new Vscing("vscing");
console.dir(vscing); // 旧版:{name: "vscing", __proto__: Object} 新版:{name: "vscing", [[Prototype]]: Object}
constructor
当你使用构造函数创建对象时,JavaScript会自动为这个对象的原型添加一个constructor属性,指向创建该对象的构造函数。这在原型链继承中尤为重要,因为子类的原型会指向父类的实例,如果没有constructor属性,子类的constructor会默认指向父类的构造函数,这可能会导致一些问题,比如在子类中调用constructor时,实际上调用的是父类的构造函数。
如何使用constructor
当你使用原型链继承时,通常需要手动设置子类的constructor属性,以确保它指向正确的构造函数。例如:
function Parent() {
this.name = "parent";
}
Parent.prototype.getName = function() {
return this.name;
};
function Child() {
this.type = "child";
}
// 设置子类的原型为父类的实例
Child.prototype = new Parent();
// 重置子类的constructor属性,确保它指向Child构造函数
Child.prototype.constructor = Child;
ES6 类中的constructor
在ES6中,类的定义使得构造函数和原型的管理更加直观。当你使用class关键字定义类时,JavaScript会自动设置constructor属性,你不需要手动设置。例如:
class Parent {
constructor() {
this.name = "parent";
}
getName() {
return this.name;
}
}
class Child extends Parent {
constructor() {
super();
this.type = "child";
}
}
在这个例子中,Child类通过extends关键字继承了Parent类,super()调用父类的构造函数。ES6类的继承机制会自动处理constructor属性,确保它指向正确的构造函数。
constructor属性在JavaScript中用于标识对象的创建者,对于原型链的正确构建和维护非常重要。在使用原型链继承时,确保正确设置constructor属性是必要的。
内置构造函数
javascript内置的构造函数:Object、Function、Array、Number、String、Boolean、Date等。
console.log({}.__proto__ === Object.prototype) // true
console.log("vscing".__proto__ === String.prototype) // true
console.log([].__proto__ === Array.prototype) // true
console.log(Vscing.__proto__ === Function.prototype) // true
原型链
JavaScript 中所有的对象都有一个内置属性,称为它的 prototype(原型)。它本身是一个对象,故原型对象也会有它自己的原型,逐渐构成了原型链。原型链终止于拥有 null 作为其原型的对象上。
JavaScript 中只有函数或者构造函数存在prototype 以及 proto__属性,对象都只有__proto。
备注: 指向对象原型的属性并不是 prototype。它的名字不是标准的,但实际上所有浏览器都使用__proto__。访问对象原型的标准方法是 Object.getPrototypeOf()。
在内部,JavaScript引擎可能会将字符串包装为String对象(临时的)以便使用方法,如toString()、toUpperCase()等。当我们访问字符串的一个方法或属性时,如str.length,JavaScript引擎会创建一个临时的String包装对象,正是在这个包装对象上才会有__proto__属性,它指向String.prototype。但是,这个包装是临时的,通常在使用完字符串方法后立即销毁。
继承
在JavaScript中,实现继承有多种方式,每种方式都有其特点和适用场景。以下是几种常见的继承方式:
原型链继承
这是JavaScript中最基本的继承方式,通过将子类的原型对象指向父类的一个实例来实现继承。
function Parent() {
this.name = "parent";
}
Parent.prototype.getName = function() {
return this.name;
};
function Child() {
this.type = "child";
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
const child = new Child();
console.log(child.getName()); // "parent"
构造函数继承
通过在子类的构造函数中调用父类的构造函数,可以实现属性的继承。
function Parent() {
this.name = "parent";
}
function Child() {
Parent.call(this);
this.type = "child";
}
const child = new Child();
console.log(child.name); // "parent"
组合继承(原型链 + 构造函数)
结合原型链继承和构造函数继承的优点,既继承了父类的属性,也继承了父类的方法。
function Parent() {
this.name = "parent";
}
Parent.prototype.getName = function() {
return this.name;
}
function Child() {
Parent.call(this);
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
const child = new Child();
console.log(child.getName()); // "parent"
console.log(child.name); // "parent"
寄生组合继承
避免了在组合继承中父类构造函数被多次调用的问题,通过创建一个中间类型的对象来继承父类的原型,然后将这个中间类型的原型赋给子类的原型。
function inheritPrototype(subType, superType) {
const prototype = Object.create(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
function Parent() {
this.name = "parent";
}
Parent.prototype.getName = function() {
return this.name;
};
function Child() {
Parent.call(this);
}
inheritPrototype(Child, Parent);
const child = new Child();
console.log(child.getName()); // "parent"
console.log(child.name); // "parent"
ES6 类继承
ES6引入了类的概念,使得继承的语法更加简洁和直观。
class Parent {
constructor() {
this.name = "parent";
}
getName() {
return this.name;
}
}
class Child extends Parent {
constructor() {
super();
this.type = "child";
}
}
const child = new Child();
console.log(child.getName()); // "parent"
console.log(child.type); // "child"