JavaScript简餐——创建对象的三种模式


前言

写本《JavaScript简餐》系列文章的目的是记录在阅读学习《JavaScript高级程序设计(第4版)》一书时出现的各个知识点。虽是对读书的笔记和总结,但是希望它轻量、简洁、犀利,不会引起阅读疲劳,可以在碎片化时间和闲暇之余轻巧地沐浴一下知识点。每篇文章只针对一个小部分进行讲解式的梳理,来达到个人复习总结和分享知识的目的。


虽然使用Object构造函数或者对象字面量可以方便地创建对象,但这些方式也有明显的不足:创建具有同样接口的多个对象需要重复写很多代码。比如我要创建三个person对象:person1、person2、person3,那我需要像下面这样来创建。(以创建Object实例的方法为例)
let person1 = new Object();          //每个对象都有自己的属性和方法,显得很臃肿有没有!
person1.name = 'Jack';
person1.age = 29;
person1.job = 'Soft Engineer';
person1.sayname = function () {
    console.log(this.name);
}
    
let person2 = new Object();
person2.name = 'Bob';
person2.age = 30;
person2.job = 'Soft Engineer';
person2.sayname = function () {
    console.log(this.name);
}    

let person3 = new Object();
person3.name = 'Lucy';
person3.age = 25;
person3.job = 'Soft Engineer';
person3.sayname = function () {
    console.log(this.name);
}

像创建上面这样都有着同样的属性和方法的对象,需要创建三遍对象实例并且赋值属性,正如开头所说的一样,要重复编写很多代码。如果要创建成百上千个对象,那岂不是…很恐怖有木有!所以他来了。


一、工厂模式

工厂模式是一种设计模式,广泛应用于软件工程领域,用于抽象创建特定对象的过程。下面的例子展示了一种按照特定接口创建对象的模式。上代码:
function createPerson (name, age, job) {   // 接受三个参数
    let obj = Object;
    obj.name = name;
    obj.age = age;
    obj.job = job;
    obj.sayname = function () {
        console.log(this.name);
    };
    return obj;  //这里返回一个创建好的对象
}

let person1 = createPerson('Jack', 29, 'SoftWare Engineer');  // 利用不同参数创建多个对象
let person2 = createPerson('Bob', 30, 'SoftWare Engineer');
let person3 = createPerson('Lucy', 25, 'SoftWare Engineer');
这里,函数createPerson()接受三个参数,根据这几个参数构建了一个包含Person信息的对象。可以用不同参数调用这个函数,每次都会返回包含属性和方法的对象,正如上面用不同参数创建了person1、person2、person3三个对象实例一样。这种工厂模式虽然可以解决创建多个类似对象的问题,但没有解决对象标识问题,即新创建的对象是什么类型的我们不知道。所以构造函数模式来了。

二、构造函数模式

ECMAScript中的构造函数是用于创建特定类型对象的。像Object就是一个原生构造函数。当然也可以自定义构造函数,以函数的形式为自己的对象类型定义属性和方法。前面的例子用构造函数模式可以这么写,就可以看作实例都是Person类型的了。
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('Jack', 29, 'SoftWare Engineer');
let person2 = new Person('Bob', 30, 'SoftWare Engineer');
let person3 = new Person('Lucy', 25, 'SoftWare Engineer');
这个例子中,Person()构造函数替代了createPerson()工厂函数。实际上Person()内部的代码跟createPerson()内部基本是一样的,不过有如下几个区别:
  1. 没有显示地创建对象。
  2. 属性和方法直接赋值给了this。
  3. 没有return。
相比于工厂模式,构造函数可以确保实例被标识为特定类型。比如这里,实例都是Person类型。不过,构造函数的问题也很明显。其问题主要在于定义的方法会在每个实例上都创建一遍。这就导致了尽管不同的实例都是Person类型的实例,但是其实例上的函数同名却不相等。也就是说:
console.log(person1.sayname == person2.sayname)  // false

当然像下面这样把函数定义转移到构造函数外部是一个解决方法。

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayname = sayname;           //将方法转移到了外面(全局作用域)
}

function sayname() {
    console.log(this.name);
}
这样虽然解决了相同逻辑的函数重复定义的问题,但全局作用域也因此被搞乱了,因为sayname()函数实际上只能在一个对象上调用。如果这个对象需要多个方法,那么就要在全局作用域下定义多个函数,这会导致自定义类型引用的代码不能很好的聚集在一起。所以原型模式来了。

三、原型模式

无论何时,只要创建一个函数,就会为这个函数创建一个prototype属性,这个属性的值是一个对象(像这样:prototype:{...})。其上面包含应该由特定引用类型的实例共享的属性和方法。实际上与这个prototype属性绑定的值也就是原型对象,是通过构造函数创建的对象的原型。(关于原型下篇文章会再来详细说一说)使用原型对象的好处是,在它上面定义的属性和方法可以被对象实例共享。原来在构造函数中直接赋值给对象实例的值,可以直接复制给它们的原型,如下所示:
function Person() {};
Person.prototype.name = 'Jack';
Person.prototype.age = 29;
Person.prototype.job = 'SoftWare Engineer';
Person.prototype.sayname = function () {
    console.log(this.name);
}

let person1 = new Person();
let person2 = new Person();

console.log(person1.sayname());  // 'Jack'
console.log(person2.sayname());  // 'Jack'
console.log(person1.sayname == person2.sayname)  // true
这个例子中,所有属性和sayname()方法都直接添加到了Person的prototype属性上,构造函数体中什么也没有。但这样定义后,调用构造函数创建的新对象仍然拥有相应的属性和方法。与构造函数模式不同,使用这种原型模式定义的属性和方法是由所有实例共享的。因此person1和person2访问的都是相同的属性和相同的sayName()函数。

总结

以上就是今天要讲的内容,本文介绍了三种创建对象的模式以及各个模式的特性和存在的问题。其中要深入理解第三种原型模式还要理解一下原型的本质。下一篇我们来讲一下什么是原型。撒花~
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值