原型 继承

目录:

  • prototype原理

  • 构造函数 原型(显式原型。隐式原型) --> 原型链

  • es6 class

  • 9种继承方法(重点是组合继承和寄生组合继承)

prototype原理——阮一峰

大大这篇讲的很好诶,我为什么之前没见过。。。以下:

new命令引入了Javascript,用来从原型对象生成一个实例对象。但是,Javascript没有"类",怎么来表示原型对象呢?

这时,他想到C++和Java使用new命令时,都会调用"类"的构造函数(constructor)。他就做了一个简化的设计,在Javascript语言中,new命令后面跟的不是类,而是构造函数

举例来说,现在有一个叫做DOG的构造函数,表示狗对象的原型。

  function DOG(name){

    this.name = name;

  }

对这个构造函数使用new,就会生成一个狗对象的实例。

  var dogA = new DOG('大毛');

  alert(dogA.name); // 大毛

注意构造函数中的this关键字,它就代表了新创建的实例对象。

三、new运算符的缺点

用构造函数生成实例对象,有一个缺点,那就是无法共享属性和方法。

比如,在DOG对象的构造函数中,设置一个实例对象的共有属性species。

  function DOG(name){

    this.name = name;

    this.species = '犬科';

  }

然后,生成两个实例对象:

  var dogA = new DOG('大毛');

  var dogB = new DOG('二毛');

这两个对象的species属性是独立的,修改其中一个,不会影响到另一个。

  dogA.species = '猫科';

  alert(dogB.species); // 显示"犬科",不受dogA的影响

每一个实例对象,都有自己的属性和方法的副本。这不仅无法做到数据共享,也是极大的资源浪费。

四、prototype属性的引入

为了解决构造函数的对象实例之间无法共享属性的缺点,Brendan Eich决定为构造函数设置一个prototype属性。

  js中每个数据类型都是对象(除了null和undefined),而每个对象都继承自另外一个对象,后者称为“原型”(prototype)对象,只有null除外,它没有自己的原型对象。

  原型对象上的所有属性和方法,都会被对象实例所共享。

  通过构造函数生成对象实例时,会将对象实例的原型指向构造函数的prototype属性。每一个构造函数都有一个prototype属性,这个属性就是对象实例的原型对象。

这个属性包含一个对象(以下简称"prototype对象"),所有实例对象需要共享的属性和方法,都放在这个对象里面;那些不需要共享的属性和方法,就放在构造函数里面。

实例对象一旦创建,将自动引用prototype对象的属性和方法。也就是说,实例对象的属性和方法,分成两种,一种是本地的,另一种是引用的

五、Prototype模式的验证方法

为了配合prototype属性,Javascript定义了一些辅助方法,帮助我们使用它。,

isPrototypeOf()

这个方法用来判断,某个proptotype对象和某个实例之间的关系。

  alert(Cat.prototype.isPrototypeOf(cat1)); //true

hasOwnProperty()

每个实例对象都有一个hasOwnProperty()方法,用来判断某一个属性到底是本地属性,还是继承自prototype对象的属性。

  alert(cat1.hasOwnProperty("name")); // true

  alert(cat1.hasOwnProperty("type")); // false

in运算符

in运算符可以用来判断,某个实例是否含有某个属性,不管是不是本地属性。

  alert("name" in cat1); // true

  alert("type" in cat1); // true

instanceof
instanceof 操作符的内部实现机制和隐式原型、显式原型有直接的关系。instanceof的左值一般是一个对象,右值一般是一个构造函数,用来判断左值是否是右值的实例。它的内部实现原理是这样的: 

//设 L instanceof R 
 L.__proto__.__proto__ ..... === R.prototype //最终返回true or false

构造函数

function Person(name,age){
     this.name = name;    
     this.age = age;   
     this.sayHello = function(){   
         console.log(this.name +"say hello");
    }
}

var boy = new Person("bella",23);    
boy.sayHello(); // bella say hello

