JS“对象”破冰之旅(二)

206 阅读4分钟

上一篇文,我们简要介绍了一下“对象”,比较全面,但比较浅显,如果只是抱着它,并做不了什么事,也写不出足够好的代码,所以我们需要继续深入~

创建对象

前面我们提到创建对象的两种最直观、最简单的方法,即使用new操作符和对象字面量,但试想一下,按照之前的方法,我们需要创建两个类似的对象,应该怎么写?

//person1
var person1 = new Object();
person1.name = "lilei";
person1.age = 18;
person1.job = “teacher”;
person1.sayName = function(){
    alert(this.name);
}

//person2
var person2 = new Object();
person2.name = "hanmeimie";
person2.age = 20;
person2.job = “doctor”;
person2.sayName = function(){
    alert(this.name);
}

这里我们创建了两个对象,显然,它们有着相同的属性和方法,我们重复书写了两遍,如果不是两个,而是多个,那么我们就要重复N遍,不说JS,在任何一门编程语言当中,这种操作都是会被认为是糟糕的,会尽量避免,为解决这个问题,人们开始探索不同的方法和技巧。

工厂模式

工厂模式,是软件设计领域广为人知的一种模式,为什么称为“工厂”,也就是说,我们可以像工厂里生产零件一样,不需要为每个产品单独做一个机器,而是某一类产品可以共用一套机器进行批量生产,所以,它可以用来解决创建多个类似对象的问题,例如:

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

var person1 = createPerson("xiaoli",28,"teacher");
var person2 = createPerson("xiaoliu",29,"doctor");

上面这段代码,使用了一个函数来封装创建对象的细节,能够接收参数来创建一个包含所有必要信息的对象,所以,当需要创建多个类似对象的时候,就不需要把所有东西都再写一遍造成不必要的代码冗余。

但是,它还有一个缺陷,无法知道一个对象的类型。

构造函数

人们一直在探索和追求更好的编码方式,有了工厂模式,仍然不满足,于是又有了许多其他的模式,构造函数便是其一。前面的代码也可以这样写:

function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function(){
        alert(this.name);
    }
}
var person1 = new Person("xiaoli",28,"teacher");
var person2 = new Person("xiaoliu",29,"doctor");

乍一看,二者十分相似,但又有着诸多细微差别:

  • 没有显示定义对象
  • 属性赋值给了this
  • 没有return语句
  • 创建新实例必须使用new操作符

还有另一个小的点,就是构造函数按惯例以大写字母开头,以区别于普通函数(毕竟构造函数也是函数)。 由此以来,这种方式创建对象,就要经历下面四个过程:

  • 创建新对象
  • 函数的作用域给新对象
  • 执行构造函数的代码
  • 新对象诞生

这个对象,它既是Object的实例(所有对象均继承自Object),同时也是Person的实例,自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型,这正是工厂模式所缺少的

上面已经提到,构造函数本身也是函数,所以,你既可以拿来使用new操作符来创建对象,也可以当做一般的函数直接调用之。前者已经介绍完,但后者又将被如何处理?

你应该记得,上一篇文章,我们介绍过一种对象,叫做“单体内置对象”,当你无法找到一个属性或者方法的归属时,就会属于它,构造函数便是如此,比如,像这样:

Person("xiaoli",28,"teacher"); 
window.sayName();//"xiaoli"

当它被作为普通函数调用的时候,其属性和方法都会被添加到window对象上去。

问题又来了,既然构造函数模式是工厂的改良版,那它还有改进空间吗?好像有。

我们说,即使是类似的对象,不同的实例之间属性是会不同的,像姓名、年龄,但方法很可能是相同的,既然方法相同,为什么要在每个对象里面都重新创建一遍?

这个时候,我们会想到一种方法——封装,将方法封装起来,为每个实例共享所共享,就像这样:

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

function sayName(){
    alert(this.name);
}

这个做法,看起来十分聪明,但是,可能有点耍小聪明的嫌疑,它依然存在着某些问题:

  • 我们创建了一个全局函数,可它明明只是提供给Person的实例用的,何来全局可言?
  • 再者,如果对象需要多个方法,我们把所有方法都定义在构造函数外部,零散地存在着,就背离我们封装对象的本意了,这不是我们想要的最终答案。

还有哪些改良或者适合不同场景的方法呢,倒也真不少,比如很常见也很重要的“原型模式”,鉴于文章篇幅已经够长,这篇就到此吧,下一篇我们再俩详细研究之。

一起加油!回见!