JS象的创建与JS继承

96 阅读8分钟

JS继承

对象的创建和JS继承 知识点是类似的 放在一起好记

前置知识:无论理解继承还是js对象的创建都需要先理解原型和原型链的知识

继承有:1.原型链继承 2.构造函数继承 3.组合继承 4.原型式继承 5.寄生式继承 6.组合寄生继承 7.extends关键字

1.原型链实现继承

原型链实现继承的关键一句是Cat.prototype = new Animal();

// 父类构造函数
function Animal() {
    this.property = "略略略";
}
// 获取父类的属性
Animal.prototype.getAnimalValue = function () {
    return this.property;
};
// 子类构造函数
function Cat() {
    this.voice = "喵喵喵";
}
// 继承了Animal
Cat.prototype = new Animal();

// 获取子类的属性
Cat.prototype.getCatValue = function () {
    return this.voice;
};

// 实例化对象
var mimi = new Cat();
alert(mimi.getAnimalValue()); //"略略略"

Cat.prototype = new Animal()转成图片理解如下图

image.png

原型链继承的缺点:

  • 原型上的属性,特别是包含引用类型值的需要注意,因为原型的属性方法会被所有实例共享
  • 创造子类型的实例时,不能向超类型的构造函数中传递参数

2.借用构造函数实现继承

借用call或则apply来调用父类构造函数,给子类this上挂属性

function Animal(voice){
    this.voice = voice || "略略略";
}
Animal.prototype.say = function(){
  console.log(this.voice);
}
function Cat(){
    Animal.call(this, "喵喵喵")
}
const mini = new Cat();
alert(mini.voice) // "喵喵喵"

这种方式解决了原型链方式不能给超类型的构造函数中传递参数,以及原型对象上的属性共享问题,但是缺点也很明显,挂载超类原型上的方法,子类是拿不到的

3.组合继承(原型链+构造函数)

原型链和构造函数果然是好兄弟

function Animal(voice){
    this.voice = voice || "略略略";
}
Animal.prototype.say = function(){
  console.log(this.voice);
}
function Cat(){
    Animal.call(this, "喵喵喵")
}
// 继承Animal
Cat.prototype = new Animal();

const mini = new Cat();
console.log(mini.voice) // "喵喵喵"
mini.say() // "喵喵喵"

Animal.callCat.prototype = new Animal()相结合,既继承了超类型的属性也继承了超类型原型上的方法

缺点是要调用两次构造函数

4. 原型式继承

原型式继承核心如下

function object(o){
    function F(){} 
    F.prototype = o;
    return new F(); 
}

ES5新增的Object.create只传一个参数时可以平替object方法

const cat = {
  voice: "喵喵喵",
  toys: ["皮球","毛球"]
}
const mimi = Object.create(cat) // 或则const mimi = object(cat)
mimi.toys.push("逗猫棒")
const maomao = Object.create(cat)
maomao.toys.push("老鼠")

console.log(mimi.toys); // ['皮球', '毛球', '逗猫棒', '老鼠'] 
console.log(cat.toys) // ['皮球', '毛球', '逗猫棒', '老鼠'] 

object方法中的F.prototype = o与原型链继承中的Cat.prototype = new Animal()本质相同,new出来的对象也是个对象,实例化之后示例就会继承其原型对象身上的方法属性。

缺点和原型链继承相同

Object.create还可以帮助实现类式继承

5. 寄生式继承

寄生式继承在原型式继承上封装了一个方法(工厂函数)返回一个对象

function createAnother(original){
    var clone = object(original);//创建一个新对象 
    // 在这里可以对对象做增强,比如添加方法
    return clone; 
}
const mimi = createAnother(cat)

寄生式继承也有原型式继承的缺点