构造函数的特点:

   a:构造函数的首字母必须大写,用来区分于普通函数

   b:内部使用的this对象,来指向即将要生成的实例对象

   c:使用New来生成实例对象

每当New一个实例对象的时候,都会去创建一个sayHello方法,这就浪费了内存资源,因为sayHello方法使一样的行为的,完全可以被两个实例对象共享。代码如下:

function Person(name,age){
    this.name = name;
    this.age = age;
    this.sayHello = function(){
        console.log(this.name + "say hello");
    }
}
var girl = new Person("bella",23);
var boy = new Person("alex",23);
console.log(girl.name);  //bella
console.log(boy.name);   //alex
console.log(girl.sayHello === boy.sayHello);  //false  但是我们希望这两个对象调用一个公共方法

构造函数的缺点:       

        所有的实例对象都可以继承构造器函数中的属性和方法。但是,同一个构造函数的对象实例之间无法共享属性和方法。

        解决思路:

      a:所有实例都会通过原型链引用到prototype

      b:prototype相当于特定类型所有实例都可以访问到的一个公共容器

      c:那么我们就将重复的东西放到公共容易就好了        

原型

为了解决构造函数的这个缺点,js提供了prototype属性来解决该问题。

显式原型:prototype

每一个函数在创建之后都会拥有一个名为 prototype 的属性,这个属性指向函数的原型对象,它默认指向一个Object空对象

注意:通过Function.prototype.bind方法构造出来的函数没有prototype属性。

隐式原型:_proto_

JavaScript中任意对象都有一个内置属性[[prototype]],在ES5之前没有标准的方法访问这个内置属性,但是大多数浏览器都支持通过_proto_来访问。ES5中有了对于这个内置属性标准的Get方法Object.getPrototypeOf(). 

在js中万物皆对象,方法(Function)是对象,方法的原型(Function.prototype)是对象,对象具有属性(_proto_)称为隐式原型,对象的隐式原型指向构造该对象的构造函数(constructor)的显式原型。
注意:Object.prototype.这个对象的是个例外,它的__proto__值为null。

  • prototype显式原型的作用:

用来实现基于原型的继承与属性的共享。

  • _proto_隐式原型的作用:

构成原型链,同样用于实现基于原型的继承。当我们访问obj这个对象中的x属性时,如果在obj中找不到,那么就会沿着_proto_依次查找。

  • constructor属性的作用:

       a:分辨原型对象到底是哪个构造函数

function Person(name,age){
    this.name = name;
    this.age = age;
}
Person.prototype.sayHello = function(){
    console.log(this.name + "say hello");
}

function Person2(name,age){
    this.name1 = name;
    this.age1 = age;
}
Person2.prototype.sayHello1 = function(){
    console.log(this.name + "say hello");
}

const girl = new Person2("p1", 1);
const boy = new Person2("p2", 12);
console.log(boy.constructor);  // [Function: Person2]
console.log(boy.constructor === Person2.prototype.constructor);  //true
console.log(girl.constructor === Person.prototype.constructor);  //false 
//webstrom在上方===符号上给出warning警示 :Binary operation argument type Person2 is not compatible with type Person 

        b:从实例新建另一个实例

const person1 = new Person();
let  temp = person1.constructor;
const person2 = new person1.constructor;
console.log(person2 instanceof Person); //true

(由于constructor属性是一种原型对象和构造函数的关系,所以在修改原型对象的时候,一定 要注意construtor的指向问题,避免instanceof失真,这点会在继承中用到)

_proto_的指向到底如何判断呢?根据ECMA定义 'to the value of its constructor’s "prototype" ' ----指向创建这个对象的函数的显式原型。所以关键的点在于找到创建这个对象的构造函数,接下来就来看一下JS中对象被创建的方式,一眼看过去似乎有三种方式:(1)对象字面量的方式 (2)new 的方式 (3)ES5中的Object.create() 但是我认为本质上只有一种方式,也就是通过new来创建。为什么这么说呢,首先字面量的方式是一种为了开发人员更方便创建对象的一个语法糖,本质就是 var o = new Object(); 

