JS-创建对象模式-工厂模式、构造函数模式、原型模式、组合模式、动态原型模式、寄生构造函数模式、稳妥构造函数模式

  • 工厂模式
  • 构造函数模式
  • 原型模式
    • 原型与原型链
    • 构造函数、原型和实例的关系
    • 原型的动态性
    • 原型模式的缺点
  • 组合使用构造函数模式和原型模式(用得最多)
  • 动态原型模式
  • 寄生构造函数模式
  • 稳妥构造函数模式

本文首发于我的个人博客 ruiqima.com
原文链接:JS 创建对象模式

工厂模式

本质:用函数封装创建对象的细节。
特点:

  • 显式创建了对象(如Object
  • 不使用new
  • return语句
  • 缺点:没有解决对象识别问题,即怎样知道一个对象的类型。(如代码倒数第二行的false
function createPerson(name, age) {
  var o = new Object()
  
  o.name = name
  o.age = age
  o.sayName = function () {}
  
  return o
}

var person = createPerson('Joker', 23)

console.log(person instanceof createPerson) 	// false
console.log(person.__proto__)	 //{}

构造函数模式

特点:

  • 没有显式创建对象
  • 直接将属性和方法赋给了this对象
  • 没有return语句
  • 创建新实例需使用new操作符
  • 可以解决工厂模式不能确定对象类型的问题
  • 缺点:会导致不同实例中的方法不是同一个Function实例,导致不同的作用域链和标识符解析。(同一个名为sayName 的方法在不同实例中是不同Function对象)(如代码倒数第三行的false
function Person(name, age) {
  this.name = name
  this.age = age
  this.friends = ['1', '2']
  this.sayName = function () {}
}

var person1 = new Person('Joker', 23)
var person2 = new Person('Joker1', 24)

person1.friends.push('3')
console.log(person1.friends) 	//[ '1', '2', '3' ]
console.log(person2.friends)	 //[ '1', '2' ]
console.log(person1.sayName === person2.sayName) 	//false

console.log(person1 instanceof Person) 	//true
console.log(person1.__proto__)	 //Person {}

原型模式

原型与原型链

我们创建的每个函数都有一个prototype属性,即原型属性。prototype属性是一个指针,指向一个包含可以由特定类型的所有实例共享的属性和方法的对象。

prototype就是通过调用构造函数而创建的那个对象实例的原型对象,使用原型对象的好处是可以让所有对象实例共享包含的属性和方法。

构造函数、原型和实例的关系

每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。

什么叫让所有对象实例共享包含的属性和方法

直接在对象实例上定义方法的缺点是,不同对象实例中包含的方法不是同一个Function实例——在ECMAScript中的函数是对象,每定义一个函数,就是实例化一个对象。以这种方式创建函数,会导致不同的作用域链和标识符解析。

而使用原型对象则可以做到让所有对象实例共享包含的属性和方法。

function Person() {}
Person.prototype={
    name :  'defaultName',
    age = 'defaultAge'
}
Person.prototype.sayName = function () {
  console.log(this.name)
}

var person1 = new Person()
var person2 = new Person()

//使用原型对象的好处,可以让所有对象实例共享它所包含的属性和方法
console.log(person1.sayName == person2.sayName) // true

原型链
JS-创建对象模式-工厂模式、构造函数模式、原型模式、组合模式、动态原型模式、寄生构造函数模式、稳妥构造函数模式_第1张图片
有关方法

ObjectName.prototype.isPrototypeOf(instanceName)
实例instanceName的原型是否是ObjectName.prototype。

Object.getPrototypeOf(instanceName)
获取实例instanceName的原型的名称。

var person = new Person()
console.log(Person.prototype.isPrototypeOf(person))		// true
console.log(Object.getPrototypeOf(person) == Person.prototype)			//true

原型的动态性

对原型对象的修改会及时体现在实例上,就算在实例创建以后。

var person1 = new Person()
Person.prototype.sayHi = function () {
  console.log('sayHi')
}
person1.sayHi()		// sayHi

但是,如果重写整个原型对象,情况则会不一样。

var person2 = new Person()
Person.prototype = {
  sayName: function () {
    console.log('sayName')
  },
}
 person2.sayName()	// person2.sayName is not a function

原因如下图,初始化person2时与原来的Person.prototype有联系,重写了Person.prototype之后相当于新new了一个出来,与原来那个已经不是同一个了。

JS-创建对象模式-工厂模式、构造函数模式、原型模式、组合模式、动态原型模式、寄生构造函数模式、稳妥构造函数模式_第2张图片
在此之后,如果再新建对象实例,则会与新new的prototype建立联系,会拥有新prototype上面的属性,但不会拥有原prototype上的属性。

var person3 = new Person()
console.log(person3.name)	// undefined
person3.sayName()		// sayName

JS-创建对象模式-工厂模式、构造函数模式、原型模式、组合模式、动态原型模式、寄生构造函数模式、稳妥构造函数模式_第3张图片

原型模式的缺点

本质是原型中的所有属性被很多实例共享的问题。对于是基本类型值的属性不要紧,但是对于引用类型属性,更改一个实例上的该属性值,会导致所有实例中的该属性值都被更改——因为改引用类型值是存在于对象原型Prototype上的,而不是对象实例中。

function Person() {}
Person.prototype = {
  arrays: ['1', '2'],
}
var person1 = new Person()
var person2 = new Person()
person1.arrays.push('3')

console.log(person1.arrays)		// [ '1', '2', '3' ]
console.log(person2.arrays)		// [ '1', '2', '3' ]

因此,很少单独使用原型模式。

组合使用构造函数模式和原型模式(用得最多)

为解决上述问题,组合使用构造函数模式与原型模式。

优点:构造函数模式用于定义实例,原型模式用于定义方法和共享的属性。每个实例都会有自己的一份实例属性的副本,又共享着对方法的引用,最大限度的节省了内存。这种混成模式还支持向构造函数传递参数。

// 1. 构造函数模式
function Person(name, age) {
  this.name = name
  this.age = age
  this.friends = ['A', 'B']
}

// 2. 原型模式
Person.prototype = {
  constructor: Person, 		//会让constructor变为可枚举的属性
  sayName: function () {
    console.log(this.name)
  },
}

var person3 = new Person('Joker', 23)
var person4 = new Person('Rekoj', 32)

// 共用方法(通过原型定义)
// 但引用类型的属性不会相互干扰(通过构造函数模式定义)
person3.friends.push('fox')
console.log(person3.friends)		// [ 'A', 'B', 'fox' ]
console.log(person4.friends)		// [ 'A', 'B' ] 
console.log(person3.friends === person4.friends)		// false
console.log(person3.sayName === person4.sayName)		//true

动态原型模式

原理跟组合使用构造函数模式和原型模式一样,但是把原型定义操作封装在了构造函数中。本质是通过构造函数初始化原型。

在构造函数中的if语句是为了判断是否在该对象上定义了sayName的方法——也就是说,当Person的原型上还未定义sayName方法时(如第一次执行new Person(...)语句时),if语句会执行,即进行原型的初始化;一旦Person的原型被初始化过(如第二次执行new Person(...)语句时),根据原型的动态性,sayName已被定义,不会再进入if语句中。

function Person(name, age, job) {
  this.name = name
  this.age = age
  this.friends = ['A', 'B']
  
  if (typeof this.sayName != 'function') {		// !important
    Person.prototype.sayName = function () {
      console.log(this.name)
    }
  }
}

var person3 = new Person('Joker', 23, 'tricker')
var person4 = new Person('Rekoj', 32, 'rekcirt')

person3.friends.push('fox')
console.log(person3.friends)
console.log(person4.friends)
console.log(person3.friends === person4.friends)
console.log(person3.sayName === person4.sayName)

且如果原型上需要定义多个方法和属性,也只需要一个if语句判断,选其中一个属性或方法判断即可。

要特别注意的是,if语句中的Person.prototype不能使用对象字面量重写原型,原因之前说过,会切断现有实例与新原型之间的联系。

寄生构造函数模式

使用情况:如,想创建一个具有额外方法的特殊数组,由于不能直接写修改Array构造函数,因此可以使用这个模式。

特点:

  • 不能依赖instanceof操作符来确定对象类型(如下面的例子,array是instanceof Array,但不是SpecialArray)
  • 使用new操作符
function SpecialArray() {
  var values = new Array()
  values.push.apply(values, arguments)
  values.toSpecialString = function () {
    return this.join('+')
  }
  return values
}

var array = new SpecialArray('a', 'b', 'c')
console.log(array.toSpecialString()) 	// a+b+c

console.log(array instanceof SpecialArray) 	// false
console.log(array.__proto__) 	// []
console.log(array instanceof Array)		//	true

稳妥构造函数模式

特点:

  • 适用于一些安全的环境中(如会禁止使用thisnew),或防止数据被其它应用程序改动(如只允许通过方法访问到属性值)
  • 新创建对象的实例方法不使用this
  • 不适用new操作符
  • 不能依赖instanceof操作符来确定对象类型(跟寄生构造函数模式类似)
function Person(name, age) {
  var o = new Object()

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

var person = Person('Joker', 23)
console.log(person.name) 		//undefined
person.sayName() 		//Joker

本文首发于我的个人博客ruiqima.com。
原文链接:https://www.ruiqima.com/zh/post/js-createobj/
本博客内文章除特别声明外均为原创,采用CC BY-NC-SA 4.0 许可协议进行许可。超出CC BY-NC-SA 4.0 许可协议的使用请联系作者获得授权。

你可能感兴趣的:(javascript)