对象创建的4种模式

1. 工厂模式

这种模式抽象了创建具体对象的过程,用函数来封装以特定接口创建对象的细节

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

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;
}
const personOne = createPerson('lee', 29, 'Software Engineer')
const personTwo = createPerson('Gred', 24, 'Doctor')

2. 构造函数模式

 (1)与工厂模式相比构造函数模式

  • 没有显示地创建对象
  • 直接将属性和方法赋值给了this对象
  • 没有return语句
  • 区别于非构造函数,构造函数以大写字母开头

(2)要创建Person的新实例,必须使用new操作符

         以这种方式调用构造函数实际上会经历以下4个步骤:

  1. 创建一个新对象
  2. 将构造函数的作用域赋给新对象(因此this就指向了这个新对象)
  3. 执行构造函数的代码(为这个新对象添加属性) 
  4. 返回新对象
function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function() {
        alert(this.name)
    };
}
const personOne = new Person('lee', 29, 'Software Engineer')
const personTwo = new Person('Gred', 24, 'Doctor')

(3)personOne和personTwo分别保存着Person的一个不同的实例。这两个对象都有一个constructor(构造函数)属性,该属性指向Person

console.log(personOne.constructor == Person) // true
console.log(personTwo.constructor == Person) // true

(4)以上例子中创建的所有对象既是Object的实例,同时也是Person的实例,这一点通过instanceof操作符可以得到验证。

console.log(personOne instanceof Person) // true
console.log(personOne instanceof Object) // true
console.log(personTwo instanceof Person) // true
console.log(personTwo instanceof Object) // true

(5)构造函数毕竟也是函数,不存在定义构造函数的特殊语法。任何函数,只要通过new操作符来调用,那它就可以作为构造函数;而任何函数,如果不通过new操作符来调用,那它跟普通函数也不会有什么两样。

// 当作构造函数使用
const person = new Person('lee', 29, 'Software Engineer');
person.sayName(); // 'lee'
 
// 作为普通函数调用
Person('Gred', 24, 'Doctor') // 添加到window
window.sayName(); // 'Gred'
 
// 在另外一个对象的作用域中调用
const o = new Object();
Person.call(o, 'Alice', 27, 'Nurse');
o.sayName(); // 'Alice'

缺点:每个方法都要在每个实例上重新创建一遍。

解决方法:把函数转移到构造函数外。

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = sayName
}
function sayName() {
        alert(this.name)
    };
const personOne = new Person('lee', 29, 'Software Engineer')
const personTwo = new Person('Gred', 24, 'Doctor')

如上,两个实例的sayName共享了一个全局的sayName方法。解决了2个函数做同一件事的问题。但是新问题又来了

在全局作用域中定义的函数实际上只能被某个对象调用,这让全局作用域有点名不副实
如果对象需要定义很多方法,那么就要定义很多全局函数,导致我们这个自定义的引用类型丝毫没有封装性可言。

3. 原型模式

(1) 针对以上构造函数的痛点,原型模式可以解决这些问题。

我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的作用是包含可以由特定类型的所有实例共享的属性和方法。prototype就是通过调用构造函数而创建的那个对象实例的原型对象。使用原型对象的好处就是不用在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中。

function Person() {}
Person.prototype.name = 'lee';
Person.prototype.age = 29;
Person.prototype.job = 'Software Engineer';
Person.prototype.sayName = function() {
    alert(this.name)
};
const personOne = new Person();
personOne.sayName(); // 'lee'
const personTwo = new Person();
personTwo.sayName(); // 'lee'
console.log(personOne.sayName == personTwo.sayName); // true


//简写
function Person(){}
Person.prototype = {
    constructor: Person,
    name: 'lee',
    age: 29,
    job: 'Software Engineer',
    sayName: function(){
        alert(this.name)
    }
};

(2)默认情况下,所有原型对象都会自动获得一个constructor(构造函数)属性这个属性是一个指向prototype属性所在函数的指针。如上例子,Person.prototype.constructor指向Person。

Person.prototype.constructor === Person // true

(3)当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。这个内部属性[ [ Prototype ] ],虽然无法访问到。但是可以通过isPrototypeOf()方法来确定对象之间是否存在这种关系。如果[ [ Prototype ] ]指向调用isPrototypeOf()方法的对象(Person.prototype),那么这个方法就返回true。

console.log(Person.prototype.isPrototypeOf(personOne)); // true
console.log(Person.prototype.isPrototypeOf(personTwo)); // true

(4)Object.getPrototypeOf() 这个方法返回[ [ Prototype ] ]的值

