【学习笔记javascript设计模式与开发实践(单例模式)----4】

第4章单例模式

 单例模式的定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

 单例模式是一种常用的模式,有一些对象我们往往只需要一个,比如线程池、全局缓存、浏览器的window对象。在js开发中,单例模式的用途同样非常广泛。试想一下,当我们单击登录按钮的时候,页面中会出现一个登录框,而这个浮窗是唯一的,无论单击多少次登录按钮,这个浮窗只会被创建一次。因此这个登录浮窗就适合用单例模式。

4.1 实现单便模式

要实现一个标准的单例模式并不复杂,无非是用一个变量来标志当前是否已经为某个类创建过对象,如果是,则在下一次获取该类的实例时,直接返回之前创建的对象:

var Singleton = function(name){
  this.name = name;
  this.instance = null;
}
Singleton.prototype.getName = function(){
   alert(this.name);
}
Singleton.getInstance = function(name){
  if(!this.instance){
     this.instance = new Singleton(name);
   }
  return this.instance;
}
 
var a = Singleton.getInstance(‘sven1’);
var b = Singleton.getInstance(‘sven2’);
alert(a===b); //true;

或:

var Singleton = function(name){
  this.name = name;
}
Singleton.prototype.getName = function(){
   alert(this.name);
}
Singleton.getInstance = (function(){
  var instance = null;
  return function(name){
if(!instance){
    instance = new Singleton(name);
}
return instance;
   }
})();

我们通过Singleton.getInstance来获取Singleton类的唯一对象,这种方式相对简单,但有一个问题,就是增加了这个类的不透明性,Singleton类的使用者必须知道这是一个单例类,跟以往通过new XX的方式来获取对象不同,这里偏要使用Singleton.getInstance来获取对象。

var a = Singleton.getInstance(‘sven1’);
var b = Singleton.getInstance(‘sven2’);
alert(a===b); //true;

4.2 透明的单例模式

我们的目标是实现一个“透明”的单例类,用户从这个类中创建对象的时候,可以像使用其他任何普通类一样。在下面的例子中,我们将使用CreateDiv单例类,它的作用是负责在页面中创建唯一的div节点,如下:

var CreateDiv = (function(){
 var instance;
 var CreateDiv =function(html){
   if(instance){
     return instance;
   }
   this.html = html;
   this.init();
   return instance = this;
 }
 CreateDiv .prototype.init= function(){
   var div = document.createElement(‘div’);
   div.innerHTML = this.html;
   document.body.appendChild(div);
 };
 return CreateDiv;
})();

上面的代码看上去会很难理解,在这段代码中,CreateDiv的构造函数实际上负责了两件事情。第一是创建对象和执行初始化的init方法,第二是保证只有一个对象。虽然我们目前我们还没学习到“单一职责原则”的概念,但可以明确的是,这是一种不好的做法,至少这个构造函数看起来好奇怪……,

假设我们某天需要利用这个类,在页面上创建多个div,即要让这个类从单例变成一人普通的可产生多个实例的类,那我们必须改写CreateDiv构造函数,把控制创建唯一那段去掉,这种修改会给我们带来不必要的烦恼。

 

4.3 用代理实现单例模式

现在我们通过引入代理方式,来解决上面提到的问题。

首先在CreateDiv构造函数中,把负责管理单例的代码移除出去,使它成为一个普通的创建div的类:

var CreateDiv = function(html){
 this.html = html;
 this.init();
}
CreateDiv.prototype.init = function(){
  var div = document.createElement(‘div’);
  div.innerHTML =this.html;
  document.body.appendChild(div);
}

接下来引入代理类的方式,我们同样完成了一个单例模式的编写,跟之前不同的是,现在我们把负责管理单例的逻辑移到代理类proxySingletonCreateDiv中。这样一来,CreateDiv就变成了一个普通类,它跟proxySingletonCreateDiv组合起来可以达到单例模式的效果。如下:

<pre name="code" class="javascript">var ProxySingletonCreateDiv =(function(){
  var instance;
  return function(html){
   if(!instance){
      instance = new CreateDiv(html);
   }
   return instance;
 }
})();
 
var a = new ProxySingletonCreateDiv(‘sven1’);
var b = new ProxySingletonCreateDiv(‘sven2’);
alert(a===b); //true
 

4.4 javascript中的单例模式

前端提到的单例模式的实现,更多的是接近传统面向对象语言中的实现,单例对象“类”中创建而来。在以类为中心的语言中,这是很自然的做法。比如在java中,如果需要某个对象,就必须先定义一个类,对象总是从类中创建而来的。

js其实是一个无类语言,也正因为如此,生搬单例模式的概念并无意义。在javaScript中创建对象的方法非常简单,既然我们只需要一个“唯一”的对象,为什么要为它先创建一个类呢(相当有同感)?

单例模式的核心是确保只有一个实例,并提供全局访问

全局变量不是单例,但在javascript中,我们经常会把全局变量当成单例来使用如:

var a ={};

但这种方式比较糟糕的问题是,全名冲突。维护不容易啊

解决方法有如下两种:

