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

2019-11-28  本文已影响0人  槑小姐_1419

对象继承

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

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高程学习-第六章(三)---对象继承

上一篇下一篇

猜你喜欢

热点阅读