使用设计模式创建JavaScript对象

JavaScript中没有类的概念,这给OO的编程带来了一些困难,但并不是不能解决的,使用以下几种的设计模式可以解决JavaScript对象的创建问题。

工厂模式

工厂模式是创建对象的一种抽象方式。

但由于JavaScript不支持类,可以用函数代替,这个函数封装了特定接口创建对象的细节。

function createPerson(name, age, job){
    var i=0;
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function () {
        return name;
    };
    return o;
}

var person1 = createPerson("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");

这样可以快速地创建相似对象,但是用这种方法创建的所有对象都是Object类,也就是你无法分辨它们的类型。

function createDog(name, age, owner){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.owner = owner;
    o.sayName = function () {
        return name;
    };
    return o;
}

var dog = createDog("hali", 2, "Nicholas");
console.log(typeof dog == typeof person1); //true

我觉得你一定不希望狗和你属于同一个类型。

构造函数模式

构造函数是很多拥有class的编程语言的做法。

我们希望,使用JavaScript也能实现构造函数。

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function () {
        console.log(this.name);
    };
}

let person1 = new Person("Nicholas", 29, "Software Engineer");
let person2 = new Person("Greg", 27, "Doctor");
console.log(typeof person1); //Person

现在,你创建的类终于有了和函数同名的类型了。

这个函数没有返回值,并且和很多编程语言一样,使用了new来创建对象。

任何函数,只要使用了new来创建,那么就是构造函数,构造函数本质上也是函数。

但依然有一个问题,同一个类之间无法实现代码的共用。

比如sayName需要被创建两次,第一次在创建person1时,第二次在创建person2时。浪费了很多空间。

如何实现公用呢?

原型模式

原型(prototype)是JavaScript很重要的一个概念。

原型模式使得对象实例可以共享属性和方法。

function Person(){
}

Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
    console.log(this.name);
};

var person1 = new Person();
var person2 = new Person();
console.log(person1.sayName() == person2.sayName()); //true

对象的原型是一个什么概念呢?

在这里插入图片描述

Person有一个Prototype,指向Person的原型,原型是另外一个对象。
当JavaScript需要访问属性时,首先在Person找,如果没找到就回去Prototype指向的地方找。

原型中还会存储构造器constructor,指向Person,这幅图里没有明示出来,你也需要知道。

console.log(Person.prototype.constructor) //Person

但是这种方式无法让Person拥有独立的变量,也就是说,person1person2都在使用同一个Prototype指向的对象,那么当person1试图改变name的时候,person2的name也会变。

一种解决方法是屏蔽。

person1.name = "Greg";
console.log(person1.name); //Greg

只要为person1重新定义name属性,就能屏蔽原型的属性,因为之前说过,搜索变量是从构造函数开始的,如果在构造函数中找到了变量也就不会去原型中搜索。

另外,我们还能用对象字面量来简写原型的定义。

比如上面的可以这样写:

Person.prototype = {
    constructor: Person,
    name: "Nicholas",
    age: 29,
    job: "Software Engineer",
    sayName: function(){
        console.log(this.name);
    }
};

构造函数和原型混成模式

既然如此,干脆把一些属性在构造函数中定义,一些在原型中定义好了。

这也是我们一般推荐的。

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
}

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

动态原型模式

混成模式虽然很好,但是需要把Person和Person.prototype定义分开写,可能会遭到一些oo使用者的反对,我们提出一个更好的模式。

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    
    if( typeof Person.prototype.sayName != "function"){
        Person.prototype.sayName = function(){
            console.log(this.name);
        };
    }
    
}

先用一个if判断是否已经存在了,是为了防止重载导致的屏蔽行为。

到这一步为止,终于我们把所有都在Person构造函数内完成了。

寄生构造函数模式

function Person(name, age, job){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function () {
        console.log(name);
    };

    return o;
}

var person1 = new Person("Nicholas", 29, "Software Engineer");
person1.sayName(); //Nicholas

通过new可以看出我们使用了构造函数,但有点奇怪的是,我们依然返回了一个对象。

其实这个对象把构造函数创建的对象覆盖掉了。

这个方法创建的对象类型也是不能分辨的。

稳妥构造函数模式

虽然我们实现了共用的属性和方法,还实现了对象自己的属性和方法,但摆在我们面前最大的问题是访问权限,根据面向对象的设计原则,实例对象的属性不应当能直接访问。

根据这个,设计了一种叫稳妥构造函数模式的方法去创建对象。

function Person(name, age, job){
    var o = new Object();

    o.sayName = function () {
        console.log(name);
    };

    return o;
}

var person1 = Person("Nicholas", 29, "Software Engineer");
person1.sayName(); //Nicholas

无法直接访问name,但可以通过函数访问。

但这个方法无法查看对象的类型。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容