js-创建对象的几种方式

本文探讨了从传统工厂模式到寄生构造函数的六种对象创建方式,包括工厂模式、构造函数、原型模式、构造函数+原型模式、动态原型和寄生构造函数。作者揭示了每种模式的优势与不足,以及如何结合使用以提升代码复用性和类型标识性。
摘要由CSDN通过智能技术生成

面对单个不可复用的对象创建时,我们往往会直接使用对象字面量来创建。如下,这是最简单的对象创建方式,但是面对创建大量属性相似的对象时,通过字面量的方式去逐一创建,需要足够的耐心,最后结果看起来还很呆。

//字面量方式创建对象
const obj = {
    name: 'lisi',
    age: 16
}

es6前并没有class的概念,而且即使是es6中的class,其也只是依赖原型的语法糖而已,其本质还是引用而非拷贝。所以我们通过函数来模拟,从而产生可复用对象,这也是之前常用的方式。产生可复用对象的方式一共有如下几种:工厂模式、构造函数模式、原型模式、构造函数+原型模式、动态原型、寄生构造函数模式。

目录

1.工厂模式

2.构造函数

3.原型模式

4.构造函数+原型模式

5.动态原型

6.寄生构造函数模式


1.工厂模式

工厂模式通过函数封装创建对象的细节或者说过程,在函数的最后返回新创建的对象。再通过调用该函数实现复用目的。

function createPerson(name, age) {
    //创建对象,并添加属性
    let o = new Object()
    o.name = name
    o.age = age
    o.sayName = function () {
        console.log(this.name);
    }
    //返回该对象
    return o
}
​
let p1 = createPerson('zs', 13)
let p2 = createPerson('lisi', 15)
console.log(p1 instanceof createPerson) //false

问题:该模式虽然解决了创建多个类似对象的问题,但是无法解决新创建的对象类型问题(打个不恰当的比喻,就是你爸妈都姓苏,然后创造了你这个实例,你也姓苏,‘苏’就相当于你的类型。而通过工厂模式创造出来的实例,不配拥有姓名)

 

2.构造函数

js中有许多原生的构造函数如:Object()Array()等,我们一般通过new的方式使用原生的构造函数创建对象或数组,当然使用字面量还是最方便的方式,构造函数默认首字母大写,便于区分。构造函数就是以函数的形式为自己的对象类型定义属性和方法。

function Person(name, age) {
    this.name = name
    this.age = age
    this.sayName = function () {
        console.log(this.name);
    }
}
​
let p1 = new Person('zs', 13)
let p2 = new Person('lisi', 15)
​
console.log(p1 instanceof Person) //true
console.log(p1.sayName === p2.sayName) //false

构造函数内部与工厂模式的内部基本相同,不通点在于:

  1. 没有显示的创建对象

  2. 属性与方法直接赋值给this

  3. 没有return

在创建实例的过程中使用new操作符,通过new会执行一下几步:

  1. 在内存中创建一个新对象

  2. 将新对象的原型指向构造函数的prototype的值

  3. 将新对象赋值给构造函数内部的this(this指向新对象)

  4. 执行构造函数内部代码(给新对象添加属性和方法)

  5. 如果构造函数返回非空对象,则返回该对象;否则返回刚创建的新对象

因为构造函数使用new操作符,会执行上述的步骤,这也是为什么构造函数与工厂模式内部代码会有以上3点不同。

问题:构造函数虽然确保实例被标识为特定类型,如上述代码中p1类型是Person。但是构造函数定义的方法会在每个实例上都创建一遍。所以p1p2上的sayName不属于同一个Function的实例。因为sayName是做同一件事,所以没有创建不同方法的必要。

 

3.原型模式

每个函数都有一个prototype属性,该属性值是个对象,该对象中包含共享的方法和属性。这个对象就是使用构造函数创建出的对象的原型。使用原型对象的好处是,在该对象上的定义的方法和属性会被实例共享。

const Person = function () {}
​
Person.prototype.name = 'lisi'
Person.prototype.age = 15
Person.prototype.sayName = function () {
    console.log(this.name);
}
​
const p1 = new Person()
const p2 = new Person()
​
console.log(p1.sayName === p2.sayName); //true

