# 原型链

WX20201118-201742@2x.png

function Cat (name) {
    this.name = name;
}

Cat.prototype.color = 'white';

var cat1 = new Cat('Tom');
console.log(cat1)//{name:'tom'}
var cat2 = new Cat('Mark');
console.log(cat2)//{name:'Mark'}
cat1.color = 'black';
console.log(cat1,cat2)//{name:'tom',color:'black'}{name:'tom'}
console.log(cat1.color)//black
console.log(Cat.color)//函数无法直接获取属性

# 什么是原型链?

访问一个对象的属性时,先在基本属性中查找,如果没有,再沿着 proto 这条链向上找,这条链便是原型链。

原型链解决的问题,多个实例共享属性方法的问题,减少开辟内存空间。

#proto】与 prototype(显式原型)

  • 所有对象都有【proto】隐式原型,而只有函数对象才有 prototype。

  • 函数是一个特殊的对象,当一个函数被创建后,这个函数会附带一个属性 prototype,prototype 代表函数的原型。

  • prototype 对象有两个属性,constructor 和 【proto】。

  • constructor 这个属性指创建原型的函数,它指向函数本身,而【proto】指向原型对象。

# constructor

NothingSpecial 只是一个普通的函数.当使用 new 调用时,就会构造一个对象并赋值给 a,此时 NothingSpecial(){}是构造函数。但 NothingSpecial 本身并不是构造函数。

function Foo() {
  }
  let a = new Foo(); // true
  console.log(a);
  • Foo.prototype.constructor == a.constructor == a[proto]constructor == Foo
function NothingSpecial() { console.log( "Don't mind me!" );
}
var a = new NothingSpecial();
// "Don't mind me!" a; // {}

var F = function(){};
Object.prototype.a = function(){};
Function.prototype.b = function(){};
var f = new F();
// f 能取到a,b吗?原理是什么?

f.__proto__ === F.prototype  ===> F.prototype__proto__ === Object.prototype ===> Object.prototype.__proto__ === null
所以 f可以找到a

F.__proto__ === Function.prototype ===> Function.prototype.__proto === Object.prototype ===> Object.prototype.__proto__ === null 
所以F既能找到b也能找到a

# 函数对象 vs 普通对象

//普通对象
var o1 = {}; 
var o2 =new Object();
var o3 = new f1();

console.log(typeof o1); //object 
console.log(typeof o2); //object 
console.log(typeof o3); //object

//函数对象

- f1 f2实际上都是通过new Function创建的函数对象

function f1(){}; 
var f2 = function(){};
var f3 = new Function('str','console.log(str)');

console.log(typeof f1); //function 
console.log(typeof f2); //function 
console.log(typeof f3); //function   
  • 所有函数对象的proto都指向Function.prototype,它是一个空函数(Empty function)
// 所有的构造器都来自于Function.prototype,甚至包括根构造器Object及Function自身

Number.__proto__ === Function.prototype  // true
Number.constructor == Function //true

String.__proto__ === Function.prototype  // true
String.constructor == Function //true

Object.__proto__ === Function.prototype  // true
Object.constructor == Function // true


RegExp.__proto__ === Function.prototype  // true
RegExp.constructor == Function //true

Error.__proto__ === Function.prototype   // true
Error.constructor == Function //true

Date.__proto__ === Function.prototype    // true
Date.constructor == Function //true

# 继承

# 原型链继承

将父类的实例,作为子类的原型

  • 即把父类的实例,赋在了Animal的prototype上,(如果Animal.prototype上之前存在属性和方法会被覆盖)
  • 该继承方法,会让子类继承父类的方法和属性。
  • 原型继承存在的问题,如果父类的属性是引用类型,被所有实例共享。
function Animal() {
    this.name = 'Animal'
    this.color = [];
    this.info = {
       name: "yhd",
       age: 18,
   };
    this.toEat= function () {
        console.log('Animal to Eat')
    }
}
Animal.prototype.toPlay = function(){
    console.log('Animal to Play')
}

function Dog() {
    this.name = 'dog'
}

Dog.prototype.toGo = function(){
    console.log('to Go')
}

Dog.prototype =new Animal()
Dog.prototype.constructor = Dog;

let dog = new Dog();
let dog2 = new Dog();
//原型继承存在的问题,如果父类的属性是引用类型,被所有实例共享。
dog.color.push('red');
console.log(dog1.color);// ['red']
console.log(dog)//{name: "dog",__proto__: Animal}

//dog.__proto__ === Animal.prototype
console.log(dog.__proto__)
//dog.__proto__  {name: "Animal",toEat:fn,color:['red'],__proto__: Object}

//dog.__proto__ == Dog.prototype
//Dog.prototype == Animal.prototype
console.log(dog.__proto__.__proto__)
//dog.__proto__.__proto__  {toPlay: fn,__proto__: Object}

# 构造继承

  • 使用父类的构造函数,来增强子类的实例,这种扩展方式 优缺点:
  • cat是不能够继承Animal.prototype,但是构造继承可以实现多继承
  • 创建子类的时候可以向父类传参
  • 父类的属性是引用类型,不会被所有实例共享
  • 方法都在构造函数中定义,每次创建实例都会创建一遍方法。
function Cat (){
    Animal.call(this)
}

let cat = new Cat();


console.log(cat)// { name:'Animal',toEat:fn,__proto__:Cat }

console.log(cat.__proto__) // { __proto__ : Object }

//cat.__proto__ === Cat.prototype
console.log(cat.__proto__.__proto__)//指向了Object


console.log(cat instanceof Cat ) //true
console.log(cat instanceof Animal) //false
console.log(cat instanceof Type) // false

构造继承实现多继承

function Type (){
    this.type = '111
}

function Cat (){
    Type.call(this)
    Animal.call(this)
}

# 组合继承

组合继承 = 构造继承 + 原型继承。

原型链继承 实现对原型属性和方法的继承,用借用构造函数继承来实现对实例属性的继承。

  • 优点 弥补了构造继承和原型继承的缺点,也可以实现多继承,也可以传参。
  • 缺点 1构造函数被实例化了两次。2创建的实例和原型上存在两份相同的属性;
function Pig(){
    Animal.call(this,'兔子')
}
Pig.prototype = new Animal();
Pig.prototype.constructor = Pig;  //将 Pig 原型对象的 constructor 指针重新指向 Pig 本身

let pig = new Pig();
console.log(pig)

# 寄生组合继承

在组合继承的基础上进行优化,减少了实例被初始化了两次和sun 和 father上出现相同的属性

function Father() {
    this.name = 'father'
}

Father.prototype.toPlay= function () {
    console.log('to play')
}
function Sun() {
    Father.call(this)
}

Sun.prototype.toGo= function () {
    console.log('to Go')
}

let c = Object.create(Father.prototype)

Sun.prototype = Object.create(Father.prototype)
Sun.prototype.constructor = Sun

function Duck(){
    Animal.call(this);
}

(function(){
    //创建一个新的实例
    let S = function(){};
    S.prototype = Animal.prototype;
    Dog.prototype = new S();
})();

let duck  = new Duck();

# Class 继承

  • ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上(Father.apply(this))
  • ES6的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this; 如果子类没有定义constructor方法,这个方法会默认添加,也就是说,不管有没有显式定义,任何一个子类都有constructor方法。
  • 在子类的构造函数中,只有调用super之后,才能使用this关键字,否则会报错
class Son extends Father {
}

//等同于
class Son extends Parent {
       constructor(...args) {
       super(...args);
      }
}