综合二

TCP/IP协议三次握手与四次握手流程解析

一、TCP报文格式
  TCP/IP协议的详细信息参看《TCP/IP协议详解》三卷本。下面是TCP报文格式图:

图1 TCP报文格式
  上图中有几个字段需要重点介绍下:
  (1)序号:Seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。
  (2)确认序号:Ack序号,占32位,只有ACK标志位为1时,确认序号字段才有效,Ack=Seq+1。
  (3)标志位:共6个,即URG、ACK、PSH、RST、SYN、FIN等,具体含义如下:
  (A)URG:紧急指针(urgent pointer)有效。
  (B)ACK:确认序号有效。
  (C)PSH:接收方应该尽快将这个报文交给应用层。
  (D)RST:重置连接。
  (E)SYN:发起一个新连接。
  (F)FIN:释放一个连接。

 

RST信息,则是client向server发送数据请求,但是server并没有运行.则client会收到来自对方主机发送的RST信息.

11个状态中,除了ESTABLISHED外,还有2个比较重要的状态:CLOSED_WAIT和TIME_WAIT.

CLOSE_WAIT状态时有对方主动调用close,向本地(这里本地,并不一定说的是client)发送FIN,本地接收到FIN,并向对方发送ACK之后,本地状态会变成CLOSE_WAIT状态.那么,本地如果需要从CLOSE_WAIT状态变成CLOSED状态,需要本地向对方发送FIN,也就是需要本地主动调用close,本地进入LAST_ACK,在本地接收到ACK之后,就进入CLOSED状态.



 需要注意的是:
  (A)不要将确认序号Ack与标志位中的ACK搞混了。
  (B)确认方Ack=发起方Req+1,两端配对。 

二、三次握手
  所谓三次握手(Three-Way Handshake)即建立TCP连接,就是指建立一个TCP连接时,需要客户端和服务端总共发送3个包以确认连接的建立。在socket编程中,这一过程由客户端执行connect来触发,整个流程如下图所示:

图2 TCP三次握手
  (1)第一次握手:Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。
  (2)第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。
  (3)第三次握手:Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。
 
  SYN攻击:
  在三次握手过程中,Server发送SYN-ACK之后,收到Client的ACK之前的TCP连接称为半连接(half-open connect),此时Server处于SYN_RCVD状态,当收到ACK后,Server转入ESTABLISHED状态。SYN攻击就是Client在短时间内伪造大量不存在的IP地址,并向Server不断地发送SYN包,Server回复确认包,并等待Client的确认,由于源地址是不存在的,因此,Server需要不断重发直至超时,这些伪造的SYN包将产时间占用未连接队列,导致正常的SYN请求因为队列满而被丢弃,从而引起网络堵塞甚至系统瘫痪。SYN攻击时一种典型的DDOS攻击,检测SYN攻击的方式非常简单,即当Server上有大量半连接状态且源IP地址是随机的,则可以断定遭到SYN攻击了,使用如下命令可以让之现行:
  #netstat -nap | grep SYN_RECV

三、四次挥手
 三次握手耳熟能详,四次挥手估计就,所谓四次挥手(Four-Way Wavehand)即终止TCP连接,就是指断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开。在socket编程中,这一过程由客户端或服务端任一方执行close来触发,整个流程如下图所示:

图3 TCP四次挥手
  由于TCP连接时全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭,上图描述的即是如此。
 (1)第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
  (2)第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
 (3)第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
  (4)第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。
  上面是一方主动关闭,另一方被动关闭的情况,实际中还会出现同时发起主动关闭的情况,具体流程如下图:


图4 同时挥手
  流程和状态在上图中已经很明了了,在此不再赘述,可以参考前面的四次挥手解析步骤。

四、附注
  关于三次握手与四次挥手通常都会有典型的面试题,在此提出供有需求的XDJM们参考:
  (1)三次握手是什么或者流程?四次握手呢?答案前面分析就是。
  (2)为什么建立连接是三次握手,而关闭连接却是四次挥手呢?
  这是因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。而关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,己方也未必全部数据都发送给对方了,所以己方可以立即close,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送。

 

 

Web程序的前端和后端联系起来

如何把一个Web程序的前端和后端联系起来。以下内容基于此理解进行回答。

先不考虑AJAX,从简单的说起。

前端和后端之所以需要对接,是因为前端页面只负责提供视图没有内容,而后端只提供内容,两者所谓的对接,就是把后端的内容放在前端页面预留出来的位置上。(虽然说是前端后端,但这一对接实际发生在服务器端)。
所以服务器端进行的活动如下:
接收用户请求——》找到负责处理的程序——》处理程序找到要传输给用户的前端页面——》该前端页面留出位置——》后端到数据库取数据——》后端把数据放在前端留出来的位置上——》结合成真正用户看到的html文件——》传输给用户。


(写完发现下面可以不用看了,没有办法用三言两语说清楚,最后你还是得找本书来看)
以博客中常见的输出文章的作者信息为例:

 
  1. <!DOCTYPE html>

  2. <html>

  3. <head></head>

  4. <body>

  5. <div>write by Aeolia on 2013-08-07</div>

  6. </body>

  7. </html>

  8.  

