再说说__proto__和prototype以及js的继承_Lawliet-程序员宅基地_js中的继承和prototype

技术标签: JavaScript  offer问题  

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的结果了。

这种方法的继承的缺点:
1. 父类的引用属性和原型对象的引用属性是所有实例共享的
2. 创建子类实例时,无法向父类构造函数传参
3. 不能多继承
第一个致命缺点,因为我们每个实例各自的属性互不干扰才对:
这里写图片描述

注意原型上的方法/属性是共享的
这里写图片描述

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

这种方式看似是原型继承和构造继承的组合,弥补了构造继承只能继承实例属性/方法,不能继承原型属性/方法的缺点,也弥补了原型继承引用属性共享的问题,可向父类传参,函数可复用,即是子类的实例,也是父类的实例。

缺点就是调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
这里写图片描述

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/major_zhang/article/details/81118387

智能推荐

随便推点