美文网首页
原型和原型链以及继承

原型和原型链以及继承

作者: Lyan_2ab3 | 来源:发表于2020-05-09 10:58 被阅读0次

    创建对象

    1、工厂模式:
    function CreatPerson(name,age,job){
    var o =new Object;
    o.name=name;
    o.age=age;
    o.job=job
    o.sayName=function(){
    }
    return o
    }
    

    通过函数的形式传入参数,返回必要的信息的Person 对象,可以多次调用。
    工厂模式解决了创建多次相似对象的问题,但是没有解决对象识别的问题。

    2、构造函数模式:

    构造函数可用来创建特定类型的对象,

    function Person(name,age,job){
    this.name=name;
    this.age=age;
    this.job=job;
    this.sayName=function(){
    }
    }
    
    var person1 = new Person('lei',22,'Doc');
    var person2 = new Person('lei2',28,'Doc2');
    
    
    • Person 和 CreatPerson 对比

      • 1、没有显式的创建对象;
      • 2、 将属性和方法赋值给了this 对象;
      • 3、 没有return
    • 创建Person 实例 ,new Person()过程

      • 1、创建一个新对象
      • 2、将构造函数的作用域赋给新的对象 (因此this 指向了新对象)
      • 3、执行构造函数中的代码(为这个对象添加属性)
      • 4、返回新的对象;

    上面例子 person1 和person2 保存着2个不同的实例,这两个对象都有一个constructor 属性,该属性指向Person
    person1.constructor==Person // true

    console.log( person1 instanceof Object) // true
    console.log( person1 instanceof Person) // true
    person1和person2 之所以都是Object 实例,是因为所有的对象都继承至 Object,

    构造函数还是有确定的:每个方法都要在每个实例上重新创建一次,因为不同实例上的同名函数是不相等的。

    3、原型模式:

    创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象。 这个对象包含了实例共享的属性和方法。不必在构造函数中定义实例的信息,可以将这些信息添加到原型对象中

    • 1、只要创建新函数,会为函数创建一个一个prototype属性,这个属性指向函数的原型对象;
    • 2、原型对象都会自动获取constructor属性,通过这个构造函数,还可以继续为原型对象添加其他的属性和方法。
    • 3、Person.prototype 指向原型对象,Person.prototype.constructor 又指回Person,原型对象除了包含constructor 还包含其他的属性。
    • 4、person1.sayName()会先查看实例上有属性吗?有则返回,没有就查找person1的原型上找找。
    • 5、当为对象实例添加属性,这个属性会屏蔽原型对象中保存的同名属性,意思就是会阻止我们访问原型中的属性,不过可以通过delete 删除
    • 6、 使用hasOwnProperty() 方法可以检测一个属性是存在于实例当中还是存在于原型当中,(这个方法是从Object继承来的),只在给定的属性存在对象实例中才会返回true
    function Person(){}
    // 字面量重写整个原型对象,相当于是一个新的对象
    Person.prototype={
    name:'lei',
    job:'dasd',
    sayName:function(){
    console.log(this.name)
    }
    }
    var friend= new Person()
    

    constructor 属性不在指向Person了,而这里相当于重写了默认的prototype 对象,而constructor 属性也变成了 新的constructor 属性(指向了Object构造函数)不再指向Person函数
    console.log(friend.contructor == Person) // false
    console.log(friend.contructor ==Object) // true

    function Person(){}
    var friend= new Person()
    // 字面量重写整个原型对象,相当于是一个新的对象
    Person.prototype={
    name:'lei',
    job:'dasd',
    sayName:function(){
    console.log(this.name)
    }
    }
    friend.sayName() // error
    

    因为先创建的实例, friend 指向的原型还不包含该命名的属性,重写原型对象切断了现有原型与之前存在的对象实例之间 的联系。但是引用的仍然是最初的原型。

    4、组合使用构造函数模式和原型模式:

    这种方式应该是最常见的方式;构造函数模式定义实例属性;原型链模式用于定义方法和共享的属性;
    有属于自己的一份实例属性,还共享对方的方法引用。极大的节省了内存。

    function Person(name,age,job){
    this.name=name;
    this.age=age;
    this.job=job;
    this.friends=['dsd','dsddd','llyan']
    }
    
    Person.prototype={
    constructor:Person;
    sayName:function(){
    }
    }
    var person1=new Person('kk',23,'ddd')
    var person2=new Person('kk',23,'ddd')
    person1.friends.push('Vam');
    // 修改了person1.friends 不会影响person2.friends,他们分别引用了不同的数组;
    
    5、动态原型模式:

    把所有的信息都封装在构造函数中,,通过构造函数初始化原型。

    使用动态模式时候,不能使用对象字面量重写原型,不然会切断现有实例和原型之间的关系

    6、寄生构造函数模式:

    这种模式的基本思想:创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后返回新的对象

    function  Person(name,age,job){
    var o =new Object();
    o.name=name;
    o.age=age;
    o.job=job
    o.sayName=function(){
    alert(this.name)
    }
    return o;
    }
    
    var friend = new Person('lei',29,'DDD');
    friend.sayName()  // 'lei'
    
    
    • 这个模式和工厂模式一样,在构造函数不返回值的情况下,默认返回一个对象,在末尾添加一个return语句,可以重写调用构造函数返回的值。
    • 构造函数返回的对象与构造函数或者构造函数的原型没有关系。
    • 不能依赖instanceof 操作确定对象类型。

    js 原型和原型链

    js 中万物皆对象,对象分为二种,普通对象(Object)和函数对象(Function)

    任何对象都具有隐式原型属性(proto),只有函数对象有显式原型属性(prototype)

    1、因为构造函数实例对象,无法共享属性和方法,所以后来引入 prototype。
    所有实例对象需要共享的属性和方法,都放在这个对象里面;那些不需要共享的属性和方法,就放在构造函数里面。
    2、其中函数对象的一个属性就是原型对象 prototype。注:普通对象没有prototype,但有proto属性。
    3、原型对象其实就是普通的对象;

    function f1(){};
    console.log(f1.prototype) // {constructor: ƒ}
    console.log(typeof f1. prototype) //Object
    
    prototype

    每个函数都有一个 prototype 属性

    function Person() {
    
    }
    // 虽然写在注释里,但是你要注意:
    // prototype是函数才会有的属性
    Person.prototype.name = 'Kevin';
    var person1 = new Person();
    var person2 = new Person();
    console.log(person1.name) // Kevin
    console.log(person2.name) // Kevin
    

    函数的 prototype 属性指向了一个对象,这个对象正是调用该构造函数而创建的实例的原型,也就是这个例子中的 person1 和 person2 的原型。
    每一个Javascript 对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性。

    proto

    这是每一个Javascript对象(除了 null )都具有的一个属性,叫proto,这个属性会指向该对象的原型。
    上面 提到函数的prototype 指向实例的原型也就是: Person.prototype===person1.proto

     function Person() { }
    var person = new Person(); 
    console.log(person.__proto__ === Person.prototype); // true
    

    constructor

    一个构造函数可以生成多个实例,但是原型指向构造函数倒是有的,这就要讲到第三个属性:constructor,每个原型都有一个 constructor 属性指向关联的构造函数。

    function Person() { } 
    console.log(Person === Person.prototype.constructor); // true
    
    
    prototype3.png
    function Person() {
    
    }
    
    var person = new Person();
    
    console.log(person.__proto__ == Person.prototype) // true
    console.log(Person.prototype.constructor == Person) // true
    // 顺便学习一个ES5的方法,可以获得对象的原型
    console.log(Object.getPrototypeOf(person) === Person.prototype) // true
    

    实例和原型

    读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止

    function Person() {
    
    }
    
    Person.prototype.name = 'Kevin';
    
    var person = new Person();
    
    person.name = 'Daisy';
    console.log(person.name) // Daisy
    
    delete person.name;
    console.log(person.name) // Kevin
    
    • 给实例对象 person 添加了 name 属性;所以打印person.name时,从对象上读取name 属性,
    • 删除了 person 的 name 属性后,读取 person.name,从 person 对象中找不到 name 属性。
    • 对象中找不到,然后从person 的原型也就是 person._proto,也就是Person.prototype中查找,

    原型的原型

    原型对象是通过 Object 构造函数生成的,结合之前所讲,实例的 proto 指向构造函数的 prototype ,所以我们再更新下关系图

    prototype4.png image.png

    当我们一层一层向上查找,直到null ,
    因为console.log(Object.prototype.proto === null) // true
    所以 Object.prototype.__proto__ 的值为 null 跟 Object.prototype 没有原型,其实表达了一个意思。
    所以查找到 Object.prototype 就可以停止查找了


    js 继承:

    原型链:

    很多面试官 会问到 原型链的理解:

    基本思想:一个引用类型继承另外一个引用类型的属性和方法;

    • 每个实例对象(object)都有一个私有属性 (__proto__) 指向它构造函数的原型对象(prototype)

    • 该原型对象也有一个自己的原型对象(__proto__),层层向上直到一个对象的原型对象为 null

    function Person(){}
    var person = new Person()
    
    person.__proto__ == Person.prototype  
    Person.prototype.__proto__ == Object.prototype
    Object.prototype.__proto__   // null
    
    Person.__proto__ == Function.prototype
    Function.prototype.__proto__ == Object.prototype
    Object.prototype.__proto__   // null
    
    

    假如我们让原型对象等于另外一个类型的实例此时:
    1、原型对象包含一个指针指向另外一个对象原型;( 因为实例有个指向原型对象的内部指针
    2、另外一个原型包含一个指向另外一个构造函数的指针。
    3、层层递进,构成了实例和原型的链条。 所谓原型链的基本概念。

    1、原型链继承

    核心:A 原型的实例是B 原型的属性
    优点:父类新增原型方法或者原型属性,子类都能访问
    缺点:
    1、无法实现多继承,
    2、来自原型对象的引用属性和实例是所有子类共享的
    3、 创建子类实例的时候,无法向父构造函数传参

     function Parent(name) {
        this.name=name;//属性
        this.sleep=function () {//实例方法
          console.log(this.name+'正在睡觉')
        }
      }
     //原型方法 getSuperValue
      Parent.prototype.getSuperValue=function (food) {
        console.log(this.name+'正在吃'+food)
      };
    //原型链继承---核心:将父类的实例作为子类的原型
      function Child() {
    
      }
    //继承Parent,即以Parent的实例为中介,使Child.prototype指向Parent的原型
    //  本质是重写了原型对象,给Child 换了一个新的原型,这个新的原型不仅有作为Parent所拥有的全部的属性和方法,还有一个内部指针指向Parent 的原型。
    Child.prototype=new Parent();
    
    Child.prototype.getSubValue = function() { //添加新方法
      return this.sub;
    }
    
    Child.prototype.getSuperValue = function() { // 重写超类中的方法
      return this.sub;
    }
    
    
    2、构造函数继承:

    解决原型中包含引用类型所带来的问题

    在子类型构造函数的内部调用超类型的构造函数,函数不过时在特定环境下执行代码的对象,因为可以使用apply() 和 call() 方法

    function Super(){
      this.color=['red','blue','green'];
    }
    function Sub(){
    // 继承了Super,实际是在将要创建的 Sub实例的环境下调用 Super构造函数。
    Super.call(this);
    }
    var instancel1 = new Sub();
    instancel1.color.push('block');
    alert(instancel1.color)  // 'red, blue, green, block '
    var instancel2 = new Sub();
    alert(instancel2.color)  // 'red, blue, green '
    

    在子类型构造函数中向超类型构造函数 传递参数。
    优点:
    1、可以向超类传递参数
    2、解决了原型中包含引用类型值被所有实例共享的问题
    缺点:
    方法都在构造函数中定义,函数复用无从谈起,另外超类型原型中定义的方法对于子类型而言都是不可见的。

    function Super(name){
      this.name=name
    }
    function Sub(){
    // 继承了Super, 传递参数
    Super.call(this,'lyan');
    this.age=25
    }
    var instancel1 = new Sub();
    
    alert(instancel1.name)  // 'lyan'
    
    alert(instancel1.age)  // 25
    
    3、组合继承(原型链 + 借用构造函数):

    指的是将原型链和借用构造函数的技术组合在一起
    使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承,既通过在原型上定义方法来实现了函数复用,又保证了每个实例都有自己的属性

    WechatIMG538.png

    缺点:
    无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。
    优点:
    1、可以向超类传递参数
    2、每个实例都有自己的属性
    3、实现了函数复用

    4、原型式继承:

    借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型
    缺点:
    同原型链实现继承一样,包含引用类型值的属性会被所有实例共享。

    function object(o){
     function F(){}
    F.prototype = o
    return new F()
    }
    
    5、寄生式继承:

    寄生式继承是与原型式继承紧密相关的一种思路。寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部已某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象

    WechatIMG540.png
    • 基于 person 返回了一个新对象 -—— person2,新对象不仅具有 person 的所有属性和方法,而且还有自己的 sayHi() 方法。寄生式继承也是一种有用的模式

    缺点:

    1、使用寄生式继承来为对象添加函数,会由于不能做到函数复用而效率低下。
    2、同原型链实现继承一样,包含引用类型值的属性会被所有实例共享。

    6、寄生组合式继承:
    function SuperType(name){
        this.name = name;
        this.colors = ["red", "blue", "green"];
    }
    SuperType.prototype.sayName = function(){
        alert(this.name);
    }
    
    function SubType(name, age){
        SuperType.call(this, name);  //第二次调用SuperType()
        
        this.age = age;
    }
    SubType.prototype = new SuperType();  //第一次调用SuperType()
    SubType.prototype.sayAge = function(){
        alert(this.age);
    }
    
    
    • 在第一次调用SuperType构造函数时,SubType.prototype会得到两个属性: name和colors;
    • 当调用SubType构造函数时,又会调用一次SuperType构造函数,这一次又在新对象上创建了实例属性name和colors。

    组合继承有一个小bug,实现的时候调用了两次超类(父类),性能上不合格啊有木有!怎么解决呢?于是“寄生继承”就出来了。

    “寄生组合继承”用了“寄生继承”修复了“组合继承”的小bug

    • 寄生组合式继承就是为了解决这一问题
    function inheritPrototype(subType, superType){
        var clone = Object.create(superType.prototype);    //创建对象 一个副本
        clone.constructor = subType;                    //增强对象
        subType.prototype = clone;                        //指定对象
    }
    function SuperType(name){
        this.name = name;
        this.colors = ["red", "blue", "green"];
    }
    SuperType.prototype.sayName = function(){
        alert(this.name);
    }
    
    function SubType(name, age){
        SuperType.call(this, name);  
        
        this.age = age;
    }
    inheritPrototype(SubType, SuperType)
    SubType.prototype.sayAge = function(){
        alert(this.age);
    }
    
    var instance = new SubType("Bob", 18);
    instance.sayName();  // Bob
    instance.sayAge();  // 18
    

    不必为了指定子类型的原型而调用超类型的构造函数,我们需要的无非就是超类型的一个副本而已。
    本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。

    相关文章

      网友评论

          本文标题:原型和原型链以及继承

          本文链接:https://www.haomeiwen.com/subject/vprcnhtx.html