JS原型、原型链、构造函数、实例、继承

1,660 阅读5分钟

构造函数

  • 定义:通过new函数名,来实例化对象的函数叫做构造函数

  • 功能:初始化对象,特点是和new一起使用。new就是在创建对象,从无到有,构造函数就是为初始化的对象添加属性和方法

  • new的理解:new申请内存,创建对象,当调用new时,后台会隐式执行new Object()创建对象。所以通过new创建的字符串、数字是引用类型。

  • 常用的构造函数

1. var arr = [] 为 var arr = new Array()的语法糖
2. var obj = {} 为 var obj = new Object()的语法糖
3. var date = new Date()

实例

var person = new Object()
JS是基于原型的面向对象语言,所有数据都可以当作对象处理,所以person是对象,
可以把它当作Object的实例。
  • 实例都是对象,但对象不一定是实例
  • 实例是类的具象化产品
  • 简单理解
动物 -- 对象
一只狗  -- 实例

prototype原型(构造函数访问原型)

原型就是一个对象,实例会“继承”这个对象的属性。在原型上定义的属性,通过“继承”,实例也拥有了这个属性。“继承”这一行为是在new操作符内部实现的

  • 构造函数内部都有一个名为prototype的属性,通过这个属性就能访问到原型 Person就是构造函数,Person.prototype就是原型
function Person(){}构造函数
  • 有个构造函数,我们可以创建实例,并在构造函数的原型上创建可以继承的属性
function Person (){}
var person = new Person()
console.log(person instanceof Person)
// person是Person的实例

constructor属性(原型访问构造函数)

从上面所述,构造函数可以通过prototype访问到原型,那么原型也应该能访问到构造函数,这就是constructor属性.

Person.prototype.constrauctor = Person

proto隐式原型(实例访问原型)

实例是通过_proto_访问到原型,所以实例可以直接通过_proto_访问原型,所以

person._proto_ === Person.prototype

实例访问构造函数

person._proto_.constructor = Person

读取实例的属性

但我们要读取一个实例的属性时,如果属性在该实例中没有找到,就会循着_proto_指定的原型上去找,如果还找不到就尝试寻找原型的原型

举例

function Person(){}
Person.prototype.type = '123'
person = new Person()
var res = Relect.ownKeys(person) // 先寻找自有属性
res = person.type
console.log(res) // 打印的是原型上的属性
function Person(){}
Person.prototype.type = '123'
person = new Person()
person.type = '123'
var res = Reflect.ownKeys(person)
res = person.type
console.log(res) // 打印的是实例上的属性

原型链

当我们需要访问实例的一个非自有属性时,就会通过_proto_连接起来的一系列原型、原型的原型、直到构造函数搜索查找属性。这个搜索过程形成的链状关系就是原型链

function Parent(){}
Parent.prototype.type="123"

function Child(){}
Child.prototype = new Parent()

var p = new Child()
console.log(p.type)

JavaScript实现继承的几种方式

  • 思路

    1. 原型链:父类的实例当作子类的原型。如此子类的原型包含父类实例定义的属性
    2. 构造函数:子类直接使用父类构造函数。如此子类的实例包含父类实例定义的属性
    3. 原型式:复制父类原型属性给子类原型。如此子类的实例包含父类实例定义的属性
    4. 寄生式:思路与3一样,只是利用工厂模式对复制父类原型对象进行增强
  • 方式

    1. 原型链继承
    2. 构造函数继承
    3. 组合继承
    4. 原型式继承
    5. 寄生式继承
    6. 寄生组合式继承

原型链继承

// 父类
function Parent(){
    this.name = 'baba';
}
Parent.prototype.getName = function(){
    return this.name
}

// 子类
function Child(){
    this.age = 10;
}
Child.prototype = new Parent()
Child.prototype.getAge = function(){
    return this.age
}

var instance = new Child()
console.log(instance)

缺点:
1. 来自原型对象的引用的属性是所有实例共享的
2. 创建子类实例时,无法向父类构造函数穿餐

构造函数继承

function Parent(name){
    this.name = name;
    this.colors = ['red', 'green'];
    this.getName = function(){
        return this.name
    }
}
function Child(name){
    Parent.call(this,name);
    this.age = 20;
}

var instance = new Child('tom')
instance.colors.push('blue')
cosnole.log(instance.colors)

借用构造函数继承解决了原型链两个问题,既可以传参,也不会造成子类实例共享父类引用属性。
缺点:
1. 但这里是使用call来实现继承的,并没有通过new生成一个父类实例。无法形成同步更新。
2. 无法复用函数
3. 每次构建实例都会在实例中保留方法函数,造成内存浪费

组合继承

将原型链继承和构造函数组合到一块
function Parent(name){
    this.name = name;
    this.colors = ['red', ;green];
}
Parent.prototype.getName = function(){
    console.log(this.name);
}

function Child(name,age){   
    // 继承父类实例属性
    Parent.call(this.name);
    // 子类实例属性
    this.age = age;
}
Child.prototype = new Parent();
Child.prototype.constructor = child
child.prototype.getAge = function (){
    console.log(this.age)
}

var instance = new Child('tom', 20)
instance.colors.push('black')
console,log(instance.colors)

缺点:调用了两次父类构造函数,一次通过call 一次通过new Parent()

原型式继承

function object(a){
    function F()
    F.prototype = a;
    return new F()
}

var person = {
    name: 'tom',
    colors: ['red', 'blue']
};

var person1 = object(person);
person1.name = 'anna';
person1.colors.push('green');

cosnole.log(person1.colors);

缺点:和原型链继承一样,所有子类实例共享父类引用类型

寄生式继承

function object(a){
    function F(){}
    F.prototype = a;
    return new F()
}
function createPerson (a){
    var clone = object(a);
    clone.getName = function(){}
    return clone
}
var person = {
    name: 'tom',
    colors: ['red','blue']
},
var person1 = createPerson(person);
person1.getName();
person1.colors.push('black');
console.log(person1.colors)

缺点:
1. 和原型链式继承一样,所有子类实例共享父类引用类型
2. 和借用构造函数一样,每次创建对象都会创建一次方法

寄生组合式继承

function object(a){
    function F(){}
    F.prototype = a;
    return new F()
}
function inheritP(Child, Parent){
    var prototype = object(Parent, Child);
    prototype.constructor = Child;
    child.prototype = prototype
}

function Parent(name){
    this.name = name;
    this.colors = ['red','black'];
}
Parent.prototype.getName = function(){}

function Child(name,age){
    Parent.call(this,name)
    this.age = age
}

// 继承父类方法
inheritP(Child, Parent);

Child.prototype.getAge = function(){}

var instance = new Child('tom', 20);
instance.colors.push('green');
instance.getNmae();
instance.getAge()

ES6实现继承

class Parent{
    constructor(name){
        this.name = name;
        this.colors = ['red','blue']
    }
    getName(){
        console.log(this.name)
    }
}
 class Child extends Parent {
    constructor(name,age){
        super(name);
        this.age = age
    }
    getAge(){
        console.log(this.age)
    }
 }
 
 var instance = new Child('tom', 20);
 instance.getAge();
 instance.getName();
 instance.colors.push('black')\
 
 本质也采用的是寄生组合式继承