理解js中的几种设计模式
工厂模式
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
构造函数模式与工厂模式的区别:
- 没有显式的创建对象
- 直接将属性和方法赋给了this对象
- 没有return 语句
要创建Person的新实例,必须使用new操作符。以这种方式调用构造函数实际上会经历一下4个步骤:
- 创建一个新对象
- 将构造函数的作用域赋给新对象(因此this就指向了这个新对象)
- 执行构造函数中的代码(为这个新对象添加属性)
- 返回新对象
构造函数的问题:
使用构造函数的重要问题,就是每个方法都要在每个实例上重新创建一遍。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中的7种设计模式
第九章Refactoring to OOP Patterns 重构为OOP模式 7种设计模式: 1,模版方法模式(template method) 2,策略模式(strategy) 3,状态模式(st ...
- 怎么理解js中的事件委托
怎么理解js中的事件委托 时间 2015-01-15 00:59:59 SegmentFault 原文 http://segmentfault.com/blog/sunchengli/119000 ...
- [ 转载 ] Java开发中的23种设计模式详解(转)
Java开发中的23种设计模式详解(转) 设计模式(Design Patterns) ——可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类 ...
- 图文结合深入理解 JS 中的 this 值
图文结合深入理解 JS 中的 this 值 在 JS 中最常见的莫过于函数了,在函数(方法)中 this 的出现频率特别高,那么 this 到底是什么呢,今天就和大家一起学习总结一下 JS 中的 th ...
- 如何理解js中的this和实际应用中需要避开哪些坑
this是什么 this就是函数内部的关键字 看下面例子理解js中的this // 例子1 function fnOne () { console.log(this) } 'use strict' f ...
- 深入理解JS中的对象(二):new 的工作原理
目录 序言 不同返回值的构造函数 深入 new 调用函数原理 总结 参考 1.序言 在 深入理解JS中的对象(一):原型.原型链和构造函数 中,我们分析了JS中是否一切皆对象以及对象的原型.原型链和构 ...
- 深入理解Js中的this
深入理解Js中的this JavaScript作用域为静态作用域static scope,但是在Js中的this却是一个例外,this的指向问题就类似于动态作用域,其并不关心函数和作用域是如何声明以及 ...
- 研究分析JS中的三种逻辑语句
JS中的三种逻辑语句:顺序.分支和循环语句. 一.顺序语句 代码规范如下:1. <script type="text/javascript"> var a = 10; ...
- 如何更好的理解js中的this,分享2段有意思的代码
关于js中this的浅析,大家可以点击[彻底理解js中this的指向,不必硬背]这篇博客了解. 今天遇到2段比较有意思的代码. ----------------第一段----------------- ...
随机推荐
- MySQL——事务(Transaction)详解
原文:https://blog.csdn.net/w_linux/article/details/79666086
- 2020年最新78道JVM面试题总结(含答案解析和思维导图)
前言 关于JVM系列面试知识点总结了一个思维导图,分享给大家 1.java中会存在内存泄漏吗,请简单描述. 会.自己实现堆载的数据结构时有可能会出现内存泄露. 2.64 位 JVM 中,int 的长度 ...
- Centos安装redis详解
redis官方网站对redis的安装已有介绍,但没有redis环境的安装,redis自动启动服务安装等等: redis官网:https://redis.io 本文以centos6为基础安装redis ...
- java之SFTP上传下载
import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.ut ...
- JavaWeb网上图书商城完整项目--12.项目所需jquery函数介绍之ajax
jquery中使用ajax发送异步请求 下面的一个案例在input输入框失去焦点的时候发送一个异步的请求: 我们来看程序的案例: 这里要强调的是返回值最好选择是json,json对应的就是对象,Jav ...
- jvm基础知识—垃圾回收机制
1.首先类的实例化.static.父类构造函数执行顺序 我们来看下面的程序代码: public class A { int a1 = 8; { int a3 = 9; System.out.print ...
- C#数据结构与算法系列(十九):选择排序算法(SelectSort)
1.介绍 选择排序算法属于内部排序算法,是从欲排序的数据中,按指定的规则选出某一元素,再依规定交换位置达到排序的目的 时间复杂度:O(n^2) 双层for 2.思想 选择排序(select sorti ...
- 计算机网络之DNS常见攻击
DNS欺骗 在Internet上存在的DNS服务器有绝大多数都是用bind来架设的,使用的bind版本主要为bind 4.9.5+P1以前版本和bind 8.2.2-P5以前版本.这些bind有个共同 ...
- 实战技巧,Vue原来还可以这样写
hookEvent,原来可以这样监听组件生命周期 1. 内部监听生命周期函数 <template> <div class="echarts"></di ...
- Jmeter(十四) - 从入门到精通 - JMeter定时器 - 下篇(详解教程)
1.简介 用户实际操作时,并非是连续点击,而是存在很多停顿的情况,例如:用户需要时间阅读文字内容.填表.或者查找正确的链接等.为了模拟用户实际情况,在性能测试中我们需要考虑思考时间.若不认真考虑思考时 ...