标签归档javascript

超级有用的前端基础技术面试问题及答案收集

说说你对闭包的理解

使用闭包主要是为了设计私有的方法和变量。闭包的优点是可以避免全局变量的污染,缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。

闭包有三个特性:

1.函数嵌套函数 2.函数内部可以引用外部的参数和变量 3.参数和变量不会被垃圾回收机制回收

请你谈谈Cookie的弊端

cookie虽然在持久保存客户端数据提供了方便,分担了服务器存储的负担,但还是有很多局限性的。 第一:每个特定的域名下最多生成20个cookie

1.IE6或更低版本最多20个cookie
2.IE7和之后的版本最后可以有50个cookie。
3.Firefox最多50个cookie
4.chrome和Safari没有做硬性限制

IE和Opera 会清理近期最少使用的cookie,Firefox会随机清理cookie。

cookie的最大大约为4096字节,为了兼容性,一般不能超过4095字节。

IE 提供了一种存储可以持久化用户数据,叫做userdata,从IE5.0就开始支持。每个数据最多128K,每个域名下最多1M。这个持久化数据放在缓存中,如果缓存没有清理,那么会一直存在。

优点:极高的扩展性和可用性

1.通过良好的编程,控制保存在cookie中的session对象的大小。
2.通过加密和安全传输技术(SSL),减少cookie被破解的可能性。
3.只在cookie中存放不敏感数据,即使被盗也不会有重大损失。
4.控制cookie的生命期,使之不会永远有效。偷盗者很可能拿到一个过期的cookie。

缺点:

1.`Cookie`数量和长度的限制。每个domain最多只能有20条cookie,每个cookie长度不能超过4KB,否则会被截掉。

2.安全性问题。如果cookie被人拦截了,那人就可以取得所有的session信息。即使加密也与事无补,因为拦截者并不需要知道cookie的意义,他只要原样转发cookie就可以达到目的了。

3.有些状态不可能保存在客户端。例如,为了防止重复提交表单,我们需要在服务器端保存一个计数器。如果我们把这个计数器保存在客户端,那么它起不到任何作用。

浏览器本地存储

在较高版本的浏览器中,js提供了sessionStorage和globalStorage。在HTML5中提供了localStorage来取代globalStorage。

html5中的Web Storage包括了两种存储方式:sessionStorage和localStorage。

sessionStorage用于本地存储一个会话(session)中的数据,这些数据只有在同一个会话中的页面才能访问并且当会话结束后数据也随之销毁。因此sessionStorage不是一种持久化的本地存储,仅仅是会话级别的存储。

而localStorage用于持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的。

web storage和cookie的区别

Web Storage的概念和cookie相似,区别是它是为了更大容量存储设计的。Cookie的大小是受限的,并且每次你请求一个新的页面的时候Cookie都会被发送过去,这样无形中浪费了带宽,另外cookie还需要指定作用域,不可以跨域调用。

除此之外,Web Storage拥有setItem,getItem,removeItem,clear等方法,不像cookie需要前端开发者自己封装setCookie,getCookie。

但是cookie也是不可以或缺的:cookie的作用是与服务器进行交互,作为HTTP规范的一部分而存在 ,而Web Storage仅仅是为了在本地“存储”数据而生

浏览器的支持除了IE7及以下不支持外,其他标准浏览器都完全支持(ie及FF需在web服务器里运行),值得一提的是IE总是办好事,例如IE7、IE6中的userData其实就是javascript本地存储的解决方案。通过简单的代码封装可以统一到所有的浏览器都支持web storage。

localStorage和sessionStorage都具有相同的操作方法,例如setItem、getItem和removeItem等

cookie 和session 的区别:

 1、cookie数据存放在客户的浏览器上,session数据放在服务器上。
 2、cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗
    考虑到安全应当使用session。
 3、session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能
     考虑到减轻服务器性能方面,应当使用COOKIE。
 4、单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。
 5、所以个人建议:
    将登陆信息等重要信息存放为SESSION
    其他信息如果需要保留,可以放在COOKIE中

CSS 相关问题

display:none和visibility:hidden的区别?

display:none  隐藏对应的元素,在文档布局中不再给它分配空间,它各边的元素会合拢,
就当他从来不存在。

visibility:hidden  隐藏对应的元素,但是在文档布局中仍保留原来的空间。

CSS中 link 和@import 的区别是?

(1) link属于HTML标签,而@import是CSS提供的; 
(2) 页面被加载的时,link会同时被加载,而@import引用的CSS会等到页面被加载完再加载;
(3) import只在IE5以上才能识别,而link是HTML标签,无兼容问题; 
(4) link方式的样式的权重 高于@import的权重.

position:absolute和float属性的异同

A:共同点:
对内联元素设置`float`和`absolute`属性,可以让元素脱离文档流,并且可以设置其宽高。

B:不同点:
float仍会占据位置,position会覆盖文档流中的其他元素。

介绍一下box-sizing属性?

box-sizing属性主要用来控制元素的盒模型的解析模式。默认值是content-box。

  • content-box:让元素维持W3C的标准盒模型。元素的宽度/高度由border + padding + content的宽度/高度决定,设置width/height属性指的是content部分的宽/高
  • border-box:让元素维持IE传统盒模型(IE6以下版本和IE6~7的怪异模式)。设置width/height属性指的是border + padding + content

标准浏览器下,按照W3C规范对盒模型解析,一旦修改了元素的边框或内距,就会影响元素的盒子尺寸,就不得不重新计算元素的盒子尺寸,从而影响整个页面的布局。

CSS 选择符有哪些?哪些属性可以继承?优先级算法如何计算? CSS3新增伪类有那些?