其中通过对象字面量,new的方式创建的对象他的隐式原型指向Object.prototype。

构造函数function person本质上是由Function构造函数创建的,它是Function的一个实例。原型对象本质上是由Object构造函数创建的。内置函数Array Number等也是有Function构造函数创建的。

再来看看Object.create(),这是ES5中新增的方法,在这之前这被称为原型式继承,

function object(o){
    function F(){}
    F.prototype = o;
    return new F()
}                           ----- 《JavaScript高级程序设计》P169

所以从实现代码 return new F() 中我们可以看到,这依然是通过new来创建的。不同之处在于由 Object.create() 创建出来的对象没有构造函数,看到这里你是不是要问,没有构造函数我怎么知道它的__proto__指向哪里呢,其实这里说它没有构造函数是指在 Object.create() 函数外部我们不能访问到它的构造函数,然而在函数内部实现中是有的,它短暂地存在了那么一会儿。假设我们现在就在函数内部,可以看到对象的构造函数是F, 现在

//以下是用于验证的伪代码
var f = new F(); 
//于是有
f.__proto__ === F.prototype //true
//又因为
F.prototype === o;//true
//所以
f.__proto__ === o;

因此由Object.create(o)创建出来的对象它的隐式原型指向o

了解了构造器,我们来看下原型链

 JS中万物都是对象,但是对象也分为:普通对象和函数对象,也就是Object 和 Function. 那么怎么区分普通对象和函数对象呢? 凡是通过New Function()创建的对象都是函数对象,其他的都是普通对象.

  js创建对象的时候都有一个__propo__内置属性,用于指向创建它的函数对象的原型对象prototype。

      只有函数才有prototype属性

girl.__proto__ === Person.prototype    //true
Person.prototype.__proto__ === Object.prototype    //true
Object.prototype.__proto__      //null通过__proto__串起来直到Object.propotype.__proto__为null的链叫做原型链

ååé¾å¾çæè¿°

prototype和__proto__的区别:

function Person(name,age){
    this.name = name;
    this.age = age;
}
Person.propotype.sayHello = function(){
    console.log(this.name + "say hello");
}
var girl = new Person("bella",23);
var boy = new Person("alex",23);
console.log(girl.name);   //bella
console.log(boy.name);    //alex
console.log(girl.sayHello === boy.sayHello);  //true
console.log(girl.construcotr);  // Person()
console.log(girl.construcotr == Person.prototype.construcotr); // true

由上图可以看出,prototype是构造函数的属性,而consructor则是构造函数的prototype属性所指向的那个对象,也就是说constructor是原型对象的属性。constructor属性定义在原型对象上面,意味着也可以被实例对象继承,因此实例对象的constructor也是构造函数。

六:理解为什么原型链继承(下面有方法)中,Cat的prototype从Object{}变为Animal{} 对象?

Cat.prototype = new Animal();new的实现过程可知,Cat.prototype._proto_ = Animal.prototype.
所以Cat.prototype当然是Animal的对象啦

因为对象的隐式原型指向构造该对象的构造函数(constructor)的显式原型。cat对象是由Animal构造出来的,所以指向Animal.prototype

函数的prototype属性:在定义函数时自动添加的,默认值是一个空object对象。对象的proto属性:创建对象时自动添加的,默认值为构造函数的prototype属性值

我的理解是_proto_(Cat的prototype的_proto_)指向谁(Animal.prototype),那他的原型对象就是什么类型。

prototype即是构造函数的属性,又是对象

new Animal的过程最后一步返回一个父类的实例对象,这个obj就是Animal{}对象

拓展:

a:为什么Object.__proto__指向Function.prototype?   Object是函数对象,是通过new Function()创建的,所以...

b:Function.__proto__ === Function.prototype //true      Function也是对象函数,通过new Function()创建,所以...

构造器Function 和 Object 的关系  https://www.cnblogs.com/libin-1/p/5820550.html

