prototype、__proto__、constructor
隐式原型__proto__
除了元对象Object,每个js对象都有隐式原型,但也可以通过Object.create(null)
强制创建一个没有__proto__的对象,__proto__指向该对象的构造函数的原型对象。
原型链指的就是__proto__串起来的链。
原型prototype
函数对象拥有prototype属性,函数的原型对象包含所有实例共享的方法,包含__proto__和constructor两个属性,constructor指回函数本身,函数原型对象也有__proto__属性,指向构造函数的原型即Function.prototype。
实例的__proto__指向函数原型对象,即f1.__proto__ === Foo.prototype
。
Object.prototype.__proto__指向null。
总结
定义函数时自动创建prototype原型对象,原型对象包含constructor构造函数,构造函数指回函数本身,new出来的实例拥有__proto__指向prototype原型对象。
类式继承(原型链继承)
将父类的实例赋给子类的prototype,缺点是父类共有属性为引用对象时,所有子类实例会公用同一个对象;无法向父类传参;子类不是父类的实例,而子类的原型是父类的实例,缺少constructor属性。
function Animal() {
this.name = '动物'
this.animal = '父类属性'
}
Animal.prototype.getname = function () {
console.log(this.name)
}
Animal.prototype.getsupername = function () {
console.log(this.animal)
}
function Dog() {
this.name = '狗'
}
Dog.prototype = new Animal()
dog = new Dog()
dog.getname()
dog.getsupername()
console.log(dog)
构造函数继承
将子类的变量在父类中执行一遍,即在子类的构造函数中执行一次父类的构造函数,缺点只执行了父类的构造函数,没有继承父类原型,父类的所有方法必须在构造函数中声明才能继承。
function Animal(name) {
this.name = name
this.animal = '父类属性'
}
Animal.prototype.getname = function () {
console.log(this.name)
}
Animal.prototype.getsupername = function () {
console.log(this.animal)
}
function Dog(name) {
Animal.call(this, name) //执行父类构造函数
}
dog = new Dog('狗')
// dog.getname() //无法继承原型方法
// dog.getsupername()
console.log(dog)
组合继承
结合前面两种方法,在子类构造函数中执行父类构造函数,子类原型指向父类的实例,缺点是父类构造函数执行了两遍,子类不是父类的实例。
function Animal(name) {
this.name = name
this.animal = '父类属性'
console.log('执行了父类构造函数')
}
Animal.prototype.getname = function () {
console.log(this.name)
}
Animal.prototype.getsupername = function () {
console.log(this.animal)
}
function Dog(name) {
Animal.call(this, name) //继承属性
}
Dog.prototype = new Animal() //继承方法
dog = new Dog('狗')
dog.getname()
dog.getsupername()
console.log(dog)
原型式继承
和类式继承相似,创建一个新对象并使用父类对象做为新对象的原型对象,和类式继承存在一样的问题,引用值会在实例间共享。和Object.create()
方法作用一致。适合不需要单独创建构造函数,但仍需要在实例间共享属性的场合。
function inherit(o) {
function F() { }
F.prototype = o
return new F()
}
let Animal = {
name: '动物'
}
animal = inherit(Animal)
console.log(animal)
寄生式继承
对原型继承的二次封装,对继承的对象进行了拓展。
function inherit(o) {
function F() { }
F.prototype = o
return new F()
}
let Animal = {
name: '动物'
}
function createDog(obj) {
let o = new inherit(obj)
o.getname = function () {
console.log(this.name)
} // 增加属性和方法
return o
}
dog = new createDog(Animal)
dog.getname()
console.log(dog)
寄生组合式继承
寄生继承和构造函数继承的组合,修复子类的原型,子类的原型指向父类原型的一份实例,construction指向子类本身。
function inherit(o) {
function F() { }
F.prototype = o
return new F()
}
function inheritPtototype(subClass, superClass) {
let p = inherit(superClass.prototype)
p.construction = subClass
subClass.prototype = p
}
function Animal(name) {
this.name = name
this.animal = '父类属性'
console.log('执行了父类构造函数')
}
Animal.prototype.getname = function () {
console.log(this.name)
}
Animal.prototype.getsupername = function () {
console.log(this.animal)
}
function Dog(name) {
Animal.call(this, name) //构造函数式继承
}
inheritPtototype(Dog, Animal) //寄生式继承
dog = new Dog('狗')
dog.getname()
dog.getsupername()
console.log(dog)
但是上面左还有一个小问题,就是遍历子类实例属性时,会把construction也遍历出来,可以通过Object.defineProperty
方法禁止遍历。
for (const key in dog) {
console.log(key);
}
所以再升级一下
function extend(subClass, superClass) {
subClass.prototype = Object.create(superClass.prototype)
Object.defineProperty(subClass.prototype, "constructor", {
value: subClass,
enumerable: false //禁止遍历
});
}
function Animal(name) {
this.name = name
this.animal = '父类属性'
console.log('执行了父类构造函数')
}
Animal.prototype.getname = function () {
console.log(this.name)
}
Animal.prototype.getsupername = function () {
console.log(this.animal)
}
function Dog(name) {
Animal.call(this, name) //构造函数式继承
}
extend(Dog, Animal) //寄生式继承
dog = new Dog('狗')
dog.getname()
dog.getsupername()
mixin多继承
const Mammal = {
skill() {
console.log(this.name + '是哺乳动物')
}
} // mixin类
function Dog(name) {
Animal.call(this, name) //构造函数式继承
}
extend(Dog, Animal) //寄生式继承
Object.assign(Dog.prototype, Mammal) //往子类原型拓展其他类方法
dog = new Dog('狗')
dog.getname()
dog.getsupername()
dog.skill()
参考
帮你彻底搞懂JS中的prototype、__proto__与constructor(图解)
JavaScript中的new操作符的原理解析
js中__proto__和prototype的区别和关系?
原型基础