1.id选择器( # myid)
2.类选择器(.myclassname)
3.标签选择器(div, h1, p)
4.相邻选择器(h1 + p)
5.子选择器(ul > li)
6.后代选择器(li a)
7.通配符选择器( * )
8.属性选择器(a[rel = "external"])
9.伪类选择器(a: hover, li:nth-child)
  • 可继承的样式: font-size font-family color, text-indent;
  • 不可继承的样式:border padding margin width height ;
  • 优先级就近原则,同权重情况下样式定义最近者为准;
  • 载入样式以最后载入的定位为准;

优先级为:

!important >  id > class > tag  

important 比 内联优先级高,但内联比 id 要高

CSS3新增伪类举例:

p:first-of-type 选择属于其父元素的首个 <p> 元素的每个 <p> 元素。
p:last-of-type  选择属于其父元素的最后 <p> 元素的每个 <p> 元素。
p:only-of-type  选择属于其父元素唯一的 <p> 元素的每个 <p> 元素。
p:only-child    选择属于其父元素的唯一子元素的每个 <p> 元素。
p:nth-child(2)  选择属于其父元素的第二个子元素的每个 <p> 元素。
:enabled  :disabled 控制表单控件的禁用状态。
:checked        单选框或复选框被选中。

position的值, relative和absolute分别是相对于谁进行定位的?

absolute 
        生成绝对定位的元素, 相对于最近一级的 定位不是 static 的父元素来进行定位。

fixed (老IE不支持)
    生成绝对定位的元素,相对于浏览器窗口进行定位。 

relative 
    生成相对定位的元素,相对于其在普通流中的位置进行定位。 

static  默认值。没有定位,元素出现在正常的流中

CSS3有哪些新特性?

CSS3实现圆角(border-radius),阴影(box-shadow),
对文字加特效(text-shadow、),线性渐变(gradient),旋转(transform)
transform:rotate(9deg) scale(0.85,0.90) translate(0px,-30px) skew(-9deg,0deg);//旋转,缩放,定位,倾斜
增加了更多的CSS选择器  多背景 rgba 
在CSS3中唯一引入的伪元素是::selection.
媒体查询,多栏布局
border-image

XML和JSON的区别?

(1).数据体积方面。
JSON相对于XML来讲,数据的体积小,传递的速度更快些。
(2).数据交互方面。
JSON与JavaScript的交互更加方便,更容易解析处理,更好的数据交互。
(3).数据描述方面。
JSON对数据的描述性比XML较差。
(4).传输速度方面。
JSON的速度要远远快于XML。

对BFC规范的理解?

      BFC,块级格式化上下文,一个创建了新的BFC的盒子是独立布局的,盒子里面的子元素的样式不会影响到外面的元素。在同一个BFC中的两个毗邻的块级盒在垂直方向(和布局方向有关系)的margin会发生折叠。
    (W3C CSS 2.1 规范中的一个概念,它决定了元素如何对其内容进行布局,以及与其他元素的关系和相互作用。)

解释下 CSS sprites,以及你要如何在页面或网站中使用它。

CSS Sprites其实就是把网页中一些背景图片整合到一张图片文件中,再利用CSS的“background-image”,“background- repeat”,“background-position”的组合进行背景定位,background-position可以用数字能精确的定位出背景图片的位置。这样可以减少很多图片请求的开销,因为请求耗时比较长;请求虽然可以并发,但是也有限制,一般浏览器都是6个。对于未来而言,就不需要这样做了,因为有了`http2`。

html部分

说说你对语义化的理解?

1,去掉或者丢失样式的时候能够让页面呈现出清晰的结构
2,有利于SEO:和搜索引擎建立良好沟通,有助于爬虫抓取更多的有效信息:爬虫依赖于标签来确定上下文和各个关键字的权重;
3,方便其他设备解析(如屏幕阅读器、盲人阅读器、移动设备)以意义的方式来渲染网页;
4,便于团队开发和维护,语义化更具可读性,是下一步吧网页的重要动向,遵循W3C标准的团队都遵循这个标准,可以减少差异化。

Doctype作用? 严格模式与混杂模式如何区分?它们有何意义?

(1)、<!DOCTYPE> 声明位于文档中的最前面,处于 <html> 标签之前。告知浏览器以何种模式来渲染文档。 

(2)、严格模式的排版和 JS 运作模式是  以该浏览器支持的最高标准运行。

(3)、在混杂模式中,页面以宽松的向后兼容的方式显示。模拟老式浏览器的行为以防止站点无法工作。

(4)、DOCTYPE不存在或格式不正确会导致文档以混杂模式呈现。   

你知道多少种Doctype文档类型?

 该标签可声明三种 DTD 类型,分别表示严格版本、过渡版本以及基于框架的 HTML 文档。
 HTML 4.01 规定了三种文档类型:Strict、Transitional 以及 Frameset。
 XHTML 1.0 规定了三种 XML 文档类型:Strict、Transitional 以及 Frameset。
Standards (标准)模式(也就是严格呈现模式)用于呈现遵循最新标准的网页,而 Quirks
 (包容)模式(也就是松散呈现模式或者兼容模式)用于呈现为传统浏览器而设计的网页。

HTML与XHTML——二者有什么区别

区别:
1.所有的标记都必须要有一个相应的结束标记
2.所有标签的元素和属性的名字都必须使用小写
3.所有的XML标记都必须合理嵌套
4.所有的属性必须用引号""括起来
5.把所有<和&特殊符号用编码表示
6.给所有属性赋一个值
7.不要在注释内容中使“--”
8.图片必须有说明文字

常见兼容性问题?

* png24位的图片在iE6浏览器上出现背景,解决方案是做成PNG8.也可以引用一段脚本处理.

* 浏览器默认的margin和padding不同。解决方案是加一个全局的*{margin:0;padding:0;}来统一。

* IE6双边距bug:块属性标签float后,又有横行的margin情况下,在ie6显示margin比设置的大。 

* 浮动ie产生的双倍距离(IE6双边距问题:在IE6下,如果对元素设置了浮动,同时又设置了margin-left或margin-right,margin值会加倍。)
  #box{ float:left; width:10px; margin:0 0 0 100px;} 

 这种情况之下IE会产生20px的距离,解决方案是在float的标签样式控制中加入 ——_display:inline;将其转化为行内属性。(_这个符号只有ie6会识别)

*  渐进识别的方式,从总体中逐渐排除局部。 

  首先,巧妙的使用“\9”这一标记,将IE游览器从所有情况中分离出来。 
  接着,再次使用“+”将IE8和IE7、IE6分离开来,这样IE8已经独立识别。

  css
      .bb{
       background-color:#f1ee18;/*所有识别*/
      .background-color:#00deff\9; /*IE6、7、8识别*/
      +background-color:#a200ff;/*IE6、7识别*/
      _background-color:#1e0bd1;/*IE6识别*/ 
      } 

*  IE下,可以使用获取常规属性的方法来获取自定义属性,
   也可以使用getAttribute()获取自定义属性;
   Firefox下,只能使用getAttribute()获取自定义属性. 
   解决方法:统一通过getAttribute()获取自定义属性.

* IE下,event对象有x,y属性,但是没有pageX,pageY属性; 
  Firefox下,event对象有pageX,pageY属性,但是没有x,y属性.

* 解决方法:(条件注释)缺点是在IE浏览器下可能会增加额外的HTTP请求数。

* Chrome 中文界面下默认会将小于 12px 的文本强制按照 12px 显示, 
  可通过加入 CSS 属性 -webkit-text-size-adjust: none; 解决.

* 超链接访问过后hover样式就不出现了 被点击访问过的超链接样式不在具有hover和active了解决方法是改变CSS属性的排列顺序:
L-V-H-A :  a:link {} a:visited {} a:hover {} a:active {}

* 怪异模式问题:漏写DTD声明,Firefox仍然会按照标准模式来解析网页,但在IE中会触发怪异模式。为避免怪异模式给我们带来不必要的麻烦,最好养成书写DTD声明的好习惯。现在可以使用[html5](http://www.w3.org/TR/html5/single-page.html)推荐的写法:`<doctype html>`

* 上下margin重合问题
ie和ff都存在,相邻的两个div的margin-left和margin-right不会重合,但是margin-top和margin-bottom却会发生重合。
解决方法,养成良好的代码编写习惯,同时采用margin-top或者同时采用margin-bottom。
* ie6对png图片格式支持不好(引用一段脚本处理)

解释下浮动和它的工作原理?清除浮动的技巧

浮动元素脱离文档流,不占据空间。浮动元素碰到包含它的边框或者浮动元素的边框停留。

1.使用空标签清除浮动。
   这种方法是在所有浮动标签后面添加一个空标签 定义css clear:both. 弊端就是增加了无意义标签。
2.使用overflow。
   给包含浮动元素的父标签添加css属性 overflow:auto; zoom:1; zoom:1用于兼容IE6。
3.使用after伪对象清除浮动。
   该方法只适用于非IE浏览器。具体写法可参照以下示例。使用中需注意以下几点。一、该方法中必须为需要清除浮动元素的伪对象中设置 height:0,否则该元素会比实际高出若干像素;

浮动元素引起的问题和解决办法?

浮动元素引起的问题:

(1)父元素的高度无法被撑开,影响与父元素同级的元素
(2)与浮动元素同级的非浮动元素(内联元素)会跟随其后
(3)若非第一个元素浮动,则该元素之前的元素也需要浮动,否则会影响页面显示的结构

解决方法: 使用CSS中的clear:both;属性来清除元素的浮动可解决2、3问题,对于问题1,添加如下样式,给父元素添加clearfix样式:

.clearfix:after{content: ".";display: block;height: 0;clear: both;visibility: hidden;}
.clearfix{display: inline-block;} /* for IE/Mac */

清除浮动的几种方法:

1,额外标签法,<div style="clear:both;"></div>(缺点:不过这个办法会增加额外的标签使HTML结构看起来不够简洁。)
2,使用after伪类

#parent:after{
    content:".";
    height:0;
    visibility:hidden;
    display:block;
    clear:both;
    }

3,浮动外部元素
4,设置`overflow`为`hidden`或者auto

IE 8以下版本的浏览器中的盒模型有什么不同

IE8以下浏览器的盒模型中定义的元素的宽高不包括内边距和边框

DOM操作——怎样添加、移除、移动、复制、创建和查找节点。

(1)创建新节点

      createDocumentFragment()    //创建一个DOM片段

      createElement()   //创建一个具体的元素

      createTextNode()   //创建一个文本节点

(2)添加、移除、替换、插入

      appendChild()

      removeChild()

      replaceChild()

      insertBefore() //在已有的子节点前插入一个新的子节点

(3)查找

      getElementsByTagName()    //通过标签名称

      getElementsByName()    //通过元素的Name属性的值(IE容错能力较强,会得到一个数组,其中包括id等于name值的)

      getElementById()    //通过元素Id,唯一性

html5有哪些新特性、移除了那些元素?如何处理HTML5新标签的浏览器兼容问题?如何区分 HTML 和 HTML5?

* HTML5 现在已经不是 SGML 的子集,主要是关于图像,位置,存储,多任务等功能的增加。

* 拖拽释放(Drag and drop) API 
  语义化更好的内容标签(header,nav,footer,aside,article,section)
  音频、视频API(audio,video)
  画布(Canvas) API
  地理(Geolocation) API
  本地离线存储 localStorage 长期存储数据,浏览器关闭后数据不丢失;
  sessionStorage 的数据在浏览器关闭后自动删除

  表单控件,calendar、date、time、email、url、search  
  新的技术webworker, websocket, Geolocation

* 移除的元素

纯表现的元素:basefont,big,center,font, s,strike,tt,u;

对可用性产生负面影响的元素:frame,frameset,noframes;

支持HTML5新标签:

* IE8/IE7/IE6支持通过document.createElement方法产生的标签,
  可以利用这一特性让这些浏览器支持HTML5新标签,

  浏览器支持新标签后,还需要添加标签默认的样式:

* 当然最好的方式是直接使用成熟的框架、使用最多的是html5shim框架
   <!--[if lt IE 9]> 
   <script> src="http://html5shim.googlecode.com/svn/trunk/html5.js"</script> 
   <![endif]--> 
如何区分: DOCTYPE声明\新增的结构元素\功能元素

iframe的优缺点?

1.`<iframe>`优点:

    解决加载缓慢的第三方内容如图标和广告等的加载问题
    Security sandbox
    并行加载脚本

2.`<iframe>`的缺点:


    *iframe会阻塞主页面的Onload事件;

    *即时内容为空,加载也需要时间
    *没有语意 

如何实现浏览器内多个标签页之间的通信?

调用localstorge、cookies等本地存储方式

线程与进程的区别

一个程序至少有一个进程,一个进程至少有一个线程. 
线程的划分尺度小于进程,使得多线程程序的并发性高。 
另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。 
线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。 
从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。

你如何对网站的文件和资源进行优化?

期待的解决方案包括:
 文件合并
 文件最小化/文件压缩
 使用 CDN 托管
 缓存的使用(多个域名来提供缓存)
 其他

请说出三种减少页面加载时间的方法。

 1.优化图片 
 2.图像格式的选择(GIF:提供的颜色较少,可用在一些对颜色要求不高的地方) 
 3.优化CSS(压缩合并css,如margin-top,margin-left...) 
 4.网址后加斜杠(如www.campr.com/目录,会判断这个“目录是什么文件类型,或者是目录。) 
 5.标明高度和宽度(如果浏览器没有找到这两个参数,它需要一边下载图片一边计算大小,如果图片很多,浏览器需要不断地调整页面。这不但影响速度,也影响浏览体验。 
当浏览器知道了高度和宽度参数后,即使图片暂时无法显示,页面上也会腾出图片的空位,然后继续加载后面的内容。从而加载时间快了,浏览体验也更好了。) 

6.减少http请求(合并文件,合并图片)。

你都使用哪些工具来测试代码的性能?

Profiler, JSPerf(http://jsperf.com/nexttick-vs-setzerotimeout-vs-settimeout), Dromaeo

什么是 FOUC(无样式内容闪烁)?你如何来避免 FOUC?

 FOUC - Flash Of Unstyled Content 文档样式闪烁
 <style type="text/css" media="all">@import "../fouc.css";</style> 
而引用CSS文件的@import就是造成这个问题的罪魁祸首。IE会先加载整个HTML文档的DOM,然后再去导入外部的CSS文件,因此,在页面DOM加载完成到CSS导入完成中间会有一段时间页面上的内容是没有样式的,这段时间的长短跟网速,电脑速度都有关系。
 解决方法简单的出奇,只要在<head>之间加入一个<link>或者<script>元素就可以了。

null和undefined的区别?

null是一个表示”无”的对象,转为数值时为0;undefined是一个表示”无”的原始值,转为数值时为NaN。

当声明的变量还未被初始化时,变量的默认值为undefined。 null用来表示尚未存在的对象,常用来表示函数企图返回一个不存在的对象。

undefined表示”缺少值”,就是此处应该有一个值,但是还没有定义。典型用法是:

(1)变量被声明了,但没有赋值时,就等于undefined。

(2) 调用函数时,应该提供的参数没有提供,该参数等于undefined。

(3)对象没有赋值的属性,该属性的值为undefined。

(4)函数没有返回值时,默认返回undefined。

null表示”没有对象”,即该处不应该有值。典型用法是:

(1) 作为函数的参数,表示该函数的参数不是对象。

(2) 作为对象原型链的终点。

new操作符具体干了什么呢?

   1、创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型。
   2、属性和方法被加入到 this 引用的对象中。
   3、新创建的对象由 this 所引用,并且最后隐式的返回 this 。

var obj  = {};
obj.__proto__ = Base.prototype;
Base.call(obj); 

js延迟加载的方式有哪些?

defer和async、动态创建DOM方式(创建script,插入到DOM中,加载完毕后callBack)、按需异步载入js

如何解决跨域问题?

    jsonp、 document.domain+iframe、window.name、window.postMessage、服务器上设置代理页面

jsonp的原理是动态插入script标签

具体参见:详解js跨域问题

documen.write和 innerHTML的区别

document.write只能重绘整个页面

innerHTML可以重绘页面的一部分

.call() 和 .apply() 的区别和作用?

作用:动态改变某个类的某个方法的运行环境。 区别参见:JavaScript学习总结(四)function函数部分

哪些操作会造成内存泄漏?

内存泄漏指任何对象在您不再拥有或需要它之后仍然存在。
垃圾回收器定期扫描对象,并计算引用了每个对象的其他对象的数量。如果一个对象的引用数量为 0(没有其他对象引用过该对象),或对该对象的惟一引用是循环的,那么该对象的内存即可回收。

setTimeout 的第一个参数使用字符串而非函数的话,会引发内存泄漏。
闭包、控制台日志、循环(在两个对象彼此引用且彼此保留时,就会产生一个循环)

详见:详解js变量、作用域及内存

JavaScript中的作用域与变量声明提升?

详见:详解JavaScript函数模式

如何判断当前脚本运行在浏览器还是node环境中?

通过判断Global对象是否为window,如果不为window,当前脚本没有运行在浏览器中

其他问题?

你遇到过比较难的技术问题是?你是如何解决的?

列举IE 与其他浏览器不一样的特性?

什么叫优雅降级和渐进增强?

优雅降级:Web站点在所有新式浏览器中都能正常工作,如果用户使用的是老式浏览器,则代码会检查以确认它们是否能正常工作。由于IE独特的盒模型布局问题,针对不同版本的IE的hack实践过优雅降级了,为那些无法支持功能的浏览器增加候选方案,使之在旧式浏览器上以某种形式降级体验却不至于完全失效.

渐进增强:从被所有浏览器支持的基本功能开始,逐步地添加那些只有新式浏览器才支持的功能,向页面增加无害于基础浏览器的额外样式和功能的。当浏览器支持时,它们会自动地呈现出来并发挥作用。

详见:css学习归纳总结(一)

WEB应用从服务器主动推送Data到客户端有那些方式?

Javascript数据推送

Commet:基于HTTP长连接的服务器推送技术

基于WebSocket的推送方案

SSE(Server-Send Event):服务器推送数据新方式

对前端界面工程师这个职位是怎么样理解的?它的前景会怎么样?

前端是最贴近用户的程序员,比后端、数据库、产品经理、运营、安全都近。
    1、实现界面交互
    2、提升用户体验
    3、有了Node.js,前端可以实现服务端的一些事情


前端是最贴近用户的程序员,前端的能力就是能让产品从 90分进化到 100 分,甚至更好,

 参与项目,快速高质量完成实现效果图,精确到1px;

 与团队成员,UI设计,产品经理的沟通;

 做好的页面结构,页面重构和用户体验;

 处理hack,兼容、写出优美的代码格式;

 针对服务器的优化、拥抱最新前端技术。

你有哪些性能优化的方法?

详情请看雅虎14条性能优化原则)。

  (1) 减少http请求次数:CSS Sprites, JS、CSS源码压缩、图片大小控制合适;网页Gzip,CDN托管,data缓存 ,图片服务器。

  (2) 前端模板 JS+数据,减少由于HTML标签导致的带宽浪费,前端用变量保存AJAX请求结果,每次操作本地变量,不用请求,减少请求次数

  (3) 用innerHTML代替DOM操作,减少DOM操作次数,优化javascript性能。

  (4) 当需要设置的样式很多时设置className而不是直接操作style。

  (5) 少用全局变量、缓存DOM节点查找的结果。减少IO读取操作。

  (6) 避免使用CSS Expression(css表达式)又称Dynamic properties(动态属性)。

  (7) 图片预加载,将样式表放在顶部,将脚本放在底部  加上时间戳。

详情:JavaScript学习总结(七)Ajax和Http状态字 – trigkit4 – SegmentFault

一个页面从输入 URL 到页面加载显示完成,这个过程中都发生了什么?

    分为4个步骤:
    (1),当发送一个URL请求时,不管这个URL是Web页面的URL还是Web页面上每个资源的URL,浏览器都会开启一个线程来处理这个请求,同时在远程DNS服务器上启动一个DNS查询。这能使浏览器获得请求对应的IP地址。
    (2), 浏览器与远程Web服务器通过TCP三次握手协商来建立一个TCP/IP连接。该握手包括一个同步报文,一个同步-应答报文和一个应答报文,这三个报文在 浏览器和服务器之间传递。该握手首先由客户端尝试建立起通信,而后服务器应答并接受客户端的请求,最后由客户端发出该请求已经被接受的报文。
    (3),一旦TCP/IP连接建立,浏览器会通过该连接向远程服务器发送HTTP的GET请求。远程服务器找到资源并使用HTTP响应返回该资源,值为200的HTTP响应状态表示一个正确的响应。
    (4),此时,Web服务器提供资源服务,客户端开始下载资源。

请求返回后,便进入了我们关注的前端模块
简单来说,浏览器会解析HTML生成DOM Tree,其次会根据CSS生成CSS Rule Tree,而javascript又可以根据DOM API操作DOM

详情:从输入 URL 到浏览器接收的过程中发生了什么事情?

平时如何管理你的项目?

先期团队必须确定好全局样式(globe.css),编码模式(utf-8) 等;

        编写习惯必须一致(例如都是采用继承式的写法,单样式都写成一行);

        标注样式编写人,各模块都及时标注(标注关键样式调用的地方);

        页面进行标注(例如 页面 模块 开始和结束);

        CSS跟HTML 分文件夹并行存放,命名都得统一(例如style.css);

        JS 分文件夹存放 命名以该JS功能为准的英文翻译。

        图片采用整合的 images.png png8 格式文件使用 尽量整合在一起使用方便将来的管理 

说说最近最流行的一些东西吧?常去哪些网站?

Node.js、Mongodb、npm、MVVM、MEAN、three.js,React 。
网站:w3cfuns,sf,hacknews,CSDN,慕课,博客园,InfoQ,w3cplus等

javascript对象的几种创建方式

1,工厂模式
2,构造函数模式
3,原型模式
4,混合构造函数和原型模式
5,动态原型模式
6,寄生构造函数模式
7,稳妥构造函数模式

javascript继承的6种方法

1,原型链继承
2,借用构造函数继承
3,组合继承(原型+借用构造)
4,原型式继承
5,寄生式继承
6,寄生组合式继承

详情:JavaScript继承方式详解

ajax过程

(1)创建XMLHttpRequest对象,也就是创建一个异步调用对象.

(2)创建一个新的HTTP请求,并指定该HTTP请求的方法、URL及验证信息.

(3)设置响应HTTP请求状态变化的函数.

(4)发送HTTP请求.

(5)获取异步调用返回的数据.

(6)使用JavaScript和DOM实现局部刷新.

详情:JavaScript学习总结(七)Ajax和Http状态字

异步加载和延迟加载

1.异步加载的方案: 动态插入script标签
2.通过ajax去获取js代码,然后通过eval执行
3.script标签上添加defer或者async属性
4.创建并插入iframe,让它异步执行js
5.延迟加载:有些 js 代码并不是页面初始化的时候就立刻需要的,而稍后的某些情况才需要的。

前端安全问题?

sql注入原理

就是通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。

总的来说有以下几点:

1.永远不要信任用户的输入,要对用户的输入进行校验,可以通过正则表达式,或限制长度,对单引号和双"-"进行转换等。
2.永远不要使用动态拼装SQL,可以使用参数化的SQL或者直接使用存储过程进行数据查询存取。
3.永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接。
4.不要把机密信息明文存放,请加密或者hash掉密码和敏感的信息。

XSS原理及防范

Xss(cross-site scripting)攻击指的是攻击者往Web页面里插入恶意html标签或者javascript代码。比如:攻击者在论坛中放一个 看似安全的链接,骗取用户点击后,窃取cookie中的用户私密信息;或者攻击者在论坛中加一个恶意表单, 当用户提交表单的时候,却把信息传送到攻击者的服务器中,而不是用户原本以为的信任站点。

XSS防范方法

1.代码里对用户输入的地方和变量都需要仔细检查长度和对”<”,”>”,”;”,”’”等字符做过滤;其次任何内容写到页面之前都必须加以encode,避免不小心把html tag 弄出来。这一个层面做好,至少可以堵住超过一半的XSS 攻击。
2.避免直接在cookie 中泄露用户隐私,例如email、密码等等。 3.通过使cookie 和系统ip 绑定来降低cookie 泄露后的危险。这样攻击者得到的cookie 没有实际价值,不可能拿来重放。
4.尽量采用POST 而非GET 提交表单

XSS与CSRF有什么区别吗?

XSS是获取信息,不需要提前知道其他用户页面的代码和数据包。CSRF是代替用户完成指定的动作,需要知道其他用户页面的代码和数据包。

要完成一次CSRF攻击,受害者必须依次完成两个步骤:

1.登录受信任网站A,并在本地生成Cookie。   2.在不登出A的情况下,访问危险网站B。

CSRF的防御

1.服务端的CSRF方式方法很多样,但总的思想都是一致的,就是在客户端页面增加伪随机数。 2.使用验证码

ie各版本和chrome可以并行下载多少个资源

IE6 两个并发,iE7升级之后的6个并发,之后版本也是6个

Firefox,chrome也是6个

javascript里面的继承怎么实现,如何避免原型链上面的对象共享

用构造函数和原型链的混合模式去实现继承,避免对象共享可以参考经典的extend()函数,很多前端框架都有封装的,就是用一个空函数当做中间变量

grunt, YUI compressor 和 google clojure用来进行代码压缩的用法。

YUI Compressor 是一个用来压缩 JS 和 CSS 文件的工具,采用Java开发。

使用方法:

//压缩JS
java -jar yuicompressor-2.4.2.jar --type js --charset utf-8 -v src.js > packed.js
//压缩CSS
java -jar yuicompressor-2.4.2.jar --type css --charset utf-8 -v src.css > packed.css

详情请见:你需要掌握的前端代码性能优化工具

Flash、Ajax各自的优缺点,在使用中如何取舍?

1、Flash ajax对比
Flash适合处理多媒体、矢量图形、访问机器;对CSS、处理文本上不足,不容易被搜索。
Ajax对CSS、文本支持很好,支持搜索;多媒体、矢量图形、机器访问不足。
共同点:与服务器的无刷新传递消息、用户离线和在线状态、操作DOM

请解释一下 JavaScript 的同源策略。

概念:同源策略是客户端脚本(尤其是Javascript)的重要的安全度量标准。它最早出自Netscape Navigator2.0,其目的是防止某个文档或脚本从多个不同源装载。

这里的同源策略指的是:协议,域名,端口相同,同源策略是一种安全协议。 指一段脚本只能读取来自同一来源的窗口和文档的属性。

为什么要有同源限制?

我们举例说明:比如一个黑客程序,他利用Iframe把真正的银行登录页面嵌到他的页面上,当你使用真实的用户名,密码登录时,他的页面就可以通过Javascript读取到你的表单中input中的内容,这样用户名,密码就轻松到手了。

什么是 “use strict”; ? 使用它的好处和坏处分别是什么?

ECMAscript 5添加了第二种运行模式:”严格模式”(strict mode)。顾名思义,这种模式使得Javascript在更严格的条件下运行。

设立”严格模式”的目的,主要有以下几个:

- 消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;
- 消除代码运行的一些不安全之处,保证代码运行的安全;
- 提高编译器效率,增加运行速度;
- 为未来新版本的Javascript做好铺垫。

注:经过测试IE6,7,8,9均不支持严格模式。

缺点: 现在网站的JS 都会进行压缩,一些文件用了严格模式,而另一些没有。这时这些本来是严格模式的文件,被 merge 后,这个串就到了文件的中间,不仅没有指示严格模式,反而在压缩后浪费了字节。

GET和POST的区别,何时使用POST?

    GET:一般用于信息获取,使用URL传递参数,对所发送信息的数量也有限制,一般在2000个字符
    POST:一般用于修改服务器上的资源,对所发送的信息没有限制。

    GET方式需要使用Request.QueryString来取得变量的值,而POST方式通过Request.Form来获取变量的值,
    也就是说Get是通过地址栏来传值,而Post是通过提交表单来传值。

然而,在以下情况中,请使用 POST 请求:
无法使用缓存文件(更新服务器上的文件或数据库)
向服务器发送大量数据(POST 没有数据量限制)
发送包含未知字符的用户输入时,POST 比 GET 更稳定也更可靠

哪些地方会出现css阻塞,哪些地方会出现js阻塞?

js的阻塞特性:所有浏览器在下载JS的时候,会阻止一切其他活动,比如其他资源的下载,内容的呈现等等。直到JS下载、解析、执行完毕后才开始继续并行下载其他资源并呈现内容。为了提高用户体验,新一代浏览器都支持并行下载JS,但是JS下载仍然会阻塞其它资源的下载(例如.图片,css文件等)。

由于浏览器为了防止出现JS修改DOM树,需要重新构建DOM树的情况,所以就会阻塞其他的下载和呈现。

嵌入JS会阻塞所有内容的呈现,而外部JS只会阻塞其后内容的显示,2种方式都会阻塞其后资源的下载。也就是说外部样式不会阻塞外部脚本的加载,但会阻塞外部脚本的执行。

CSS怎么会阻塞加载了?CSS本来是可以并行下载的,在什么情况下会出现阻塞加载了(在测试观察中,IE6下CSS都是阻塞加载)

当CSS后面跟着嵌入的JS的时候,该CSS就会出现阻塞后面资源下载的情况。而当把嵌入JS放到CSS前面,就不会出现阻塞的情况了。

根本原因:因为浏览器会维持html中css和js的顺序,样式表必须在嵌入的JS执行前先加载、解析完。而嵌入的JS会阻塞后面的资源加载,所以就会出现上面CSS阻塞下载的情况。

嵌入JS应该放在什么位置?

   1、放在底部,虽然放在底部照样会阻塞所有呈现,但不会阻塞资源下载。

   2、如果嵌入JS放在head中,请把嵌入JS放在CSS头部。

   3、使用defer(只支持IE)

   4、不要在嵌入的JS中调用运行时间较长的函数,如果一定要用,可以用`setTimeout`来调用

Javascript无阻塞加载具体方式

  • 将脚本放在底部。<link>还是放在head中,用以保证在js加载前,能加载出正常显示的页面。<script>标签放在</body>前。
  • 成组脚本:由于每个<script>标签下载时阻塞页面解析过程,所以限制页面的<script>总数也可以改善性能。适用于内联脚本和外部脚本。
  • 非阻塞脚本:等页面完成加载后,再加载js代码。也就是,在window.onload事件发出后开始下载代码。 (1)defer属性:支持IE4和fierfox3.5更高版本浏览器 (2)动态脚本元素:文档对象模型(DOM)允许你使用js动态创建HTML的几乎全部文档内容。代码如下:
<script>
var script=document.createElement("script");
script.type="text/javascript";
script.src="file.js";
document.getElementsByTagName("head")[0].appendChild(script);
</script>

此技术的重点在于:无论在何处启动下载,文件额下载和运行都不会阻塞其他页面处理过程。即使在head里(除了用于下载文件的http链接)。

闭包相关问题?

详情请见:详解js闭包

js事件处理程序问题?

详情请见:JavaScript学习总结(九)事件详解

eval是做什么的?

它的功能是把对应的字符串解析成JS代码并运行;
应该避免使用eval,不安全,非常耗性能(2次,一次解析成js语句,一次执行)。

JavaScript原型,原型链 ? 有什么特点?

*  原型对象也是普通的对象,是对象一个自带隐式的 __proto__ 属性,原型也有可能有自己的原型,如果一个原型对象的原型不为null的话,我们就称之为原型链。
*  原型链是由一些用来继承和共享属性的对象组成的(有限的)对象链。

事件、IE与火狐的事件机制有什么区别? 如何阻止冒泡?

 1. 我们在网页中的某个操作(有的操作对应多个事件)。例如:当我们点击一个按钮就会产生一个事件。是可以被 JavaScript 侦测到的行为。  
 2. 事件处理机制:IE是事件冒泡、firefox同时支持两种事件模型,也就是:捕获型事件和冒泡型事件。;
 3.  ev.stopPropagation();注意旧ie的方法 ev.cancelBubble = true;

ajax 是什么?ajax 的交互模型?同步和异步的区别?如何解决跨域问题?

详情请见:JavaScript学习总结(七)Ajax和Http状态字

1. 通过异步模式,提升了用户体验

  2. 优化了浏览器和服务器之间的传输,减少不必要的数据往返,减少了带宽占用

  3. Ajax在客户端运行,承担了一部分本来由服务器承担的工作,减少了大用户量下的服务器负载。

  2. Ajax的最大的特点是什么。

  Ajax可以实现动态不刷新(局部刷新)
  readyState属性 状态 有5个可取值: 0=未初始化 ,1=启动 2=发送,3=接收,4=完成

ajax的缺点

  1、ajax不支持浏览器back按钮。

  2、安全问题 AJAX暴露了与服务器交互的细节。

  3、对搜索引擎的支持比较弱。

  4、破坏了程序的异常机制。

  5、不容易调试。

跨域: jsonp、 iframe、window.name、window.postMessage、服务器上设置代理页面

js对象的深度克隆

  function clone(Obj) {   
        var buf;   
        if (Obj instanceof Array) {   
            buf = [];  //创建一个空的数组 
            var i = Obj.length;   
            while (i--) {   
                buf[i] = clone(Obj[i]);   
            }   
            return buf;   
        }else if (Obj instanceof Object){   
            buf = {};  //创建一个空对象 
            for (var k in Obj) {  //为这个对象添加新的属性 
                buf[k] = clone(Obj[k]);   
            }   
            return buf;   
        }else{   
            return Obj;   
        }   
    }  

AMD和CMD 规范的区别?

详情请见:详解JavaScript模块化开发

网站重构的理解?

网站重构:在不改变外部行为的前提下,简化结构、添加可读性,而在网站前端保持一致的行为。也就是说是在不改变UI的情况下,对网站进行优化,在扩展的同时保持一致的UI。

对于传统的网站来说重构通常是:

表格(table)布局改为DIV+CSS
使网站前端兼容于现代浏览器(针对于不合规范的CSS、如对IE6有效的)
对于移动平台的优化
针对于SEO进行优化
深层次的网站重构应该考虑的方面

减少代码间的耦合
让代码保持弹性
严格按规范编写代码
设计可扩展的API
代替旧有的框架、语言(如VB)
增强用户体验
通常来说对于速度的优化也包含在重构中

压缩JS、CSS、image等前端资源(通常是由服务器来解决)
程序的性能优化(如数据读写)
采用CDN来加速资源加载
对于JS DOM的优化
HTTP服务器的文件缓存

如何获取UA?

<script> 
    function whatBrowser() {  
        document.Browser.Name.value=navigator.appName;  
        document.Browser.Version.value=navigator.appVersion;  
        document.Browser.Code.value=navigator.appCodeName;  
        document.Browser.Agent.value=navigator.userAgent;  
    }  
</script>

js数组去重

以下是数组去重的三种方法:

Array.prototype.unique1 = function () {
  var n = []; //一个新的临时数组
  for (var i = 0; i < this.length; i++) //遍历当前数组
  {
    //如果当前数组的第i已经保存进了临时数组,那么跳过,
    //否则把当前项push到临时数组里面
    if (n.indexOf(this[i]) == -1) n.push(this[i]);
  }
  return n;
}

Array.prototype.unique2 = function()
{
    var n = {},r=[]; //n为hash表,r为临时数组
    for(var i = 0; i < this.length; i++) //遍历当前数组
    {
        if (!n[this[i]]) //如果hash表中没有当前项
        {
            n[this[i]] = true; //存入hash表
            r.push(this[i]); //把当前数组的当前项push到临时数组里面
        }
    }
    return r;
}

Array.prototype.unique3 = function()
{
    var n = [this[0]]; //结果数组
    for(var i = 1; i < this.length; i++) //从第二项开始遍历
    {
        //如果当前数组的第i项在当前数组中第一次出现的位置不是i,
        //那么表示第i项是重复的,忽略掉。否则存入结果数组
        if (this.indexOf(this[i]) == i) n.push(this[i]);
    }
    return n;
}

HTTP状态码

100  Continue  继续,一般在发送post请求时,已发送了http header之后服务端将返回此信息,表示确认,之后发送具体参数信息
200  OK   正常返回信息
201  Created  请求成功并且服务器创建了新的资源
202  Accepted  服务器已接受请求,但尚未处理
301  Moved Permanently  请求的网页已永久移动到新位置。
302 Found  临时性重定向。
303 See Other  临时性重定向,且总是使用 GET 请求新的 URI。
304  Not Modified  自从上次请求后,请求的网页未修改过。

400 Bad Request  服务器无法理解请求的格式,客户端不应当尝试再次使用相同的内容发起请求。
401 Unauthorized  请求未授权。
403 Forbidden  禁止访问。
404 Not Found  找不到如何与 URI 相匹配的资源。

500 Internal Server Error  最常见的服务器端错误。
503 Service Unavailable 服务器端暂时无法处理请求(可能是过载或维护)。

js操作获取和设置cookie

//创建cookie
function setCookie(name, value, expires, path, domain, secure) {
    var cookieText = encodeURIComponent(name) + '=' + encodeURIComponent(value);
    if (expires instanceof Date) {
        cookieText += '; expires=' + expires;
    }
    if (path) {
        cookieText += '; expires=' + expires;
    }
    if (domain) {
        cookieText += '; domain=' + domain;
    }
    if (secure) {
        cookieText += '; secure';
    }
    document.cookie = cookieText;
}

//获取cookie
function getCookie(name) {
    var cookieName = encodeURIComponent(name) + '=';
    var cookieStart = document.cookie.indexOf(cookieName);
    var cookieValue = null;
    if (cookieStart > -1) {
        var cookieEnd = document.cookie.indexOf(';', cookieStart);
        if (cookieEnd == -1) {
            cookieEnd = document.cookie.length;
        }
        cookieValue = decodeURIComponent(document.cookie.substring(cookieStart + cookieName.length, cookieEnd));
    }
    return cookieValue;
}

//删除cookie
function unsetCookie(name) {
    document.cookie = name + "= ; expires=" + new Date(0);
}

说说TCP传输的三次握手策略

为了准确无误地把数据送达目标处,TCP协议采用了三次握手策略。用TCP协议把数据包送出去后,TCP不会对传送  后的情况置之不理,它一定会向对方确认是否成功送达。握手过程中使用了TCP的标志:SYN和ACK。
发送端首先发送一个带SYN标志的数据包给对方。接收端收到后,回传一个带有SYN/ACK标志的数据包以示传达确认信息。最后,发送端再回传一个带ACK标志的数据包,代表“握手”结束
若在握手过程中某个阶段莫名中断,TCP协议会再次以相同的顺序发送相同的数据包。

说说你对Promise的理解

依照 Promise/A+ 的定义,Promise 有四种状态:

pending: 初始状态, 非 fulfilled 或 rejected.
fulfilled: 成功的操作.
rejected: 失败的操作.
settled: Promise已被fulfilled或rejected,且不是pending

另外, fulfilled 与 rejected 一起合称 settled。

Promise 对象用来进行延迟(deferred) 和异步(asynchronous ) 计算。

Promise 的构造函数

构造一个 Promise,最基本的用法如下:

var promise = new Promise(function(resolve, reject) {
    if (...) {  // succeed
        resolve(result);
    } else {   // fails
        reject(Error(errMessage));
    }
});

Promise 实例拥有 then 方法(具有 then 方法的对象,通常被称为 thenable)。它的使用方法如下:

promise.then(onFulfilled, onRejected)

接收两个函数作为参数,一个在 fulfilled 的时候被调用,一个在 rejected 的时候被调用,接收参数就是 future,onFulfilled 对应 resolve, onRejected 对应 reject。

Javascript垃圾回收方法

标记清除(mark and sweep)

这是JavaScript最常见的垃圾回收方式,当变量进入执行环境的时候,比如函数中声明一个变量,垃圾回收器将其标记为“进入环境”,当变量离开环境的时候(函数执行结束)将其标记为“离开环境”。

垃圾回收器会在运行的时候给存储在内存中的所有变量加上标记,然后去掉环境中的变量以及被环境中变量所引用的变量(闭包),在这些完成之后仍存在标记的就是要删除的变量了

引用计数(reference counting)

在低版本IE中经常会出现内存泄露,很多时候就是因为其采用引用计数方式进行垃圾回收。引用计数的策略是跟踪记录每个值被使用的次数,当声明了一个 变量并将一个引用类型赋值给该变量的时候这个值的引用次数就加1,如果该变量的值变成了另外一个,则这个值得引用次数减1,当这个值的引用次数变为0的时 候,说明没有变量在使用,这个值没法被访问了,因此可以将其占用的空间回收,这样垃圾回收器会在运行的时候清理掉引用次数为0的值占用的空间。

在IE中虽然JavaScript对象通过标记清除的方式进行垃圾回收,但BOM与DOM对象却是通过引用计数回收垃圾的,也就是说只要涉及BOM及DOM就会出现循环引用问题。

谈谈性能优化问题

代码层面:避免使用css表达式,避免使用高级选择器,通配选择器。 缓存利用:缓存Ajax,使用CDN,使用外部js和css文件以便缓存,添加Expires头,服务端配置Etag,减少DNS查找等 请求数量:合并样式和脚本,使用css图片精灵,初始首屏之外的图片资源按需加载,静态资源延迟加载。 请求带宽:压缩文件,开启GZIP,

移动端性能优化

尽量使用css3动画,开启硬件加速。适当使用touch事件代替click事件。避免使用css3渐变阴影效果。 尽可能少的使用box-shadow与gradients。box-shadow与gradients往往都是页面的性能杀手

什么是Etag?

浏览器下载组件的时候,会将它们存储到浏览器缓存中。如果需要再次获取相同的组件,浏览器将检查组件的缓存时间, 假如已经过期,那么浏览器将发送一个条件GET请求到服务器,服务器判断缓存还有效,则发送一个304响应, 告诉浏览器可以重用缓存组件。

那么服务器是根据什么判断缓存是否还有效呢?答案有两种方式,一种是前面提到的ETag,另一种是根据Last-Modified

Expires和Cache-Control

Expires要求客户端和服务端的时钟严格同步。HTTP1.1引入Cache-Control来克服Expires头的限制。如果max-age和Expires同时出现,则max-age有更高的优先级。

Cache-Control: no-cache, private, max-age=0
ETag: abcde
Expires: Thu, 15 Apr 2014 20:00:00 GMT
Pragma: private
Last-Modified: $now // RFC1123 format

栈和队列的区别?

栈的插入和删除操作都是在一端进行的,而队列的操作却是在两端进行的。
队列先进先出,栈先进后出。
栈只允许在表尾一端进行插入和删除,而队列只允许在表尾一端进行插入,在表头一端进行删除 

栈和堆的区别?

栈区(stack)—   由编译器自动分配释放   ,存放函数的参数值,局部变量的值等。
堆区(heap)   —   一般由程序员分配释放,   若程序员不释放,程序结束时可能由OS回收。
堆(数据结构):堆可以被看成是一棵树,如:堆排序;
栈(数据结构):一种先进后出的数据结构。 

关于Http 2.0 你知道多少?

HTTP/2引入了“服务端推(serverpush)”的概念,它允许服务端在客户端需要数据之前就主动地将数据发送到客户端缓存中,从而提高性能。 HTTP/2提供更多的加密支持 HTTP/2使用多路技术,允许多个消息在一个连接上同时交差。 它增加了头压缩(header compression),因此即使非常小的请求,其请求和响应的header都只会占用很小比例的带宽。

IE7/IE8下javascript的时间函数Date()不兼容

在一个Ajax请求中,把获得的字符串形式的时间参数,通过js下的 Date() 函数转化为时间格式的数据,在IE9+和chrome、firefox下均可直接运行,但是在IE7和IE8下会无法转化,返回NAN的数据(IE6没试,估计更不行,也不想再做什么东西都要去专门适配IE6了,让它早死早超生吧)。

但是问题还是需要解决啊,所以查了点资料,看看如何在IE7/8下,能把字符串格式数据转化为时间格式,找到了这个资料,有很多JS的小知识,大家可以保存起来,找时间看看,说不准哪天你做东西碰到问题了,也能在这里边找到解决办法。为了保险,我自己把这个页面也保存了一份。
在这里,我们可以看到,它给了一个把字符串格式的时间转化为时间格式的函数。
An Extended ISO 8601 local Date format YYYY-MM-DD can be parsed to a Date with the following:-

/**Parses string formatted as YYYY-MM-DD to a Date object.
* If the supplied string does not match the format, an
* invalid Date (value NaN) is returned.
* @param {string} dateStringInRange format YYYY-MM-DD, with year in
* range of 0000-9999, inclusive.
* @return {Date} Date object representing the string.
*/
function parseISO8601(dateStringInRange) {
var isoExp = /^s*(d{4})-(dd)-(dd)s*$/,
date = new Date(NaN), month,
parts = isoExp.exec(dateStringInRange);

if(parts) {
month = +parts[2];
date.setFullYear(parts[1], month – 1, parts[3]);
if(month != date.getMonth() + 1) {
date.setTime(NaN);
}
}
return date;
}
我们只要把这个函数拿来,替代原来的Date()函数就可以啦。

Javascript获取当前日期时间及其它操作

var myDate = new Date();
myDate.getYear();        //获取当前年份(2位)
myDate.getFullYear();    //获取完整的年份(4位,1970-????)
myDate.getMonth();       //获取当前月份(0-11,0代表1月)
myDate.getDate();        //获取当前日(1-31)
myDate.getDay();         //获取当前星期X(0-6,0代表星期天)
myDate.getTime();        //获取当前时间(从1970.1.1开始的毫秒数)
myDate.getHours();       //获取当前小时数(0-23)
myDate.getMinutes();     //获取当前分钟数(0-59)
myDate.getSeconds();     //获取当前秒数(0-59)
myDate.getMilliseconds();    //获取当前毫秒数(0-999)
myDate.toLocaleDateString();     //获取当前日期
var mytime=myDate.toLocaleTimeString();     //获取当前时间
myDate.toLocaleString( );        //获取日期与时间

日期时间脚本库方法列表

Date.prototype.isLeapYear 判断闰年
Date.prototype.Format 日期格式化
Date.prototype.DateAdd 日期计算
Date.prototype.DateDiff 比较日期差
Date.prototype.toString 日期转字符串
Date.prototype.toArray 日期分割为数组
Date.prototype.DatePart 取日期的部分信息
Date.prototype.MaxDayOfDate 取日期所在月的最大天数
Date.prototype.WeekNumOfYear 判断日期所在年的第几周
StringToDate 字符串转日期型
IsValidDate 验证日期有效性
CheckDateTime 完整日期时间检查
daysBetween 日期天数差

js代码:

//—————————————————  
// 判断闰年  
//—————————————————  
Date.prototype.isLeapYear = function()   
  
   return (0==this.getYear()%4&&((this.getYear()%100!=0)||(this.getYear()%400==0)));   
  
 
//—————————————————  
// 日期格式化  
// 格式 YYYY/yyyy/YY/yy 表示年份  
// MM/M 月份  
// W/w 星期  
// dd/DD/d/D 日期  
// hh/HH/h/H 时间  
// mm/m 分钟  
// ss/SS/s/S 秒  
//—————————————————  
Date.prototype.Format = function(formatStr)   
  
   var str = formatStr;   
   var Week = [‘日’,’一’,’二’,’三’,’四’,’五’,’六’];  
 
   str=str.replace(/yyyy|YYYY/,this.getFullYear());   
   str=str.replace(/yy|YY/,(this.getYear() % 100)>9?(this.getYear() % 100).toString():’0′ + (this.getYear() % 100));   
 
   str=str.replace(/MM/,this.getMonth()>9?this.getMonth().toString():’0′ + this.getMonth());   
   str=str.replace(/M/g,this.getMonth());   
 
   str=str.replace(/w|W/g,Week[this.getDay()]);   
 
   str=str.replace(/dd|DD/,this.getDate()>9?this.getDate().toString():’0′ + this.getDate());   
   str=str.replace(/d|D/g,this.getDate());   
 
   str=str.replace(/hh|HH/,this.getHours()>9?this.getHours().toString():’0′ + this.getHours());   
   str=str.replace(/h|H/g,this.getHours());   
   str=str.replace(/mm/,this.getMinutes()>9?this.getMinutes().toString():’0′ + this.getMinutes());   
   str=str.replace(/m/g,this.getMinutes());   
 
   str=str.replace(/ss|SS/,this.getSeconds()>9?this.getSeconds().toString():’0′ + this.getSeconds());   
   str=str.replace(/s|S/g,this.getSeconds());   
 
   return str;   
  
 
//+—————————————————  
//| 求两个时间的天数差 日期格式为 YYYY-MM-dd   
//+—————————————————  
function daysBetween(DateOne,DateTwo)  
  
   var OneMonth = DateOne.substring(5,DateOne.lastIndexOf (‘-‘));  
   var OneDay = DateOne.substring(DateOne.length,DateOne.lastIndexOf (‘-‘)+1);  
   var OneYear = DateOne.substring(0,DateOne.indexOf (‘-‘));  
 
   var TwoMonth = DateTwo.substring(5,DateTwo.lastIndexOf (‘-‘));  
   var TwoDay = DateTwo.substring(DateTwo.length,DateTwo.lastIndexOf (‘-‘)+1);  
   var TwoYear = DateTwo.substring(0,DateTwo.indexOf (‘-‘));  
 
   var cha=((Date.parse(OneMonth+’/’+OneDay+’/’+OneYear)- Date.parse(TwoMonth+’/’+TwoDay+’/’+TwoYear))/86400000);   
   return Math.abs(cha);  
 
 
 
//+—————————————————  
//| 日期计算  
//+—————————————————  
Date.prototype.DateAdd = function(strInterval, Number) {   
   var dtTmp = this;  
   switch (strInterval) {   
       case ‘s’ :return new Date(Date.parse(dtTmp) + (1000 * Number));  
       case ‘n’ :return new Date(Date.parse(dtTmp) + (60000 * Number));  
       case ‘h’ :return new Date(Date.parse(dtTmp) + (3600000 * Number));  
       case ‘d’ :return new Date(Date.parse(dtTmp) + (86400000 * Number));  
       case ‘w’ :return new Date(Date.parse(dtTmp) + ((86400000 * 7) * Number));  
       case ‘q’ :return new Date(dtTmp.getFullYear(), (dtTmp.getMonth()) + Number*3, dtTmp.getDate(), dtTmp.getHours(), dtTmp.getMinutes(), dtTmp.getSeconds());  
       case ‘m’ :return new Date(dtTmp.getFullYear(), (dtTmp.getMonth()) + Number, dtTmp.getDate(), dtTmp.getHours(), dtTmp.getMinutes(), dtTmp.getSeconds());  
       case ‘y’ :return new Date((dtTmp.getFullYear() + Number), dtTmp.getMonth(), dtTmp.getDate(), dtTmp.getHours(), dtTmp.getMinutes(), dtTmp.getSeconds());  
   
 
 
//+—————————————————  
//| 比较日期差 dtEnd 格式为日期型或者有效日期格式字符串  
//+—————————————————  
Date.prototype.DateDiff = function(strInterval, dtEnd) {   
   var dtStart = this;  
   if (typeof dtEnd == ‘string’ )//如果是字符串转换为日期型  
    
       dtEnd = StringToDate(dtEnd);  
   
   switch (strInterval) {   
       case ‘s’ :return parseInt((dtEnd – dtStart) / 1000);  
       case ‘n’ :return parseInt((dtEnd – dtStart) / 60000);  
       case ‘h’ :return parseInt((dtEnd – dtStart) / 3600000);  
       case ‘d’ :return parseInt((dtEnd – dtStart) / 86400000);  
       case ‘w’ :return parseInt((dtEnd – dtStart) / (86400000 * 7));  
       case ‘m’ :return (dtEnd.getMonth()+1)+((dtEnd.getFullYear()-dtStart.getFullYear())*12) – (dtStart.getMonth()+1);  
       case ‘y’ :return dtEnd.getFullYear() – dtStart.getFullYear();  
   
 
 
//+—————————————————  
//| 日期输出字符串,重载了系统的toString方法  
//+—————————————————  
Date.prototype.toString = function(showWeek)  
  
   var myDate= this;  
   var str = myDate.toLocaleDateString();  
   if (showWeek)  
    
       var Week = [‘日’,’一’,’二’,’三’,’四’,’五’,’六’];  
       str += ‘ 星期’ + Week[myDate.getDay()];  
   
   return str;  
 
 
//+—————————————————  
//| 日期合法性验证  
//| 格式为:YYYY-MM-DD或YYYY/MM/DD  
//+—————————————————  
function IsValidDate(DateStr)   
  
   var sDate=DateStr.replace(/(^\s+|\s+$)/g,”); //去两边空格;   
   if(sDate==”) return true;   
   //如果格式满足YYYY-(/)MM-(/)DD或YYYY-(/)M-(/)DD或YYYY-(/)M-(/)D或YYYY-(/)MM-(/)D就替换为”   
   //数据库中,合法日期可以是:YYYY-MM/DD(2003-3/21),数据库会自动转换为YYYY-MM-DD格式   
   var s = sDate.replace(/[\d]{ 4,4 }[\-/]{ 1 }[\d]{ 1,2 }[\-/]{ 1 }[\d]{ 1,2 }/g,”);   
   if (s==”) //说明格式满足YYYY-MM-DD或YYYY-M-DD或YYYY-M-D或YYYY-MM-D   
    
       var t=new Date(sDate.replace(/\-/g,’/’));   
       var ar = sDate.split(/[-/:]/);   
       if(ar[0] != t.getYear() || ar[1] != t.getMonth()+1 || ar[2] != t.getDate())   
        
           //alert(‘错误的日期格式!格式为:YYYY-MM-DD或YYYY/MM/DD。注意闰年。’);   
           return false;   
        
    
   else   
    
       //alert(‘错误的日期格式!格式为:YYYY-MM-DD或YYYY/MM/DD。注意闰年。’);   
       return false;   
    
   return true;   
  
 
//+—————————————————  
//| 日期时间检查  
//| 格式为:YYYY-MM-DD HH:MM:SS  
//+—————————————————  
function CheckDateTime(str)  
  
   var reg = /^(\d+)-(\d{ 1,2 })-(\d{ 1,2 }) (\d{ 1,2 }):(\d{ 1,2 }):(\d{ 1,2 })$/;   
   var r = str.match(reg);   
   if(r==null)return false;   
   r[2]=r[2]-1;   
   var d= new Date(r[1],r[2],r[3],r[4],r[5],r[6]);   
   if(d.getFullYear()!=r[1])return false;   
   if(d.getMonth()!=r[2])return false;   
   if(d.getDate()!=r[3])return false;   
   if(d.getHours()!=r[4])return false;   
   if(d.getMinutes()!=r[5])return false;   
   if(d.getSeconds()!=r[6])return false;   
   return true;   
  
 
//+—————————————————  
//| 把日期分割成数组  
//+—————————————————  
Date.prototype.toArray = function()  
  
   var myDate = this;  
   var myArray = Array();  
   myArray[0] = myDate.getFullYear();  
   myArray[1] = myDate.getMonth();  
   myArray[2] = myDate.getDate();  
   myArray[3] = myDate.getHours();  
   myArray[4] = myDate.getMinutes();  
   myArray[5] = myDate.getSeconds();  
   return myArray;  
 
 
//+—————————————————  
//| 取得日期数据信息  
//| 参数 interval 表示数据类型  
//| y 年 m月 d日 w星期 ww周 h时 n分 s秒  
//+—————————————————  
Date.prototype.DatePart = function(interval)  
  
   var myDate = this;  
   var partStr=”;  
   var Week = [‘日’,’一’,’二’,’三’,’四’,’五’,’六’];  
   switch (interval)  
    
       case ‘y’ :partStr = myDate.getFullYear();break;  
       case ‘m’ :partStr = myDate.getMonth()+1;break;  
       case ‘d’ :partStr = myDate.getDate();break;  
       case ‘w’ :partStr = Week[myDate.getDay()];break;  
       case ‘ww’ :partStr = myDate.WeekNumOfYear();break;  
       case ‘h’ :partStr = myDate.getHours();break;  
       case ‘n’ :partStr = myDate.getMinutes();break;  
       case ‘s’ :partStr = myDate.getSeconds();break;  
   
   return partStr;  
 
 
//+—————————————————  
//| 取得当前日期所在月的最大天数  
//+—————————————————  
Date.prototype.MaxDayOfDate = function()  
  
   var myDate = this;  
   var ary = myDate.toArray();  
   var date1 = (new Date(ary[0],ary[1]+1,1));  
   var date2 = date1.dateAdd(1,’m’,1);  
   var result = dateDiff(date1.Format(‘yyyy-MM-dd’),date2.Format(‘yyyy-MM-dd’));  
   return result;  
 
 
//+—————————————————  
//| 取得当前日期所在周是一年中的第几周  
//+—————————————————  
Date.prototype.WeekNumOfYear = function()  
  
   var myDate = this;  
   var ary = myDate.toArray();  
   var year = ary[0];  
   var month = ary[1]+1;  
   var day = ary[2];  
   document.write(‘< script language=VBScript\> \n’);  
   document.write(‘myDate = Datue(”+month+’-‘+day+’-‘+year+”) \n’);  
   document.write(‘result = DatePart(‘ww’, myDate) \n’);  
   document.write(‘ \n’);  
   return result;  
 
 
//+—————————————————  
//| 字符串转成日期类型   
//| 格式 MM/dd/YYYY MM-dd-YYYY YYYY/MM/dd YYYY-MM-dd  
//+—————————————————  
function StringToDate(DateStr)  
  
 
   var converted = Date.parse(DateStr);  
   var myDate = new Date(converted);  
   if (isNaN(myDate))  
    
       //var delimCahar = DateStr.indexOf(‘/’)!=-1?’/’:’-‘;  
       var arys= DateStr.split(‘-‘);  
       myDate = new Date(arys[0],–arys[1],arys[2]);  
   
   return myDate;  
 

若要显示:当前日期加时间(如:2009-06-12 12:00)

function CurentTime()
  
       var now = new Date();
      
       var year = now.getFullYear();       //年
       var month = now.getMonth() + 1;     //月
       var day = now.getDate();            //日
      
       var hh = now.getHours();            //时
       var mm = now.getMinutes();          //分
      
       var clock = year + “-“;
      
       if(month < 10)
           clock += “0”;
      
       clock += month + “-“;
      
       if(day < 10)
           clock += “0”;
          
       clock += day + ” “;
      
       if(hh < 10)
           clock += “0”;
          
       clock += hh + “:”;
       if (mm < 10) clock += ‘0’; 
       clock += mm; 
       return(clock); 
   }

What is causing the following error: “string.split is not a function” in javascript?

I have the following JavaScript code shown below:

<script type="text/javascript">

$(document).ready(function() {
     var string = document.location;
     var string2 = string.split('/');
});

</script>

When I run this code, I get the following error showing in the Firebug console:

string.split is not a function
var string2 = string.split('/');

What is the cause for this error?

shareimprove this question
2
document.location is an object. Try: var string=document.location.href – Teemu Apr 13 ’12 at 18:04

4 Answers

Change this…

var string = document.location;

to this…

var string = document.location + '';

This is because document.location is a Location object. The default .toString() returns the location in string form, so the concatenation will trigger that.


You could also use document.URL to get a string.

避免使用 forEach

首先声明,本文和性能无关。执行 for 循环总是比执行 Array.forEach 快。如果性能测试显示迭代的开销足够显著并且性能优先,那么你绝对应该使用 for 循环而不是 forEach (总是使用 for 循环是典型的过早优化。 forEach 仍然可以在 1 微秒内遍历长度为 50 的数组)。本文和编码风格有关,是我对 forEach 和其它 Array.prototype 方法的思考,与性能无关。

forEach 为副作用而生

当人们想要把代码重构成一个更加实用的风格时,往往首选 [].forEach _.each forEach 直接模拟最基本的 for 循环——遍历数组并且执行一些操作——所以它是一个很机械的转换。但是就像 for 循环一样, forEach 在程序的某个地方必定会产生副作用。它必须修改父作用域中的对象,或者调用一个外部方法,或者使用迭代函数以外的其它变量。使用 forEach 也意味着你的迭代函数和它所在的作用域产生了耦合。

在编程中,我们通常认为副作用是不好的。他们使程序更难理解,可能导致 bug 的产生,而且难以重构。当然, forEach 在大项目中引起的副作用是微不足道的,但是这些副作用是不必要的。

当然也有一些副作用是无法避免的。

arr.forEach(function (item) {
    console.log(item);
});

这种情况完全可以接受。

forEach 隐藏了迭代的意图

阅读 forEach 代码段的时候,你并不能马上知道它的作用,只知道它会在某个地方产生副作用,然后必须阅读这段代码或者注释才明白。这是一个非语义的方法。

除了 forEach ,还有更好的迭代方法。比如 map ——在使用迭代函数以后会返回一个新数组;比如 filter ——返回由符合条件的元素组成的新数组;比如 some (或者 _.any )——如果数组中至少有一个元素满足要求时返回 true ;比如 every (或者 _.all )——如果数组中所有元素满足要求时返回 true ;比如 reduce ——遍历数组并且使用数组中的所有元素进行某种操作迭代生成一个新的变量,数组中的很多方法都可以用 reduce 来实现。ES5 的数组方法非常强大,希望你对此并不陌生。 Lodash /Underscore 库增强了 ES5 的方法,增加了很多有用且语义化的迭代函数(此外还提供了可用于对象的数组原型方法的更优实现)。

重构

下面是一些实际项目中使用 each 的例子,看看如何更好地重构它们。

例 1

var obj = {};

arr.forEach(function (item) {
    obj[item.key] = item;
});

这是一个很常见的操作——将数组转换为对象。由于迭代函数依赖 obj ,所以 forEach 跟它所在的作用域耦合在一起。迭代函数不能在它的闭包作用域之外执行。我们换个方式来重写它:

var obj = arr.reduce(function (newObj, item) {
    newObj[item.key] = item;
    return newObj;
}, {});

现在归并函数只依赖于它的形参,没有别的。 reduce 无副作用——遍历集合,并且只产出一个东西。它是 ES5 方法中最不语义的方法,但它很灵活,可以用来实现所有其余的函数。

Lodash 还有更语义化的写法:

var obj  = _.zipObject(_.pluck(arr, 'key'), arr);

这里需要遍历2次,但是看起来更直观。

译者注:实际上有更好的方法

var obj = _.indexBy(arr, 'key');

例 2

var replacement = 'foo';
var replacedUrls = urls;

urls.forEach(function replaceToken(url, index) {
    replacedUrls[index] = url.replace('{token}', replacement);
});

map 重构:

var replacement = 'foo';
var replacedUrls;

replacedUrls = urls.map(function (url) {
    return url.replace('{token}', replacement);
});

map 函数仍然依赖于 replacement 的作用域,但是迭代的意图更加清晰。前一种方法改变了 urls 数组,而 map 函数则分配了一个新的数组。需要注意的是,它对 urls 的修改不易被察觉,其它地方可能仍然期望 urls 中会含有 {token} 。采用分配新数组的方法可以防止这个小细节引发的问题,代价就是需要多一点内存开销。

例 3

var html = '';
var self = this;

_.each(this.values, function (value) {
    var id = 'radio_button_' + self.groupName + '_' + value.id;

    html += ''
        + '<li>'
        +   '<input type="radio" name="' + self.groupName + '" id="' + id + '" value="' + value.id + '">'
        +   '<label for="' + id + '">' + value.description + '</label>'
        + '</li>';

    if (!touchEnabled) {
        var tooltip = value.getTooltip();
        if (tooltip) {
            self.tooltips.push(tooltip);
        }
    }
});

这个例子稍微复杂一点。这段代码实际上做了两件事:拼接 HTML 字符串,为每一个 value 创建 tooltips 。其实迭代函数没必要这么复杂——或者如 Rich Hickey 所说的 「 complected 」。它将两种操作放在一个函数里,而实际上没有必要。第一部分操作是典型的 reduce 函数的应用范围,所以我们把这两部分操作分开:

var html;
var self = this;

html = _.reduce(this.values, function (str, value) {
    var id = 'radio_button_' + self.groupName + '_' + value.id;

    str += ''
        + '<li>'
        +   '<input type="radio" name="' + self.groupName + '" id="' + id + '" value="' + value.id + '">'
        +   '<label for="' + id + '">' + value.description + '</label>'
        + '</li>';

    return str;
}, '');

_.each(this.values, function (value) {

    if (!touchEnabled) {
        var tooltip = value.getTooltip();
        if (tooltip) {
            self.tooltips.push(tooltip);
        }
    }
});

现在第一部分就可以接受了。在 values 上迭代,最后生成 HTML 字符串。它仍然依赖于 self.groupName ,不过可以通过偏函数( partial application )来避免。

译者注:Underscore 中提供了偏函数 _.partial 可以帮助我们解决这个问题,相应的代码如下:

var part = _.partial(function (groupName, str, value) {
  // ....
}, self.groupName);

_.reduce(this.values, part, '');

现在来看一下第二部分。如果 touchEnabled 为假,可以得到 tooltip 。这时不确定会不会返回一个有效的 tooltip ,因此将每个实例对应的 tooltip 放进数组中的操作是带条件的。我们可以把多个方法串联起来解决这个问题:

if (!touchEnabled) {
    this.tooltips = this.tooltips.concat(
        this.values
            .map(function (value) {
                return value.getTooltip();
            })
            .filter(_.identity)
    );
}

我们将 touch 检查移到循环的外面,因为只需要检查一次就够了。然后对集合使用 map 方法,在每次迭代中调用 getTooltip() ,然后过滤掉不符合条件的值。最后结果合并到 tooltips 数组。这种方法每次都会创建临时数组,但是正如我在其它例子中所说的,表达清晰更重要。

你可以定义一个辅助函数把上面的内联函数去掉:

var dot = _.curry(function (methodName, object) {
    return object[methodName]();
});

// ...
this.tooltips = this.tooltips.concat(
    this.values
        .map(dot('getTooltip'))
        .filter(_.identity)
);

这样更简洁直观。

译者注:这里其实可以直接用 _.invoke 函数和 _.union 函数,更加简洁。

this.tooltips = _.union(this.tooltips, _.filter(_.invoke(this.values, 'getTooltip'), _.identity));

例 4

var matches = [];
var nonMatches = [];

values.forEach(function(value) {
    if (matchesSomething(value)) {
        matches.push(value);
    }
    else {
        nonMatches.push(value);
    }
});

这个例子看起来很简单——基于判断条件将数组拆分成两个。但还不够简单。我会这样重写:

var matches = values.filter(matchesSomething);
var nonMatches = values.filter(not(matchesSomething));

迭代函数实际上在做两件事,拆分成两个迭代函数更加清晰。如果确实有成千上万的值,或者 matchesSomething 操作非常耗时,我会保留第一种方案。

译者注:这段代码其实可以用 reduce 加以改进:

var result = values.reduce(function (result, value) {
    if (matchesSomething(value)) {
        result.matches.push(value);
    }
    else {
        result.nonMatches.push(value);
    }
}, {matches: [], nonMatches: []});

重构时你会发现多了些东西,如果这些东西使程序更简单,那就没问题。多个简单的东西组合起来会比一个大而复杂的东西更容易理解。

转换器

让我们再看一下例 3 的最终代码:

this.tooltips = this.tooltips.concat(
    this.values
        .map(dot('getTooltip'))
        .filter(_.identity)
);

map filter 的串联操作意味着临时数组的创建和删除。对于元素较少的数组来说是可以接受的,额外的开销可以忽略不计。但是如果这个数组包含了数千个元素,或者要做很多次的映射和过滤操作呢?这时单一的迭代函数又变得诱人了。

幸运的是,随着 Transducers (其实是 transform 和 reducer 的合成词,transform 是变换,reducer 就是 JS 中的 reducer)的出现,你可以任性地将很多 map filter 函数放在一个迭代中。也就是说,先在第一个元素上应用所有变换( map filter 或者其它),然后依次处理其它元素。本文中不会深入研究 Transducers 的原理( 这里有关于它的介绍 ),不过经过 Transducers 改造以后会是这样:

var t = require('transducers-js');

var tooltips = t.transduce(
t.comp( // 变换函数
t.map(dot('getTooltip')),
t.filter(_.identity),
// 想加多少map和filter就加多少
t.map(someFunction),
t.filter(somePredicate)
),
concat, // reducer
[], // 初始值
values // 输入
);

它看上去有点不一样,和 reduce 类似,但是它只涉及到一个迭代器,而且只分配了一个唯一的数组。你想加入多少 map filter ,就加入多少,它只会迭代一次。通过使用其它函数替换 concat ,你也可以让它返回任何类型的结果。如果你想了解更多,那就深入地研究一下Transducers 吧。

译者注:有了 ES6 的 Generator,这事就是原生支持的了。

总结

  • forEach 总会产生副作用。如果你想避免产生副作用,那就不要使用它了。
  • forEach 隐藏了迭代的意图。推荐使用更加语义化的迭代方法,例如 map filter some
  • 如果每次迭代包含了太多操作,将它们拆分到不同的函数中。
  • 通过多个方法的串联调用,将不同的数据转换隔离开来。如果性能不可接受,那就使用 Transducers 重构它。
  • 改造后的程序最终会多了操作,但是如果你处理得当,那么每一步都会更容易理解。
  • 如果你确实需要循环产生的副作用,完全可以用 forEach
  • 最后,如果性能测试表明更加语义化的迭代函数是性能瓶颈或者被频繁执行, 那就用 for 循环好了。

原文: http://aeflash.com/2014-11/avoid-foreach.html

遍历集合,会产生副作用。——如 mori.each 文档 所说

首先声明,本文和性能无关。执行 for 循环总是比执行 Array.forEach 快。如果性能测试显示迭代的开销足够显著并且性能优先,那么你绝对应该使用 for 循环而不是 forEach (总是使用 for 循环是典型的过早优化。 forEach 仍然可以在 1 微秒内遍历长度为 50 的数组)。本文和编码风格有关,是我对 forEach 和其它 Array.prototype 方法的思考,与性能无关。

JavaScript裸体识别技术

当第一次听说nude.js的时候,我非常怀疑这种浏览器端的裸体识别技术,有几个理由:

  1. 正常情况下,裸体识别应该在服务器端进行,这样,那些色情图片或色情视频才能在发送给浏览者前被发现。
  2. 我不相信完全依赖计算机能过滤掉所有色情内容(虽然我是个程序员)。
  3. 黑白裸体图像能被识别出来吗?准确率能有多少?
  4. 如果在客户端发现了色情图片,你能怎么做?这种技术如何使用?

我用nude.js试验了不少内容,让我来告诉你如何使用它,以及我这些怀疑的答案是什么。

从技术的角度上讲,nude.js使用了HTML5画布(Canvas)和WebWorker技术来操作图像进行分析。图像被复制到画布上,使用裸体识别算法进行比较,包括下面几种特征识别:

  • 在图像中查找类似肤色的像素。
  • 根据捕捉到的像素定位或构造皮肤位置。
  • 分析皮肤位置判断是裸体或非裸体。
  • 就裸露情况进行分级,判断是否属于裸露。

nude.js支持IE9+ (excanvas), Firefox 3.6+, WebKit引擎的浏览器(谷歌浏览器, Safari, Mobile Safari,Opera)。nude.js还支持对视频进行截屏分析。

nude.js的用法

首先要在你的页面上引入nude.js脚本,然后是需要分析的图片:

<!-- 对于不支持画布技术的IE -->
<!--[if IE]>
<script type="text/javascript" src="excanvas_r3/excanvas.compiled.js"></script>	
<![endif]-->
<script src="nude.js/compressed/nude.min.js"></script>

<!-- 需要识别的图片 -->
<img src='data:image/png;base64,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' data-original="dvt1.jpg" alt="Dita Von Tease" id="image1" onclick="onImageClick('image1');" />
<img src='data:image/png;base64,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' data-original="dvt2.jpg" alt="Dita Von Tease" id="image2" onclick="onImageClick('image2');" />
<img src='data:image/png;base64,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' data-original="dvt3.jpg" alt="Dita Von Tease" id="image3" onclick="onImageClick('image3');" />
<img src='data:image/png;base64,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' data-original="dvt4.jpg" alt="Dita Von Tease" id="image4" onclick="onImageClick('image4');" />

nude.js的用法很简单,因为它只有两个方法:<code>load</code> 和 <code>scan</code>。其中 <code>load</code> 方法接受图片的 <code>id</code> 或图片本身,将其拷贝到画布里。而 <code>scan</code> 方法根据算法对画布数据进行分析,返回 <code>true</code> 或 <code>false</code>。

function onImageClick(node) {
	nude.load(node);
	// 扫描
	nude.scan(function(result){ 
		alert(result ? "在" + node.id + "发现裸露图像!" : "非裸体");
	});
}

你可以想象出,这些过程是在浏览器里进行的,所以不要期望很迅速的得出结果。而且,很显然,图片尺寸是分析速度的一个重要因素。如果你的网站严重的依赖nude.js,小心那些大尺寸的图片。

裸体识别的结果

对于高质量的裸露女性的JPEG图片,这个小脚本检测成功率非常高。但对于暗色调或黑人皮肤却不是很成功。在我的测试中,nude.js没有识别出黑白裸露图像,也没有发现视频的色情影像(可能是我的视频清晰度不够)。

javascript数组增加包含/删除元素的功能

 Array.prototype.contains = function ( needle ) {
 for (i in this) {
 if (this[i] == needle) return true;
 }
 return false;
 }
 
 
 Array.prototype.indexOf = function(val) { 
 for (var i = 0; i < this.length; i++) { if (this[i] == val) return i; } 
 return -1; 
 }
 
 
 
 Array.prototype.remove = function(val) { 
 var index = this.indexOf(val); 
 if (index > -1) { this.splice(index, 1); } 
 };
 

javascript描述数据结构:二叉树的遍历和基本操作

树型结构是一类非常重要的非线性结构。直观地,树型结构是以分支关系定义的层次结构。

树在计算机领域中也有着广泛的应用,例如在编译程序中,用树来表示源程序的语法结构;在数据库系统中,可用树来组织信息;在分析算法的行为时,可用树来描述其执行过程等等。

下面讲解的内容完整代码在这:https://github.com/LukeLin/data-structure-with-js/blob/master/Binary%20tree/BinaryTree.js

首先看看树的一些概念:

1.树(Tree)是n(n>=0)个结点的有限集。在任意一棵非空树中:

(1)有且仅有一个特定的称为根(Root)的结点;

(2)当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1,T2,T3,…Tm,其中每一个集合本身又是一棵树,并且称为根的子树(Subtree)。

例如,(a)是只有一个根结点的树;(b)是有13个结点的树,其中A是根,其余结点分成3个互不相交的子集:T1={B,E,F,K,L},t2={D,H,I,J,M};T1,T2和T3都是根A的子树,且本身也是一棵树。

enter image description here

2.树的结点包含一个数据元素及若干指向其子树的分支。结点拥有的子树数称为结点的度(Degree)。例如,(b)中A的度为3,C的度为1,F的度为0.度为0的结点称为叶子(Leaf)或者终端结点。度不为0的结点称为非终端结点或分支结点。树的度是树内各结点的度的最大值。(b)的树的度为3.结点的子树的根称为该结点的孩子(Child)。相应的,该结点称为孩子的双亲(Parent)。同一个双亲的孩子之间互称兄弟(Sibling)。结点的祖先是从根到该结点所经分支上的所有结点。反之,以某结点为根的子树中的任一结点都称为该结点的子孙。

3.结点的层次(Level)从根开始定义起,根为第一层,跟的孩子为第二层。若某结点在第l层,则其子树的根就在第l+1层。其双亲在同一层的结点互为堂兄弟。例如,结点G与E,F,H,I,J互为堂兄弟。树中结点的最大层次称为树的深度(Depth)或高度。(b)的树的深度为4。

4.如果将树中结点的各子树看成从左至右是有次序的(即不能交换),则称该树为有序树,否则称为无序树。在有序树中最左边的子树的根称为第一个孩子,最右边的称为最后一个孩子。

5.森林(Forest)是m(m>=0)棵互不相交的树的集合。对树中每个结点而言,其子树的集合即为森林。

接下来看看二叉树相关的概念:

二叉树(Binary Tree)是另一种树型结构,它的特点是每个结点至多只有两棵子树(即二叉树中不存在度大于2的结点),并且,二叉树的子树有左右之分(其次序不能任意颠倒。)

二叉树的性质:

1.在二叉树的第i层上至多有2的i-1次方个结点(i>=1)。

2.深度为k的二叉树至多有2的k次方-1个结点,(k>=1)。

3.对任何一棵二叉树T,如果其终端结点数为n0,度为2的结点数为n2,则n0 = n2 + 1;

一棵深度为k且有2的k次方-1个结点的二叉树称为满二叉树。

深度为k的,有n个结点的二叉树,当且仅当其每一个结点都与深度为k的满二叉树中编号从1至n的结点一一对应时,称之为完全二叉树。

下面是完全二叉树的两个特性:

4.具有n个结点的完全二叉树的深度为Math.floor(log 2 n) + 1

5.如果对一棵有n个结点的完全二叉树(其深度为Math.floor(log 2 n) + 1)的结点按层序编号(从第1层到第Math.floor(2 n) + 1,每层从左到右),则对任一结点(1<=i<=n)有:

(1)如果i=1,则结点i、是二叉树的根,无双亲;如果i>1,则其双亲parent(i)是结点Math.floor(i/2)。

(2)如果2i > n,则结点i无左孩子(结点i为叶子结点);否则其左孩子LChild(i)是结点2i.

(3)如果2i + 1 > n,则结点i无右孩子;否则其右孩子RChild(i)是结点2i + 1;

enter image description hereenter image description here

二叉树的存储结构

1.顺序存储结构 用一组连续的存储单元依次自上而下,自左至右存储完全二叉树上的结点元素,即将二叉树上编号为i的结点元素存储在加上定义的一维数组中下标为i-1的分量中。“0”表示不存在此结点。这种顺序存储结构仅适用于完全二叉树。 因为,在最坏情况下,一个深度为k且只有k个结点的单支树(树中不存在度为2的结点)却需要长度为2的n次方-1的一维数组。

2.链式存储结构 二叉树的结点由一个数据元素和分别指向其左右子树的两个分支构成,则表示二叉树的链表中的结点至少包含三个域:数据域和左右指针域。有时,为了便于找到结点的双亲,则还可在结点结构中增加一个指向其双亲结点的指针域。利用这两种结构所得的二叉树的存储结构分别称之为二叉链表和三叉链表。 在含有n个结点的二叉链表中有n+1个空链域,我们可以利用这些空链域存储其他有用信息,从而得到另一种链式存储结构—线索链表。

二叉树的遍历主要分三种:

先(根)序遍历:根左右 中(根)序遍历:左根右 后(根)序遍历:左右根

二叉树的顺序存储结构:

enter image description here

二叉树的链式存储形式:enter image description here

// 顺序存储结构
    var tree = [1, 2, 3, 4, 5, , 6, , , 7];


    // 链式存储结构
    function BinaryTree(data, leftChild, rightChild) {
        this.data = data || null;
        // 左右孩子结点
        this.leftChild = leftChild || null;
        this.rightChild = rightChild || null;
    }

遍历二叉树(Traversing Binary Tree):是指按指定的规律对二叉树中的每个结点访问一次且仅访问一次。

1.先序遍历二叉树

1)算法的递归定义是:

若二叉树为空,则遍历结束;否则

⑴ 访问根结点;

⑵ 先序遍历左子树(递归调用本算法);

⑶ 先序遍历右子树(递归调用本算法)。

算法实现:

// 顺序存储结构的递归先序遍历
    var tree = [1, 2, 3, 4, 5, , 6, , , 7];

    console.log('preOrder:');
    void function preOrderTraverse(x, visit) {
        visit(tree[x]);
        if (tree[2 * x + 1]) preOrderTraverse(2 * x + 1, visit);
        if (tree[2 * x + 2]) preOrderTraverse(2 * x + 2, visit);
    }(0, function (value) {
        console.log(value);
    });

    // 链式存储结构的递归先序遍历
    BinaryTree.prototype.preOrderTraverse = function preOrderTraverse(visit) {
        visit(this.data);
        if (this.leftChild) preOrderTraverse.call(this.leftChild, visit);
        if (this.rightChild) preOrderTraverse.call(this.rightChild, visit);
    };

2)非递归算法:

设T是指向二叉树根结点的变量,非递归算法是: 若二叉树为空,则返回;否则,令p=T;

(1) p为根结点;

(2) 若p不为空或者栈不为空;

(3) 若p不为空,访问p所指向的结点, p进栈, p = p.leftChild,访问左子树;

(4) 否则;退栈到p,然后p = p.rightChild, 访问右子树

(5) 转(2),直到栈空为止。

代码实现:

// 链式存储的非递归先序遍历

    // 方法1
    BinaryTree.prototype.preOrder_stack = function (visit) {
        var stack = new Stack();
        stack.push(this);

        while (stack.top) {
            var p;
            // 向左走到尽头
            while ((p = stack.peek())) {
                p.data && visit(p.data);
                stack.push(p.leftChild);
            }

            stack.pop();

            if (stack.top) {
                p = stack.pop();
                stack.push(p.rightChild);
            }
        }
    };

    // 方法2
     BinaryTree.prototype.preOrder_stack2 = function (visit) {
        var stack = new Stack();
        var p = this;

        while (p || stack.top) {
            if (p) {
                stack.push(p);
                p.data && visit(p.data);
                p = p.leftChild;
            } else {
                p = stack.pop();
                p = p.rightChild;
            }
        }
    };

2.中序遍历二叉树:

1)算法的递归定义是:

若二叉树为空,则遍历结束;否则

⑴ 中序遍历左子树(递归调用本算法);

⑵ 访问根结点;

⑶ 中序遍历右子树(递归调用本算法)。

// 顺序存储结构的递归中序遍历
    var tree = [1, 2, 3, 4, 5, , 6, , , 7];

    console.log('inOrder:');
    void function inOrderTraverse(x, visit) {
        if (tree[2 * x + 1]) inOrderTraverse(2 * x + 1, visit);
        visit(tree[x]);
        if (tree[2 * x + 2]) inOrderTraverse(2 * x + 2, visit);
    }(0, function (value) {
        console.log(value);
    });

    // 链式存储的递归中序遍历
    BinaryTree.prototype.inPrderTraverse = function inPrderTraverse(visit) {
        if (this.leftChild) inPrderTraverse.call(this.leftChild, visit);
        visit(this.data);
        if (this.rightChild) inPrderTraverse.call(this.rightChild, visit);
    };

2) 非递归算法