清楚了prototype__proto__的概念与关系之后我们会对“Js中一切皆为对象”这句话有更加深刻的理解。进而我们会想到,既然__proto__是(几乎)所有对象都内置的属性,而且指向父类的原型,那是不是意味着我们可以“逆流而上”一直找到源头呢?我们来看下面的例子:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

// Node

var Obj = function(){}; 

var o = new Obj(); 

o.__proto__ === Obj.prototype;  //=> true 

o.__proto__.constructor === Obj; //=> true

 

Obj.__proto__ === Function.prototype; //=> true 

Obj.__proto__.constructor === Function; //=> true

 

Function.__proto__ === Function.prototype; //=> true 

Object.__proto__ === Object.prototype;     //=> false 

Object.__proto__ === Function.prototype;   //=> true

 

Function.__proto__.constructor === Function;//=> true 

Function.__proto__.__proto__;               //=> {} 

Function.__proto__.__proto__ === o.__proto__.__proto__; //=> true 

o.__proto__.__proto__.__proto__ === null;   //=> true

从上面的例子和图解可以看出,prototype对象也有__proto__属性,向上追溯一直到null

new关键词的作用就是完成上图所示实例与父类原型之间关系的串接,并创建一个新的对象;instanceof关键词的作用也可以从上图中看出,实际上就是判断__proto__(以及__proto__.__proto__...)所指向是否父类的原型:

1

2

3

4

5

6

7

8

9

10

var Obj = function(){}; 

var o = new Obj();

 

instanceof Obj; //=> true 

instanceof Object; //=> true 

instanceof Function; //=> false

 

o.__proto__ === Obj.prototype; //=> true 

o.__proto__.__proto__ === Object.prototype; //=> true 

o.__proto__.__proto__ === Function;  //=> false

本文参考:https://www.cnblogs.com/thonrt/p/5900510.html

es6 class

参考:https://www.cnblogs.com/wuhaoquan/p/8820706.html

https://segmentfault.com/a/1190000015565616

类的数据类型就是函数,类本身指向构造函数。(es5的构造函数Animal对应es6的Animal类的构造方法)

类中的方法之前不能加逗号。类的所有方法都定义在类的prototype属性上。

向类添加多个方法:Object.assign( Animal.prototype ,{ toSleep(){}, toEat(){} } )

类内部定义的所有方法都是不可枚举的。(不同于es5)