原型模式解决了上述构造函数,每个实例上方法重新创建的问题,但是原型的问题同样存在

//以上面代码为基础
Person.prototype.house = {
    size: 120,
    floor: 6,
}
console.log(p1.house, p2.house); //{size: 120, floor: 6} {size: 120, floor: 6}
​
p1.house.floor = 9
​
console.log(p1.house, p2.house); //{size: 120, floor: 9} {size: 120, floor: 9}

问题:1. 原型弱化了向构造函数传递初始化参数的能力,导致所有实例默认获取相同的属性值 2.因为所有属性所有实例共享,当原型上某个属性值是引用类型的时候,一个实例修改该属性值,会导致所有实例上该属性值都发生改变,这是原型模式的最大问题。

 

4.构造函数+原型模式

通过以上三种方式,我们发现都存在一定的问题:

  1. 工厂模式的类型缺失

  2. 构造函数的方法多次创建

  3. 原型模式的引用属性修改

我们不难发现,构造函数与原型模式之间刚好可以弥补对方的缺点,所以有了比较普遍的构造函数+原型。将属性存放在构造函数中,将方法挂载到原型上。

function Person (name, age) {
    this.name = name
    this.age = age
    //当然这里为了方便直接写死
    this.house = {
        size: 120,
        floor: 6
    }
}
​
Person.prototype.sayName = function () {
    console.log(this.name);
}
​
const p1 = new Person('lisi', 12)
const p2 = new Person('zs', 15)
​
console.log(p1.sayName === p2.sayName); //true
p1.house.size = 100
console.log(p1.house, p2.house); //{ size: 100, floor: 6 } { size: 120, floor: 6 }

 

5.动态原型

是不是感觉构造函数+原型的方式已经够完美了,但是依旧存在不足,即每次创建实例的过程中,不管原型上是否有某个方法,都会向原型中添加方法,例如以上例子的sayName()方法。在第一次实例化后,原型上就已经挂载了sayName(),但是在第二次实例化后,依旧会再次将sayName()挂载到原型上去。之后挂载方法的操作显然是多余的。所以动态原型就是解决这个问题而产生的。

const Person = function (name, age) {
    this.name = name
    this.age = age
    
    if (typeof this.sayName !== 'function') {
        Person.prototype.sayName = function () {
            console.log(this.name);
        }
    }
}
​
const p1 = new Person('lisi', 12)
const p2 = new Person('zs', 15)
​
console.log(p1.sayName === p2.sayName); //true

由于new操作时,会执行构造函数中的代码,通过if判断是否有sayName方法,如果没有则挂载,如果有了就不会挂载,所以一般而言,创建第一个实例时会执行if中的代码,之后再创建实例就不会执行挂载操作了。

 

6.寄生构造函数模式

寄生构造函数实现与工厂模式一模一样,并且作用也一样,只是在创建对象时使用了new操作符,而这个new并没有实际的效果。new实际的作用只是起到语义的效果。

function createPerson(name, age) {
    //创建对象,并添加属性
    let o = new Object()
    o.name = name
    o.age = age
    o.sayName = function () {
        console.log(this.name);
    }
    //返回该对象
    return o
}
​
let p1 = new createPerson('zs', 13)
let p2 = new createPerson('lisi', 15)
​
console.log(p1 instanceof createPerson) //false

再看一个例子,便于理解

function MyArray(...params) {
    const newArr = new Array(...params)
    newArr.toPipedString = function(){
        return this.join("|");
    }
    return newArr
}
​
const a1 = new MyArray(2,3,4)
console.log(a1.toPipedString());

上面一段代码,作用就是为创建自己的数组构造函数,并扩展了一个方法。 寄生构造函数的本意就是扩展js中原有的构造函数,例如Array()Date(),但是直接在原型上扩展会造成原有构造函数被污染。所以通过寄生构造函数的模式来扩展。并为了与原有构造函数保持一致通过new操作符,甚至首字母大写的写法。当然你也可以通过工厂函数去扩展原有构造函数,但是这并不优雅。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值