很明显其中的作者名称‘Aeolia’和发布日期‘2013-08-07’要替换掉
具体方法看你后台用的是什么技术:
1,后台php
把HTML文件改为php文件

 
  1. <?php

  2. //从数据库获得数据,存在变量writer和date中

  3. ?>

  4. <!DOCTYPE html>

  5. <html>

  6. <head></head>

  7. <body>

  8. <div>write by <?php echo writer;?>on <?php echo date;?></div>

  9. </body>

  10. </html>

  11.  

===============================================================
2,后台JSP
Servlet文件(*代表此处有省略)

 
  1. package *

  2. import *

  3. public class Servlet extends HttpServlet {

  4. public void 处理GET请求的方法{

  5. //1,从数据库获得数据,存为变量writer和date

  6. //2,把变量writer和date设置为request的属性

  7. //3,调用要跳转的JSP页面

  8. }

  9. }

  10.  

JSP文件
把HTML文件改为JSP文件

 
  1. <%

  2. //从request里把writer和date取出来。

  3. %>

  4. <!DOCTYPE html>

  5. <html>

  6. <head></head>

  7. <body>

  8. <div>write by <%=writer%>on <%=date%></div>

  9. </body>

  10. </html>

  11.  


==============================================================
3,Ruby on Rails
controller文件

class Controller < ApplicationController
    def index
           //数据库里取article对象
           //把article对象的数据respond到视图中
    end
end

视图文件
把HTML文件后面添加后缀erb,为index.html.erb,放在视图文件夹下
<div>write by <%=article.writer%>on <%=article.date%></div>

 

CSS浏览器前缀兼容写法

Vendor prefix—浏览器引擎前缀,是一些放在CSS属性前的小字符串,用来确保这种属性只在特定的浏览器渲染引擎下才能识别和生效。谷歌浏览器和 Safari浏览器使用的是WebKit渲染引擎,火狐浏览器使用的是Gecko引擎,Internet Explorer使用的是Trident引擎,Opera以前使用Presto引擎,后改为WebKit引擎。一种浏览器引擎里一般不实现其它引擎前缀标 识的CSS属性,但由于以WebKit为引擎的移动浏览器相当流行,火狐等浏览器在其移动版里也实现了部分WebKit引擎前缀的CSS属性。

浏览器引擎前缀(Vendor Prefix)有哪些?

-moz-     /* 火狐等使用Mozilla浏览器引擎的浏览器 */
-webkit-  /* Safari, 谷歌浏览器等使用Webkit引擎的浏览器 */
-o-       /* Opera浏览器(早期) */
-ms-      /* Internet Explorer (不一定) */ 

为什么需要浏览器引擎前缀(Vendor Prefix)?

这些浏览器引擎前缀(Vendor Prefix)主要是各种浏览器用来试验或测试新出现的CSS3属性特征。可以总结为以下3点:

  • 试验一些还未成为标准的的CSS属性——也许永远不会成为标准
  • 对新出现的标准的CSS3属性特征做实验性的实现
  • 对CSS3中一些新属性做等效语义的个性实现

这些前缀并非所有都是需要的,但通常你加上这些前缀不会有任何害处——只要记住一条,把不带前缀的版本放到最后一行:

-moz-border-radius: 10px; 
-webkit-border-radius: 10px; 
-o-border-radius: 10px; 
border-radius: 10px; 

有些新的CSS3属性已经试验了很久,一些浏览器已经对这些属性不再使用前缀。Border-radius属性就是一个非常典型的例子。最新版的浏览器都支持不带前缀的Border-radius属性写法。

需要使用Vendor Prefixes的CSS3属性

主要的需要添加浏览器引擎前缀(vendor-prefix)的属性包括:

  • @keyframes
  • 移动和变换属性(transition-property, transition-duration, transition-timing-function, transition-delay)
  • 动画属性 (animation-name, animation-duration, animation-timing-function, animation-delay)
  • border-radius
  • box-shadow
  • backface-visibility
  • column属性
  • flex属性
  • perspective属性

完整的列表不只这些,而且还会增加。

浏览器引擎前缀(vendor-prefix)的用法

当需要使用浏览器引擎前缀(vendor-prefix)时,最好是把带有各种前缀的写法放在前面,然后把不带前缀的标准的写法放到最后。比如:

/* 简单属性 */
.myClass {
	-webkit-animation-name: fadeIn;
	-moz-animation-name: fadeIn;
	-o-animation-name: fadeIn;
	-ms-animation-name: fadeIn;
	animation-name: fadeIn;  /* 不带前缀的放到最后 */
}
/* 复杂属性 keyframes */
@-webkit-keyframes fadeIn {
	0% { opacity: 0; } 100% { opacity: 0; }
}
@-moz-keyframes fadeIn {
	0% { opacity: 0; } 100% { opacity: 0; }
}
@-o-keyframes fadeIn {
	0% { opacity: 0; } 100% { opacity: 0; }
}
@-ms-keyframes fadeIn {
	0% { opacity: 0; } 100% { opacity: 0; }
}
/* 不带前缀的放到最后 */
@keyframes fadeIn {
	0% { opacity: 0; } 100% { opacity: 0; }
}

Internet Explorer

