详解js中的几种常用设计模式


Posted in Javascript onJuly 16, 2020

工厂模式

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中的几种常用设计模式的详细内容,更多关于JS 设计模式的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
Mootools 1.2教程 同时进行多个形变动画
Sep 15 Javascript
Iframe自适应高度绝对好使的代码 兼容IE,遨游,火狐
Jan 27 Javascript
按下Enter焦点移至下一个控件的实现js代码
Dec 11 Javascript
JavaScript中的apply和call函数详解
Jul 20 Javascript
教你如何使用firebug调试功能了解javascript闭包和this
Mar 04 Javascript
jQuery插件Skippr实现焦点图幻灯片特效
Apr 12 Javascript
浅谈jQuery.easyui的datebox格式化时间
Jun 25 Javascript
jquery实现仿新浪微博带动画效果弹出层代码(可关闭、可拖动)
Oct 12 Javascript
详解js树形控件—zTree使用总结
Dec 28 Javascript
js学习总结之dom2级事件基础知识详解
Jul 27 Javascript
JavaScript代码调试方法实例小结
Jan 05 Javascript
vue cli3.0打包上线静态资源找不到路径的解决操作
Aug 03 Javascript
JS寄快递地址智能解析的实现代码
Jul 16 #Javascript
详解js中的原型,原型对象,原型链
Jul 16 #Javascript
详解Webpack4多页应用打包方案
Jul 16 #Javascript
快速了解Vue父子组件传值以及父调子方法、子调父方法
Jul 15 #Javascript
微信小程序12行js代码自己写个滑块功能(推荐)
Jul 15 #Javascript
TypeScript 引用资源文件后提示找不到的异常处理技巧
Jul 15 #Javascript
微信小程序实现列表的横向滑动方式
Jul 15 #Javascript
You might like
php实现指定字符串中查找子字符串的方法
2015/03/17 PHP
php加密解密字符串示例
2016/10/13 PHP
在云虚拟主机部署thinkphp5项目的步骤详解
2017/12/21 PHP
PHP实现数组转JSon和JSon转数组的方法示例
2018/06/14 PHP
线路分流自动智能跳转代码,自动选择最快镜像网站(js)
2011/10/31 Javascript
JQuery入门——移除绑定事件unbind方法概述及应用
2013/02/05 Javascript
js控制输入框获得和失去焦点时状态显示的方法
2015/01/30 Javascript
JQuery创建DOM节点的方法
2015/06/11 Javascript
js获取鼠标点击的对象,点击另一个按钮删除该对象的实现代码
2016/05/13 Javascript
在javascript中创建对象的各种模式解析
2016/05/16 Javascript
jQuery插件formValidator实现表单验证
2016/05/23 Javascript
详解BootStrap中Affix控件的使用及保持布局的美观的方法
2016/07/08 Javascript
Three.js基础部分学习
2017/01/08 Javascript
微信小程序 使用腾讯地图SDK详解及实现步骤
2017/02/28 Javascript
详解Vue2.0配置mint-ui踩过的那些坑
2018/04/23 Javascript
修改Nodejs内置的npm默认配置路径方法
2018/05/13 NodeJs
解决echarts的多个折现数据出现坐标和值对不上的问题
2018/12/28 Javascript
微信小程序 生成携带参数的二维码
2019/10/23 Javascript
js的Object.assign用法示例分析
2020/03/05 Javascript
Python中文编码那些事
2014/06/25 Python
Python selenium 三种等待方式解读
2016/09/15 Python
python负载均衡的简单实现方法
2018/02/04 Python
python使用adbapi实现MySQL数据库的异步存储
2019/03/19 Python
python tkinter实现屏保程序
2019/07/30 Python
TENSORFLOW变量作用域(VARIABLE SCOPE)
2020/01/10 Python
基于Python生成个性二维码过程详解
2020/03/05 Python
HTML5对手机页面长按会粘贴复制禁用的解决方法
2016/07/19 HTML / CSS
美国家用和厨房电器销售网站:Appliances Connection
2020/01/24 全球购物
PHP如何删除一个Cookie值
2012/11/15 面试题
程序集与命名空间有什么不同
2014/07/25 面试题
JAVA招聘远程笔试题
2015/07/23 面试题
开门红主持词
2014/04/02 职场文书
整改落实情况汇报材料
2014/10/29 职场文书
巾帼建功标兵先进事迹材料
2016/02/29 职场文书
Vue Element UI自定义描述列表组件
2021/05/18 Vue.js
Python实现随机生成迷宫并自动寻路
2021/06/13 Python