Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

继承 #27

Open
Twlig opened this issue Mar 9, 2022 · 0 comments
Open

继承 #27

Twlig opened this issue Mar 9, 2022 · 0 comments

Comments

@Twlig
Copy link
Owner

Twlig commented Mar 9, 2022

原型继承

子类原型赋值为父类实例,通过这种办法继承父类的原型方法。

function Person() {
    this.name = "Amy";  //实例属性name
}
Person.prototype.age = 22;
Person.prototype.getName = function() {
    return this.name;
}
function Student() {
    this.grade = 1;
}
Student.prototype = new Person();  //关键代码!,此处已经new了,所以name是Person实例属性,不是Amy的
Student.prototype.getGrade = function() {
    return this.grade;
}
var Amy = new Student();
Amy.getName();
Amy.getGrade();

image

Amy实例的结构如上图。

  • Amy实例

    • grade实例属性。

    • 隐式原型。

  • new Student()

    • Amy实例产生grade实例属性

    • Amy实例的隐式原型被赋值为构造函数Student的显式原型(Student.prototype属性)

  • Student.prototype = new Person()

    • Student.prototype属性被赋值为Person的实例
    • Person实例含有:name实例属性,Person实例的隐式原型
    • Person实例的隐式原型有age属性和getName方法

存在的问题:

  • 原型属性是所有实例共享的,但实际上属性应该是每个实例独立的才合理,除非是常量。因此通常会在构造函数中定义而不会定义在原型上。

  • 子类型在实例化时不能给父类型的构造函数传参。

  • 因此,原型链的继承方式更适合继承方法。方法大家一起共享是很合理的。

盗用构造函数(经典继承)

在子类构造函数中调用父类构造函数

function SuperType(name){ 
 this.name = name; 
} 
function SubType(name) { 
 // 继承 SuperType 并传参
 SuperType.call(this, name);   //调用call,把this对象指向SubType实例。
 // 实例属性
 this.age = 29; 
} 
let instance = new SubType("Nicholas"); 
console.log(instance.name); // "Nicholas"; 
console.log(instance.age); // 29

name和age都成为了instance实例属性,因此就不存在不同对象共享相同属性的问题。

存在的问题:

  • 子类不能访问父类原型上的方法

组合继承(经典伪继承)

综合原型链和盗用构造函数,将两者的优点集中了起来。

  • 使用原型链继承原型上的属性和方法 (目的:访问父类原型)

  • 通过盗用构造函数继承实例属性 (目的:每个实例属性独立)

function Person(name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.getMsg = function() {
    console.log("My name is " + name + ", I am " + age + "years old.");
}
function Student(grade, name, age) {
    Person.call(this, name, age);
    this.grade = grade;
}
Student.prototype = new Person();
Student.prototype.sayGrade = function() {
    console.log("My grade is " + this.grade);
}
var Amy = new Student(1, "Amy", 8);  //Student {name: 'Amy', age: 8, grade: 1}
var LiLei = new Student(3, "LiLei", 10);  //Student {name: 'LiLei', age: 10, grade: 3}

存在问题:

  • 存在效率问题。最主要的效率问题就是父类构造函数始终会被调用两次

    • 一次在是创建子类原型时调用,另一次是在子类构造函数中调用。

原型式继承(Object.create)

经典原型式继承

function createNew(o) {
    function F(){};
    F.prototype = o;
    return new F();
}
let person = { 
     name: "Nicholas", 
     friends: ["Shelby", "Court", "Van"] 
}; 
let newObj = createNew(person);

Object.create()方法封装了上述操作,使用现有对象作为新创建对象的原型

这个方法接收两个参数:

  • 现有对象。新对象 隐式原型上的属性和方法。

  • 给新对象定义额外属性的对象(第二个可选)。新对象实例属性和方法。

    Object.create()的第二个参数与 Object.defineProperties()的第二个参数一样:每个新增属性都通过各自的描述符来描述。

let person = { 
     name: "Nicholas", 
     friends: ["Shelby", "Court", "Van"] 
}; 
let anotherPerson = Object.create(person);

//等价于 
function F(){};
F.prototype = person;
anotherPerson = new F();

//第二个参数
let Greg = Object.create(person, { 
     name: { 
         value: "Greg" 
     },
    age: {
        value: 22
    }
});

寄生式继承

和原型式继承特别像,只是在原型式继承的基础上,给新对象添加了其他属性或者方法(和带第二个参数的Object.creat()函数很像)。

function createAnother(original){ 
 let clone = object(original); // 通过调用函数创建一个新对象
 clone.sayHi = function() { // 以某种方式增强这个对象
 console.log("hi"); 
 }; 
 return clone; // 返回这个对象
}

寄生式组合继承

组合继承中,父类构造函数始终会被调用两次:一次是在子类构造函数中调用,一次在是创建子类原型时调用。

  • 在子类构造函数中调用已经把每个实例所需的父类实例的属性和方法创建成当前实例的属性和方法了。

  • 如果在创建子类原型时再调用父类构造函数,就会造成子类原型上还有父类实例的属性和方法

  • 而我们只是需要父类原型上的方法和属性而已,所以在创建子类原型时不需要new一个父类,直接继承原型更高效。

function object(o) {  //原型式继承
    function F(){};
    F.prototype = o;
    return new F();
}

function inheritPrototype(subType, superType) {  //寄生式继承
 let prototype = object(superType.prototype); // 创建对象
 prototype.constructor = subType; // 增强对象 
 subType.prototype = prototype; // 赋值对象
}

function Person(name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.getMsg = function() {
    console.log("My name is " + name + ", I am " + age + "years old.");
}
function Student(grade, name, age) {
    Person.call(this, name, age);
    this.grade = grade;
}
inheritPrototype(Student, Person);  //原本是 Student.prototype = new Person();
Student.prototype.sayGrade = function() {
    console.log("My grade is " + this.grade);
}
var Amy = new Student(1, "Amy", 8);  //Student {name: 'Amy', age: 8, grade: 1}
var LiLei = new Student(3, "LiLei", 10);  //Student {name: 'LiLei', age: 10, grade: 3}

⚠️注意

想过为啥不能直接把父类的原型赋值给子类,后面试了一下:

function inheritPrototype(subType, superType) { 
 subType.prototype = superType.prototype; // 赋值对象
}

如果像这样的话就没有子类和父类的原型链层级了,就相当于原型链是:实例隐式原型(Person) ——》Person隐式原型 (Object)——》Object隐式原型(null)

而实际原型链应该是:实例隐式原型(Student) ——》Student隐式原型 (Person)——》Person隐式原型(Object)——》Object隐式原型(null)

所以,必须创建一个中间对象o,使得o的prototype等于父类的prototype。然后让创建o的实例,让子类的prototype等于o的实例。就相当于把父类的prototype提取出来,创建一个空对象来包裹它。也就是相当于提炼吧,本身的父类有很多杂质不需要。保留需要的prototype,再用空对象去包裹它。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant