面向对象编程(二)

161 阅读7分钟

创建对象的5种模式

对象的字面量方式

new构造函数

var obj = new Object();//空对象{}
obj.name = 'john';
console.log(obj);

对象字面量(语法糖)

var person = {
    name:'john',
    age:25
};
console.log(person);

Object.create(object)

一个实例对象继续生成实例对象

create()中的参数作为返回实例对象b的原型对象,在a中定义的属性和方法,都能够被b实例对象继承下来。

var a = {
    getX:function(){
    console.log('X');
    }
};
var b = Object.create(a);
b.getX();//x

工厂模式

能创建多个类似对象

存在的问题:没有解决对象识 别的问题

function createPerson(name,age){
    //创建对象
    var o = new Object();//{}
    o.name = name;
    o.age = age;
    o.sayName = function(){
        console.log(this.name);
    }
    return o;//返回对象
}
var p1 = createPerson('john',25);
p1.sayName();
var p2 = createPerson();
var p3 = createPerson();
var p4 = createPerson();
...
console.log(p1 instanceof Object);//true
console.log(p2 instanceof Object);//true
console.log(p3 instanceof Object);//true
console.log(p4 instanceof Object);//true
...

构造函数模式

问题:浪费内存空间

function Person(name,age){
    this.name = name;
    this.age = age;
    this.sayName= function(){
        console.log(this.name);
    }
}
var man = new Person('john',18);
var woman = new Person('huahua',24);
//具有相同的sayName方法在man和woman实例中占用了不同的内存空间
console.log(man.sayName===woman.sayName);//false

构造函数拓展模式

function Person(name,age){
    this.name = name;
    this.age = age;
    this.sayName= sayName;
}
//定义多个全局函数,会严重污染全局空间
function sayName(){
    console.log(this.name);
}
var man = new Person('john',18);
var woman = new Person('huahua',24);
console.log(man.sayName===woman.sayName);//true

寄生构造函数模式

由工厂模式和构造函数模式结合而来。

创建一个函数,函数体内部实例化一个对象,并且将对象返回,在外部使用new关键字来实例化对象

问题:

  1. 定义了相同的方法,浪费内存空间
  2. instanceof和prototype属性都没有意义
function Person(name,age){
    //这里的Person并不充当构造函数
    var o = new Object();
    o.name = name;
    o.age = age;
    o.sayName = function(){
        console.log(this.name);
    }
    return o;
}

var man = new Person('john',18);
var woman = new Person('huahua',24);
console.log(man.sayName===woman.sayName);//false
console.log(man.__proto__===Person.prototype);//false
console.log(man instanceof Person)

该模式尽量避免使用

稳妥构造函数模式

没有公共属性,并且他的方法也不引用this对象

function Person(name){
    var o = new Object();
    //name 属于私有属性,结合闭包函数
    o.sayName = function(){
        console.log(name);
    }
    return o;
}
//p1对象称为稳妥对象
var p1 = Person('john');
var p2 = Person('job');
p1.sayName();
console.log(p1.sayName===p2.sayName);//false

问题:

  1. 浪费内存空间
  2. instanceof和prototype属性都没有意义

原型模式

function Person(){};
/*
Person.prototype.name = 'john';
Person.prototype.age = 18;
Person.prototype.sayName = function(){
    console.log(this.name);
};
*/
Person.prototype = {
    constructor:Person,
    name:'john',
    age:18,
    sayName:function(){
        console.log(this.name);
    }
};
Person.prototype.constructor = Person;
var p1 = new Person();
p1.sayName();
var p2 = new Person();
p2.sayName();
console.log(p1.sayName===p2.sayName);//true
console.log(p1.constructor = Person);//true
p1.name = 'huahua';
cnsole.log(p1.name);//huahua
console.log(p2.name);//huahua why?

问题:

在于引用类型的属性会被所有实例对象共享并且修改,这是很少使用原型模式的原因。

组合模式

结合了原型模式和构造函数模式

用就完了

function Person(name,age){
    //定制了当前对象自己的属性
    this.name = name;
    this.age = age;
    this.friends = ['john','jack']
}
//定制各个实例对象的共享属性
Person.prototype = {
//改变原型对象的同时,要改变该原型对象的constructor属性,让他指向了当前的构造函数Person
    constructor:Person,
    sayName:function(){
        console.log(this.name);
    }
}
var p1 = new Person('huahua',18);
var p2 = new Person('sunny',26);
p1.friends.push('lili');
console.log(p1.friends);
console.log(p2.friends);

动态原型模式

function Person(name,age){
    //定制了当前对象自己的属性
    this.name = name;
    this.age = age;
    this.friends = ['john','jack'];
    
    //定制
    if(typeeof this.sayName!="function"){
        //初始化原型对象上的属性
        Person.prototype.sayName = function(){
	    console.log(this.name);
        };
    }
}
var p1 = new Person('huahua',18);
var p2 = new Person('suuny',26);
p1.friends.push('lili');
console.log(p1.friends);
console.log(p2.friends);

总结

需要重点掌握的:

1.字面量方式

问题:创建多个对象会造成冗余代码

2.工厂模式

解决1的问题

问题:对象识别的问题

3.构造函数模式

解决2的问题

问题:方法重复被创建

4.原型模式

解决3的问题

特点:在于方法可以被共享

问题:给当前实例定制的引用类型的属性会被所有的实例所共享

5.组合模式(构造函数和原型模式)

解决4的问题

构造函数:定义实例属性

原型模式:用于定义方法和共享的属性,还支持向构造函数中传递参数

该模式使用广泛

