工厂模式

function createPerson(name, age){
var o = new Object(); // 创建一个对象
o.name = name;
o.age = age;
o.sayName = function(){
console.log(this.name)
}
return o; // 返回这个对象
}
var person1 = createPerson('ccc', 18)
var person2 = createPerson('www', 18)

工厂函数的问题:

工厂模式虽然解决了创建多个相似对象的问题,但是没有解决对象识别问题(即怎样知道一个对象的类型)。如下

person1 instanceof createPerson      // --> false
person1 instanceof Object // --> true

构造函数模式

function Person(name , age){
this.name = name;
this.age = age;
this.sayName = function(){
console.log(this.name)
}
} var person1 = new Person('ccc', 18)
var person2 = new Person('www', 18)
person1.sayName() // --> 'ccc'

person1 和person2 分别保存着Person的一个不同的实例。这两个对象都有一个constructor(构造函数)属性指向Person。这正是构造函数模式胜过工厂模式的地方。如下:

console.log(person1 instanceof Person)      // --> true
console.log(person1 instanceof Object) // --> true
console.log(person2 instanceof Person) // --> true
console.log(person2 instanceof Object) // --> true

构造函数模式与工厂模式的区别:

  1. 没有显式的创建对象
  2. 直接将属性和方法赋给了this对象
  3. 没有return 语句

要创建Person的新实例,必须使用new操作符。以这种方式调用构造函数实际上会经历一下4个步骤:

  1. 创建一个新对象
  2. 将构造函数的作用域赋给新对象(因此this就指向了这个新对象)
  3. 执行构造函数中的代码(为这个新对象添加属性)
  4. 返回新对象

构造函数的问题:

使用构造函数的重要问题,就是每个方法都要在每个实例上重新创建一遍。person1和person2中都有一个名为sayName()的方法,但那两个方法不是同一个Function实例。因为在ECMAscript中函数就是对象,因此每定义一个函数,也就是实例化了一个对象。从逻辑角度上讲,此时的构造函数也可以你这样定义:

function Person(name , age){
this.name = name;
this.age = age;
this.sayName = new Function('console.log(this.name)') // eslint: The Function constructor is eval. (no-new-func)
}

这会导致,创建的不同的实例上的同名函数是不相等的,比如:console.log(person1.sayName() === person2.sayName()) // -->false然而创建两个完全相同的任务的Function实例是没有必要的。可以通过把函数定义转移到构造函数外部来解决这个问题。

function Person(name , age){
this.name = name;
this.age = age;
this.sayName = sayName
}
function sayName(){
console.log(this.name)
}
var person1 = new Person('ccc', 18)
var person2 = new Person('www', 18)

这样,由于sayName包含的是一个指向函数的指针,因此person1和person2对象就共享了在全局作用域中定义的同一个sayName()函数。这样做确实解决了两个函数做同一件事的问题,可是新问题又来了:在全局作用域中定义的函数实际上只能被某个对象调用,这让全局作用域有点名不副实。

带来的新问题:

如果对象需要定义很多方法,那么就要定义很多个全局函数,于是我们这个自定义的引用类型就丝毫没有封装性可言。

原型模式

关于原型,原型链内容不在此描述,只讨论原型设计模式

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

function Person(){
}
Person.prototype.name = 'ccc'
Person.prototype.age = 18
Person.prototype.sayName = function(){
console.log(this.name)
}
var person1 = new Person()
person1.sayName() // --> ccc var person2 = new Person()
person2.sayName() // --> ccc console.log(person1.sayName === person2.sayName) // --> true

原型模式的问题:

它省略了为构造函数传递参数初始化参数的环节,结果所有的实例在默认情况下都将取得相同的属性值。另外,原型模式的最大问题是由其共享的本性所导致的。看如下问题:

function Person(){
}
Person.prototype = {
constructor: Person,
name: 'ccc',
age: 18,
friends:['www', 'aaa'],
sayName: function () {
console.log(this.name)
}
}
var person1 = new Person()
var person2 = new Person() person1.friends.push('bbb') console.log(person1.friends) // --> ["www", "aaa", "bbb"]
console.log(person2.friends) // --> ["www", "aaa", "bbb"]
console.log(person1.friends === person2.friends) // --> true

带来的新问题:

如果我们的初衷就是这样,所有的实例共用一个数组,那么这个结果就是想要的。可是,实例一般都是要有属于自己的全部属性的,这个问题正是我们很少看到有人单独使用原型模式的原因所在。

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

创建自定义类型的最常见方式,就是组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。这种方式还支持向构造函数传递参数。

function Person(name, age){
this.name = name;
this.age = age;
this.friends = ['aaa', 'bbb']
}
Person.prototype = {
constructor: Person,
sayName: function(){
console.log(this.name)
}
}
var person1 = new Person('ccc', 18)
var person2 = new Person('www', 18)
person1.friends.push('ddd') console.log(person1.friends) // --> ["aaa", "bbb", "ddd"]
console.log(person2.friends) // --> ["aaa", "bbb"]
console.log(person1.friends === person2.friends) // --> false
console.log(person1.sayName === person2.sayName) // --> true

这种构造函数与原型混成的模式,是目前ECMAscript中使用最广泛、认同度最高的一种创建自定义类型的方法。可以说,这是用来定义引用类型的一种默认方式。

动态原型模式

动态原型模式就是可以通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型。