T是指向二叉树根结点的变量,非递归算法是: 若二叉树为空,则返回;否则,令p=T

⑴ 若p不为空,p进栈, p=p.leftChild ;

⑵ 否则(即p为空),退栈到p,访问p所指向的结点,p=p.rightChild ;

⑶ 转(1);

直到栈空为止。

// 方法1
    inOrder_stack1: function (visit) {
        var stack = new Stack();
        stack.push(this);

        while (stack.top) {
            var p;
            // 向左走到尽头
            while ((p = stack.peek())) {
                stack.push(p.leftChild);
            }

            stack.pop();

            if (stack.top) {
                p = stack.pop();
                p.data && visit(p.data);
                stack.push(p.rightChild);
            }
        }
    },
    // 方法2
    inOrder_stack2: function (visit) {
        var stack = new Stack();
        var p = this;

        while (p || stack.top) {
            if (p) {
                stack.push(p);
                p = p.leftChild;
            } else {
                p = stack.pop();
                p.data && visit(p.data);
                p = p.rightChild;
            }
        }
    },

3.后序遍历二叉树:

1)递归算法

若二叉树为空,则遍历结束;否则

⑴ 后序遍历左子树(递归调用本算法);

⑵ 后序遍历右子树(递归调用本算法) ;

⑶ 访问根结点 。