Internet Explorer 9 开始支持很多(但并不是全部)CSS3里的新属性。比如,你也可以在IE里使用不带浏览器引擎前缀(vendor-prefix)的border-radius属性。

IE6到IE8都不支持CSS3,很遗憾的是,使用这些低版本浏览器的用户还很多。所以,确保你的网站设计在不支持CSS3的情况下也能正常显示。对于一些属性:border-radius , linear-gradient, 和 box-shadow, 你可以使用CSS3Pie,它是一个很小的文件,把它放到你的网站的根目录下,就能让你的页面中IE6,IE8中也支持这些属性。

常用数据库语句汇总

2017年03月31日 15:58:51

阅读数:8444



--建表语句
/*  create table userInfo(
    id number(6,0),
    username varchar2(20),
    userpwd varchar2(20),
    email varchar2(30),
    regdate date 
)  */

//向表里添加字段
--alter table userInfo add remard varchar2(500)
//删除字段(注意关键字drop column)
--alter table userInfo drop column sex
//修改表中字段的数据类型
--alter table userInfo modify userpwd number(6,0)
//修改表字段名
--alter table userInfo rename column remard to remarks
//修改表名
--rename userInfo to usersinfo
//查询表信息
--select * from usersInfo
//删除表中的全部数据(速度较drop来说快)
--truncate table usersInfo
//删除整个表
--drop table usersInfo

/*******************************添加、复制、修改、删除表数据*********************************************/
//添加表中数据(所以字段)
--insert into usersInfo values(1,'Mery','123456','1446397444@qq.com',sysdate)
//给表中指定字段添加值
--insert into USERSINFO(id,USERNAME,USERPWD) values(2,'panada','a')
//为表中字段添加默认值
/*  create table userInfo(
    id number(6,0),
    regdate date default sysdate --将系统日期作为默认日期
) 
insert into userinfo(id)values(1)   --必须指定列添加
select * from USERINFO
*/
//修改表中字段为其添加默认值
--alter table usersinfo modify userpwd default '000000'
--insert into usersinfo(id)values(3)   --必须指定列添加

/*在建表时复制数据
语法:create table table_newname
      as
      select column1,column2,...|* from table_oldname
*/
--create table userinfo_new as (select * from usersinfo) 
--create table userinfo_new1 as (select id,USERNAME from usersinfo)
--select * from userinfo_new

/*在添加时复制旧表数据(注:insert into table_newname【必须是存在的】)
    insert into table_newname [(column1,column2,column3,...)] 
        select column1,column2,...|* from table_oldname
    注:添加字段与查询字段必须一一对应
*/
--insert into userinfo_new select * from usersinfo

/*更新表数据语法:
    update tablename set column1=value1,...[where conditions]
    注:当where条件子句不存在时表示更新表中所有行指定字段数据
*/
--update usersinfo set REGDATE=sysdate      --不指定条件,更新所有数据
--update usersinfo set 

在这我们可以看见,重复的项目已经被去掉了,包括NaN。正常情况下,NaN === NaN 返回的是false,但是在set里,一样能够帮你去重,厉害了。

但是这里大家可以看到,set返回的是一个对象,但是我们想要的是数组啊。

这回,就该轮到Array.from出场了,它的作用,就是可以把类数组对象、可迭代对象转化为数组。

这回我们再看,已经变成数组了。

这样一来,就用上面的一段代码就可以实现数组去重了,是不是比原来的又是循环又是判断省事多了。

呵呵,先别高兴,老话说的好,越是好东西,就越会有兼容性问题。在这里,也不例外。

我经过测试,目前主流的浏览器,Chrome,Firfox,Opera,Safari,包括微软的Edge,都是支持的,唯独IE系列不支持。

所以,慎用 :)   。

 

 

手写原型继承和应用

2017年01月21日 12:29:29

阅读数:976

原型继承是js的一种继承方式,原型继承是什么意思呢,其实就是说构造函数和子构造函数,或者说类和子类之间(当然js中不存在类),唯一的继承传递方式是通过原型,而不是其他语言直接通过extends(当然ES6的语法糖出现了extends)。所以你需要手写prototype。(封装手写prototype的方法请看我的另一篇文章详解js中extend函数) 我们先看一个简单的例子

   function Parent(){
      this.job = 'teacher';
   }
   Parent.prototype.showJob = function(){
      alert(this.job);
   }
   var child = new Parent();
   child.showJob();      //'teacher' 

很明显,这个例子中的child获得属性job和一个方法showJob,为什么会获得呢? 这时候来看看new Parent()到底做了什么

   var obj = { };   //obj获得Parent的this引用
   obj.job = 'teacher';   
   obj.__proto__ = Parent.prototype;
   var child = obj;

所以为什么child获得了属性job,是因为他执行了构造函数,child对象上获得了属性job。而为什么child获得了方法showJob, 是因为对象上有一个隐藏的原型__proto__ ,它指向了Parent.prototype。当我们在对象child上调用方法时,它首先检查对象自己是否具有这个方法,没有的话搜索自己的隐藏原型中有没有这个方法,所以当一个对象上有方法,它要么存在于自身中,要么存在于隐式原型中,要实现原型长链的继承,只能从隐式原型上想办法。 
所以,原型继承是什么,它的本质是改变其他对象的__proto__,或者说让它丰富起来,以获得父构造函数或者祖先构造函数的方法,请看代码。

   function Parent(){
   }
   Parent.prototype.showJob = function(){}
   function Child(){
   }
   Child.prototype = new Parent();
   Child.prototype.constructor = Child;
   var grandChild = new Child();