1.    使用命名空间

适当地使用命名空间,并不会杜绝全局变量,但可以减少全局变量的数量。

如下:

namespace1={
   a:function(){
     alert(1);
   },
   b:function(){
     alert(2);
   }
}

把a 和 b都定义为namespace1的属性,这样可以减少变量和全局作用域打交道的机会。另外我们还可以动态地创建命名空间(Object-Oriented javascript)

var MyApp = {};
MyApp.namespace= function(name){
   var parts = name.split(‘.’);
   var current = MyApp;
   for(var i in parts){
    if(!current[parts[i]]){
     current[parts[i]] = {};
    }
    current = current[parets[i]];
   }
}
MyApp.namespace(‘event’);
MyApp.namespace(‘dom.style’);
consle.dir(MyApp);
//结果
{
   event:{},
   dom:{
      style:{}
   }
}

2.    全用闭包封装私有变量

这种方法把一些变量封装在闭包的内部,只暴露一些接口跟外界通信:

var user =(function(){
var __name=’sven’,
   __age = 29;
  return {
      getUserInfo:function(){
         return __name+’-‘+__age;
      }
  }
})();

4.5 惰性单例

需要才创建,这种技术在实际开发时非常有用,有用的程序超出我们的想象……,如我们在前面所讲的Singleton.getInstance的实现。但javascript中并不适用(因为它是基于类的创建方式生搬硬套感觉在实际应用中真真没啥用)。

Singleton.getInstance=(function(){
var instance =null;
returnfunction(name){
   if(!instance){
     instance = new Singleton(name);
  }
  return instance;
}
})();

Demo Web QQ登录页面,当点击导航的QQ头像时,会弹出一个登录浮窗,很明显这个浮窗在页面里总是唯一的,不可能出现同时存在两个登录窗口的情况。

第一种解决方案在页面加载完成的时候便创建好这个div浮窗,这个浮窗一开始肯定是隐藏状态的,当用户点击登录按钮的时候,它才开始显示

<pre name="code" class="html"><html>
  <body>
    <button id=”loginBtn”>登录</button>
  </body>
  <script>
   var loginLayer =(function(){
     var div = document.createElement(‘div’);
     div.innerHTML = “登录浮窗”;
     div.style.display = ‘none’;
     document.body.appendChild(div);
     return div;
   })();
   document.getElementById(‘loginBtn’).οnclick= function(){
     loginLayer.style.display = ‘block’;
   }
  </script>
</html>

 

这种方式的缺点就是登录这个页面,不一定是启用登录QQ界面,如我们只是看看天气,根本不需要进行登录操作,因为登录浮窗总是一开始就被创建好,那么很有可能将白白浪费一些DOM节点。

那么我们将其改造一下,

<html>
  <body>
    <button id=”loginBtn”>登录</button>
  </body>
  <script>
  var createLoginLayer = function(){
    var div = document.createElement(‘div’);
    div.innerHTML = “登录浮窗”;
    div.style.display = ‘none’;
    document.body.appendChild(div);
    return div;
  };
  document.getElementById(‘loginBtn’).οnclick= function(){
    var loginLayer = createLoginLayer();
    loginLayer.style.display = ‘block’;
  }
  </script>
</html>

在上例中虽然达到惰性的目的,但失去了单例的效果。当我们每次点击登录按钮的时候,都会创建一个新的登录浮窗div。虽然我们可以在点击浮窗上的关闭按钮时把这个浮窗从页面中删除,但这样频繁地创建和删除节点明显是很不合理的,也是不必要的。

所以可以把 createLoginLayer改成单例模式

<pre name="code" class="javascript">var createLoginLayer = (function(){
  var div;
  return function(){
    if(!div){
       div = document.createElement(‘div’);
       div.innerHTML = “登录浮窗”;
       div.style.display = ‘none’;
       document.body.appendChild(div);
    }
   return div;
  }
})();

document.getElementById(‘loginBtn’).οnclick= function(){
    var loginLayer = createLoginLayer();
    loginLayer.style.display = ‘block’;
}

 

4.6 通用的惰性单例

上一节中我们完成的一个可用的惰性单例,但是我们发现它还有如下一些问题。

o  这段代码仍然是违反单一职责原则的,创建对象和管理单例的逻辑都放在createLoginLayer对象的内部

o  如果我们下次需要创建页面中唯一的iframe,或者script标签,用来跨域请求数据,就必须得如法炮制,把createLoginLayer函数几乎照抄一遍:

var  createIframe = (function(){
var iframe;
return function(){
   if(!iframe){
      iframe =document.createElement(‘iframe’);
      iframe.style.display = ‘none’;
     document.body.appendChild(iframe);
   }
   return iframe;
}
})();

其实我是要把不变的部分隔离出来,先不考虑创建一个div还是一个iframe有多少差异,管理单例的逻辑其实是完全可以抽象出来的,这个逻辑始终是一样的:用一个变量来标志是否创建过对象,如果是,则在下次直接返回这个已经创建好的对象:

var obj;
if(!obj){
  obj =xx ; //bala…bala…
}