// 顺序存储结构的递归后序遍历
    var tree = [1, 2, 3, 4, 5, , 6, , , 7];

    console.log('postOrder:');
    void function postOrderTraverse(x, visit) {
        if (tree[2 * x + 1]) postOrderTraverse(2 * x + 1, visit);
        if (tree[2 * x + 2]) postOrderTraverse(2 * x + 2, visit);
        visit(tree[x]);
    }(0, function (value) {
        console.log(value);
    });


    // 链式存储的递归后序遍历
    BinaryTree.prototype.postOrderTraverse = function postOrderTraverse(visit) {
        if (this.leftChild) postOrderTraverse.call(this.leftChild, visit);
        if (this.rightChild) postOrderTraverse.call(this.rightChild, visit);
        visit(this.data);
    };

2) 非递归算法

在后序遍历中,根结点是最后被访问的。因此,在遍历过程中,当搜索指针指向某一根结点时,不能立即访问,而要先遍历其左子树,此时根结点进栈。当其左子树遍历完后再搜索到该根结点时,还是不能访问,还需遍历其右子树。所以,此根结点还需再次进栈,当其右子树遍历完后再退栈到到该根结点时,才能被访问。 因此,设立一个状态标志变量mark:

mark=0表示刚刚访问此结点,mark=1表示左子树处理结束返回,