6. 寄生组合式继承

  function Animal(params) {
    this.toys = ["毛球","皮球"]
  }
  Animal.prototype.say = function () {
    console.log(this.voice);
  }
  function object(obj) {
    const F = function () {}
    F.prototype = obj
    return new F()
  }
  // 本质也可以理解为一个工厂函数 做了该做的事
  function inherit(father,child) {
    const prototype = object(father)
    prototype.constructor = child // 改变原型对象的constructor的指向
    child.prototype = prototype // 修复实例
  }

  function Cat() {
    Animal.call(this) // 继承属性
    this.toys.push("逗猫棒")
    this.voice = "喵喵"
  }

  inherit(Animal.prototype,Cat) // 用来继承方法

  const maomao = new Cat()
  const dog = new Animal() // 打印toys看看

inherit主要做了什么呢? 看图解:

image.png

object+inherit的方式不调用超类型构造函数,解决了第三种继承方式调用两次构造函数造成的浪费,核心是借用F一个空的构造函数来作为桥梁,搭建子类到超类的原型链路

7.es6 extends关键字 super关键字 类继承

其中TS中extends关键字也可以用来实现interface的继承

extends关键字用法很简单,但是要配合super关键字使用:

1. super作为父类构造函数子类constructor中调用

 class Animal{
  constructor (){
    this.name = "animal" // 给Animal实例对象上加属性
  }
  say(){  // 相当于给Animal.prototype加方法
    console.log(this.name);
  }
 }
 class Cat extends Animal {
  constructor(){ // constructor可以不写,默认会有并且会帮调用super
    super() // 这里super代表的是父类构造函数
    this.name = "cat"
  }
 }
 const cat = new Cat()
 cat.say() // cat

这里super代表的是父类构造函数有且只能在子类构造器中调用super方法,不调用js会报错:

Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor

与上面组合继承中的调用父类构造函数不同,类继承中子类中调用super虽然执行的是父类的constructor,但是this指向子类,返回的是子类的实例,有点类似在11行执行了Animal.prototype.constructor.call(this)

2. 在普通方法或则子类constructor中 super作为对象调用

 class Cat extends Animal {
  constructor(){
    super()
    this.name = "cat"
  }
  watch(){
    super.say();// cat
    console.log(super.name);// undefined
  }
 }
 const cat = new Cat()
 cat.watch()

为什么拿不到super.name呢? 是因为super作为对象调用时相当于Animal.prototype,属性name挂在Animal实例对象上,方法say挂载Animal原型对象上。

watch方法中通过super调用的say方法中的this指向子类实例。类似于Animal.prototype.say.call(this)

注意:如果通过super给属性赋值,此时super就等同于this

  watch(){
    super.name = "mimi" // 等于this.name = "mimi"
    super.say() // mimi
    console.log(super.name); // undefined
  }

3. 在static关键字方法(静态方法)中super作为对象调用

在静态方法中super代表父类Animalthis指向子类Cat,在普通方法或者constructor中super代表Animal.prototype,this指向子类实例

 class Animal{
  constructor (){
    this.name = "animal"
  }
  say(){
    console.log(this.name);
  }
  static say(){ // Animal的私有方法
    console.log(this.name);
  }
 }
 class Cat extends Animal {
  constructor(){
    super()
    this.name = "cat"
  }
  watch(){
    super.say() // mimi
    console.log(super.name); // undefined
  }
  static watch(){ // Cat的私有方法
    // super.name="mimi"  //Animal.name为只读属性
    super.say() // Cat 父类子类默认有name属性
    console.log(super.name); // Animal
  }
 }
 Cat.watch()
console.log(Cat.name,Animal.name); // Cat Animal

创建对象的6种方式

翻了下之前做的学习笔记,作为记录,也为了回忆知识点。

创建对象和继承的有些原理很相似,放在一起记

这6种方式分别是:1. new Object() 2.字面量 3.工厂模式 4. 构造函数模式 5.原型模式 6. 混合模式

new操作符+Object

new Object() 直接返回一个对象

 var person = new Object();
     person.name = "lisi";
     person.age = 21;
     person.family = ["lida","lier","wangwu"];
     person.say = function(){
         alert(this.name);
  }

