一定要知道的事之原型和继承

136 阅读5分钟

↓↓↓来自【路遥_流年 】的思维导图: 72mdr6.png 006599C4.gif

「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战」。

原型是什么?原型链是什么?

00674929.jpg

// 定义Person大类,有name属性和drink方法
class Person {
    constructor(name){
        this.name = name;
    }
    drink(){
        console.log('i can drink!');
    }
}


// 定义Student类,并继承Person属性和方法
class Student extends Person {
    constructor(name,score){
        super(name);
        this.score = score;
    }
    getScore(){
        console.log(`我是${this.name}得了${this.score}分`);
    }
}

const stu = new Student('jessi',100)
console.log(stu.name);
stu.getScore()
stu.drink()

/**
 * 1、 Student.prototype === stu.__proto__  //true  显示原型和隐式原型都指向同一个对象
 * 2、 当访问一个属性或方法时候,首先在自己本身里寻找,如果没有再去访问它的原型,或原型的原型,这样一层层的的关系就是原型链
 */

image.png

hasOwnProperty 检测属性或方法是否属于本身

image.png

53fd26d252ce665f234484be39c57de.jpg

拓展:类型判断

const obj = {}
const arr = []
console.log(typeof(obj));  // object
console.log(typeof(arr));  // object 

// typeOf无法判断数组和对象

console.log(obj instanceof(Object));  // true
console.log(arr instanceof(Array));   // true

image.png

继承方法

原型链继承

优点:继承父类的属性和方法,也继承父类原型上的方法和属性

缺点:(1) 原型属性在所有实例中是共享的,其中一个实例改变,其他实例也会跟着改变 (2)没办法给父类传参

 // 父类
function Father(){
    this.name = 'baba';
    this.colors = ["red", "blue", "green"]; 
}
Father.prototype.getName = function(){
    return `my name is ${this.name}`
}
// 子类
function Son(age){
    this.age = age
}
// 原型链继承父类
Son.prototype = new Father();  //继承了SuperType的所有属性和方法
// 子类自己添加新方法
Son.prototype.getAge = function (){
    return `my age is ${this.age}`
}

// 实例化对象
var s = new Son(18);
var s2 = new Son(28);

console.log(s.name); //baba
console.log(s2.name); //baba
console.log(s.age)  //18
console.log(s2.age)  //28
console.log(s.getName())  //my name is baba
console.log(s.getAge()) //my age is 18
 
console.log(s.colors)  //[ 'red', 'blue', 'green' ]
s.colors.push('yellow')
console.log(s.colors)  //[ 'red', 'blue', 'green', 'yellow' ]
console.log(s2.colors)  //[ 'red', 'blue', 'green', 'yellow' ]

借助构造函数继承

方法:在子类型构造函数的内部通过使用 apply()和 call()等方法调用超类型构造函数。

优点: (1) 每个实例不会共享属性,其中一个实例改变属性另一个实例不受影响; (2) 子类可以向父类传递参数。

缺点:不能继承父类原型上的属性和方法

// 父类
function Father(name){
    this.name = name;
    this.colors = ["red", "blue", "green"]; 
    this.say = function (){
        console.log('gua~gua~gua')
    }
}
Father.prototype.getName = function(){
    return `my name is ${this.name}`
}
// 子类
function Son(name,age){
    Father.call(this,name)  //借助构造函数继承
    this.age = age
}

// 子类自己添加新方法
Son.prototype.getAge = function (){
    return `my age is ${this.age}`
}

// 实例化对象
var s1 = new Son('lily',18);
var s2 = new Son('tom',28);

console.log(s1.name)  //lily
console.log(s1.age)  //18
s1.say()  //gua~gua~gua
console.log(s1.getAge())    //my age is 18
// console.log(s1.getName())   //不能访问父类原型上的属性和方法   s1.getName is not a function


s1.colors.push('yellow')
console.log(s1.colors)  //[ 'red', 'blue', 'green', 'yellow' ]
console.log(s2.colors)   //[ 'red', 'blue', 'green' ]

组合式继承

方法:原型链继承 + 借助构造函数继承 结合 优点:既可以传参又可以复用(属性方法不被共享) 缺点:耗内存(子类构造函数被多次调用)

// 父类
function Father(name){
    this.name = name;
    this.colors = ["red", "blue", "green"]; 
    this.say = function (){
        console.log('gua~gua~gua')
    }
}
Father.prototype.getName = function(){
    return `my name is ${this.name}`
}
// 子类
function Son(name,age){
    Father.call(this,name)  //借助构造函数继承
    this.age = age
}
// 原型链继承
Son.prototype = new Father();
Son.prototype.constructor = Son;    //将 constructor指回 Son

// 子类自己添加新方法
Son.prototype.getAge = function (){
    return `my age is ${this.age}`
}

// 实例化对象
var s1 = new Son('lily',18);
var s2 = new Son('tom',28);