mark=2表示右子树处理结束返回。每次根据栈顶的mark域决定做何动作

算法实现思路:

(1) 根结点入栈,且mark = 0;

(2) 若栈不为空,出栈到node;

(3) 若node的mark = 0,修改当前node的mark为1,左子树入栈;

(4) 若node的mark = 1,修改当前node的mark为2,右子树入栈;

(5) 若node的mark = 2,访问当前node结点的值;

(6) 直到栈为空结束。

postOrder_stack: function (visit) {
        var stack = new Stack();
        stack.push([this, 0]);

        while (stack.top) {
            var a = stack.pop();
            var node = a[0];

            switch (a[1]) {
                case 0:
                    stack.push([node, 1]);  // 修改mark域
                    if (node.leftChild) stack.push([node.leftChild, 0]);  // 访问左子树
                    break;
                case 1:
                    stack.push([node, 2]);
                    if (node.rightChild) stack.push([node.rightChild, 0]);
                    break;
                case 2:
                    node.data && visit(node.data);
                    break;
                default:
                    break;
            }
        }
    }

下面是完整代码,其中包括二叉树的遍历和基本操作:

// 顺序存储结构
(function () {
    // 顺序存储结构的遍历
    var tree = [1, 2, 3, 4, 5, , 6, , , 7];

    console.log('preOrder:');
    void function preOrderTraverse(x, visit) {
        visit(tree[x]);
        if (tree[2 * x + 1]) preOrderTraverse(2 * x + 1, visit);
        if (tree[2 * x + 2]) preOrderTraverse(2 * x + 2, visit);
    }(0, function (value) {
        console.log(value);
    });

    console.log('inOrder:');
    void function inOrderTraverse(x, visit) {
        if (tree[2 * x + 1]) inOrderTraverse(2 * x + 1, visit);
        visit(tree[x]);
        if (tree[2 * x + 2]) inOrderTraverse(2 * x + 2, visit);
    }(0, function (value) {
        console.log(value);
    });

    console.log('postOrder:');
    void function postOrderTraverse(x, visit) {
        if (tree[2 * x + 1]) postOrderTraverse(2 * x + 1, visit);
        if (tree[2 * x + 2]) postOrderTraverse(2 * x + 2, visit);
        visit(tree[x]);
    }(0, function (value) {
        console.log(value);
    });
}());

var Stack = require('../Stack/stack');
var Queue = require('../Queue/Queue').Queue;