现在我们将管理单例的逻辑从原来的代码中抽离出来,这些逻辑被封装在getSingleton函数内部,创建对象的方法fn被当成参数动态传入函数:

var getSingle = function(fn){
  var result;
  return function(){
    return result || (result =fn.apply(this,arguments))
  }
}
 

接下来将用于创建登录浮窗的方法用参数fn的形式传入getSingle,我们不仅可以传入createLoginLayer,还能传入createScript、createIframe、createXhr等。

之后再让getSingle返回一个新的函数,并且一个变量result来保存fn的计算结果。result变量因为身在闭包中,它永远不会被销毁。在将来的请求中,如果result已经被赋值,那么它将返回这个值。如下:

var createLoginLayer = function(){
  var div = document.createElement(‘div’);
  div.innerHTML = ‘我是登录窗口’;
  div.style.display = ‘none’;
  return div;
}
var createSingleLoginLayer =getSingle(createLoginLayer);
document.getElementById(‘loginBtn’).οnclick= function(){
    var loginLayer = createSingleLoginLayer ();
    loginLayer.style.display = ‘block’;
}

如我们还可以创建唯一一个iframe用于动态加载第三方页面

var createSingleIframe =getSingle(function(){
var iframe =document.createElement(‘iframe’);
  document.body.appendChild(iframe);
  return iframe;
});
 
document.getElementById(‘loginBtn’).οnclick= function(){
  var loginLayer =createSingleIframe();
  loginLayer.src =‘xxx’;
}

这个例子挺有意思,单例模式的用途不止用于创建对象,比如我们通常渲染完页面中一个列表之后,接下来要给列表绑定click事件,如果是通过ajax动态往列表里追回数据,在使用事件代理的前提下,click事件实际上只需要在第一次渲染列表的时候被绑定一次,但是我们不想去判断当前是否是第一次渲染列表,如果我们是借助于jQuery,我们通常选择给节点绑定one事件

var bindEvent = function(){
 $(‘div’).one(“click”,function(){
   alert(‘click’);
 });
};
var render = function(){
  console.log(‘开始渲染列表’);
  bindEvent();
}
render();
render();
render(); //<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>

如果利用getSingle函数,也能达到一样的效果:

var bindEvent = getSingle(function(){
 document.getElementById(‘div1’).onclick = function(){
   alert(‘click’);
  }
 return true;
});
var render = function(){
  console.log(‘开始渲染列表’);
  bindEvent();
}
render();
render();
render();

可以看到,render函数和bindEvent函数都分别执行了3次,但div实际上只被绑定了一个事件。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ava实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),可运行高分资源 Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现
C语言是一种广泛使用的编程语言,它具有高效、灵活、可移植性强等特点,被广泛应用于操作系统、嵌入式系统、数据库、编译器等领域的开发。C语言的基本语法包括变量、数据类型、运算符、控制结构(如if语句、循环语句等)、函数、指针等。下面详细介绍C语言的基本概念和语法。 1. 变量和数据类型 在C语言中,变量用于存储数据,数据类型用于定义变量的类型和范围。C语言支持多种数据类型,包括基本数据类型(如int、float、char等)和复合数据类型(如结构体、联合等)。 2. 运算符 C语言中常用的运算符包括算术运算符(如+、、、/等)、关系运算符(如==、!=、、=、<、<=等)、逻辑运算符(如&&、||、!等)。此外,还有位运算符(如&、|、^等)和指针运算符(如、等)。 3. 控制结构 C语言中常用的控制结构包括if语句、循环语句(如for、while等)和switch语句。通过这些控制结构,可以实现程序的分支、循环和多路选择等功能。 4. 函数 函数是C语言中用于封装代码的单元,可以实现代码的复用和模块化。C语言中定义函数使用关键字“void”或返回值类型(如int、float等),并通过“{”和“}”括起来的代码块来实现函数的功能。 5. 指针 指针是C语言中用于存储变量地址的变量。通过指针,可以实现对内存的间接访问和修改。C语言中定义指针使用星号()符号,指向数组、字符串和结构体等数据结构时,还需要注意数组名和字符串常量的特殊性质。 6. 数组和字符串 数组是C语言中用于存储同类型数据的结构,可以通过索引访问和修改数组中的元素。字符串是C语言中用于存储文本数据的特殊类型,通常以字符串常量的形式出现,用双引号("...")括起来,末尾自动添加'\0'字符。 7. 结构体和联合 结构体和联合是C语言中用于存储不同类型数据的复合数据类型。结构体由多个成员组成,每个成员可以是不同的数据类型;联合由多个变量组成,它们共用同一块内存空间。通过结构体和联合,可以实现数据的封装和抽象。 8. 文件操作 C语言中通过文件操作函数(如fopen、fclose、fread、fwrite等)实现对文件的读写操作。文件操作函数通常返回文件指针,用于表示打开的文件。通过文件指针,可以进行文件的定位、读写等操作。 总之,C语言是一种功能强大、灵活高效的编程语言,广泛应用于各种领域。掌握C语言的基本语法和数据结构,可以为编程学习实践打下坚实的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值