console.log(Object.getPrototypeOf(personOne) == Person.prototype); // true
console.log(Object.getPrototypeOf(personOne).name); // 'lee'
console.log(Object.getPrototypeOf(personTwo) == Person.prototype); // true
console.log(Object.getPrototypeOf(personTwo).name); // 'lee'

(5)使用delete可以删除实例中的属性

personOne.name = 'Alice';
console.log(personOne.name) // 'Alice'  来自实例
console.log(personTwo.name) // 'lee'  来自原型 
delete personOne.name;
console.log(personOne.name) // 'lee'  来自原型 

(6)hasOwnProperty()方法可以检测一个属性是存在于实例中还是存在于原型中。如果该属性存在于实例中,则返回true。

personOne.name = 'Alice';
console.log(personOne.hasOwnProperty('name')) // true
console.log(personTwo.hasOwnProperty('name')) // false

(7)in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中。

personOne.name = 'Alice';
console.log('name' in personOne) // true name在实例中
console.log('name' in personTwo) // true name在原型中

(8)getOwnPropertyNames()方法得到所有实例属性,无论它是否可枚举。

const keys = Object.getOwnPropertyNames(Person.prototype);
console.log(keys); // ['constructor','name','age','job','sayName']

(9)可以随时给原型添加属性和方法,但是如果是重写整个原型对象,就相当于把原型修改为另外一个对象就等于切断了构造函数与最初原型之间的联系。

function Person(){}
const friend = new Person();
Person.prototype = {
    name: 'lee',
    age: 29,
    job: 'Software Engineer',
    sayName: function(){
        alert(this.name)
    }
};
friend.sayName(); // error
//因为这时的friend指向的是以前的Person,而以前的是没有sayName方法的,所以报错。
//而且这里旧的原型对象和新的原型对象的指针都指向Person

缺点:

(1)所有实例在默认情况下都将取得相同的属性值。(上面例子中定义的name,age等)(2)原型中存在引用类型,会导致每一个实例共享这个引用类型。如下例子:每一个实例中的friends会因为其中一个实例的修改,都共享了这个修改后的结果。

function Person(){}
Person.prototype = {
    constructor: Person,
    name: 'lee',
    age: 29,
    friends: ['Jack', 'Bob'],
    job: 'Software Engineer',
    sayName: function(){
        alert(this.name)
    }
};
const personOne = new Person();
const personTwo = new Person();
personOne.friends.push('Ivan');
console.log(personOne.friends); // ['Jack', 'Bob', 'Ivan']
console.log(personTwo.friends); // ['Jack', 'Bob', 'Ivan']
console.log(personOne.friends === personTwo.friends); // true

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

构造函数模式用于定义实例属性,而原型模式用于定义方法和共享属性。每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存。

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ['Jack', 'Bob'];
}
Person.prototype = {
    constructor: Person,
    sayName: function() {
        alert(this.name);
    }
    
}
const personOne = new Person('lee', 29, 'Software Engineer')
const personTwo = new Person('Gred', 24, 'Doctor')
personOne.friends.push('Van');
console.log(personOne.friends); // ['Jack', 'Bob', 'Ivan']
console.log(personTwo.friends); // ['Jack', 'Bob']
console.log(personOne.friends === personTwo.friends); // false
console.log(personOne.sayName === personTwo.sayName); // true

相关推荐

  1. 对象创建4模式

    2024-06-11 01:00:02       10 阅读
  2. 工厂方法模式:灵活创建对象实例

    2024-06-11 01:00:02       14 阅读
  3. @Scheduled注解创建定时任务 3 模式

    2024-06-11 01:00:02       9 阅读
  4. 创建单例模式方式

    2024-06-11 01:00:02       9 阅读
  5. 23设计模式——创建模式

    2024-06-11 01:00:02       10 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-06-11 01:00:02       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-11 01:00:02       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-11 01:00:02       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-11 01:00:02       20 阅读

热门阅读

  1. centos中sudo: apt: command not found

    2024-06-11 01:00:02       10 阅读
  2. k8s-CCE使用node节点使用VIP--hostNetwork&hostPort

    2024-06-11 01:00:02       8 阅读
  3. 2244. 完成所有任务需要的最少轮数

    2024-06-11 01:00:02       11 阅读
  4. debugger(四):源代码

    2024-06-11 01:00:02       7 阅读
  5. 第四章 Three.js 绘制基本几何体

    2024-06-11 01:00:02       9 阅读
  6. Spark RDD算子

    2024-06-11 01:00:02       11 阅读
  7. MySQL中的一行记录是怎么存储的

    2024-06-11 01:00:02       7 阅读
  8. 部署higress到华为云

    2024-06-11 01:00:02       10 阅读
  9. select模块

    2024-06-11 01:00:02       10 阅读