s1.colors.push('black')
console.log(s1.colors)  //[ 'red', 'blue', 'green', 'black' ]
console.log(s2.colors)  //[ 'red', 'blue', 'green' ]
console.log(s1.getName())  //my name is lily
console.log(s2.getName())  //my name is tom

原型式继承

本质是浅拷贝,在es5中,新增了一个函数Object.create()实现了原型式继承

优点: 兼容性好,最简单的对象继;

缺点: ① 多个实例共享属性; ② 无法传参

var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"],
    sayname: function () {
        console.log(this.name);
    }
};
var p1 = Object.create(person);
var p2 = Object.create(person, {
    name: {
        value: "Greg"
    }
});
//相当于:
//var anotherPerson =  Object.create(person);
//anotherPerson.name = "Greg";
p2.friends.push("Rob");
p1.friends.push("Barbie");

console.log(person.friends);   //[ 'Shelby', 'Court', 'Van', 'Rob', 'Barbie' ]
p2.sayname();  //"Greg"

寄生式继承

把原型式继承再次封装,然后在对象上扩展新的方法,再把新对象返回 实例对象不仅具有 person 的所有属性和方法,而且还有自己的 sayName()方法。

缺点: (1) 在createAnother()中为对象添加函数,不能做到函数复用 (2) 由于是浅复制person对象,pp的改变会影响person对象

//封装继承函数
function createsunny(obj) {
    var clone = Object(obj);  //通过Object()创建一个新对象
    clone.sayName = function() {
         //添加自己的方法
         console.log(this.name);
     };  
     return clone;  //返回这个对象
}  

var person = {
    name: "lily",     
    friends: ["Shel", "Court", "Van"] 
};  
var pp = createsunny(person); 
pp.name='sunny';
pp.friends.push('Barbie');

console.log(person.name); //sunny
console.log(person.friends); // ["Shel", "Court", "Van", "Barbie"]
console.log(pp.name); //sunny
console.log(pp.friends); // ["Shel", "Court", "Van", "Barbie"]
pp.sayName(); //sunny

寄生组合式继承

通过借用构造函数来继承属性,通过“ 原型链 ”来继承方法,不同的是,这里的“ 原型链 ”本质上是使用寄生式继承来继承超类型的原型。

优点: (1) 既能具有组合继承的优点,又可以不必两次调用超类型的构造函数

(2) 避免了在 SubType.prototype 上面创建不必要的、多余的属性(在原型链继承时,SubType.prototype被重写为SuperType的实例,因此具有了他的实例属性)

function inheritPrototype(subType, superType) {
    var copy= Object(superType.prototype); //创建新对象
    copy.constructor = subType; //增强对象
    subType.prototype = copy; //指定对象 
}

① 通过浅复制创建超类型原型的一个副本(copy) ② 将副本的constructor 属性重新指向subType(在原型链继承中,constructor指向superType) ③ 把副本赋值给subType的原型

// 父类
function Father(name) {
    this.name = name;
    this.colors = ["red", "blue", "green"];
    this.say = function () {
        console.log('gua~gua~gua')
    }
}
Father.prototype.getName = function () {
    console.log(`father's name is ${this.name}`)
}
// 子类
function Son(name, age) {
    Father.call(this, name)  //借助构造函数继承
    this.age = age
}
inheritPrototype(Son, Father); //调用函数代替原型链继承
// 子类自己的方法
Son.prototype.sayAge = function () {
    console.log(this.age);
}; 

var sub1 = new Son("tom", 29);
var sub2 = new Son("jerry", 27);
sub1.colors.push("black");

console.log(sub1.colors);      //[ 'red', 'blue', 'green', 'black' ]
sub1.getName();          //father's name is tom
sub1.sayAge();           //29  
console.log(sub2.colors);      //[ 'red', 'blue', 'green' ]
sub2.getName();          //father's name is jerry
sub2.sayAge();           //27  

扩展:call(),apply(),bind()区别

相同:调用一个对象的一个方法,用另一个对象替换当前对象,call、apply、bind中的this被强绑定在指定的那个对象上;

B.apply(A, arguments);即A对象应用B对象的方法。

B.call(A, args1,args2);即A对象调用B对象的方法。

B.bind(A, args1,args2)();

区别: 当传入多个参数时候,apply方法需要以数组方式传入;bind需要调用

var name = 'lily', age = 18;
var obj = {
    name: 'kiki',
    objage: this.age,
    myFun: function () {
        console.log(this.name + '~~' + this.age)
    }
};
var pp = {
    name: 'grace',
    age: 22
};
obj.myFun();   //kiki~~undefined
obj.myFun.call(pp);  //grace~~22
obj.myFun.apply(pp);  //grace~~22
obj.myFun.bind(pp)();   //grace~~22  bind的区别就是返回一个函数,需要调用才可

当传入多个参数时 在这里插入图片描述