这时候grandChild获得了showJob方法,因为它的__proto__中有showJob方法,而为什么它的隐式原型中有这个方法呢,因为new Child()中grandChild.__proto__指向了Child的prototype, 至于为什么Child的prototype中有showJob方法,因为Child.prototype.__proto__等于Parent.prototype. 
至于为什么有这么一句

  Child.prototype.constructor = Child;

这是因为原本Child.prototype中有一个constructor属性指向Child本身,当执行Child.prototype = new Parent()的时候,Child.prototype.constructor指向了Parent,否则下一次new Child的时候,constructor的指向就会不正确,当然,这个在实际开发中即时漏掉也不会有大问题,因为我们很少会对constructor进行读写。 
以上代码还有另一个问题,为什么我们要把showJob这个方法写在Parent.prototype上呢,如果写成如下

    function Parent(){
        this.showJob = function(){}
    }
    function Child(){
    }
    Child.prototype = new Parent();

当然这样写也可以,child.prototype对象上有了showJob方法,而不是child.prototype.__proto__,这对于我们原型链的继承并没有影响。然而这样写的方法一多,child.prototype对象上的方法就越多,如果new了多次的话,在内存上会比写在原型上多一些消耗。

那么在实际开发中,会怎么实现面向对象的原型继承呢。正常在我们拿到需求的时候,如果需求逻辑复杂,且在多个页面中有相似逻辑的时候,我们就会想到使用面向对象了,因为面向对象解决的就是逻辑的封装和复用。 
假设页面A,页面B,页面C中存在相同逻辑,那么我们可以封装父构造函数对象

   function Parent(){}
   Parent.prototype = {
      method1: function(){},
      method2: function(){},
      ...
   }
   function A(){      //页面A
      this.A = A;
   }
   A.prototype = new Parent();
   A.prototype.otherMethod = function(){};

   var a = new A();   //使用对象
   a.init...

首先将页面A,页面B,页面C中相同逻辑抽离,相同逻辑可以是同一个ajax请求返回数据,或者是数据格式化等等的相同操作。将这些方法在Parent.prototype中定义,至于A,B,C页面自己特有的方法,则在如A.prototype中定义。这样很好地了解决了我们的问题,逻辑清晰,代码复用性强。 
如果在Parent方法参数中加入了回调callback,并且在callback中想调用子函数方法或者属性,可以参考我另一篇博文call和apply上手分析

hasOwnProperty

hasOwnProperty方法可以检测一个属性是存在于实例中,还是存在于原型中。

 

in

in运算符和hasOwnProperty不同,只要存在在原型上或者对象上就返回true

   function Parent(){
      this.name = 'sysyzhyupeng';
   }
   Parent.prototype.job = 'teacher';
   Parent.prototype.showJob = function(){
   }
   var parent = new Parent();
   'name' in parent;  // true
   'job' in parent  // true
   for(_key in parent){
      console.log(_key);  // 'name', 'job', 'showJob'
   }

在使用for-in循环时,返回的是所有能通过对象访问的且可枚举的属性。所有开发人正常定义的属性都是可枚举的,只有在IE8及更早版本除外。

Object.keys

ES5的Object.keys方法可以返回对象上的所有可枚举属性(注意只有对象上的,从原型上继承的没有)

 

 

手写JS网址参数读取

获取当前完整的url地址以及参数的方法

javascript 获取当前 URL 参数的两种方法:

//返回的是字符串形式的参数,例如:class_id=3&id=2&  
function getUrlArgStr(){  
    var q=location.search.substr(1);  
    var qs=q.split('&');  
    var argStr='';  
    if(qs){  
        for(var i=0;i<qs.length;i++){  
            argStr+=qs[i].substring(0,qs[i].indexOf('='))+'='+qs[i].substring(qs[i].indexOf('=')+1)+'&';  
        }  
    }  
    return argStr;  
}  
//返回的是对象形式的参数  
function getUrlArgObject(){  
    var args=new Object();  
    var query=location.search.substring(1);//获取查询串  
    var pairs=query.split(",");//在逗号处断开  
    for(var i=0;i<pairs.length;i++){  
        var pos=pairs[i].indexOf('=');//查找name=value  
        if(pos==-1){//如果没有找到就跳过  
            continue;  
        }  
        var argname=pairs[i].substring(0,pos);//提取name  
        var value=pairs[i].substring(pos+1);//提取value  
        args[argname]=unescape(value);//存为属性  
    }  
    return args;//返回对象  
}  

 

另外列出一些 javascript 获取url中各个部分的功能方法:

window.location.host; //返回url 的主机部分,例如:www.xxx.com  
window.location.hostname; //返回www.xxx.com  
window.location.href; //返回整个url字符串(在浏览器中就是完整的地址栏),例如:www.xxx.com/index.php?class_id=3&id=2  
window.location.pathname; //返回/a/index.php或者/index.php  
window.location.protocol; //返回url 的协议部分,例如: http:,ftp:,maito:等等。  
window.location.port //url 的端口部分,如果采用默认的80端口,那么返回值并不是默认的80而是空字符 

 