以下模式了解即可

  • 构造函数拓展模式
  • 寄生构造函数模式
  • 稳妥构造函数模式
  • 动态原型模式

实现继承的方式

原型链继承

在原型对象的所有属性和方法,都能被实例所共享。

//Animal类
function Animal(){
    this.name = 'john';
}
Animal.prototype.getName = function(){
    return this.name;
}

//Dog类
function Dog(){}
//Dog继承了Animal

//本质:重写原型对象,将一个父对象的属性和方法作为一个子对象的原型对象的属性和方法
Dog.prototype = new Animal();
Dog.prototype.construcor = Dog;
var d1 = new Dog();
console.log(d1.name);//john
console.log(d1.getName());//john

原型链继承的原理

存在的问题

问题一:

父类中的实例属性,一旦赋值给子类的原型属性,此时这些属性都属于子类的共享属性

问题二:

实例化子类型的时候,不能向父类型的构造函数传参。

//Animal类
function Animal(){
    this.name = 'john';
    this.colors = ['red','green','blue'];
}
Animal.prototype.getName = function(){
    return this.name;
}

//Dog类
function Dog(){}
//Dog继承了Animal

//本质:重写原型对象,将一个父对象的属性和方法作为一个子对象的原型对象的属性和方法
Dog.prototype = new Animal();
Dog.prototype.construcor = Dog;
var d1 = new Dog();
var d2 = new Dog();
console.log(d1.colors);
console.log(d2.colors);
d1.colors.push('black');
console.log(d1.colors);//['red','green','blue','black'];
console.log(d2.colors);//['red','green','blue','black']; why ?

借用构造函数继承

经典继承:在子类的构造函数内部调用父类的构造函数

解决了原型链继承存在的问题

function Animal(name){
    this.name = name;
    this.colors = ['red','green','blue'];
}
Animal.prototype.getName = function(){
    return this.name;
}
function Dog(name){
//继承了Animal
//当new实例的时候,内部构造函数的this指向当前的实例d1,然后在当前的构造函数内部再去通过call调用父类的构造函数,那么父类的构造函数的this指向了d1,但是方法不能被继承下来
    Animal.call(this,name);
}
var d1 = new Dog('huahua');
var d2 = new Dog('huanhuan');
d1.colors.push('black')

console.log(d1.name);//huahua
console.log(d1.colors);//['red','green','blue','black']
console.log(d2.name);//huanhuan
console.log(d2.colors);//['red','green','blue']

问题

父类定义的共享方法不能被子类继承

组合继承

解决了原型链继承和借用构造函数继承的问题,最常用

function Animal(name){
    this.name = name;
    this.colors = ['red','green','blue'];
}
Animal.prototype.getName = function(){
    return this.name;
}
function Dog(name){
//继承了Animal
//让父类的实例属性继承下来,实例修改引用类型的值,另一个实例的引用类型的值不会发生修改
    Animal.call(this,name);
}
//重写原型对象把父类的共享方法继承下来。
Dog.prototype = new Animal();
Dog.prototype.constructor = Dog;
var d1 = new Dog('huahua');
var d2 = new Dog('huanhuan');

问题:

无论在什么情况下,都会调用父类构造函数两次

1.一个是我们初始化子类的原型对象时

2.在子类构造函数内部调用父类的构造函数

寄生组合式继承

function Animal(name){
    this.name = name;
    this.colors = ['red','green','blue'];
}
Animal.prototype.getName = function(){
    return this.name;
}
function Dog(name){
//继承了Animal
//让父类的实例属性继承下来,实例修改引用类型的值,另一个实例的引用类型的值不会发生修改
    Animal.call(this,name);
}
//重写原型对象把父类的共享方法继承下来。
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
var d1 = new Dog('huahua');
var d2 = new Dog('huanhuan');

总结

1.原型链继承

特点:

重写子类的原型对象,父类原型对象上的属性和方法都会被子类继承

问题:
  • 在父类中定义的实例引用类型的属性,一旦被修改,其他的实例属性也会被修改
  • 实例化子类时,不能传递参数到父类

2.借用构造函数继承

特点:

在子类的构造函数内部间接 (call,apply,bind) 调用父类的构造函数。

优点:

仅仅的把父类中的实例属性当作子类的实例属性,并且还能传参。

原理:

改变父类中的this指向

问题:

父类中共有的方法不能被继承

3.组合继承

特点:

结合了原型链继承和借用构造函数继承的优点

优点:

原型链继承:公有的方法能被继承下来

借用构造函数:实例属性能够被子类继承下来

问题:

调用了两次父类的构造函数

第一次:实例化对象时

第二次:子类的构造函数内部

4.寄生组合式继承

原理
var b = Object.create(a);//将a对象作为b实例的原型对象

把子类的原型对象指向父类的原型对象

Dog.prototype = Object.create(Animal.prototype);

这种方式最实用,用就完了

多重继承

一个对象同时继承多个对象

function Person(){
    this.name = 'Person'
}
Person.prototype.sayName = function(){
    console.log(this.name);
}
//定制Parent
function Parent(){
    this.age = 30
}
Parent.prototype.sayAge= function(){
    console.log(this.age);
}

function Me(){
    //继承Person的属性
    Person.call(this);
    //继承Parent的属性
    Parent.call(this);
}
//继承Person的方法
Me.prototype = Object.create(Person.prototype);

//不能重写原型对象来实现另一个对象的继承
//Me.prototype = Object.create(Parent.prototype);
Object.assign(Me.prototype, Parent.prototype);
//同时指定构造函数
Me.prototype.constructor = Me;
var me = new Me();