// 链式存储结构
function BinaryTree(data, leftChild, rightChild) {
    this.data = data || null;
    // 左右孩子结点
    this.leftChild = leftChild || null;
    this.rightChild = rightChild || null;
}
exports.BinaryTree = BinaryTree;
BinaryTree.prototype = {
    constructor: BinaryTree,
    // 判断两棵树是否相似
    isSimilar: function isSimilar(tree) {
        return tree &&
            this.leftChild && isSimilar.call(this.leftChild, tree.leftChild) &&
            this.rightChild && isSimilar.call(this.rightChild, tree.rightChild);
    },
    createBinaryTree: function (tree) {
        void function preOrderTraverse(node, x, visit) {
            visit(node, tree[x]);

            if (tree[2 * x + 1]) preOrderTraverse(node.leftChild = new BinaryTree(), 2 * x + 1, visit);
            if (tree[2 * x + 2]) preOrderTraverse(node.rightChild = new BinaryTree(), 2 * x + 2, visit);
        }(this, 0, function (node, value) {
            node.data = value;
        });
    },

    // 先序遍历二叉树的非递归算法
    preOrder_stack: function (visit) {
        var stack = new Stack();
        stack.push(this);

        while (stack.top) {
            var p;
            // 向左走到尽头
            while ((p = stack.peek())) {
                p.data && visit(p.data);
                stack.push(p.leftChild);
            }

            stack.pop();

            if (stack.top) {
                p = stack.pop();
                stack.push(p.rightChild);
            }
        }
    },
    preOrder_stack2: function (visit) {
        var stack = new Stack();
        var p = this;

        while (p || stack.top) {
            if (p) {
                stack.push(p);
                p.data && visit(p.data);
                p = p.leftChild;
            } else {
                p = stack.pop();
                p = p.rightChild;
            }
        }
    },
    inOrder_stack1: function (visit) {
        var stack = new Stack();
        stack.push(this);

        while (stack.top) {
            var p;
            // 向左走到尽头
            while ((p = stack.peek())) {
                stack.push(p.leftChild);
            }

            stack.pop();

            if (stack.top) {
                p = stack.pop();
                p.data && visit(p.data);
                stack.push(p.rightChild);
            }
        }
    },
    inOrder_stack2: function (visit) {
        var stack = new Stack();
        var p = this;

        while (p || stack.top) {
            if (p) {
                stack.push(p);
                p = p.leftChild;
            } else {
                p = stack.pop();
                p.data && visit(p.data);
                p = p.rightChild;
            }
        }
    },
    // 为了区分两次过栈的不同处理方式,在堆栈中增加一个mark域,
    // mark=0表示刚刚访问此结点,mark=1表示左子树处理结束返回,
    // mark=2表示右子树处理结束返回。每次根据栈顶的mark域决定做何动作
    postOrder_stack: function (visit) {
        var stack = new Stack();
        stack.push([this, 0]);

        while (stack.top) {
            var a = stack.pop();
            var node = a[0];

            switch (a[1]) {
                case 0:
                    stack.push([node, 1]);  // 修改mark域
                    if (node.leftChild) stack.push([node.leftChild, 0]);  // 访问左子树
                    break;
                case 1:
                    stack.push([node, 2]);
                    if (node.rightChild) stack.push([node.rightChild, 0]);
                    break;
                case 2:
                    node.data && visit(node.data);
                    break;
                default:
                    break;
            }
        }
    },

    preOrderTraverse: function preOrderTraverse(visit) {
        visit(this.data);
        if (this.leftChild) preOrderTraverse.call(this.leftChild, visit);
        if (this.rightChild) preOrderTraverse.call(this.rightChild, visit);
    },
    inPrderTraverse: function inPrderTraverse(visit) {
        if (this.leftChild) inPrderTraverse.call(this.leftChild, visit);
        visit(this.data);
        if (this.rightChild) inPrderTraverse.call(this.rightChild, visit);
    },
    postOrderTraverse: function postOrderTraverse(visit) {
        if (this.leftChild) postOrderTraverse.call(this.leftChild, visit);
        if (this.rightChild) postOrderTraverse.call(this.rightChild, visit);
        visit(this.data);
    },

    levelOrderTraverse: function (visit) {
        var queue = new Queue();
        queue.enQueue(this);

        while (queue.rear) {
            var p = queue.deQueue();
            p.data && visit(p.data);
            p.leftChild && queue.enQueue(p.leftChild);
            p.rightChild && queue.enQueue(p.rightChild);
        }
    },
    // 求先序序列为k的结点的值
    getPreSequence: function (k) {
        var count = 0;

        void function recurse(node) {
            if (node) {
                if (++count === k) {
                    console.log('Value is: ' + node.data);
                } else {
                    recurse(node.leftChild);
                    recurse(node.rightChild);
                }
            }
        }(this);
    },
    // 求二叉树中叶子结点的数目
    countLeaves: function () {
        return function recurse(node) {
            if (!node) return 0;
            else if (!node.leftChild && !node.rightChild) return 1;
            else return recurse(node.leftChild) + recurse(node.rightChild);
        }(this);
    },
    // 交换所有结点的左右子树
    revoluteBinaryTree: function revoluteBinaryTree() {
        var temp = this.leftChild;
        this.leftChild = this.rightChild;
        this.rightChild = temp;

        if (this.leftChild) revoluteBinaryTree.call(this.leftChild);
        if (this.rightChild) revoluteBinaryTree.call(this.rightChild);
    },
    // 求二叉树中以值为x的结点为根的子树深度
    getSubDepth: function getSubDepth(x) {
        if (this.data === x) {
            console.log('subDepth: ' + this.getDepth());
        } else {
            if (this.leftChild) getSubDepth.call(this.leftChild, x);
            if (this.rightChild) getSubDepth.call(this.rightChild, x);
        }
    },
    getDepth: function getDepth() {
        if (this === global) return 0;
        else {
            var m = getDepth.call(this.leftChild);
            var n = getDepth.call(this.rightChild);
            return (m > n ? m : n) + 1;
        }
    },
    // 删除所有以元素x为根的子树
    delSubX: function delSubX(x) {
        if (this.data === x) {
            this.leftChild = null;
            this.rightChild = null;
        } else {
            if (this.leftChild) delSubX.call(this.leftChild);
            if (this.rightChild) delSubX.call(this.rightChild);
        }
    },
    // 非递归复制二叉树
    copyBinaryTree_stack: function () {
        // 用来存放本体结点的栈
        var stack1 = new Stack();
        // 用来存放新二叉树结点的栈
        var stack2 = new Stack();
        stack1.push(this);
        var newTree = new BinaryTree();
        var q = newTree;
        stack2.push(newTree);
        var p;

        while (stack1.top) {
            // 向左走到尽头
            while ((p = stack1.peek())) {
                if (p.leftChild) q.leftChild = new BinaryTree();
                q = q.leftChild;
                stack1.push(p.leftChild);
                stack2.push(q);
            }

            p = stack1.pop();
            q = stack2.pop();

            if (stack1.top) {
                p = stack1.pop();
                q = stack2.pop();
                if (p.rightChild) q.rightChild = new BinaryTree();
                q.data = p.data;
                q = q.rightChild;
                stack1.push(p.rightChild);  // 向右一步
                stack2.push(q);
            }
        }

        return newTree;
    },
    // 求二叉树中结点p和q的最近祖先
    findNearAncient: function (pNode, qNode) {
        var pathP = [];
        var pathQ = [];
        findPath(this, pNode, pathP, 0);
        findPath(this, qNode, pathQ, 0);

        for (var i = 0; pathP[i] == pathQ[i] && pathP[i]; i++);
        return pathP[--i];
    },
    toString: function () {
    },
    // 求一棵二叉树的繁茂度
    lushDegree: function () {
        var countArr = [];
        var queue = new Queue();
        queue.enQueue({
            node: this,
            layer: 0
        });
        // 利用层序遍历来统计各层的结点数
        var r;
        while (queue.rear) {
            r = queue.deQueue();
            countArr[r.layer] = (countArr[r.layer] || 0) + 1;

            if (r.node.leftChild)
                queue.enQueue({
                    node: r.node.leftChild,
                    layer: r.layer + 1
                });
            if (r.node.rightChild)
                queue.enQueue({
                    node: r.node.rightChild,
                    layer: r.layer + 1
                });
        }

        // 最后一个队列元素所在层就是树的高度
        var height = r.layer;
        for (var max = countArr[0], i = 1; countArr[i]; i++)
            // 求层最大结点数
            if (countArr[i] > max) max = countArr[i];

        return height * max;
    },
    // 求深度等于书的高度减一的最靠左的结点
    printPath_maxDepthS1: function () {
        var maxh = this.getDepth();
        var path = [];

        if (maxh < 2) return false;
        find_h(this, 1);

        function find_h(tree, h) {
            path[h] = tree;

            if (h == maxh - 1) {
                var s = ' ';
                for (var i = 1; path[i]; i++) s += path[i].data + (path[i + 1] ? ' -> ' : '');
                console.log(s);
                return;
            } else {
                if (tree.leftChild) find_h(tree.leftChild, h + 1);
                if (tree.rightChild) find_h(tree.rightChild, h + 1);
            }

            path[h] = null;
        }
    },
    // 求树结点的子孙总数填入descNum域中,并返回
    descNum: function () {
        return  function recurse(node) {
            var d;
            if (!node) return -1;
            else d = recurse(node.leftChild) + recurse(node.rightChild) + 2;

            node.descNum = d;

            return d;
        }(this);
    }
};

// 判断二叉树是否完全二叉树
BinaryTree.isFullBinaryTree = function (tree) {
    var queue = new Queue();
    var flag = 0;
    queue.enQueue(tree);

    while (queue.rear) {
        var p = queue.deQueue();

        if (!p) flag = 1;
        else if (flag) return false;
        else {
            queue.enQueue(p.leftChild);
            queue.enQueue(p.rightChild);
        }
    }

    return true;
};

// 求从tree到node结点路径的递归算法
function findPath(tree, node, path, i) {
    var found = false;

    void function recurse(tree, i) {
        if (tree == node) {
            found = true;
            return;
        }

        path[i] = tree;
        if (tree.leftChild) recurse(tree.leftChild, i + 1);
        if (tree.rightChild && !found) recurse(tree.rightChild, i + 1);
        if (!found) path[i] = null;
    }(tree, i);
}

var global = Function('return this;')();

void function test() {
    var tree = [1, 2, 3, 4, 5, , 6, , , 7];
    var test = new BinaryTree;
    test.createBinaryTree(tree);
    test.preOrderTraverse(function (value) {
        console.log('preOrder: ' + value);
    });
    test.inPrderTraverse(function (value) {
        console.log('inOrder: ' + value);
    });
    test.postOrderTraverse(function (value) {
        console.log('postOrder: ' + value);
    });
    test.preOrder_stack(function (data) {
        console.log('preOrderNonRecusive: ' + data);
    });
    test.preOrder_stack2(function (data) {
        console.log('preOrder_stack2: ' + data);
    });
    test.inOrder_stack1(function (value) {
        console.log('inOrder_stack1: ' + value);
    });
    test.inOrder_stack2(function (value) {
        console.log('inOrder_stack2: ' + value);
    });
    test.postOrder_stack(function (value) {
        console.log('postOrder_stack: ' + value);
    });
    test.getPreSequence(5);
    console.log(test.countLeaves());
    test.getSubDepth(6);    // 1
    test.getSubDepth(2);    // 3
    test.levelOrderTraverse(function (value) {
        console.log('levelOrderTraverse: ' + value);
    });

    var newTree = test.copyBinaryTree_stack();

    var node1 = test.leftChild.leftChild;   // 4
    var node2 = test.leftChild.rightChild.leftChild;    // 7
    var ancient = test.findNearAncient(node1, node2);
    console.log(ancient);

    console.log('expect false: ' + BinaryTree.isFullBinaryTree(test));
    newTree.rightChild.leftChild = new BinaryTree(7);
    newTree.leftChild.rightChild.leftChild = null;
    console.log('expect true: ' + BinaryTree.isFullBinaryTree(newTree));
    console.log('lush degree: ' + test.lushDegree());

    test.printPath_maxDepthS1();
    console.log(test.descNum());
}();

相关:

JavaScript处理生成树形结构的几个场景与方案

前言

近日,Mac 下著名软件 Homebrew 的作者,因为没解出来二叉树翻转的白板算法题,惨遭 Google 拒绝,继而引发推特热议。

在 JavaScript 中也有很多树形结构。比如 DOM 树,省市区地址联动,文件目录等; JSON 本身就是树形结构。

很多前端面试题也跟树形结构的有关,比如在浏览器端写遍历 DOM 树的函数,比如在 nodejs 运行时遍历文件目录等。

这里演示用 JavaScript 遍历树形结构的几种策略。

场景1:遍历 DOM 树

方案1:递归模式

function walkDom(node, callback) {
    if (node === null) { //判断node是否为null
        return
    }
    callback(node) //将node自身传入callback
    node = node.firstElementChild //改变node为其子元素节点
    while (node) {
        walkDom(node, callback) //如果存在子元素,则递归调用walkDom
        node = node.nextElementSibling //从头到尾遍历元素节点
    }
}
walkDom(document, function(node) {console.count()}) //包含document节点
document.querySelectorAll('*').length //数量比上面输出的少1,因为不包含document节点

将上述代码黏贴到任意页面的控制台 console 中执行。

方案2:循环模式

function walkDom(node, callback) {
    if (node === null) {
        return
    }
    var stack = [node] //存入数组
    var target
    while(stack.length) { //数组长度不为0,继续循环
        target = stack.shift() //取出元素
        callback(target) //传入callback
        Array.prototype.push.apply(stack, target.children) //将其子元素一股脑推入stack,增加长度
    }
}
walkDom(document, function(node) {console.count()}) //包含document节点
document.querySelectorAll('*').length //数量比上面输出的少1,因为不包含document节点

在循环模式中,shift方法可以换成pop,从尾部取出元素;push方法可以换成unshift从头部添加元素。不同的顺序,影响了是「广度优先」还是「深度优先」。

场景2:在 nodejs 运行时里遍历文件目录

子场景1:同步模式

方案1:递归

var fs = require('fs')
var Path = require('path')

function readdirs(path) {
    var result = { //构造文件夹数据
        path: path,
        name: Path.basename(path),
        type: 'directory'
    }
    var files = fs.readdirSync(path) //拿到文件目录下的所有文件名
    result.children = files.map(function(file) {
        var subPath = Path.resolve(path, file) //拼接为绝对路径
        var stats = fs.statSync(subPath) //拿到文件信息对象
        if (stats.isDirectory()) { //判断是否为文件夹类型
            return readdirs(subPath) //递归读取文件夹
        }
        return { //构造文件数据
            path: subPath,
            name: file,
            type: 'file'
        }
    })
    return result //返回数据
}

var cwd = process.cwd()
var tree = readdirs(cwd)
fs.writeFileSync(Path.join(cwd, 'tree.json'), JSON.stringify(tree)) //保存在tree.json中,去查看吧

将上面的代码保存在 tree.js 中,然后在当前文件夹打开命令行,输入node tree.js,目录信息保存在生成tree.json文件中。

方案2:循环

var fs = require('fs')
var Path = require('path')

function readdirs(path) {
    var result = { //构造文件夹数据
        path: path,
        name: Path.basename(path),
        type: 'directory'
    }
    var stack = [result] //生成一个栈数组
    while (stack.length) { //如果数组不为空,读取children
        var target = stack.pop() //取出文件夹对象
        var files = fs.readdirSync(target.path) //拿到文件名数组
        target.children = files.map(function(file) {
            var subPath = Path.resolve(target.path, file) //转化为绝对路径
            var stats = fs.statSync(subPath) //拿到文件信息对象
            var model = { //构造文件数据结构
                path: subPath,
                name: file,
                type: stats.isDirectory() ? 'directory' : 'file'
            }
            if (model.type === 'directory') {
                stack.push(model) //如果是文件夹,推入栈
            }
            return model //返回数据模型
        })
    }
    return result //返回整个数据结果
}

var cwd = process.cwd()
var tree = readdirs(cwd)
fs.writeFileSync(Path.join(cwd, 'tree.json'), JSON.stringify(tree)) //保存在tree.json中,去查看吧

循环策略中的popshiftpushunshift也可以互换以调整优先级,甚至用可以用splice方法更精细的控制stack数组。循环模式比递归模式更可控。

子场景2:异步模式

方案1:过程式 Promise

var fs = require('fs')
var Path = require('path')
//promise包装的fs.stat方法
var stat = function(path) {
    return new Promise(function(resolve, reject) {
        fs.stat(path, function(err, stats) {
            err ? reject(err) : resolve(stats)
        })
    })
}
//promise包装的fs.readdir方法
var readdir = function(path) {
    return new Promise(function(resolve, reject) {
        fs.readdir(path, function(err, files) {
            err ? reject(err) : resolve(files)
        })
    })
}
//promise包装的fs.writeFile
var writeFile = function(path, data) {
    return new Promise(function(resolve, reject) {
        fs.writeFile(path, JSON.stringify(data || ''), function(err) {
            err ? reject(err) : resolve
        })
    })
}

function readdirs(path) {
    return readdir(path) //异步读取文件夹
    .then(function(files) { //拿到文件名列表
        var promiseList = files.map(function(file) { //遍历列表
            var subPath = Path.resolve(path, file) //拼接为绝对路径
            return stat(subPath) //异步读取文件信息
            .then(function(stats) { //拿到文件信息
                //是文件夹类型的,继续读取目录,否则返回数据
                return stats.isDirectory() ?
                readdirs(subPath) : {
                    path: subPath,
                    name: file,
                    type: 'file'
                }
            })
        })
        return Promise.all(promiseList) //等待所有promise完成
    })
    .then(function(children) { //拿到包含所有数据的children数组
        return { //返回结果
            path: path,
            name: Path.basename(path),
            type: 'directory',
            children: children
        }
    })
}

var cwd = process.cwd()

readdirs(cwd)
.then(writeFile.bind(null, Path.join(cwd, 'tree.json'))) //保存在tree.json中,去查看吧
.catch(console.error.bind(console)) //出错了就输出错误日志查看原因

上面的函数都能工作,但都是一个个function的调用,显得太「过程式」;

能不能用面向对象的方式来写呢?

当然可以。

其实面向对象的写法,更清晰。

为了更加语义化,以及增显逼格。

我们用 ES6 的 class 来写这个树形结构类。

方案2:ES6-class + ES6-Promise

import fs from 'fs'
import {join, resolve, isAbsolute, basename, extname, dirname, sep} from 'path'

/**
 * 获取目录下的所有文件
 * @param {string} path
 * @return {promise} resolve files || reject error
 */
let readdir = (path) => {
    return new Promise((resolve, reject) => {
        fs.readdir(path, (err, files) => {
            err ? reject(err) : resolve(files)
        })
    })
}

/**
* 将data写入文件
* @param {string} path 路径
* @param {data} data
* @return {promise} resolve path || reject error
*/
let writeFile = (path, data) => {
    return new Promise((resolve, reject) => {
        fs.writeFile(path, data, (err) => {
            err ? reject(err) : resolve(path)
        })
    })
}

/**
* 获取文件属性
* @param {string} path
* @return {promise} resolve stats || reject error
*/
let stat = (path) => {
    return new Promise((resolve, reject) => {
        fs.stat(path, (err, stats) => {
            err ? reject(err) : resolve(stats)
        })
    })
}

/**
* 判断path是否存在
* @param {string} path 路径
* @return {promise} resolve exists
*/
let exists = (path) => {
    return new Promise((resolve) => fs.exists(path, resolve))
}


//文档类
class Document {
    constructor(path) {
        this.path = path
        this.name = basename(path)
    }
    //存在性判断
    exists() {
        return exists(this.path)
    }
    //异步获取文件信息
    stat() {
        return stat(this.path)
    }
    //输出基本数据
    json() {
        return JSON.stringify(this)
    }
    //将基本数据保存在path路径的文件中
    saveTo(path) {
        if (isAbsolute(path)) {
            return writeFile(path, this.json())
        }
        return writeFile(resolve(this.path, path), this.json())
    }
}

//文件类,继承自文档类
class File extends Document {
    constructor(path) {
        super(path) //必须先调用超类构造方法
        this.type = 'file' //type 为 file
        this.extname = extname(path) //新增扩展名
    }
    //写入数据
    write(data = '') {
        return writeFile(this.path, data)
    }
    //其他文件特有方法如 read unlink 等
}


//文件夹类,继承自文档类
class Directory extends Document {
    constructor(path) {
        super(path) //必须先调用超类构造方法
        this.type = 'directory'
    }
    //读取当前文件夹
    readdir() {
        return readdir(this.path) //读取目录
        .then((files) => { //拿到文件名列表
            let promiseList = files.map((file) => {
                let subPath = resolve(this.path, file) //拼接为绝对路径
                return stat(subPath) //获取文件信息
                .then((stats) => {
                    //根据文件信息,归类为Directory或File类型
                    return stats.isDirectory() ?
                    new Directory(subPath) :
                    new File(subPath)
                })
            })
            return Promise.all(promiseList)
        })
        .then((children) => { //拿到children数组
            this.children = children //保存children属性
            return children //返回children
        })
    }
    //深度读取文件目录
    readdirs() {
        return this.readdir() //读取当前文件夹
        .then((children) => { //拿到children
            let promiseList = []
            children.map((child) => {
                if (child instanceof Directory) { //是文件夹实例,继续深度读取文件目录
                    promiseList.push(child.readdirs())
                }
            })
            return Promise.all(promiseList) //等待所有子元素深度读取目录完毕
        })
        .then(() => this) //返回this
    }
    //其他文件夹特有方法如 addFile removeFile addDir remveDir 等
}