另一种方法:

eg:http://csdn.blog.net/flysun3344/article?name=ajax&id=3

例如上面为当前页面的url。我们需要获取name值和id的值,那么需要在页面进入http://csdn.blog.net/flysun3344/article?name=ajax&id=3这个页面时在js中用下面这个方法:

 

 
  1. /**

  2. * 取得url参数

  3. */

  4. function getUrlParam(name) {

  5. var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"); // 构造一个含有目标参数的正则表达式对象

  6. var r = window.location.search.substr(1).match(reg); // 匹配目标参数

  7. if (r != null) return unescape(r[2]); return null; // 返回参数值

  8. }

在js中调用定义的getUrlParam即可:

 

 

手写代码
  1. var name = getUrlParam('name');

  2. var id = getUrlParam('id');

  1. function getPar(par){

  2. //获取当前URL

  3. var local_url = document.location.href;

  4. //获取要取得的get参数位置

  5. var get = local_url.indexOf(par +"=");

  6. if(get == -1){

  7. return false;

  8. }

  9. //截取字符串

  10. var get_par = local_url.slice(par.length + get + 1);

  11. //判断截取后的字符串是否还有其他get参数

  12. var nextPar = get_par.indexOf("&");

  13. if(nextPar != -1){

  14. get_par = get_par.slice(0, nextPar);

  15. }

  16. return get_par;

  17.  

 

手写闭包

什么是闭包?
先看一段代码:

?

1

2

3

4

5

6

7

8

9

10

function a(){

  var n = 0;

  function inc() {

    n++;

    console.log(n);

  }

  inc();

  inc();

}

a(); //控制台输出1,再输出2

简单吧。再来看一段代码:

?

1

2

3

4

5

6

7

8

9

10

function a(){

  var n = 0;

  this.inc = function () {

    n++;

    console.log(n);

  };

}

var c = new a();

c.inc();  //控制台输出1

c.inc();  //控制台输出2

简单吧。

什么是闭包?这就是闭包!

有权访问另一个函数作用域内变量的函数都是闭包。这里 inc 函数访问了构造函数 a 里面的变量 n,所以形成了一个闭包。

再来看一段代码:

?

1

2

3

4

5

6

7

8

9

10

11

function a(){

  var n = 0;

  function inc(){

    n++;

    console.log(n);

  }

  return inc;

}

var c = a();

c();  //控制台输出1

c();  //控制台输出2

看看是怎么执行的:

var c = couter(),这一句 couter()返回的是函数 inc,那这句等同于 var c = inc;

c(),这一句等同于 inc();  注意,函数名只是一个标识(指向函数的指针),而()才是执行函数。

后面三句翻译过来就是:  var c = inc;  inc();  inc();,跟第一段代码有区别吗? 没有。

什么是闭包?这就是闭包!

所有的教科书教程上都喜欢用最后一段来说明闭包,但我觉得这将问题复杂化了。这里面返回的是函数名,没看过谭浩强C/C++程序设计的同学可能一下子没反应出带不带()的区别,也就是说这种写法自带一个陷阱。虽然这种写法更显高大上,但我还是喜欢将问题单一化,看看代码 1 和代码 2,你还会纠结函数的调用,你会纠结 n 的值吗?

为啥要这样写?
我们知道,js的每个函数都是一个个小黑屋,它可以获取外界信息,但是外界却无法直接看到里面的内容。将变量 n 放进小黑屋里,除了 inc 函数之外,没有其他办法能接触到变量 n,而且在函数 a 外定义同名的变量 n 也是互不影响的,这就是所谓的增强“封装性”。

而之所以要用 return 返回函数标识 inc,是因为在 a 函数外部无法直接调用 inc 函数,所以 return inc 与外部联系起来,代码 2 中的 this 也是将 inc 与外部联系起来而已。

常见的陷阱
看看这个:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

function createFunctions(){

  var result = new Array();

  for (var i=0; i < 10; i++){

    result[i] = function(){

      return i;

    };

  }

  return result;

}

var funcs = createFunctions();

for (var i=0; i < funcs.length; i++){

  console.log(funcs[i]());

}

乍一看,以为输出 0~9 ,万万没想到输出10个10?

这里的陷阱就是:函数带()才是执行函数! 单纯的一句 var f = function() { alert('Hi'); }; 是不会弹窗的,后面接一句 f(); 才会执行函数内部的代码。上面代码翻译一下就是:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

var result = new Array(), i;

result[0] = function(){ return i; }; //没执行函数,函数内部不变,不能将函数内的i替换!

result[1] = function(){ return i; }; //没执行函数,函数内部不变,不能将函数内的i替换!

...

result[9] = function(){ return i; }; //没执行函数,函数内部不变,不能将函数内的i替换!

i = 10;

funcs = result;

result = null;

 

console.log(i); // funcs[0]()就是执行 return i 语句,就是返回10

console.log(i); // funcs[1]()就是执行 return i 语句,就是返回10

...

console.log(i); // funcs[9]()就是执行 return i 语句,就是返回10

为什么只垃圾回收了 result,但却不收了 i 呢? 因为 i 还在被 function 引用着啊。好比一个餐厅,盘子总是有限的,所以服务员会去巡台回收空盘子,但还装着菜的盘子他怎么敢收? 当然,你自己手动倒掉了盘子里面的菜(=null),那盘子就会被收走了,这就是所谓的内存回收机制。

至于 i 的值怎么还能保留,其实从文章开头一路读下来,这应该没有什么可以纠结的地方。盘子里面的菜,吃了一块不就应该少一块吗?

总结一下
闭包就是一个函数引用另外一个函数的变量,因为变量被引用着所以不会被回收,因此可以用来封装一个私有变量。这是优点也是缺点,不必要的闭包只会徒增内存消耗!另外使用闭包也要注意变量的值是否符合你的要求,因为他就像一个静态私有变量一样。闭包通常会跟很多东西混搭起来,接触多了才能加深理解,这里只是开个头说说基础性的东西。

 

 

全面理解Javascript闭包和闭包的几种写法及用途

 

好久没有写博客了,过了一个十一长假都变懒了,今天总算是恢复状态了。好了,进入正题,今天来说一说javascript里面的闭包吧!本篇博客主要讲一些实用的东西,主要将闭包的写法、用法和用途。

 

一、什么是闭包和闭包的几种写法和用法                                                      

 

1、什么是闭包

 

闭包,官方对闭包的解释是:一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。闭包的特点:

  1. 作为一个函数变量的一个引用,当函数返回时,其处于激活状态。

  2. 一个闭包就是当一个函数返回时,一个没有释放资源的栈区。

  简单的说,Javascript允许使用内部函数---即函数定义和函数表达式位于另一个函数的函数体内。而且,这些内部函数可以访问它们所在的外部函数中声明的所有局部变量、参数和声明的其他内部函数。当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就会形成闭包。

 

 

 

2、闭包的几种写法和用法

 

首先要明白,在JS中一切都是对象,函数是对象的一种。下面先来看一下闭包的5种写法,简单理解一下什么是闭包。后面会具体解释。

 

复制代码

//第1种写法  

function Circle(r) {  

      this.r = r;  

}  

Circle.PI = 3.14159;  

Circle.prototype.area = function() {  

  return Circle.PI * this.r * this.r;  

}  

  

var c = new Circle(1.0);     

alert(c.area());   

复制代码

这种写法没什么特别的,只是给函数添加一些属性。

 

复制代码

//第2种写法  

var Circle = function() {  

   var obj = new Object();  

   obj.PI = 3.14159;  

     

   obj.area = function( r ) {  

       return this.PI * r * r;  

   }  

   return obj;  

}  

  

var c = new Circle();  

alert( c.area( 1.0 ) );  

复制代码

这种写法是声明一个变量,将一个函数当作值赋给变量。

 

复制代码

//第3种写法  

var Circle = new Object();  

Circle.PI = 3.14159;  

Circle.Area = function( r ) {  

       return this.PI * r * r;  

}  

  

alert( Circle.Area( 1.0 ) );  

复制代码

这种方法最好理解,就是new 一个对象,然后给对象添加属性和方法。

 

复制代码

//第4种写法  

var Circle={  

   "PI":3.14159,  

 "area":function(r){  

          return this.PI * r * r;  

        }  

};  

alert( Circle.area(1.0) );  

复制代码

这种方法使用较多,也最为方便。var obj = {}就是声明一个空的对象。

 

//第5种写法  

var Circle = new Function("this.PI = 3.14159;this.area = function( r ) {return r*r*this.PI;}");  

  

alert( (new Circle()).area(1.0) );  

说实话,这种写法我是没用过,大家可以参考一下。

 

总的来说,上面几种方法,第2中和第4中较为常见,大家可以根据习惯选择。

 

上面代码中出现了JS中常用的Prototype,那么Prototype有什么用呢?下面我们来看一下:

 

复制代码

    var dom = function(){

        

    };

 

    dom.Show = function(){

        alert("Show Message");

    };

    

    dom.prototype.Display = function(){

        alert("Property Message");

    };

 

    dom.Display(); //error

    dom.Show();  

    var d = new dom();

    d.Display();

    d.Show(); //error

复制代码

我们首先声明一个变量,将一个函数赋给他,因为在Javascript中每个函数都有一个Portotype属性,而对象没有。添加两个方法,分别直接添加和添加打破Prototype上面,来看下调用情况。分析结果如下:

 

   1、不使用prototype属性定义的对象方法,是静态方法,只能直接用类名进行调用!另外,此静态方法中无法使用this变量来调用对象其他的属性!

   2、使用prototype属性定义的对象方法,是非静态方法,只有在实例化后才能使用!其方法内部可以this来引用对象自身中的其他属性!

 

 

 

下面我们再来看一段代码:

 

复制代码

var dom = function(){

        var Name = "Default";

        this.Sex = "Boy";

        this.success = function(){

            alert("Success");

        };

    };

 

    alert(dom.Name);

    alert(dom.Sex);

复制代码

大家先看看,会显示什么呢? 答案是两个都显示Undefined,为什么呢?这是由于在Javascript中每个function都会形成一个作用域,而这些变量声明在函数中,所以就处于这个函数的作用域中,外部是无法访问的。要想访问变量,就必须new一个实例出来。

 

复制代码

var html = {

        Name:'Object',

        Success:function(){

            this.Say = function(){

                    alert("Hello,world");

            };

            alert("Obj Success");

        }

    };

复制代码

再来看看这种写法,其实这是Javascript的一个"语法糖",这种写法相当于:

 

复制代码

    var html = new Object();

    html.Name = 'Object';

    html.Success = function(){

            this.Say = function(){

                    alert("Hello,world");

            };

            alert("Obj Success");

复制代码

变量html是一个对象,不是函数,所以没有Prototype属性,其方法也都是公有方法,html不能被实例化。否则会出现如下错误:

 

 

 

但是他可以作为值赋给其它变量,如var o = html; 我们可以这样使用它:

 

    alert(html.Name);

    html.Success();

说到这里,完了吗?细心的人会问,怎么访问Success方法中的Say方法呢?是html.Success.Say()吗?

 

当然不是,上面刚说过由于作用域的限制,是访问不到的。所以要用下面的方法访问:

 

复制代码

var s = new html.Success();

s.Say();

 

//还可以写到外面

html.Success.prototype.Show = function(){

    alert("HaHa");

};

var s = new html.Success();

s.Show();

复制代码

关于Javascript作用域的问题,不是一两句能说清楚的,有兴趣的大家可以网上找些资料看看。

 

 

 

二、Javascript闭包的用途                                                                  

 

事实上,通过使用闭包,我们可以做很多事情。比如模拟面向对象的代码风格;更优雅,更简洁的表达出代码;在某些方面提升代码的执行效率。

 

1、匿名自执行函数

 

  我们知道所有的变量,如果不加上var关键字,则默认的会添加到全局对象的属性上去,这样的临时变量加入全局对象有很多坏处,

比如:别的函数可能误用这些变量;造成全局对象过于庞大,影响访问速度(因为变量的取值是需要从原型链上遍历的)。

除了每次使用变量都是用var关键字外,我们在实际情况下经常遇到这样一种情况,即有的函数只需要执行一次,其内部变量无需维护,

比如UI的初始化,那么我们可以使用闭包:

 

复制代码

var data= {    

    table : [],    

    tree : {}    

};    

     

(function(dm){    

    for(var i = 0; i < dm.table.rows; i++){    

       var row = dm.table.rows[i];    

       for(var j = 0; j < row.cells; i++){    

           drawCell(i, j);    

       }    

    }    

       

})(data);   

复制代码

我们创建了一个匿名的函数,并立即执行它,由于外部无法引用它内部的变量,因此在函数执行完后会立刻释放资源,关键是不污染全局对象。

 

 

 

2、结果缓存

 

我们开发中会碰到很多情况,设想我们有一个处理过程很耗时的函数对象,每次调用都会花费很长时间,

 

那么我们就需要将计算出来的值存储起来,当调用这个函数的时候,首先在缓存中查找,如果找不到,则进行计算,然后更新缓存并返回值,如果找到了,直接返回查找到的值即可。闭包正是可以做到这一点,因为它不会释放外部的引用,从而函数内部的值可以得以保留。

 

复制代码

var CachedSearchBox = (function(){    

    var cache = {},    

       count = [];    

    return {    

       attachSearchBox : function(dsid){    

           if(dsid in cache){//如果结果在缓存中    

              return cache[dsid];//直接返回缓存中的对象    

           }    

           var fsb = new uikit.webctrl.SearchBox(dsid);//新建    

           cache[dsid] = fsb;//更新缓存    

           if(count.length > 100){//保正缓存的大小<=100    

              delete cache[count.shift()];    

           }    

           return fsb;          

       },    

     

       clearSearchBox : function(dsid){    

           if(dsid in cache){    

              cache[dsid].clearSelection();      

           }    

       }    

    };    

})();    

     

CachedSearchBox.attachSearchBox("input");    

复制代码

这样我们在第二次调用的时候,就会从缓存中读取到该对象。

 

 

 

3、封装

 

复制代码

var person = function(){    

    //变量作用域为函数内部,外部无法访问    

    var name = "default";       

       

    return {    

       getName : function(){    

           return name;    

       },    

       setName : function(newName){    

           name = newName;    

       }    

    }    

}();    

     

print(person.name);//直接访问,结果为undefined    

print(person.getName());    

person.setName("abruzzi");    

print(person.getName());    

   

得到结果如下:  

   

undefined  

default  

abruzzi  

复制代码

 

 

4、实现类和继承

 

复制代码

function Person(){    

    var name = "default";       

       

    return {    

       getName : function(){    

           return name;    

       },    

       setName : function(newName){    

           name = newName;    

       }    

    }    

    };   

 

    var p = new Person();

    p.setName("Tom");

    alert(p.getName());

 

    var Jack = function(){};

    //继承自Person

    Jack.prototype = new Person();

    //添加私有方法

    Jack.prototype.Say = function(){

        alert("Hello,my name is Jack");

    };

    var j = new Jack();

    j.setName("Jack");

    j.Say();

    alert(j.getName());

复制代码

我们定义了Person,它就像一个类,我们new一个Person对象,访问它的方法。

 

下面我们定义了Jack,继承Person,并添加自己的方法。

 

 

手写JS继承

前言

JS作为面向对象的弱类型语言,继承也是其非常强大的特性之一。那么如何在JS中实现继承呢?让我们拭目以待。

手写JS继承的实现方式

既然要实现继承,那么首先我们得有一个父类,代码如下:

// 定义一个动物类
function Animal (name) {
  // 属性
  this.name = name || 'Animal';
  // 实例方法
  this.sleep = function(){
    console.log(this.name + '正在睡觉!');
  }
}
// 原型方法
Animal.prototype.eat = function(food) {
  console.log(this.name + '正在吃:' + food);
};

1、原型链继承

核心: 将父类的实例作为子类的原型

function Cat(){ 
}
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';

// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.eat('fish'));
console.log(cat.sleep());
console.log(cat instanceof Animal); //true 
console.log(cat instanceof Cat); //true

特点:

  1. 非常纯粹的继承关系,实例是子类的实例,也是父类的实例
  2. 父类新增原型方法/原型属性,子类都能访问到
  3. 简单,易于实现

缺点:

  1. 要想为子类新增属性和方法,必须要在new Animal()这样的语句之后执行,不能放到构造器中
  2. 无法实现多继承
  3. 来自原型对象的引用属性是所有实例共享的(详细请看附录代码: 示例1)
  4. 创建子类实例时,无法向父类构造函数传参

推荐指数:★★(3、4两大致命缺陷)

2017-8-17 10:21:43补充:感谢 MMHS 指出。缺点1中描述有误:可以在Cat构造函数中,为Cat实例增加实例属性。如果要新增原型属性和方法,则必须放在new Animal()这样的语句之后执行。

2、构造继承

核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}

// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true

特点:

  1. 解决了1中,子类实例共享父类引用属性的问题
  2. 创建子类实例时,可以向父类传递参数
  3. 可以实现多继承(call多个父类对象)

缺点:

  1. 实例并不是父类的实例,只是子类的实例
  2. 只能继承父类的实例属性和方法,不能继承原型属性/方法
  3. 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

推荐指数:★★(缺点3)

3、实例继承

核心:为父类实例添加新特性,作为子类实例返回

function Cat(name){
  var instance = new Animal();
  instance.name = name || 'Tom';
  return instance;
}

// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // false

特点:

  1. 不限制调用方式,不管是new 子类()还是子类(),返回的对象具有相同的效果

缺点:

  1. 实例是父类的实例,不是子类的实例
  2. 不支持多继承

推荐指数:★★

4、拷贝继承

function Cat(name){
  var animal = new Animal();
  for(var p in animal){
    Cat.prototype[p] = animal[p];
  }
  Cat.prototype.name = name || 'Tom';
}

// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true

特点:

  1. 支持多继承

缺点:

  1. 效率较低,内存占用高(因为要拷贝父类的属性)
  2. 无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)

