JS 创建对象与继承

132 阅读4分钟

1 创建对象

1.1 工厂模式

function createPerson(name, age){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.sayHi = function() {
        alert('hi');
    }
    return o;
}

优点:解决了创建多个相似对象的问题;

缺点:没有解决对象识别的问题,怎样知道一个对象的类型;

1.2 构造函数模式

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.sayHi = function() {
        alert('hi');
    }
}

ver person = new Person('zac', 100);

优点:可以将实例识别为一种特定的类型,此处是Person类型;

缺点:构造函数中的每个方法在创建实例的时候都会再创建一次,在js中函数方法也是对象,没有办法做到函数复用;

new 的作用

  1. 创建一个新的对象
  2. 将构造函数的作用域赋给新对象(this指向新对象)
  3. 执行构造函数中的代码(给新对象添加属性)
  4. 返回新对象

1.3 原型模式

每个函数在js中就是对象,都会有一个prototype属性,指向该原型对象,这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法;

每个对象还会有__proto__,指向其构造函数的原型对象;

function Person() {}
Person.prototype.name = 'zac';
Person.prototype.sayHi = function() {
    alert('hi');
}
var person = new Person();

优点:原型中的所有属性是被很多实例共享的,对于函数和包含基本值的属性非常合适;

缺点:一方面原型模式省略了为构造函数传递初始化参数的环节,所有实例在默认情况下属性是一样的,另一方面引用值属性被所有实例共享,之间的修改会互相影响;

1.4 组合使用构造函数模式和原型模式

function Person(name) {
    this.name = name;
    this.friends = ['bill', 'john'];
}
Person.prototype = {
    constructor: Person,
    sayHi: function() {
        alert('hi');
    }
}

1.5 动态原型模式

把所有信息封装在构造函数中,通过在构造函数中初始化原型对象,统一通过检查某个应该存在的方法是否存在来决定是否要初始化原型

function Person(name, age) {
    this.name = name;
    this.age = age;
    if (typeof this.sayHi !== 'function') {
        Person.prototype.sayHi = function() {
            alert('hi');
        }
    }
}

判断部分的代码在初次调用构造函数时才会执行;

1.6 寄生构造函数模式

创建一个函数,该函数仅仅是封装创建对象的代码,然后在返回新创建的对象,很像典型的构造函数; 用来在特殊情况下为对象创建构造函数,如创建一个具有额外方法的特殊数组;

function SpecialArray() {
    var values = new Array();
    values.push.apply(values, arguments);
    values.toPipedString = function() {
        return this.join('|');
    };
    return values;
}

缺点:返回的对象与构造函数没有关系,不能使用instanceof操作符确定对象类型;

1.7 稳妥构造函数模式

没有公共属性,方法也不引用this对象。适合在一些安全的环境中(禁止使用this和new), 和寄生构造函数类似,不同在于创建对象的实例方法不使用this,不使用new操作符调用构造函数;

function Person(name) {
    var o = new Object();
    
    o.sayName = function() {
        alert(name);
    }
    return o;
}

2 继承

2.1 原型链

利用原型让一个引用类型继承另一个引用类型的属性和方法;

function SuperType() {
    this.superProto = true;
}

function SubType() {
    this.subProto = false;
}

SubType.prototype = new SuperType();

问题

  1. 包含引用类型值的原型,原先的实例属性变成现在的原型属性,被所有实例共享;
  2. 在创建子类型时不能向超类型的构造函数中传递参数;

2.2 借用构造函数

函数只不过是在特定环境中执行代码的对象,通过使用apply和call方法可以在将来新创建的对象上执行构造函数;

function SuperType(name) {
    this.name = name;
}
function SubType() {
    SuperType.call(this, 'bill');
    this.age = 100;
}
var temp = new SubType();

优点:可以传递参数给父类型;

缺点:引用类型问题和函数无法复用,每次调用父构造函数时都创建函数;

2.3 组合继承

原型链和借用构造函数技术结合;

function SuperType(name) {
    this.name = name;
}
SuperType.prototype.sayHi = function() {
    alert('hi');
}
function SubType(name, age) {
    SuperType.call(this, name);
    this.age = age;
}

SubType.prototype = new SuperType();
SubType.prototype.constructor = SuperType;
SubType.prototype.sayHaha = function() {
    alert('haha');
}

2.4 原型式继承

借助原型可以基于已有对象创建新对象,同时还不必因此创建新对象。

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

object 等同与 Object.create();

2.5 寄生式继承

寄生式继承是与原型式继承紧密相关的一种思路,创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后返回对象;

function createAnthoer(original){
    var clone = object(original);
    clone.sayHi = function() {
        alert('hi');
    };
    return clone;
}

和原型式继承差不多,核心就是把内容和增强内容封装在一个工厂函数中;

2.6 寄生组合式继承

组合继承虽然好,但是无论在什么情况下都会调用两次超类型的构造函数:一次是在创建子类型原型时,一次是在子类型构造函数内部;

function inheritPrototype(subType, superType) {
    var prototype = object(superType.prototype);
    prototype.constructor = subType;
    subType.prototype = prototype;
}

function SuperType(name) {
    this.name = name;
    this.colors = ['red', 'blue'];
}

SuperType.prototype.sayName = function() {
    alert(this.name);
}

function SubType(name, age) {
    SuperType.call(this, name);
    this.age = age;
}

inheritPrototype(SubType, SuperType);

SubType.prototype.sayAge = function() {
    alert(this.age);
}