再说说__proto__和prototype以及js的继承
1.__proto__和prototype
JS中的原型链已经是一个老生常谈的问题,毕竟也是JS 这门语言的特色之一了。
首先“万物皆对象“,虽然这句话一直有争议,但是有它的道理的,null类型这些的争论这里就不说了。
对象中有个属性__proto__,被称为隐式原型,这个隐式原型指向构造改对象的构造函数的原型,这也保证了实例能够访问在构造函数原型中定义的属性和方法。这个实例可能是如图中的new Foo()出来的实例。
构造该对象的f1,f2构造函数是fuction Foo(),它的原型是Foo.prototype,那么f1,f2就指向了构造该对象的构造函数的原型,也就是Foo.prototype,那么构造函数Foo()它的proto指向哪里了,还是找它的构造函数,它的构造函数是Function(),那么它的proto就指向了Fuction.prototype,沿着proto这条路最上就是Object.prototype,Object的proto就是null了。
刚刚说的对象有个proto属性,方法也是对象,方法中除了有proto之外(这个proto指向构造该函数/对象的构造原型,也就是上一层了),还有prototype,这个属性就是原型属性,他是一个指针,指向一个对象,这个对象就叫原型对象,这里放着包含所有实例共享的属性和方法,这个原型对象里面有一个属性constructor,这个属性也包含一个指针,指回了原构造函数。
1.构造函数Foo()构造函数的原型属性Foo.prototype指向了原型对象,在原型对象里有共有的方法,所有构造函数声明的实例(这里是f1,f2)都可以共享这个方法。
2.原型对象Foo.prototypeFoo.prototype保存着实例共享的方法,有一个指针constructor指回构造函数。
3.实例f1和f2是Foo这个对象的两个实例,这两个对象也有属性__proto__,指向构造函数的原型对象,这样子就可以像上面1所说的访问原型对象的所有方法。
4.构造函数Foo()除了是方法,也是对象,它也有__proto__属性,指向谁呢?指向它的构造函数的原型对象。函数的构造函数不就是Function嘛,因此这里的__proto__指向了Function.prototype。其实除了Foo(),Function(), Object()也是一样的道理。原型对象也是对象,它的__proto__属性,又指向谁呢?同理,指向它的构造函数的原型对象。这里是Object.prototype.最后,Object.prototype的__proto__属性指向null。
5.对象有属性__proto__,指向该对象的构造函数的原型对象。
方法除了有属性__proto__,还有属性prototype,prototype指向该方法的原型对象。
6.再看图。
2.继承
1.父类的实例作为子类的原型
function Animal(name) {
// 属性
this.name = name || "Animal";
// 实例方法
this.sleep = function() {
console.log(this.name + '正在睡觉!');
}
}
// 原型方法
Animal.prototype.eat = function(food) {
console.log(this.name + '正在吃' + food);
};
function Cat() {
}
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';
var cat = new Cat();
console.log(cat.name);
console.log(cat.eat('fish'));
console.log(cat.sleep());
console.log(cat instanceof Animal); //true
console.log(cat instanceof Cat); //true
cat.proto === Cat.prototype //true
构造cat对象的构造函数是Cat(),它的原型是Cat.prototype,那么隐式原型__proto__就指向构造该对象的构造函数的原型对象,那么cat的__proto__就指向的是Cat.prototype。
Cat.prototype.proto === Animal.prototype //true
原型对象也是对象,是对象就有__proto__,Animal的实例返回给了这个原型对象,那么这个原型对象的隐式原型__proto__就指向的是构造该对象的构造函数的原型,我们看看这个原型对象的构造函数是谁
那么Animal()构造函数的原型对象就是Animal.prototype了。自然就有上面true的结果了。
这种方法的继承的缺点:
- 父类的引用属性和原型对象的引用属性是所有实例共享的
- 创建子类实例时,无法向父类构造函数传参
- 不能多继承
第一个致命缺点,因为我们每个实例各自的属性互不干扰才对:
注意原型上的方法/属性是共享的
2.构造继承
没有用到原型,使用父类的构造函数来增强子类实例,等于直接是复制父类的实例属性给子类。
经典继承也叫做 “借用构造函数” 或 “伪造对象” 。其基本思想是:在子类型构造函数的内部调用超类型构造函数。函数只不过是在特定环境中执行代码的对象,因此可以通过使用apply() 和call() 方法也可以在新创建的对象上执行构造函数。(JS高程)
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true
特点:
解决了1中,子类实例共享父类引用属性的问题
创建子类实例时,可以向父类传递参数(通过call的后面参数)
可以实现多继承(call多个父类对象)
缺点:
实例并不是父类的实例,只是子类的实例
只能继承父类的实例属性和方法,不能继承原型属性/方法
无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
3.组合继承
通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
Cat.prototype = new Animal();
//组合继承也是需要修复构造函数指向的。
Cat.prototype.constructor = Cat;
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true
这种方式看似是原型继承和构造继承的组合,弥补了构造继承只能继承实例属性/方法,不能继承原型属性/方法的缺点,也弥补了原型继承引用属性共享的问题,可向父类传参,函数可复用,即是子类的实例,也是父类的实例。
缺点就是调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)