推荐指数:★(缺点1)

5、组合继承

核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}
Cat.prototype = new Animal();

// 感谢 @学无止境c 的提醒,组合继承也是需要修复构造函数指向的。

Cat.prototype.constructor = Cat;

// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true

特点:

  1. 弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法
  2. 既是子类的实例,也是父类的实例
  3. 不存在引用属性共享问题
  4. 可传参
  5. 函数可复用

缺点:

  1. 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)

推荐指数:★★★★(仅仅多消耗了一点内存)

6、寄生组合继承

核心:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}
(function(){
  // 创建一个没有实例方法的类
  var Super = function(){};
  Super.prototype = Animal.prototype;
  //将实例作为子类的原型
  Cat.prototype = new Super();
})();

// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); //true

感谢 @bluedrink 提醒,该实现没有修复constructor。

Cat.prototype.constructor = Cat; // 需要修复下构造函数

特点:

  1. 堪称完美

缺点:

  1. 实现较为复杂

推荐指数:★★★★(实现复杂,扣掉一颗星)

附录代码:

示例一:

function Animal (name) {
  // 属性
  this.name = name || 'Animal';
  // 实例方法
  this.sleep = function(){
    console.log(this.name + '正在睡觉!');
  }
  //实例引用属性
  this.features = [];
}
function Cat(name){
}
Cat.prototype = new Animal();

var tom = new Cat('Tom');
var kissy = new Cat('Kissy');

console.log(tom.name); // "Animal"
console.log(kissy.name); // "Animal"
console.log(tom.features); // []
console.log(kissy.features); // []

tom.name = 'Tom-New Name';
tom.features.push('eat');

//针对父类实例值类型成员的更改,不影响
console.log(tom.name); // "Tom-New Name"
console.log(kissy.name); // "Animal"
//针对父类实例引用类型成员的更改,会通过影响其他子类实例
console.log(tom.features); // ['eat']
console.log(kissy.features); // ['eat']

原因分析:

关键点:属性查找过程

执行tom.features.push,首先找tom对象的实例属性(找不到),
那么去原型对象中找,也就是Animal的实例。发现有,那么就直接在这个对象的
features属性中插入值。
在console.log(kissy.features); 的时候。同上,kissy实例上没有,那么去原型上找。
刚好原型上有,就直接返回,但是注意,这个原型对象中features属性值已经变化了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值