字面量方式直接创建对象

var preson ={}

var person = {
         name: "lisi",
         age: 21,
         family: ["lida","lier","wangwu"],
         say: function(){
             alert(this.name);
         }
    };

工厂模式

记忆点: 写一个方法将重复的代码封装起来

function createPerson(name,age,family) {
    var o = new Object();
    o.name = name;
    o.age = age;
    o.family = family;
    o.say = function(){
        alert(this.name);
    }
    return o;
}
//instanceof无法判断它是谁的实例,只能判断他是对象,构造函数都可以判断出
var person1 =  createPerson("lisi",21,["lida","lier","wangwu"]);   
var person2 =  createPerson("wangwu",18,["lida","lier","lisi"]);
console.log(person1 instanceof Object);//true

上述两种模式在创建多个类似对象时,会产生大量重复代码,工厂模式则能够解决这种问题 工厂模式解决了实例化多个对象的问题,但是没有解决对象识别的问题。实例化的对象的原型都是Object.prototype

构造函数模式

记忆点:在构造函数中挂载属性和方法

function Person(name,age,family) {
    this.name = name;
    this.age = age;
    this.family = family;
    this.say = function(){
        alert(this.name);
    }
}
//谁调用了这个函数,this就指向这个对象
var person1 = new Person("lisi",21,["lida","lier","wangwu"]);
var person2 = new Person("lisi",21,["lida","lier","lisi"]);
console.log(person1 instanceof Object); //true
console.log(person1 instanceof Person); //true
console.log(person2 instanceof Object); //true
console.log(person2 instanceof Person); //true
console.log(person1.constructor);      //constructor 属性返回对创建

可以看出通过构造函数创建的对象知道自己从哪里来(instanceof

当然构造函数也有缺点: 每个实例都包含不同的fucntion实例,虽然这些方法在做同一件事,但是实例化后产生了不同的对象。因此产生了原型模式。

原型模式

原理:创建一个构造函数Person,在其原型对象Person.prototype上挂载属性和方法,实例化出来的对象上就会继承原型身上的属性和方法。

记忆点:在原型对象上挂载属性和方法

function Person() {
 }
  
  Person.prototype.name = "lisi";
  Person.prototype.age = 21;
  Person.prototype.family = ["lida","lier","wangwu"];
  Person.prototype.say = function(){
      alert(this.name);
  };
 console.log(Person.prototype);   //Object{name: 'lisi', age: 21, family: Array[3]}
 var person1 = new Person();        //创建一个实例person1
 console.log(person1.name);        //lisi
 
 var person2 = new Person();        //创建实例person2
 person2.name = "wangwu";         
 person2.family = ["lida","lier","lisi"];
 console.log(person2);            //Person {name: "wangwu", family: Array[3]}
 // console.log(person2.prototype.name);         //报错
 console.log(person2.age);              //21

原型模式的好处就是所有实例对象共享他的属性和方法,此外还可以设置自己的属性(也叫私有属性),还可以覆盖原型对象上的同名方法。 缺点就是设置很多属性的时候很麻烦。

混合模式(构造函数模式+原型模式)

记忆点:在构造函数里面写属性,在原型上挂方法

混合模式是最完善的一种方式,既共享相同方法的引用,又保证了每个实例有自己的私有属性,最大程度节省了内存。

 function Person(name,age,family){
      this.name = name;
      this.age = age;
      this.family = family;
  }
  
  Person.prototype = {
      constructor: Person,  //每个函数都有prototype属性,指向该函数原型对象,原型对象都有constructor属性,这是一个指向prototype属性所在函数的指针
      say: function(){
         alert(this.name);
     }
 }
 
 var person1 = new Person("lisi",21,["lida","lier","wangwu"]);
 console.log(person1);
 var person2 = new Person("wangwu",21,["lida","lier","lisi"]);
 console.log(person2);