美文网首页
JS高程学习-第六章(三)---对象继承

JS高程学习-第六章(三)---对象继承

作者: 槑小姐_1419 | 来源:发表于2019-11-28 14:31 被阅读0次

    对象继承

    1. 理解原型链
    1. 所有引用类型(函数、对象、数组),都存在对象特性,即可以自由拓展属性。(除了null以外)

    2. 所有的引用类型(函数、对象、数组),都有一个__proto__(我们这里称他为隐形原型)属性,属性值是一个普通的对象。

    3. 所有函数都有一个prototype属性,属性值也是一个普通的函数

    4. 所有的引用类型(函数、对象、数组),*proto属性值指向它的构造函数的 prototype(显性属性)属性值。

    5. 当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的_proto_*(即他的构造函数的prototype)中寻找。如果没有,则会接着往上找,一直上溯到Object.prototype,也就是说所有对象都继承Object.prototype的属性,Object.prototype的原型是null,null没有任何属性和方法。

    构造函数、原型和实例的关系

    1. 每个构造函数(函数)都有一个原型对象 prototype
    2. 原型对象都包含一个指向构造函数的指针 construtor
    3. 而实例都包含一个指向原型对象的内部指针__proto__
    4. 实例的 __proto__ 指向构造函数的 prototype

    把一个对象的__proto__指向另一个原型对象,而这个原型对象的__proto__又会指向另一个原型对象,这些就会形成原型链

    特殊的Function

    • 函数都是由Function构造出来的,Function作为函数,是由其自身构建出来,故Function的原型指针指向其自身的原型对象。
    console.log(Function.__proto__ === Function.prototype); // true
    console.log(Function.prototype.constructor === Function);
    
    function Person (name,age){
        this.name = name ; 
        this.age= age; 
        this.class = ['en','math'];
        this.sayName = function(){
            alert(this.name);
        }
    }   
    
    Person.prototype.like = 'fruit';
    

    继承 我们需要继承什么?

    继承的最终目的 :用最少的代码 可以实现继承公有属性和方法的同时,拥有自己的属性和方法

    2.原型链继承

    原理 让新实例的原型等于父类的实例

    优点 实例可继承的属性有:实例的构造函数的属性,父类构造函数属性,父类原型的属性。(新实例不会继承父类实例的属性!)

    缺点

    1.新实例无法向父类构造函数传参。

    2.继承单一。只能继承一个父类

    3.所有新实例都会共享父类原型的属性。(原型上的属性是共享的,一个实例修改了原型属性,另一个实例的原型属性也会被修改!)

    function Per (name) {
        
        this.name = name;
    }
    
    Per.prototype = new Person();
    
    var per = new Per("la");
    var per2 = new Per("la");
    
    per2.class.push('ss') // 
    
    per.class //
    
    

    per.__proto__ --> (Per.prototype = new Person)
    Per.prototype.__proto__-->Person.prototype
    Person.prototype.__proto__ -->Object.prototype
    Object.prototype.__proto__ --> null

    原型链继承

    如图 蓝色链为 原型链 红色为构造函数和原型的关系

    3.借用构造函数继承 伪造对象 或经典继承

    原理 用.call()和.apply()将父类构造函数引入子类函数(在子类函数中做了父类函数的自执行(复制))

    优点传递参数

    1、只继承了父类构造函数的属性,没有继承父类原型的属性。

    2、解决了原型链继承缺点1、2、3。

    3、可以继承多个构造函数属性(call多个)。

    4、在子实例中可向父实例传参。

    缺点

    1、只能继承父类构造函数的属性。

    2、无法实现构造函数的复用。(每次用每次都要重新调用)

    3、每个新实例都有父类构造函数的副本,臃肿。(构造函数缺点 所有属性都绑定在对应的对象上)

    function Con(name){
        Person.call(this,"jer",10)// 调用了 父构造函数 可以传参 提高自由度
        // Person2.call(this,"") 多个构造函数  多继承
        this.name = name;
    }
    
    var con1 = new Con('rr');
    
    console.log(con1 instanceoof Person)
    
    
    4.组合继承(组合原型链继承和借用构造函数继承)(常用)

    原理 结合了两种模式的优点,传参和复用

    优点

    1、可以继承父类原型上的属性,可以传参,可复用。

    2、每个新实例引入的构造函数属性是私有的。

    缺点 调用了两次父类构造函数(耗内存),

    // 子类的构造函数会代替原型上的那个父类构造函数(没有理解)。

    function C(name){
        Person.call(this,name);//构造函数继承属性
    }
    
    C.prototype = new Person();//原型继承方法  new的时候调用第二次 
    
    var per1=new C("aa");
    
    per1.name;   // 构造函数属性
    per1.age;  // 原型属性
    
    
    5.原型式继承

    原理 先创建了一个临时性的构造函数,然后将传入的对象作为个构造函数的原型,最后返回这个构造函数的实例 ,这个函数就变成了个可以随意增添属性的实例或对象。object.create()就是这个原理 。

    优点类似于复制一个对象,用函数来包装。

    缺点

    1、所有实例都会继承原型上的属性(共享问题)。

    2、无法实现复用。(新实例属性都是后面添加的) (不能传参)

    function D(obj){
        function F(){}
        F.prototype = obj;
        return new F();
    }
    
    var per4 = new Person()
    var per5 = D(per4);
    console.log(per5.name)
    
    
    per5 instanceof Person //
    
    //
    new 默认原型对象 create 指定原型对象
    
    Object.create()是Object的内置方法,可以创建一个新对象,使用现有的对象来提供新创建的对象__proto__
    
    Object.create ( proto, [ propertiesObject ] ) 
    
    方法内部定义一个新的空对象obj
    将obj.__proto__的对象指向传入的参数proto
    将传入的对象属性复制到obj并且返回obj
    
    
    
    6.寄生式继承

    原理 就是给原型式继承外面套了个壳子。

    优点 传参

    缺点 没用到原型,无法复用

    function D(obj){
        function F(){}
        F.prototype = obj;
        return new F();
    }
    var per4 = new Person();
    
    function E(obj,name){
        var sub = D(obj);// 继承原型
        sub.name = name; //在原来的基础上加上私有的东西
        return sub;
    }
    var per6 = E(per4,'ee');
    
    // 给原型式继承 加个处理函数传参
    
    7.寄生组合式继承(最理想)

    原理

    寄生:在函数内返回对象然后调用

    组合:1、函数的原型等于另一个实例。

    ​ 2、在函数中用apply或者call引入另一个构造函数,可传参

    优点 修复了组合继承的问题

    缺点 过于繁琐,故不如组合继承

    // 寄生
    function G(obj){
        function F(){};
        F.prototype = obj;
        return new F();
    }
    // G 是 F实例的另一种表示
    var g1 = G(Person.prototype);
    
    //组合
    function Sub(){
        Person.call(this);
    }
    //重点
    Sub.prototype = g1;  //继承 实例
    g1.constructor = Sub; // 修复 实例
    var sub1 = new Sub(); 
    //sub1 就继承了继承 函数属性,父类实例,g1 的函数属性 
    
    sub1.age;
    
    
    8.ES6继承 (class)(extends)

    原理Class之间通过使用extends关键字,这比通过修改原型链实现继承,要方便清晰很多

    新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

    相当于构造函数的另一种写法
    class Point{
        constructor(x,y){
            this.x = x;
            this.y = y;
        }
        toString(){
          console.log(this.x);   
        }
    }
    class Colorpoint extends Point {
        //这个就是默认方法  使用new 生成实例时  会调用这个方法,
        //如果未定义 会自动添加 
        
        constructor(x,y,color){
            
            //子类必须在constructor方法中调用super方法,否则新建实例时会报错
            //这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工,如果不调用super方法,子类就得不到this对象。
            super(x,y); //调用父类构造函数(Point.prototype.constructor.call(this,x,y))
            this.color = color
            
            // 隐式返回 this
            // 如果显示返回对象 就是该对象
        }
        toString(){
            //通过 super调用父类的方法
            return this.color + ' ' + super.toString(); 
        }
    }
    
    class A extends B{}
    A.__proto__ === B;  //继承属性
    A.prototype.__proto__ == B.prototype;//继承方法
    
    typeOf(Colorpoint)
    //类的数据类型就是函数,类本身就指向构造函数。
    //类的所有方法都定义在类的prototype属性上面。
    Colorpoint.prototype.constructor === Colorpoint // true
    
    Object.assign(Colorpoint.prototype, {
      toString(){},
      toValue(){}
    });
    
    与 es5 不同之处
    
    1. toString方法是Colorpoint类内部定义的方法,它是不可枚举的。这一点与 ES5 的行为不一致。
    2. 必须使用new 调用
    3.不存在变量提升,必须先声明在使用
    
    与es5 相同之处
    1.prototype对象的constructor属性,直接指向“类”的本身
    2. 与 ES5 一样,实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。
    3.类的所有实例共享一个原型对象。
    
    Colorpoint.hasOwnProperty('x') // true
    Colorpoint.hasOwnProperty('color') // true
    Colorpoint.hasOwnProperty('toString') // false
    
    
    

    “extends” 语法会设置两个原型:

    1. 在构造函数的 "prototype" 之间设置原型(为了获取实例方法)
    2. 在构造函数之间会设置原型(为了获取静态方法)
    //继承对象
    class A extends Object   和   class A 区别
    继承自对象                      继承自函数
    构造函数中需要调用父类构造函数 
    

    super

    1. 作为函数使用 调用父类构造函数

    2. 作为对象使用 静态时指向父类本身可以调用父类本身的属性和方法·指向父类的原型

    //动态时由于super指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的。
    class A {
      p() {
        return 2;
      }
    }
    
    class B extends A {
      constructor() {
        super();//  调用父类函数时 会绑定子类的this
        console.log(super.p()); // 2
      }
    }
    
    let b = new B();
    
    class A {
      constructor() {
        this.p = 2;
      }
    }
    
    class B extends A {
      get m() {
        return super.p;
      }
    }
    
    let b = new B();
    b.m // undefined
    
    // 用在静态方法之中,这时super将指向父类,而不是父类的原型对象。
    class Parent {
      static myMethod(msg) {
        console.log('static', msg);
      }
    
      myMethod(msg) {
        console.log('instance', msg);
      }
    }
    
    class Child extends Parent {
      static myMethod(msg) {
        super.myMethod(msg);
      }
    
      myMethod(msg) {
        super.myMethod(msg);
      }
    }
    
    Child.myMethod(1); // static 1
    
    var child = new Child();
    child.myMethod(2); // instance 2
    
    //在子类的静态方法中通过super调用父类的方法时,方法内部的this指向当前的子类,而不是子类的实例。
    
    class A {
      constructor() {
        this.x = 1;
      }
      static print() {
        console.log(this.x);
      }
    }
    
    class B extends A {
      constructor() {
        super();
        this.x = 2;
      }
      static m() {
        super.print();
      }
    }
    
    B.x = 3;
    B.m() // 3
    
    
    1. 所以如果通过super对某个属性赋值,这时super就是this,赋值的属性会变成子类实例的属性。
    class A {
      constructor() {
        this.x = 1;
      }
    }
    
    class B extends A {
      constructor() {
        super();
        this.x = 2;
        super.x = 3;
        console.log(super.x); // undefined  A.prototype.x
        console.log(this.x); // 3
      }
    }
    
    let b = new B();
    
    
    1. 由于对象总是继承其他对象的,所以可以在任意一个对象中,使用super关键字。
    2. 直接打印 super会报错 由于浏览器不知道是函数还是对象, 所以必须显式指定是作为函数、还是作为对象使用
    3. 箭头函数没有 super
    4. [[HomeObject]] super 的特殊特征

    学习整理

    JS高程学习-第六章(一)---认识对象
    JS高程学习-第六章(二)---创建对象
    JS高程学习-第六章(三)---对象继承

    相关文章

      网友评论

          本文标题:JS高程学习-第六章(三)---对象继承

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