let cwd = process.cwd()

new Directory(cwd)
.readdirs()
.then((tree) => {
    tree.saveTo('tree.json') //让它自己保存在tree.json里
})
.catch(console.error.bind(console)) //输出错误日志

因为当前 JavaScript 引擎对 ES6 的支持度还不够,所以上述代码不能直接运行。可以通过以下两种方式来验证代码能不能跑起来。

第一种,先 npm install -g bable 全局安装 babel 工具,再以 babel-node tree.js的方式取代 node tree.js 来运行上述代码。

第二种,将上述代码黏贴到 https://babeljs.io/repl/,拿到编译为 ES5 的代码,将代码保存在 tree.js文件中,以 ES5 的形式执行。

结语

以上就是我知道的一些用 JavaScript 处理树形结构的几种方法,希望看过后对你有帮助。

AngularJS简介

未来的发展趋势是前端后端只靠json数据来进行通信:后端只处理和发送一段json数据到前端,然后计算和模板渲染都在前端进行。而前端的改动后,形成json数据然后传回到后端。未来趋势就是:后台程序再也不做模板的任何处理

AngularJS 的作用简单说就是就是把后台的json值直接用html进行渲染,然后html的操作又直接在形成json传回后台。

banner-angularjs

未来的后台MVC,试图不再是模板了,而是一段结构整齐标准的JSON,而这个JSON作为前台的model直接在AngularJS直接使用。

或者说后台的试图是前台的模型,而整个前台就是后台的视图。后台程序再也不做模板的任何处理了。

Angular是框架的一种,不学也能开发前端。学backbone或者ember也能开发前端。没有js的mvc,就光弄个jquery,也能开发前端。

那 到底要不要学?个人觉得就是要看发展趋势。angular是否简化的前端开发,是否符合未来前端的开发趋势。以现在的js的发展程度来看,angular 是符合发展趋势的,第一,解耦前端,第二,可以模块化,第三可测试,第四天生支持json,第五依赖注入等等等,还有一些其他特性使得angular跟随 甚至是推动了前端的开发趋势。

所以说angular学习是有好处的:
1.了解前端的开发趋势
2.学习MV*的设计方法
3.学习模块化编程
4.学习如何测试模块
5.使用angular简化开发流程
6.随着google的大力支持和逐渐流行,公司开始使用angular,有些岗位需要有angular的知识才能工作。

有这么个有趣的事情,不知道大家经历过么?
各种开源框架–>自己写框架–>不用框架好了回归正题,AngularJS是什么?楼主可是真懒哈,这里有个链接 AngularJS_百度百科

接下来我讲下我对AngularJS框架的理解,其实就是借助于MVC的思想来剥离前端各部分代码,各就各位。说白了,你可以认为是一种代码的规范,或者是一个个框框把功能相似的代码放到一起,便于维护管理。优点很多,楼上的都答的很全,不多说了。
看个例子,
<article>
<h2>{{title}}</h2>
<p>{{content}}</p>
</article>

main.js:
var articleService = new ArticleService();
var article = articleService .fecthOneArticle();
var articleView = new ArticleView();
if(article) {
articleView.render(article);
}else {
articleView.showErrorDialog(“An error occurred!”);
}

ArticleService.js
this.fecthOneArticle = function() {
// 利用ajax从服务器端获取数据,并返回
return article;
}

ArticleView.js
this.render = function(data) {
var elements = document.getElementsByTagName(‘*’);
var savedElements = [];
var pattern = /^{{.*}}$/i;
for(var i = 0; i < elements.length; i++) {
var text = elements[i].innerText;
if(pattern.test(text)) {
// 获取{{与}}之间的作为key
savedElements[key] = elements[i];
}

// 接下来就是遍历data中的数据,然后填充到相应的element上,不相写了,
//回家还写代码,真是苦逼死了。
}
}

article的数据结构:
{
title: “AngularJS”,
content: “什么是AngularJS?”
}

作者:小明
链接:https://www.zhihu.com/question/21497720/answer/70106937
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

程序员技术网站 stackoverflow 有一则精彩问答,地址:javascript – “Thinking in AngularJS” if I have a jQuery background?

How do I “think in AngularJS” if I have a jQuery background?

该问题至今已集结了 4538 个赞,其中 Josh David Miller 的答案得票最高,其文洋洋洒洒,循循善诱,娓娓道来,众望所归地荣获 7200 多赞,这篇文章将他的西文翻译成国语,以飨读者。

问题摘要我可以熟练使用jQuery进行客户端应用的开发,但是现在我希望开始使用Angular.js。哪位能描述一下这个过程中必要的模式变化吗?希望您的答案能够围绕下面这些具体的问题:

  1. 我如何对客户端web应用进行不同方式的架构和设计?它们之间最大的区别是什么?(译者注:指jQuery和Angular.js)
  2. 有什么是我不该做或者不该使用的;而又有什么是我应该做或者应该使用的呢?
  3. 有没有一些服务端的考量/约束呢?

我在寻找的就是一个关于jQuery和Angular.js之间的详细的比较。

1. 不要先设计页面,然后再使用DOM操作来改变它的展现在jQuery中,你通常会设计一个页面,然后再给它动态效果。这是因为jQuery的设计就是为了扩充DOM并在这个简单的前提下疯狂的生长的。

但是在AngularJS里,必须从头开始就在头脑中思考架构。必须从你想要完成的功能开始,然后设计应用程序,最后来设计视图,而非“我有这么一个DOM片段,我想让他可以实现XXX效果”。

2. 不要用AngularJS来加强jQuery类似的,不要以这样的思维开始:用jQuery来做X,Y和Z,然后只需要把AngularJS的models和controllers加在这上面。这在刚开始的时候显得非常诱人,这也是为什么我总是建议AngularJS的新手完全不使用jQuery,至少不要在习惯使用“Angular Way”开发之前这么做。

我在邮件列表里看到很多开发者使用150或200行代码的jQuery插件创造出这些复杂的解决方案,然后使用一堆callback函数以及$apply把它粘合到AngularJS里,看起来复杂难懂;但是他们最终还是把它搞定了!问题是在大多数情况下这些jQuery插件可以使用很少的AngularJS代码重写,而且所有的一切都很简单直接容易理解。

这里的底线是:

当你选择解决方案时,首先“think in AngularJS”;如果想不出一个解决方案,去社区求助;如果还是没有简单的解决方案,再考虑使用jQuery。但是不要让jQuery成为你的拐杖,导致你永远无法真正掌握AngularJS。

3. 总是以架构的角度思考首先要知道Single-page应用是应用,不是网页。所以我们除了像一个客户端开发者般思考外,还需要像一个服务器端开发者一样思考。我们必须考虑如何把我们的应用分割成独立的,可扩展且可测试的组件。

那么如何做到呢?如何“think in AngularJS”?这里有一些基本原则,对比jQuery。

视图是“Official Record”在jQuery里,我们编程改变视图。我们会将一个下拉菜单定义为一个ul :

    <ul class="main-menu">
    <li class="active"> <a href="#/home">Home</a> </li>
    <li> <a href="#/menu1">Menu 1</a> 
        <ul>
            <li><a href="#/sm1">Submenu 1</a></li> 
            <li><a href="#/sm2">Submenu 2</a></li>
            <li><a href="#/sm3">Submenu 3</a></li>
        </ul>
    </li>
    <li> <a href="#/home">Menu 2</a> </li>
    </ul>

在jQuery里,我们会在应用逻辑里这样启用这个下拉菜单:

$('.main-menu').dropdownMenu();

当我们只关注视图,这里不会立即明显的体现出任何(业务)功能。对于小型应用,这没什么不妥。但是在规模较大的应用中,事情就会变得难以理解且难以维护。

而在AngularJS里,视图是基于视图的功能。ul声明就会像这样:

    <ul class="main-menu" dropdown-menu> ... </ul>

这两种方式做了同样的东西,但是在AngularJS的版本里任何人看到这个模版都可以知道将会发生什么事。不论何时一个新成员加入开发团队,他看到这个就会知道有一个叫做dropdownMenu的directive作用在这个标签上;他不需要靠直觉去猜测代码的功能或者去看任何代码。视图本身告诉我们会发生什么事。清晰多了。

首次接触AngularJS的开发者通常会问这样一个问题:如何找到所有的某类元素然后给它们加上一个directive。但当我们告诉他:别这么做时,他总会显得非常的惊愕。而不这么做的原因是这是一种半jQuery半AngularJS的方式,这么做不好。这里的问题在于开发者尝试在AngularJS的环境里“do jQuery”。这么做总会有一些问题。视图是official record。在一个directive外,绝不要改变DOM。所有的directive都应用在试图上,意图非常清晰。

记住:

不要设计,然后写标签。你需要架构,然后设计。

数据绑定这是到现在为止最酷的AngularJS特性。这个特性使得前面提到的很多DOM操作都显得不再需要。AngularJS会自动更新视图,所以你自己不用这么做!在jQuery里,我们响应事件然后更新内容,就像这样:

$.ajax({ 
    url: '/myEndpoint.json', 
    success: function ( data, status ) { 
        $('ul#log').append('<li>Data Received!</li>'); 
    } 
});

对应的视图:

    <ul class="messages" id="log"> </ul>

除了要考虑多个方面,我们也会遇到前面视图中的问题。但是更重要的是,需要手动引用并更新一个DOM节点。如果我们想要删除一个log条目,也需要针对DOM编码。那么如何脱离DOM来测试这个逻辑?如果想要改变展现形式怎么办?

这有一点凌乱琐碎。但是在AngularJS里,可以这样来实现:

$http('/myEndpoint.json').then(function (response) {
    $scope.log.push({
        msg: 'Data Received!'
    });
});

视图看起来是这个样子的:

    <ul class="messages"> 
        <li ng-repeat="entry in log"></li>
    </ul>

但是其实还可以这样来做:

    <div class="messages"> 
        <div class="alert" ng-repeat="entry in log"></div> 
    </div>

现在如果我们想使用Bootstrap的alert boxes,而不是一个无序列表,根本不需要改变任何的controller代码!更重要的是,不论log在何处或如何被更新,视图便会随之更新。自动的。巧妙!

尽管我没有在这里展示,数据绑定其实是双向的。所以这些log信息在视图里也可以是可编辑的。只需要这么做:

    <div class="highlight">
        <pre><input ng-model="entry.msg" /></pre>
    </div>

简单快乐。

清晰的模型(Model)层在jQuery里,DOM在一定程度上扮演了模型的角色。但在AngularJS中,我们有一个独立的模型层可以灵活的管理。完全与视图独立。这有助于上述的数据绑定,维护了关注点的分离(独立的考虑视图和模型),并且引入了更好的可测性。后面还会提到这点。

关注点分离上面所有的内容都与这个愿景相关:保持你的关注点分离。视图负责展现将要发生的事情;模型表现数据;有一个service层来实现可复用的任务;在directive里面进行DOM操作和扩展;使用controller来把上面的东西粘合起来。这在其他的答案里也有叙述,我在这里只增加关于可测试性的内容,在后面的一个段落里详述。

依赖注入依赖注入帮我们实现了关注点分离。如果你来自一个服务器语言(java或php),可能对这个概念已经非常熟悉,但是如果你是一个来自jQuery的客户端开发者,这个概念可能看起来有点傻而多余。但其实不是的。。。

大体来讲,DI意味着可以非常自由的声明组件,然后在另一个组件里,只需要请求一个该组件的实例,就可以得到它。不需要知道(关心)加载顺序,或者文件位置,或类似的事情。这种强大可能不会立刻显现,但是我只提供一个(常见。。)的例子:测试。

就说在你的应用里,我们需要一个服务通过REST API来实现服务器端存储,并且根据不同的应用状态,也有可能使用(客户端)本地存储。当我们运行controller的测试时,不希望必须和服务器交互 —— 毕竟是在测试controller逻辑。我们可以只添加一个与本来使用的service同名的mock service,injector会确保controller自动得到假的那个service —— controller不会也不需要知道有什么不同。

说起测试……

4. 总是 —— 测试驱动开发这其实是关于架构的第3节。但是它太重要了,所以我把它单独拿出来作为一个顶级段落。

在所有那些你见过,用过或写过的jQuery插件中,有多少是有测试集的?不多,因为jQuery经不起测试的考验。但是AngularJS可以。

在jQuery中,唯一的测试方式通常是独立地创建附带sample/demo页面的组件,然后我们的测试在这个页面上做DOM操作。所以我们必须独立的开发一个组件,然后集成到应用里。多不方便!在使用jQuery开发时,太多的时间,我们挑选迭代而非测试驱动开发。谁又能责怪我们呢?

但是因为有了关注点分离,我们可以在AngularJS中迭代地做测试驱动开发!例如,想要一个超级简单的directive来展现我们的当前路径。可以在视图里声明:

    <a href="/hello" when-active>Hello</a>

OK,现在可以写一个测试:

it('should add "active" when the route changes', inject(function () {
    var elm = $compile('<a href="/hello" when-active>Hello</a>')($scope);
    $location.path('/not-matching');
    expect(elm.hasClass('active')).toBeFalsey();
    $location.path('/hello');
    expect(elm.hasClass('active')).toBeTruthy();
}));

执行这个测试来确认它是失败的。然后我们可以开始写这个directive了:

.directive('whenActive', function ($location) {
    return {
        scope: true,
        link: function (scope, element, attrs) {
            scope.$on('$routeChangeSuccess', function () {
                if ($location.path() == element.attr('href')) {
                    element.addClass('active');
                } else {
                    element.removeClass('active');
                }
            });
        }
    };
});

测试现在通过了,然后我们的menu按照请求的方式执行。开发过程既是迭代的也是测试驱动的。太酷了。

5. 概念上,Directives并不是打包的jQuery你经常会听到“只在directive里做DOM操作”。这是必需的。请给它应有的尊重!

但让我们再深入一点……

一些directive仅仅装饰了视图中已经存在的东西(想想ngClass)并且因此有时候仅仅直接做完DOM操作然后就完事了。但是如果一个directive像一个“widget”并且有一个模版,那么它也要做到关注点分离。也就是说,模版本身也应该很大程度上与其link和controller实现保持独立。

AngularJS拥有一整套工具使这个过程非常简单;有了ngClass我们可以动态地更新class;ngBind使得我们可以做双向数据绑定。ngShow和ngHide可编程地展示和隐藏一个元素;以及更多地 —— 包括那些我们自己写的。换句话说,我们可以做到任何DOM操作能实现的特性。DOM操作越少,directive就越容易测试,也越容易给它们添加样式,在未来也越容易拥抱变化,并且更加的可复用和发布。

我见过很多AngularJS新手,把一堆jQuery扔到directive里。换句话说,他们认为“因为不能在controller里做DOM操作,就把那些代码弄到directive里好了”。虽然这么做确实好一些,但是依然是错误的。

回想一下我们在第3节里写的那个logger。即使要把它放在一个directive里,我们依然希望用“Angular Way”来做。它依然没有任何DOM操作!有很多时候DOM操作是必要的,但其实比你想的要少得多!在应用里的任何地方做DOM操作之前,问问你自己是不是真的需要这么做。有可能有更好的方式。

这里有一个示例,展示出了我见过最多的一种模式。我们想做一个可以toggle的按钮。(注意:这个例子有一点牵强、啰嗦,这是为了表达出使用同样方式处理问题的更复杂的情况。)

.directive('myDirective', function () {
    return {
        template: '<a class="btn">Toggle me!</a>',
        link: function (scope, element, attrs) {
            var on = false;
            $(element).click(function () {
                if (on) {
                    $(element).removeClass('active');
                } else {
                    $(element).addClass('active');
                }
                on = !on;
            });
        }
    };
});

这里有一些错误的地方。首先,jQeury根本没必要出现。我们在这里做的事情都根本用不着jQuery!其次,即使已经将jQuery用在了页面上,也没有理由用在这里。第三,即使假设这个directive依赖jQuery来工作,jqLite(angular.element)在加载后总会使用jQuery!所以我们没必要使用$ —— 用angular.element就够了。第四,和第三条紧密关联,jqLite元素不需要被$封装 —— 传到link里的元素本来就会是一个jQuery元素!第五,我们在前面段落中说过,为什么要把模版的东西混到逻辑里?

这个directive可以(即使是更复杂的情况下!)写得更简单:

.directive('myDirective', function () {
    return {
        scope: true,
        template: '<a class="btn" ng-class="{active: on}" ng-click="toggle()">Toggle me!</a>',
        link: function (scope, element, attrs) {
            scope.on = false;
            scope.toggle = function () {
                scope.on = !$scope.on;
            };
        }
    };
});

再一次地,模版就在模版里,当有样式需求时,你(或你的用户)可以轻松的换掉它,不用去碰逻辑。重用性 —— boom!

当然还有其他的好处,像测试 —— 很简单!不论模版中有什么,directive的内部API从来不会被碰到,所以重构也很容易。可以不碰directive就做到任意改变模版。不论你怎么改,测试总是通过的。

所以如果directive不仅仅是一组类似jQuery的函数,那他们是什么?Directive实际是HTML的扩展。如果HTML没有做你需要它做的事情,你就写一个directive来实现,然后就像使用HTML一样使用它。

换句话说,如果AngularJS库没有做的一些事情,想想开发团队会如何完成它来配合ngClick,ngClass等。

总结不要用jQuery.连include也不要。它会让你停滞不前。如果遇到一个你认为已经知道如何使用jQuery来解决的问题,在使用$之前,试试想想如何在AngularJS的限制下解决它。如果你不知道,问!20次中的19次,最好的方式不需要jQuery。如果尝试使用jQuery会增加你的工作量。

javascript 时间戳与日期格式之间的互转

1. 将时间戳转换成日期格式
// 简单的一句代码
var date = new Date(时间戳); //获取一个时间对象 注意:如果是uinx时间戳记得乘于1000。比如php函数time()获得的时间戳就要乘于1000

/**
1. 下面是获取时间日期的方法,需要什么样的格式自己拼接起来就好了
2. 更多好用的方法可以在这查到 -> http://www.w3school.com.cn/jsref/jsref_obj_date.asp
*/
date.getFullYear(); // 获取完整的年份(4位,1970)
date.getMonth(); // 获取月份(0-11,0代表1月,用的时候记得加上1)
date.getDate(); // 获取日(1-31)
date.getTime(); // 获取时间(从1970.1.1开始的毫秒数)
date.getHours(); // 获取小时数(0-23)
date.getMinutes(); // 获取分钟数(0-59)
date.getSeconds(); // 获取秒数(0-59)

例子

var add0 = function(m){return m<10?’0’+m:m };

var gettime = function(chuo){
var time = new Date(parseInt(chuo));
// var time = new Date(1000 * parseInt(chuo)); //如果是uinx时间戳记得乘于1000。比如php函数time()获得的时间戳就要乘于1000
var y = time.getFullYear();
var m = time.getMonth()+1;
var d = time.getDate();
var h = time.getHours();
var mm = time.getMinutes();
var s = time.getSeconds();

return y + ‘-‘ + add0(m) + ‘-‘ + add0(d) + ‘ ‘ + add0(h) + ‘:’ + add0(mm) + ‘:’ + add0(s);
};