Class 语法的 `extends' 后接的不限于指定一个类,更可以是表达式。

class Animal {

  constructor(name) {

    this.speed = 0;

    this.name = name;

  }

  run(speed) {

    this.speed += speed;

    alert(`${this.name} runs with speed ${this.speed}.`);

  }

  stop() {

    this.speed = 0;

    alert(`${this.name} stopped.`);

  }

}

class Rabbit extends Animal {

  hide() {

    alert(`${this.name} hides!`);

  }

stop() { //重写父方法

    super.stop(); //调用父方法

    this.hide();

  }

}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.

rabbit.hide(); // White Rabbit hides!

rabbit.stop(); // White Rabbit stopped. White rabbit hides!

 

constructor 方法

constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。constructor方法默认返回实例对象,也可以重新生产其他对象。

name属性:总是返回紧跟在class关键字后面的类名。class Animal{ }       Animal.name //Animal

变量提升:Class不存在变量提升(不同于es5)。类和模块的内部默认就是严格模式

new Animal(); //报错 ReferencrError

class Animal{}

super():

子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。(在普通方法中,super指向父类的原型对象,代表父类实例 ???;在静态方法中,指向父类。 )

另一个需要注意的地方是,在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有super方法才能返回父类实例。

extends:

ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。

ES6 的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this

Object.getPrototypeOf () 可以用来从子类上获取父类。因此,可以使用这个方法判断,一个类是否继承了另一个类。

类的 prototype 属性 和 __proto__属性

大多数浏览器的 ES5 实现之中,每一个对象都有__proto__属性,指向对应的构造函数的prototype属性。Class 作为构造函数的语法糖,同时有prototype属性和__proto__属性,因此同时存在两条继承链。

1)子类的__proto__属性,表示构造函数的继承,总是指向父类。
2)子类的prototype属性的__proto__属性表示方法的继承,总是指向父类的prototype属性

实例的 __proto__ 属性

子类实例的__proto__属性的__proto__属性,指向父类实例的__proto__属性。也就是说,子类的原型的原型,是父类的原型。通过子类实例的__proto__属性的__proto__属性可以修改父类实例的行为

 

9种继承方法(组合继承、寄生组合继承)

//搜索顺序:搜索实例,搜索原型,搜索父类原型
function Animal(name){
    this.name = name || 'Animal';
    this.sleep = function () {
        console.log(`--${this.name}正在睡觉--`);
    }
    this.allfoods = ['fishs'];
}
Animal.prototype.eat = function (food) {
    console.log(`...${this.name}正在吃${food}...`);
}

console.log("------------------1.原型链继承-----------------");
function Cat() {
    Cat.prototype.eye = "blue";
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat; //---修复构造函数的指向---
Cat.prototype.name = "cat";
let cat = new Cat();
console.log(cat.name);
console.log("eye:"+cat.eye);//??可以在Cat构造函数中为Cat实例增加实例属性。但如果要新增原型属性和方法,则必须放在new Animal()这样的语句之后执行。我试了可以啊,eye属性没有被覆盖?
cat.eat('fish');
console.log(cat instanceof Cat);//true(instanceof实质检测的是原型)
console.log(cat instanceof Animal);//true
let otherCat = new Cat();//原型被实例共享
cat.allfoods.push('abcd');
console.log(cat.allfoods); //[ 'fishs', 'abcd' ]
console.log(otherCat.allfoods); //[ 'fishs', 'abcd' ]
// 核心:将父类的实例作为子类的原型(构造函数是一个function,原型是一个对象,实例对象的constructor指向构造函数,proto_指向原型对象)
/*优点:
    * 实例是子类的实例,也是父类的实例
    * 父类新增原型方法/原型属性,子类都能访问到 */
/*缺点:
    * 实例共享原型对象的属性
    * 创建子类实例时,无法向父类构造函数传参
    * 不能实现多继承
    * 如果要新增原型属性和方法,则必须要放在替换原型语句后,因为替换原型会改变原型的指向。(可以在Cat构造函数中,为Cat实例增加实例属性,这个没关系。)*/

console.log("------------------2.构造继承-----------------");
function Cat2(name) {
    Animal.call(this);
    this.name = name || 'cat2';
}
let cat2 = new Cat2();
console.log("name:"+cat2.name);
cat2.sleep();
/*cat2.eat('fish'); 报错 子类没有父类prototype的方法*/
console.log(cat2 instanceof Cat2);
console.log(cat2 instanceof Animal);//实例并不是父类的实例,只是子类的实例
/*优点:
      解决了1中,子类实例共享父类引用(原型)属性的问题 (借用父类的构造函数来增强子类实例,等于把父类的实例属性复制了一份给子类实例化)
      创建子类实例时,可以向父类传递参数
      可以实现多继承(call多个)
  缺点:
      只能继承父类的实例属性和方法,不能继承原型属性/方法
      实例并不是父类的实例,只是子类的实例
      无法实现函数复用,(每次用,每次都要重新调用)
      每个子类都有父类实例函数的副本,影响性能
*/

console.log("------------------3.实例继承-----------------");
function Cat3(name) {
    let animal = new Animal(name);
    animal.name = name || 'cat3';
    return animal;
}
let cat3 = new Cat3('cat33');
console.log("name:"+cat3.name);
cat3.sleep();
cat3.eat('fish3');
console.log(cat3 instanceof Cat3);//false
console.log(cat3 instanceof Animal);//true
/*缺点:
    * 不支持多继承
    * 实例是父类的实例,不是子类的实例*/

console.log("------------------4.拷贝继承(不推荐)-----------------");
function Cat4(name) {
    let animal = new Animal();
    for(let pro in animal){
        Cat4.prototype[pro] = animal[pro];
    }
    Cat4.prototype.name = name || 'cat4';
}
let cat4 = new Cat4();
console.log("name:"+cat4.name);
cat4.sleep();
cat4.eat('fish4');
console.log(cat4 instanceof Cat4); //true
console.log(cat4 instanceof Animal);//false
/*优点:支持多继承
* 缺点:
    * 效率较低,内存占用高(因为要拷贝父类的属性)
    * 无法获取父类不可枚举的方法(不可枚举方法,不能使用for in访问到)
    * 浅拷贝
*/

console.log("------------------5.组合继承(推荐,结合了两种模式的优点,传参和复用)-----------------");
function Cat5(name) {
    Animal.call(this);
    this.name = name || 'cat5';
}
Cat5.prototype = new Animal();
Cat5.prototype.constructor = Cat5; //---组合继承需要修复构造函数的指向---
let cat5 = new Cat5();
console.log("name:"+cat5.name);
cat5.sleep();
cat5.eat('fish5');
console.log(cat5 instanceof Cat5); //true
console.log(cat5 instanceof Animal);//true
//核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
/*优点:
    * 可以继承实例属性/方法,也可以继承原型属性/方法
    * 既是子类的实例,也是父类的实例  (方法2的缺点)
    * 创建子类实例时,可以向父类传递参数  (方法1的缺点)
    * 不存在引用属性共享问题  (方法1的缺点)(借用父类的构造函数来增强子类实例,等于把父类的实例属性复制了一份给子类实例化)
    * 能传参 (方法2的优点)
    * 函数可复用 (方法1的优点)
* 缺点:
    * 子类原型上有一份多余的父类实例属性,因为调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
((两次调用构造函数是因为new的实现有一步parent.call(obj))*/


//以上方法都是构造函数方式的继承,对于非构造函数如何处理?即这两个对象都是普通对象,不是构造函数
//方案1:prototype (大宗师 Douglas Crockford 写的 Object.create 函数如下)
function object(parent) { //这个object()函数,其实只做一件事,就是把子对象的prototype属性,指向父对象,从而使得子对象与父对象连在一起
    function F() {}
    F.prototype = parent;
    return new F();
}
//请问怎样才能让"医生"去继承"中国人",也就是说,我怎样才能生成一个"中国医生"的对象?
let Chinese = { nation:'中国' };
let Doctor = object(Chinese);//使用的时候,第一步先在父对象的基础上,生成子对象
Doctor.career = '医生';//加上子对象本身的属性
console.log(Doctor.nation); // 中国
//方案2:浅/深拷贝  (浅拷贝参考上面方法4(如果父对象的属性等于数组或另一个对象,那么实际上,子对象获得的只是一个内存地址,而不是真正拷贝,因此存在父对象被篡改的可能))
//深拷贝:能够实现真正意义上的数组和对象的拷贝。它的实现并不难,只要递归调用"浅拷贝"就行了。
  function deepCopy(p, c) {
      var c = c || {};
      for (var i in p) {
          if (typeof p[i] === 'object') {
              c[i] = (p[i].constructor === Array) ? [] : {};
              deepCopy(p[i], c[i]);
          } else {
              c[i] = p[i];
          }
      }
      return c;
  }


console.log("------------------6.原型式继承-----------------");
//根据上面继承自对象的方法,我们同样可以通过继承构造函数实例对象,实现构造函数继承
let animal = new Animal();
function extendObj(parent){
    function F(){}
    F.prototype = parent;
    return new F();
}
let cat6 = extendObj(animal);
console.log("name:"+cat6.name);
cat6.sleep();
cat6.eat('fish6');
console.log(cat6 instanceof Animal); //true
//就是 ES5 Object.create 的模拟实现。特点:类似于复制一个对象,用函数包装。
//缺点:包含引用类型的属性值始终都会共享相应的值,无法实现复用。注意:改变一个实例的name值,别的实例name值不会发生改变,不是他们都有独立的name值,而是给这个实例添加了name值,不是修改了原型上的name值。


console.log("------------------7.寄生式继承-----------------");
function createObj (parent) {
    var clone = extendObj(parent);//原型式继承
    clone.sayName = function () {
        console.log('hi');
    };
    return clone;
}
let cat7 = createObj(animal);
console.log("name:"+cat7.name);
cat7.sleep();
cat7.eat('fish7');
console.log(cat7 instanceof Animal); //true
//基于原型式继承,创建一个仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象,最后返回对象。
//缺点:跟借用构造函数模式一样,不能做到函数复用,每次创建对象都会创建一遍方法。


console.log("------------------8.寄生组合继承(推荐,修复了组合继承)-----------------");
//组合继承方式中,为了继承原型属性和方法使用new,副作用是new中又调用了一次构造函数,所以我们不用new,而是间接的让Child.prototype访问到Parent.prototype
function cloneObj(parent){
    function F(){}
    F.prototype = parent;
    return new F();
}
function Cat8(name) {
    Animal.call(this);
    this.name = name || 'cat8';
}
Cat8.prototype = cloneObj(Animal.prototype); //对原型对象赋值,参考原型式:let cat6 = extendObj(animal); extendObj方法传入参数是对象,返回对象,而非函数
Cat8.prototype.constructor = Cat8; //修复指向
let cat8 = new Cat8();
console.log(cat8.name);
cat8.sleep();
cat8.eat('fish8');
console.log(cat8 instanceof Animal); //true
console.log(cat8 instanceof Cat8); //true
//优点:修复了组合继承的问题。这种方式的高效率体现它只调用了一次 Parent 构造函数,并且因此避免了在 Parent.prototype 上面创建不必要的、多余的属性。
//与此同时,原型链还能保持不变;因此,还能够正常使用 instanceof 和 isPrototypeOf。
// 开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。


console.log("------------------9.class-----------------");
class Animal1{
    constructor(name) {
        this.speed = 0;
        this.name = name;
    }
    run(speed) {
        this.speed += speed;
        console.log(`${this.name} runs with speed ${this.speed}.`);
    }
    stop() {
        this.speed = 0;
        console.log(`${this.name} stopped.`);
    }
}
class Rabbit extends Animal1 {
    hide() {
        console.log(`${this.name} hides!`);
    }
    stop() { //重写父方法
        super.stop(); //调用父方法
        this.hide();
    }
}
let rabbit = new Rabbit("White Rabbit");
rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
rabbit.stop(); // White Rabbit stopped. White rabbit hides!


/*输出结果*//*
------------------1.原型链继承-----------------
cat
eye:blue
...cat正在吃fish...
true
true
    [ 'fishs', 'abcd' ]
    [ 'fishs', 'abcd' ]
------------------2.构造继承-----------------
name:cat2
--cat2正在睡觉--
true
false
------------------3.实例继承-----------------
name:cat33
--cat33正在睡觉--
...cat33正在吃fish3...
false
true
------------------4.拷贝继承(不推荐)-----------------
name:cat4
--cat4正在睡觉--
...cat4正在吃fish4...
true
false
------------------5.组合继承-----------------
name:cat5
--cat5正在睡觉--
...cat5正在吃fish5...
true
true
中国
------------------6.原型式继承-----------------
name:Animal
--Animal正在睡觉--
...Animal正在吃fish6...
true
------------------7.寄生式继承-----------------
name:Animal
--Animal正在睡觉--
...Animal正在吃fish7...
true
------------------8.寄生组合继承(修复了组合继承)-----------------
cat8
--cat8正在睡觉--
...cat8正在吃fish8...
true
true
------------------9.class-----------------
White Rabbit runs with speed 5.
White Rabbit hides!
White Rabbit stopped.
White Rabbit hides!
*/

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值