Js面向对象和继承机制

最后更新于:2018-03-29 23:42:17

理解对象

'ESMA-262'把对象定义为:"无序属性的集合,其属性可以包含基本值,对象或者函数。"严格来讲,这就相当于说对象是一种没有特定顺序的值。对象的每个属性或方法都有一个名字,而每个名字都映射到一个值。 <JavaScript高级程序设计>

通过构造函数

let person = new Object();
person.name = "Miko";
person.age = 28;
person.sayName = function(){
    alert(this.name);
}

字面量创建 | 单例模式

let person = {
    name:"Miko",
    age:28,
    sayName:function(){
        alert(this.name);
    }
}

:构造函数和字面量方式都可以创建单个对象,但有个明显的缺点,在创建很多对象时,会产生大量重复代码。

工厂模式

function creatPerson(name,age){
    let person = new Object();
    person.name = name;
    person.age = age;
    person.sayName = function() {
        alert(this.name);
    }
    return person;
}

:工厂模式解决了创建多个相似对象的问题,但却没有解决对象识别问题。

构造函数模式

function Person(name,age){
    this.name = name;
    this.age = age;
    this.sayName = function(){
        alert(this.name);
    }
    //this.sayName = new Function("alert(this.name)");
}
function sayName(){
    alert(this.name);
}
let person1 = new Person("Miko",28);
let person2 = new Person("Nike",17);

new操作符实际上做了以下工作:

  1. 创建了一个新对象。
  2. 改变了this指向到这个新对象。
  3. 执行构造函数中的代码。(为这个对象添加属性)
  4. 返回新对象。 :构造函数模式的主要问题是每个方法都要在每个实例上重新创建一次,然而不同实例上的同名函数是不想等的,所有可以把函数转移到构造函数外面,如②,这个问题解决了。但新的问题出现了,如果对象需要定义很多方法,那么就要定义很多全局函数,于是我们自定义的引用类型就丝毫没有封装性可言了。

原型模式

  • 每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,这个对象内部放着所有实例的共享属性和方法。
function Person(){};
Person.prototype.name = "Miko";
Person.prototype.age = 28;
Person.prototype.sayName = function(){
    alert(this.name);
};
let person = new Person();

:但是在大多数情况下,并不是所有的属性方法都需要共享。

构造函数模式+原型模式

function Person(name,age){
    this.name = name;
    this.age = age;
}
Person.prototype = function(){
    constructor:Person;
    sayName:function(){
        alert(this.name);
    }
}

:目前使用最为广泛的创建自定义引用类型的方法,把私有属性写入构造函数内部,把公有方法放在原型上。

继承

继承是OO语言的三大特性之一(继承,封装,多态),许多的面向对象语言都支持接口继承和实现继承两种方式,由于JavaScript函数没有签名,所以无法实现接口继承。

原型继承

关于原型基础请看:Js原型对象

function Big() {
    this.age = 20;
}

function Small() {

}
// 重要 Small的原型指向Big的实例,所以会继承Big的属性和方法。
Small.prototype = new Big();

let result = new Small();
console.log(result.age);// 20

存在的问题:

  1. 共享了属性和方法。
  2. 创建实例时,不能向超类型的构造函数中传递参数。

构造函数继承

基本思想:在子类型构造函数中调用超类型构造函数。

function Big(name) {
    this.name = name;
};

function Small() {
    // 关键语句,继承了Big
    Big.call(this, "Node");
    // 实例自己的属性
    this.age = 5;
};
let res = new Small();
console.log(res.name,res.age);// Node 5

存在的问题:

  1. 无法复用
  2. 在超类型中定义的方法,在子类型中是不可见的,结果所有的类型只能使用构造函数。

组合继承

原型+构造函数,发挥二者之长,目前常用的继承模式。

function Dogs(name) {
    this.name = name;
    this.color = ['black', 'white'];
};
Dogs.prototype.sayName = function () {
    console.log(this.name);
};

function Cat(name, age) {
    // 继承属性,第二次调用超类型函数
    Dogs.call(this, name);
    this.age = age;
};
//继承方法,第一次调用超类型函数
Cat.prototype = new Dogs();
//实例指向类本身
Cat.prototype.constructor = Cat;
Cat.prototype.sayAge = function (age) {
    console.log(this.age);
};

let cat=new Cat('Bai',3);
cat.color.push('oringe');
console.log(cat.color);//[ 'black', 'white', 'oringe' ]
cat.sayName();//Bai
cat.sayAge();//3

let dog=new Cat('Killy',5);
console.log(dog.color);//[ 'black', 'white' ]
dog.sayName();//Killy
dog.sayAge();//5

原型式继承(基于一个对象)

以一个对象作为基础,之后所有的对象都在此对象上进行操作。相当于给每一个需要继承的对象一个此对象的副本。缺陷和使用原型模式一样,都会共享响应的值。

function object(o) {
    function F() {};
    F.prototype = o;
    return new F();
}
// person对象作为基础/原型
let person = {
    name: 'Kiro',
    friends: ['A', 'B']
}

let anothorPerson = object(person);
anothorPerson.name = "Killy";
anothorPerson.friends.push('C');

let yetAnothorPerson = object(person);
yetAnothorPerson.name = "Nice";
yetAnothorPerson.friends.push('D');
console.log(yetAnothorPerson.friends); //[ 'A', 'B', 'C', 'D' ]
console.log(anothorPerson.name); //Killy
console.log(yetAnothorPerson.name); //Nice

寄生式继承

基于上面的一个扩展,创建另一个函数,增强了返回的对象,与之填上特有的属性和方法,缺点是不能做到函数的复用。

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

function createAnother(k) {
    let clone = object(k);
    clone.sayName = function () {
        console.log(1);
    }
    return clone;
}

let person = {
    name:'Nike',
    frides:['A','b']
}
let anothorPerson=createAnother(person);
console.log(anothorPerson.name);//Nike
console.log(anothorPerson.sayName);//ƒ () {console.log(1)};

寄生组合式继承

最理想的继承方式,组合继承的最大问题是会调用两次超类型构造函数,第一次调用超类型函数得到namecolor属性放到了Cat的原型上,第二次调用才放到实例上。结合寄生式继承,我们可以先用对象的方式解决,然后在调用一次超类型函数。

// 重写后的组合继承
function Dogs(name) {
    this.name = name;
    this.color = ['black', 'white'];
}
Dogs.prototype.sayName = function () {
    console.log(this.name);
}

function Cat(name, age) {
    Dogs.call(this, name);
    this.age = age;
}

// 用对象的方法解决
function object(o) {
    function F() {};
    F.prototype = o;
    return new F();
}

function inheritPrototype(Dogs, Cat) {
    // 创建对象
    let prototype = object(Dogs.prototype);
    // 增强对象,指定类
    prototype.constructor = Cat;
    // 指定对象
    Cat.prototype = prototype;

}
// 对象方法完毕

inheritPrototype(Dogs, Cat);
Cat.prototype.sayAge = function (age) {
    console.log(this.age);
}

let cat = new Cat('Bai', 3);
cat.color.push('oringe');
console.log(cat.color); //[ 'black', 'white', 'oringe' ]
cat.sayName(); //Bai
cat.sayAge(); //3

let dog = new Cat('Killy', 5);
console.log(dog.color); //[ 'black', 'white' ]
dog.sayName(); //Killy
dog.sayAge(); //5