封装的时间格式器

/**
* 和PHP一样的时间戳格式化函数
* @param {string} format 格式
* @param {int} timestamp 要格式化的时间 默认为当前时间
* @return {string} 格式化的时间字符串
*/
function date(format, timestamp){
var a, jsdate=((timestamp) ? new Date(timestamp*1000) : new Date());
var pad = function(n, c){
if((n = n + “”).length < c){ return new Array(++c - n.length).join("0") + n; } else { return n; } }; var txt_weekdays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; var txt_ordin = {1:"st", 2:"nd", 3:"rd", 21:"st", 22:"nd", 23:"rd", 31:"st"}; var txt_months = ["", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; var f = { // Day d: function(){return pad(f.j(), 2)}, D: function(){return f.l().substr(0,3)}, j: function(){return jsdate.getDate()}, l: function(){return txt_weekdays[f.w()]}, N: function(){return f.w() + 1}, S: function(){return txt_ordin[f.j()] ? txt_ordin[f.j()] : 'th'}, w: function(){return jsdate.getDay()}, z: function(){return (jsdate - new Date(jsdate.getFullYear() + "/1/1")) / 864e5 >> 0},

// Week
W: function(){
var a = f.z(), b = 364 + f.L() – a;
var nd2, nd = (new Date(jsdate.getFullYear() + “/1/1”).getDay() || 7) – 1;
if(b < = 2 && ((jsdate.getDay() || 7) - 1) <= 2 - b){ return 1; } else{ if(a <= 2 && nd >= 4 && a >= (6 – nd)){
nd2 = new Date(jsdate.getFullYear() – 1 + “/12/31”);
return date(“W”, Math.round(nd2.getTime()/1000));
} else{
return (1 + (nd < = 3 ? ((a + nd) / 7) : (a - (7 - nd)) / 7) >> 0);
}
}
},

// Month
F: function(){return txt_months[f.n()]},
m: function(){return pad(f.n(), 2)},
M: function(){return f.F().substr(0,3)},
n: function(){return jsdate.getMonth() + 1},
t: function(){
var n;
if( (n = jsdate.getMonth() + 1) == 2 ){
return 28 + f.L();
} else{
if( n & 1 && n < 8 || !(n & 1) && n > 7 ){
return 31;
} else{
return 30;
}
}
},

// Year
L: function(){var y = f.Y();return (!(y & 3) && (y % 1e2 || !(y % 4e2))) ? 1 : 0},
//o not supported yet
Y: function(){return jsdate.getFullYear()},
y: function(){return (jsdate.getFullYear() + “”).slice(2)},

// Time
a: function(){return jsdate.getHours() > 11 ? “pm” : “am”},
A: function(){return f.a().toUpperCase()},
B: function(){
// peter paul koch:
var off = (jsdate.getTimezoneOffset() + 60)*60;
var theSeconds = (jsdate.getHours() * 3600) + (jsdate.getMinutes() * 60) + jsdate.getSeconds() + off;
var beat = Math.floor(theSeconds/86.4);
if (beat > 1000) beat -= 1000;
if (beat < 0) beat += 1000; if ((String(beat)).length == 1) beat = "00"+beat; if ((String(beat)).length == 2) beat = "0"+beat; return beat; }, g: function(){return jsdate.getHours() % 12 || 12}, G: function(){return jsdate.getHours()}, h: function(){return pad(f.g(), 2)}, H: function(){return pad(jsdate.getHours(), 2)}, i: function(){return pad(jsdate.getMinutes(), 2)}, s: function(){return pad(jsdate.getSeconds(), 2)}, //u not supported yet // Timezone //e not supported yet //I not supported yet O: function(){ var t = pad(Math.abs(jsdate.getTimezoneOffset()/60*100), 4); if (jsdate.getTimezoneOffset() > 0) t = “-” + t; else t = “+” + t;
return t;
},
P: function(){var O = f.O();return (O.substr(0, 3) + “:” + O.substr(3, 2))},
//T not supported yet
//Z not supported yet

// Full Date/Time
c: function(){return f.Y() + “-” + f.m() + “-” + f.d() + “T” + f.h() + “:” + f.i() + “:” + f.s() + f.P()},
//r not supported yet
U: function(){return Math.round(jsdate.getTime()/1000)}
};

return format.replace(/[\]?([a-zA-Z])/g, function(t, s){
if( t!=s ){
// escaped
ret = s;
} else if( f[s] ){
// a date function exists
ret = f[s]();
} else{
// nothing special
ret = s;
}
return ret;
});
}

调用方法 能够很方便的将时间戳转换成日期的格式,如:

date(‘Y-m-d’,’1350052653′);//很方便的将时间戳转换成了2012-10-11
date(‘Y-m-d H:i:s’,’1350052653′);//得到的结果是2012-10-12 22:37:33

2. 将日期格式转换成时间戳
// 也很简单
var strtime = ‘2014-04-23 18:55:49:123’;
var date = new Date(strtime); //传入一个时间格式,如果不传入就是获取现在的时间了,这样做不兼容火狐。
// 可以这样做
var arr = strtime.replace(/ |:/g, ‘-‘).split(‘-‘);
date = new Date(Date.UTC(arr[1], arr[2], arr[3], arr[4], arr[5]));

// 有三种方式获取,在后面会讲到三种方式的区别
time1 = date.getTime();
time2 = date.valueOf();
time3 = Date.parse(date);

/*
三种获取的区别:
第一、第二种:会精确到毫秒
第三种:只能精确到秒,毫秒将用0来代替
比如上面代码输出的结果(一眼就能看出区别):
1398250549123
1398250549123
1398250549000
*/
3. 注意
获取到的时间戳除于1000就可以获得unix的时间戳了,在传值给PHP时用得到

dsoframer.ocx 实现在线的word编辑

关于 dsoframer.ocx 的资料和API 我这里就不多说了。。百度 google 有很多

 

但是都是简单的介绍api 对于刚刚接触的人还是很难入手的。

 

所以我这里主要讲下如何来具体的使用,分享给大家,希望对大家帮助啊:

 

经过几天的研究,嘎嘎。。战果如下:

这里说明下 各个文件

dsoframer.ocx

大家都认识吧,微软提供的

dsoframer.js

封装了对 dsoframer.ocx 的操作

dsoframer.jsp

显示word的业务界面

dsoframer.CAB

这个是重头戏啊,引用这个文件,

可以在让 dsoframer.ocx 在浏览器中自动下载并注册 当初可弄了老半天的)

至于如何签名自己的 ocx,使其下载并注册,网页也有很多资料。

我这里也提供一个:http://blog.csdn.net/xjzdr/article/details/5991585

流程都好看。最要命的是 里面的 inf 安装文件别写错了,否则就安装失败了,所以要千万记着。

这里偷偷的告诉你们一个秘密: 我是把别人公司的 CAB 安装解压,让回一步一步的模仿写出来的。。嘎嘎

具体是哪个公司,嘿嘿,,不好说。。秘密!  你们可以拷贝我的就好了啊。。

还有一点就是要注意  写版本 FileVersion=2,0,0,0  的时候 是逗号而不是点,不然报错!

下面看具体代码

 

dsoframer.jsp  界面:

Html代码  
  1. <%@ page language=”java” pageEncoding=”UTF-8″ contentType=”text/html; charset=UTF-8″ %>
  2. <%
  3. String path = request.getContextPath();
  4. String basePath = request.getScheme()+”://”+request.getServerName()+”:”+request.getServerPort()+path+”/”;
  5. String id = “1”;
  6. %>
  7. <html>
  8.     <head>
  9.         <title>调查报告</title>
  10.         <script language=”javascript” src=”dsoframer.js”></script>
  11.         <script type=”text/javascript”>
  12.             /*用法说明:
  13.               1,创建 word对象
  14.               2,设置文件上传url
  15.               3,在页面加载时,打开word文档,根据是否传人docUrl参数,决定是本地新建,还是从服务器端获取
  16.               4,在页面关闭时,执行上传操作。
  17.             */
  18.              var word = new word();
  19.              //决定路径
  20.              word.setUploadUrl(“http://127.0.0.1:8070/word/upload_handle.jsp”);
  21.              var docurl = “”;
  22.              function load(){
  23.                  //方法:openDoc(docName, docUrl)
  24.                  // docName:必填,本地保存的文件名, 也为上传到服务器上时的文件名
  25.                  // docUrl: 填时,为从服务器端获取doc文档的路径, 不填时,表示本地新建doc文档
  26.                  word.openDoc(‘1.doc’,”http://127.0.0.1:8070/word/upload/1.doc”);
  27.                  /**
  28.                   //这里实现读取服务器的模板,并保存到服务器的业务路径中
  29.                  $.post(“/ZsytpServlet”,{type:”ajaxWord”,id:<%=id%>},function(result){
  30.                         var dataObj=eval(“(“+result+”)”);//转换为json对象
  31.                         docurl = dataObj.docurl;
  32.                         if(docurl == “”){
  33.                             word.openDoc(‘<%=id%>.doc’,”<%=basePath%>/webapps/zsytp/templ/”+dataObj.fhtk+”.doc”);
  34.                             document.getElementById(‘oframe’).SetFieldValue(“f_name”,dataObj.f_name,””);
  35.                             document.getElementById(‘oframe’).SetFieldValue(“m_name”,dataObj.m_name,””);
  36.                             document.getElementById(‘oframe’).SetFieldValue(“town”,dataObj.town,””);
  37.                             document.getElementById(‘oframe’).SetFieldValue(“f_name1″,dataObj.f_name,””);
  38.                             document.getElementById(‘oframe’).SetFieldValue(“m_name1″,dataObj.m_name,””);
  39.                             document.getElementById(‘oframe’).SetFieldValue(“sqsj”,dataObj.sqsj,””);
  40.                             document.getElementById(‘oframe’).SetFieldValue(“f_name2″,dataObj.f_name,””);
  41.                             document.getElementById(‘oframe’).SetFieldValue(“f_birthday”,dataObj.f_birthday,””);
  42.                             document.getElementById(‘oframe’).SetFieldValue(“f_hjdz”,dataObj.f_hjdz,””);
  43.                             document.getElementById(‘oframe’).SetFieldValue(“m_name2″,dataObj.m_name,””);
  44.                             document.getElementById(‘oframe’).SetFieldValue(“m_birthday”,dataObj.m_birthday,””);
  45.                             document.getElementById(‘oframe’).SetFieldValue(“m_hjdz”,dataObj.m_hjdz,””);
  46.                         }else{
  47.                         //实现读取业务路径的内容,并修改
  48.                             word.openDoc(‘<%=id%>.doc’,”<%=basePath%>/webapps/zsytp/word/”+docurl);
  49.                         }
  50.                  });
  51.                  **/
  52.              }
  53.              //为了简化,我定义关闭窗口的时候,保存到服务器上面,并且删除本地的临时文件
  54.              function unload(){
  55.                  word.saveDoc();
  56.                  word.close();
  57.              }
  58.              //给书签赋值
  59.              function setFileVal(){
  60.                 document.getElementById(‘oframe’).SetFieldValue(“dm”,”2006-03-16 22:22:22″,””);
  61.              }
  62.              //插入红头文件
  63.              //等等。。网上都有 API
  64.              //真正开发的时候,,需要用到的方法,保存文件到服务器上
  65.              //可以查看里面的 document.getElementById(‘oframe’).HttpAddPostString(“id”,id); 等向后台穿参数
  66.              function uploadFile(){
  67.                 //word.saveDocAndParm(‘1’,docurl);
  68.              }
  69.         </script>
  70.     </head>
  71.     <body onload=”load();” onunload=”unload();”>
  72.        <input  type=”button” value=”保存文件到服务器” onclick=”uploadFile()” >
  73.        <hr/>
  74.        <!–
  75.        <object classid=”clsid:00460182-9E5E-11d5-B7C8-B8269041DD57″ codebase=”dsoframer.ocx” id=”oframe” width=”100%” height=”100%”>
  76.          <param name=”BorderStyle” value=”1″>
  77.          <param name=”TitlebarColor” value=”52479″>
  78.          <param name=”TitlebarTextColor” value=”0″>
  79.        </object>
  80.       –>
  81.       <object classid=”clsid:00460182-9E5E-11d5-B7C8-B8269041DD57″ codebase=”dsoframer.CAB#Version=2.0.0.0″ id=”oframe” width=”100%” height=”100%”>
  82.              <param name=”BorderStyle” value=”1″>
  83.              <param name=”TitlebarColor” value=”52479″>
  84.              <param name=”TitlebarTextColor” value=”0″>
  85.        </object>
  86.     </body>
  87. </html>

upload_handle.jsp 上传的业务代码。。可以自己修改吧。。嘎嘎

 

Java代码  
  1. <%@ page language=”java” import=”java.util.*” pageEncoding=”UTF-8″ contentType=”text/html; charset=UTF-8″%>
  2. <%@page import=”org.apache.commons.fileupload.servlet.ServletFileUpload”%>
  3. <%@page import=”org.apache.commons.fileupload.disk.DiskFileItemFactory”%>
  4. <%@page import=”java.io.File”%>
  5. <%@page import=”org.apache.commons.fileupload.FileItem”%>
  6. <%@page import=”java.text.SimpleDateFormat”%>
  7. <%@page import=”java.io.BufferedInputStream”%>
  8. <%@page import=”java.io.BufferedOutputStream”%>
  9. <%@page import=”java.io.FileOutputStream”%>
  10. <%@page import=”org.apache.commons.fileupload.util.Streams”%>
  11. <%
  12.     try{
  13.         // 解析 request,判断是否有上传文件
  14.         boolean isMultipart = ServletFileUpload.isMultipartContent(request);
  15.         System.out.println(“———“+isMultipart);
  16.         if (isMultipart) {
  17.             Date date = new Date();//获取当前时间
  18.             SimpleDateFormat sdfFolder = new SimpleDateFormat(“yyMM”);
  19.             String fileRealPath = “”;//文件存放真实地址
  20.             String fileRealResistPath = “”;//文件存放真实相对路径
  21.             String id=”1″;//id
  22.             String docUrl=””; //路径
  23.             String firstFileName=””;
  24.             String yyMM = sdfFolder.format(date);
  25.             //上传文件夹绝对路径
  26.             String physicsPath = request.getRealPath(“”)
  27.                     + “\\upload\\” + yyMM + “\\”;
  28.             File file = new File(physicsPath);
  29.             if (!file.isDirectory()) {
  30.                 file.mkdir();
  31.             }
  32.             // 创建磁盘工厂,利用构造器实现内存数据储存量和临时储存路径
  33.             DiskFileItemFactory factory = new DiskFileItemFactory();
  34.             // 设置最多只允许在内存中存储的数据,单位:字节
  35.             // factory.setSizeThreshold(4096);
  36.             // 设置文件临时存储路径
  37.             // factory.setRepository(new File(“D:\\Temp”));
  38.             // 产生一新的文件上传处理程式
  39.             ServletFileUpload upload = new ServletFileUpload(factory);
  40.             // 设置路径、文件名的字符集
  41.             upload.setHeaderEncoding(“UTF-8”);
  42.             // 设置允许用户上传文件大小,单位:字节
  43.             upload.setSizeMax(-1);
  44.             //upload.setSizeMax(1024 * 1024);
  45.             // 解析请求,开始读取数据
  46.             // Iterator<FileItem> iter = (Iterator<FileItem>) upload.getItemIterator(request);
  47.             // 得到所有的表单域,它们目前都被当作FileItem
  48.             BufferedInputStream in = null;
  49.             List fileItems = upload.parseRequest(request);
  50.             // 依次处理请求
  51.             Iterator iter = fileItems.iterator();
  52.             while (iter.hasNext()) {
  53.                 FileItem item = (FileItem) iter.next();
  54.                 if (item.isFormField()) {
  55.                     // 如果item是正常的表单域
  56.                     String name = item.getFieldName();
  57.                     String value = item.getString(“UTF-8”);
  58.                     if(name.equals(“id”))
  59.                         id=value;//附件标题赋值
  60.                     else if(name.equals(“docUrl”))
  61.                         docUrl=value;//附件ID赋值
  62.                 } else {
  63.                     // 如果item是文件上传表单域
  64.                     // 获得文件名及路径
  65.                     String fileName = item.getName();
  66.                     if (fileName != null) {
  67.                         firstFileName=item.getName().substring(item.getName().lastIndexOf(“\\”)+1);
  68.                         in = new BufferedInputStream(item.getInputStream());// 获得文件输入流
  69.                     }
  70.                 }
  71.             }
  72.             String formatName = firstFileName.substring(firstFileName.lastIndexOf(“.”));//获取文件后缀名
  73.             if(docUrl != null && !””.equals(docUrl.trim())){
  74.                 fileRealPath = request.getRealPath(“”) + “\\word\\” + docUrl;//文件存放真实地址
  75.             }else{
  76.                 fileRealPath = physicsPath + id+ formatName;//文件存放真实地址
  77.                 docUrl = yyMM + “/” + id + formatName;
  78.             }
  79.             BufferedOutputStream outStream = new BufferedOutputStream(new FileOutputStream(new File(fileRealPath)));// 获得文件输出流
  80.             Streams.copy(in, outStream, true);// 开始把文件写到你指定的上传文件夹
  81.             //上传成功,则插入数据库
  82.             if (new File(fileRealPath).exists()) {
  83.                 //虚拟路径赋值
  84.                 fileRealResistPath=sdfFolder.format(date)+”/”+fileRealPath.substring(fileRealPath.lastIndexOf(“\\”)+1);
  85.                 //DB db=new DB();
  86.                 //System.out.println(“!!!”+(String) session.getAttribute(“fpsssn”)+”&&”+v07.can.myname(session));
  87.                 //int count = db.executeUpdate(“update   zy_zsytp  set  docurl ='”+docUrl+”‘  where  id ='”+id+”‘”);
  88.                 //if(count>0){
  89.                 //  response.setContentType(“text/html;charset=UTF-8”);
  90.                 //  out.print(“<script>alert(‘上传成功!’);</script>”);
  91.                 //}
  92.                 //db.destroy();
  93.                 //保存到数据库
  94.             }
  95.         }
  96.     }catch(Exception e){
  97.         e.printStackTrace();
  98.         //response.setContentType(“text/html;charset=UTF-8”);
  99.         //out.print(“<script>window.alert(‘上传失败!文件大小超过1MB!’);</script>”);
  100.     }
  101. %>

 

效果图:

stay connected

228,480

Fans

21,563

Followers

20,563

Followers

8,125

Subscribers

2,253

Subscribers

10,563

Followers

最新文章

entertainment
Buckingham Palace soil used in Tate exhibit
Oct 13, 2016
Sports
Legendary coach Steve Spurrier was truly
Oct 13, 2016
Technology
Acer reveals all-in-one Windows 10 PC
Oct 13, 2016
entertainment
Revival allows Selena Gomez to shed
Oct 13, 2016
entertainment
Buckingham Palace soil used in Tate exhibit
Oct 13, 2016
Sports
Legendary coach Steve Spurrier was truly
Oct 13, 2016
Technology
Acer reveals all-in-one Windows 10 PC
Oct 13, 2016
entertainment
Revival allows Selena Gomez to shed
Oct 13, 2016