function Person(name, age){
// 属性
this.name = name
this.age = age
// 方法
if(typeof this.sayName !== 'function'){
Person.prototype.sayName = function(){
console.log(this.name)
}
}
} var person1 = new Person('ccc', 18)
person1.sayName() // --> ccc

这里只有在sayName()方法不存在的情况下,才会将它添加到原型中。这段代码只会在初次调用构造函数时才会执行。

注意:

  • 在这里对原型所做的修改,能够立即在所有实例中得到反映。
  • 使用动态原型模式时,不能使用对象字面量重写原型。如果在已经创建了实例的情况下重写原型,那么就会切断现有实例与新原型之间的联系。(参考原型与原型链中的内容

其它模式

还有寄生构造函数模式和稳妥构造函数模式,可自行了解。以上所以知识内容来自《JavaScript高级程序设计》(第三版)。

理解js中的几种设计模式的更多相关文章

  1. JS中的7种设计模式

    第九章Refactoring to OOP Patterns 重构为OOP模式 7种设计模式: 1,模版方法模式(template method) 2,策略模式(strategy) 3,状态模式(st ...

  2. 怎么理解js中的事件委托

    怎么理解js中的事件委托 时间 2015-01-15 00:59:59  SegmentFault 原文  http://segmentfault.com/blog/sunchengli/119000 ...

  3. [ 转载 ] Java开发中的23种设计模式详解(转)

    Java开发中的23种设计模式详解(转)   设计模式(Design Patterns) ——可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类 ...

  4. 图文结合深入理解 JS 中的 this 值

    图文结合深入理解 JS 中的 this 值 在 JS 中最常见的莫过于函数了,在函数(方法)中 this 的出现频率特别高,那么 this 到底是什么呢,今天就和大家一起学习总结一下 JS 中的 th ...

  5. 如何理解js中的this和实际应用中需要避开哪些坑

    this是什么 this就是函数内部的关键字 看下面例子理解js中的this // 例子1 function fnOne () { console.log(this) } 'use strict' f ...

  6. 深入理解JS中的对象(二):new 的工作原理

    目录 序言 不同返回值的构造函数 深入 new 调用函数原理 总结 参考 1.序言 在 深入理解JS中的对象(一):原型.原型链和构造函数 中,我们分析了JS中是否一切皆对象以及对象的原型.原型链和构 ...

  7. 深入理解Js中的this

    深入理解Js中的this JavaScript作用域为静态作用域static scope,但是在Js中的this却是一个例外,this的指向问题就类似于动态作用域,其并不关心函数和作用域是如何声明以及 ...

  8. 研究分析JS中的三种逻辑语句

    JS中的三种逻辑语句:顺序.分支和循环语句. 一.顺序语句 代码规范如下:1. <script type="text/javascript"> var a = 10;  ...

  9. 如何更好的理解js中的this,分享2段有意思的代码

    关于js中this的浅析,大家可以点击[彻底理解js中this的指向,不必硬背]这篇博客了解. 今天遇到2段比较有意思的代码. ----------------第一段----------------- ...

随机推荐

  1. Redis系列(五):数据结构List双向链表中基本操作操作命令和源码解析

    1.介绍 List是通过ListNode实现的双向链表. 1.双端:获取某个结点的前驱和后继结点都是O(1) 2.无环:表头的prev指针和表尾的next指针都指向NULL,对链表的访问都是以NULL ...

  2. Java容器:HashMap连环炮

    本文来源于:https://mp.weixin.qq.com/s/oRx-8XXbgage9Hf97WrDQQ, 公众号:安琪拉的博客 前言 HashMap应该算是Java后端工程师面试的必问题,因为 ...

  3. synchronized与锁升级

    1 为什么需要synchronized? 当一个共享资源有可能被多个线程同时访问并修改的时候,需要用锁来保证数据的正确性.请看下图: 线程A和线程B分别往同一个银行账户里面添加货币,A线程从内存中读取 ...

  4. Java WebService _CXF、Xfire、AXIS2、AXIS1_四种发布方式(优缺点对比)

    xis,axis2,Xfire以及cxf对比 http://ws.apache.org/axis/ http://axis.apache.org/axis2/java/core/ http://xfi ...

  5. JavaWeb网上图书商城完整项目--day02-27.查询所有分类功能之Servlet和Service层

    我们在上面实现了数据库层的代码,现在我们来实现业务层和Servlet层的代码:业务层的代码如下: package com.weiyuan.goods.category.service; import ...

  6. java基础-java与c#的可变参数

    正文 可变参数,必须最为参数的最后一个参数:可变参数只能有一个: c#可变参数例子:       class Program { static void Main(string[] args) { T ...

  7. MongoDB入门四

    MongoDB针对实时位置 db.CallRecordInfo.find().count()db.SendInfo.find().count()db.RiderReaTimePositon.find( ...

  8. Python实用笔记 (4)循环

    for...in循环 names = ['Michael', 'Bob', 'Tracy'] for name in names: print(name) 运行如下: Michael Bob Trac ...

  9. 07 . Prometheus监控Memcached并配置Grafana

    List CentOS7.3 prometheus-2.2.1.linux-amd64.tar.gz redis_exporter-v0.30.0.linux-amd64.tar.gz ` 节点名 I ...

  10. C# 获取枚举的描述Description

    方法类: public static class EnumExtensions { #region Enum /// <summary> /// 获取枚举变量值的 Description ...