通俗易懂,C#如何安全、高效地玩转任何种类的内存之Span。 给萌新的Flexbox简易入门教程 浅谈Quartz定时任务调度 浅谈JavaScript之Event(上篇) 探索JavaScr...

通俗易懂,C#如何安全、高效地玩转任何种类的内存之Span。

 

前言

作为.net程序员,使用过指针,写过不安全代码吗?

为什么要使用指针,什么时候需要使用它?

如果能很好地回答这两个问题,那么就能很好地理解今天了主题了。C#构建了一个托管世界,在这个世界里,只要不写不安全代码,不操作指针,那么就能获得.Net至关重要的安全保障,即什么都不用担心;那如果我们需要操作的数据不在托管内存中,而是来自于非托管内存,比如位于本机内存或者堆栈上,该如何编写代码支持来自任意区域的内存呢?这个时候就需要写不安全代码,使用指针了;而如何安全、高效地操作任何类型的内存,一直都是C#的痛点,今天我们就来谈谈这个话题,讲清楚 What、How 和 Why ,让你知其然,更知其所以然,以后有人问你这个问题,就让他看这篇文章吧,呵呵。

what - 痛点是什么?

回答这个问题前,先总结一下如何用C#操作任何类型的内存:

  1. 托管内存(managed memory )

    var mangedMemory = new Student();

    很熟悉吧,只需使用new操作符就分配了一块托管内存,而且还不用手工释放它,因为它是由垃圾收集器(GC)管理的,GC会智能地决定何时释放它,这就是所谓的托管内存。默认情况下,GC通过复制内存的方式分代管理小对象(size < 85000 bytes),而专门为大对象(size >= 85000 bytes)开辟大对象堆(LOH),管理大对象时,并不会复制它,而是将其放入一个列表,提供较慢的分配和释放,而且很容易产生内存碎片。

  2. 栈内存(stack memory )

    unsafe{
        var stackMemory = stackalloc byte[100]; }

    很简单,使用stackalloc关键字非常快速地就分配好了一块内存,也不用手工释放,它会随着当前作用域而释放,比如方法执行结束时,就自动释放了。栈内存的容量非常小( ARM、x86 和 x64 计算机,默认堆栈大小为 1 MB),当你使用栈内存的容量大于1M时,就会报StackOverflowException 异常 ,这通常是致命的,不能被处理,而且会立即干掉整个应用程序,所以栈内存一般用于需要小内存,但是又不得不快速执行的大量短操作,比如微软使用栈内存来快速地记录ETW事件日志。

  3. 本机内存(native memory )

    IntPtr nativeMemory0 = default(IntPtr), nativeMemory1 = default(IntPtr); try { unsafe { nativeMemory0 = Marshal.AllocHGlobal(256); nativeMemory1 = Marshal.AllocCoTaskMem(256); } } finally { Marshal.FreeHGlobal(nativeMemory0); Marshal.FreeCoTaskMem(nativeMemory1); }

    通过调用方法Marshal.AllocHGlobalMarshal.AllocCoTaskMem来分配非托管内存,非托管就是垃圾回收器(GC)不可见的意思,并且还需要手工调用方法Marshal.FreeHGlobal or Marshal.FreeCoTaskMem 释放它,千万不能忘记,不然就产生内存碎片了。

抛砖引玉 - 痛点

首先我们设计一个解析完整或部分字符串为整数的API,如下

public interface IntParser
{
    // allows us to parse the whole string.
    int Parse(string managedMemory); // allows us to parse part of the string. int Parse(string managedMemory, int startIndex, int length); // allows us to parse characters stored on the unmanaged heap / stack. unsafe int Parse(char* pointerToUnmanagedMemory, int length); // allows us to parse part of the characters stored on the unmanaged heap / stack. unsafe int Parse(char* pointerToUnmanagedMemory, int startIndex, int length); }

从上面可以看到,为了支持解析来自任何内存区域的字符串,一共写了4个重载方法。

接下来在来设计一个支持复制任何内存块的API,如下

public interface MemoryblockCopier
{
    void Copy<T>(T[] source, T[] destination);
    void Copy<T>(T[] source, int sourceStartIndex, T[] destination, int destinationStartIndex, int elementsCount); unsafe void Copy<T>(void* source, void* destination, int elementsCount); unsafe void Copy<T>(void* source, int sourceStartIndex, void* destination, int destinationStartIndex, int elementsCount); unsafe void Copy<T>(void* source, int sourceLength, T[] destination); unsafe void Copy<T>(void* source, int sourceStartIndex, T[] destination, int destinationStartIndex, int elementsCount); }

脑袋蒙圈没,以前C#操纵各种内存就是这么复杂、麻烦。通过上面的总结如何用C#操作任何类型的内存,相信大多数同学都能够很好地理解这两个类的设计,但我心里是没底的,因为使用了不安全代码和指针,这些操作是危险的、不可控的,根本无法获得.net至关重要的安全保障,并且可能还会有难以预估的问题,比如堆栈溢出、内存碎片、栈撕裂等等,微软的工程师们早就意识到了这个痛点,所以span诞生了,它就是这个痛点的解决方案

how - span如何解决这个痛点?

先来看看,如何使用span操作各种类型的内存(伪代码):

  1. 托管内存(managed memory )

    var managedMemory = new byte[100];
    Span<byte> span = managedMemory;
  2. 栈内存(stack memory )

    var stackedMemory = stackalloc byte[100];
    var span = new Span<byte>(stackedMemory, 100);
  3. 本机内存(native memory )

    var nativeMemory = Marshal.AllocHGlobal(100);
    var nativeSpan = new Span<byte>(nativeMemory.ToPointer(), 100);

span就像黑洞一样,能够吸收来自于内存任意区域的数据,实际上,现在,在.Net的世界里,Span就是所有类型内存的抽象化身,表示一段连续的内存,它的API设计和性能就像数组一样,所以我们完全可以像使用数组一样地操作各种内存,真的是太方便了。

现在重构上面的两个设计,如下:

public interface IntParser
{
    int Parse(Span<char> managedMemory); int Parse(Span<char>, int startIndex, int length); } public interface MemoryblockCopier { void Copy<T>(Span<T> source, Span<T> destination); void Copy<T>(Span<T> source, int sourceStartIndex, Span<T> destination, int destinationStartIndex, int elementsCount); }

上面的方法根本不关心它操作的是哪种类型的内存,我们可以自由地从托管内存切换到本机代码,再切换到堆栈上,真正的享受玩转内存的乐趣。

why - 为什么span能解决这个痛点?

浅析span的工作机制

先来窥视一下源码:

我已经圈出的三个字段:偏移量、索引、长度(使用过ArraySegment<byte> 的同学可能已经大致理解到设计的精髓了),这就是它的主要设计,当我们访问span表示的整体或部分内存时,内部的索引器会按照下面的算法运算指针(伪代码):

ref T this[int index]
{
    get => ref ((ref reference + byteOffset) + index * sizeOf(T)); }

整个变化的过程,如图所示:

上面的动画非常清楚了吧,旧span整合它的引用和偏移成新的span的引用,整个过程并没有复制内存,而是直接返回引用,因此性能非常高,因为新span获得并更新了引用,所以垃圾回收器(GC)知道如何处理新的span,从而获得了.Net至关重要的安全保障,而这些都是span内部默默完成的,开发人员根本不用担心,非托管世界依然美好。
正是由于span的高性能,目前很多基础设施都开始支持span,甚至使用span进行重构,比如:System.String.Substring方法,我们都知道此方法是非常消耗性能的,首先会创建一个新的字符串,然后在复制原始字符串的字符集给它,而使用span可以实现Non-Allocating、Zero-coping,下面是我做的一个基准测试:

使用String.SubString和Span.Slice分别截取长度为10和1000的字符串的前一半,从指标Mean可以看出方法SubString的耗时随着字符串长度呈线性增长,而Slice几乎保持不变;从指标Allocated Memory/Op可以看出,方法Slice并没有被分配新的内存,实践出真知,可以预见Span未来将会成为.Net下编写高性能应用程序的重要积木,应用前景也会非常地广,微服务、物联网都是它发光发热的好地方。

基准测试示例

总结

看完本篇博客,应该对Span的What、Why、How了如指掌了,那么我的目的就达到了,不懂的同学可以多读几遍,下一篇,我将会畅谈Span的应用场景、优缺点,让大家能够安全高效地使用好它,大家也可以在评论留言自己的应用场景,我会在写下一篇博客时多多参考。

最后

如果有什么疑问和见解,欢迎评论区交流。
如果你觉得本篇文章对您有帮助的话,感谢您的【推荐】。
如果你对高性能编程感兴趣的话可以关注我,我会定期的在博客分享我的学习心得。
欢迎转载,请在明显位置给出出处及链接

延伸阅读

https://github.com/dotnet/corefxlab/blob/master/docs/specs/span.md

https://msdn.microsoft.com/en-us/magazine/mt814808

https://github.com/dotnet/BenchmarkDotNet/pull/492

https://github.com/dotnet/coreclr/issues/5851

https://adamsitnik.com/Span

做一个有底蕴的软件工程师
 
 
 

给萌新的Flexbox简易入门教程

 

转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具、解决方案和服务,赋能开发者。

【年末促销】葡萄城 2018 岁末福利火热放送中 

原文出处:https://www.sitepoint.com/flexbox-css-flexible-box-layout/

 

近几年,CSS领域出现了一些复杂的专用布局工具,用以代替原有的诸如使用表格、浮动和绝对定位之类的各种变通方案。Flexbox,或者说是弹性盒子布局模块(Flexible Box Layout Module)是这些新布局工具中的第一个,接着是CSS网格布局模块(CSS Grid Layout Module)。我们会在本文给出一个易于理解的flexbox入门介绍。

随着CSS网格布局的引入,你可能会问flexbox布局是否真的还有必要。虽然它们所能做的事情有一些重叠,但其各自在CSS布局中有着非常特别的目的。一般来说,flexbox在一维场景(比如,一串类似的元素)下有最佳应用,而网格是二维场景下理想的布局方案(例如整个页面的元素)。

即便如此,flexbox仍可以用于整个页面的布局,这样它能为那些还不支持网格布局的浏览器提供合适的兼容处理。(必须承认,网格布局正在大多数现代浏览器中快速得到支持,不过对flexbox的支持仍然更为广泛,所以如果你想让你的布局在稍微老旧的浏览器中也生效,使用flexbox作为网格布局的降级方案是很容易的)。

使用Flexbox的好处

flexbox的一些好处是:

  • 页面元素能被任意方向地放置(靠左、靠右、从上往下甚至从下往上)
  • 布局内容的可视顺序能够被反转或重排
  • 元素大小能“弹性”适应可用空间,并根据容器或者兄弟元素进行相应地对齐
  • 能轻松实现等列宽布局(与每一列里面的内容无关)

为了阐述其多样的属性和可能性,让我们假设下面有这样的布局用例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
< div  class="example">
   < header >
     header content here
   </ header >
   < main  class="main">
     < nav >
       nav content here
     </ nav >
     < div  class="content">
       main content here
     </ div >
     < aside >
       aside content here
     </ aside >
   </ main >
   < footer >
     footer content here
   </ footer >
</ div >

 

首先,是把元素一起放进.main里,比如,<nav>和<aside>。如果没有flexbox,我们可能会把三个元素全部进行浮动,但想让它按理想的方式工作显得并不直观。而且,按传统的方式做这件事会出现一个众所周知的问题:每一列仅仅和它的内容一样高。因此,你可能需要把三个元素都设置为统一的高度,或者使用某种黑科技

让flexbox来救场吧。

让我们Flex

flexbox的要点是出现在display属性上的flex值,它需要被设置在容器元素上。如此设置会让它的子元素变成“弹性项目(flex item)”。这些弹性项目拥有一些易于使用的默认属性。比如,它们被紧挨着放置,那些没有特别指明宽度的元素自动占满了剩余的空间。

因此,如果你给.main设置了display:flex,它的子元素.content就被自动挤在<nav>和<aside>之间。不需要再多余的计算,多么方便是吧?作为附加奖赏,所有三个元素神奇地拥有了相同的高度。  

1
2
3
.main {
   display : flex;
}

请查看下面的例子,包含了所有的细节:flexbox-demo-1

项的顺序:Flebox的order属性

另外一个flexbox的能力,是能够轻松改变元素的显示顺序。让我们假设你为一个客户制作了上面的布局,而她现在想要.content出现在<nav>之前。

通常,你需要深入到HTML源码中去,在那里改变元素的顺序。而有了Flexbox,你可以完全使用CSS完成这项任务。只需把.content的order属性设置为-1,那么这一列就会出现在前面,这本例就是最左边。

1
2
3
4
5
6
7
.main {
   display : flex;
}
 
.content {
   order:  -1 ;
}

本例中,你不需要改变其他列的order。例子在flexbox-demo-2

如果你倾向于显式地为每一列指定order,你可以将.content的order设为1,把<nav>的order设为2,把<aside>的设为3。

HTML源码独立于CSS的Flexbox样式

但你的客户并不满足。她想让<footer>成为页面的第一个元素,显示在<header>之前。那好,同样的,flexbox是你的朋友(虽然像在此例中,可能你得跟你的客户好好谈谈,而不是跟随指示)。因为你不仅要重排列内部元素,还要重排外部的,display:flex规则将被设置在<div class="example">之上。注意这里是如何在页面中嵌套使用flex容器来达到你想要的效果的。

因为<header>,<main class="main">和<footer>相互堆叠着,你需要首先设置一个垂直上下文,它能够通过设置flex-direction:column来快速完成。还有,<footer>的order被设置为-1,如此一来它就出现在页面的最上头。就这么简单。

1
2
3
4
5
6
7
8
.example {
   display : flex;
   flex- direction : column;
}
 
footer {
   order:  -1 ;
}

所以,如果你想把一行元素修改为一列,或者相反,你可以使用flex-direction属性,并设置它相应地为column或row(row是默认值)。

完整的例子在flexbox-demo-3

然而,强大的能力也到来了更多的责任:谨记,一些用户可能会使用键盘来导航你的基于flexbox的网站,如果你HTML源码中元素的顺序和屏幕上显示的有所出入,那么无障碍访问的能力就成为需要认真对待的问题。如果想了解得更多,请不要错过HTML源码顺序 vs CSS显示顺序,网站无障碍访问和易用性的专家Adrian Roselli针对这个问题给出了深入讨论。

如何在Flexbox中对齐子项

Flexbox能非常直观地处理子项的水平对齐和垂直对齐。

你可以使用align-items对flex容器中的所有子项设置统一的对齐。如果你想给个别元素设置不同的对齐方式,使用align-self。元素的对齐方式跟它所在父容器的flex-direction有关。如果它的值是row(意味着元素水平排列),对齐方式是指在垂直轴上。如果flex-direction被设置为column(意味着元素垂直排列),对齐方式就是指在水平轴上。

例如,你让一些元素在容器中分别有不同的对齐方式,你需要:

  • 设置每个元素的align-self属性为合适的值。可能的值有:center,stretch(元素撑满它的容器),flex-start,flex-end和baseline(元素被放置在父容器的baseline上)
  • 把容器元素设置为display:flex
  • 最后,注意父容器的flex-direction属性,因为它关系到子元素的对齐方式。  

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.example {
   display : flex;
   flex- direction : column;
}
 
. red  {
   align-self:  center ;
}
 
. blue  {
   align-self: flex-start;
}
 
.pink {
   align-self: flex-end;
}

试试在下面的例子中,把父容器的flex-direction在row和column之间切换,看看它们引起的实时变化。

例子flexbox-demo-4.html

如果想要容器中所有的元素有统一的对齐方式,你可以在容器上使用align-items。可以的值有center,flex-start,flex-end,stretch(默认值:子项被拉伸以适应它们的容器)和baseline(子项被放置在父容器的baseline上)。

1
2
3
4
.example {
   display : flex;
   align-items:  center ;
}

像往常一样,试着把父容器的flex-direction在row和column之间切换,看看它们如何影响着你设置align-items值时所发生的作用。

例子flexbox-demo-5.html

在Flexbox里两端对齐

另一个控制对齐的属性是justify-content,当你想让多个元素等分空间时非常有用。

可接受的值有:center,flex-start,flex-end, space-between(元素利用主轴之间的空间而排布)和space-around(元素利用主轴之前、之间和之后的空间而排布)。

例如,在之前你一直使用的简单HTML模板里,你可以在<main>里找到三个元素:<nav>,.content和<aside>。之前,它们都被挤在页面的左边。如果你想让它们之间有一些空间,但是不让第一个元素的左边有空间,也不想让最后一个元素的右边有空间,你可以把.main(即它们的父容器)里的justify-content设置为space-between。

1
2
3
4
.main {
   display : flex;
   justify- content : space-between;
}

也试一下设置为space-around,观察不同的结果。例子在flexbox-demo-6

在上面的例子中,我同样把<header>中的文字水平和垂直对齐了,分别是把justify-content(水平居中)和align-items(垂直居中)都设置为center。

1
2
3
4
5
6
header {
   height 100 vh;
   display : flex;
   justify- content center ;
   align-items:  center ;
}

Flexbox中弹性子项的大小

使用flex属性,你能够对照flex容器中其他元素来控制弹性子项的大小。

这个属性是以下独立属性的简写:

  • flex-grow:一个数字,指明元素如何相对其他flex项来拉伸
  • flex-shrink:一个数字,指明元素如何相对其他flex项来收缩
  • flex-basis:元素的长度。可接受的值有:auto,inherit或者一个数字后面紧跟着%,px,em或其他长度单位。

例如,想得到三个等宽的列,只需给每一列设置flex:1,其他什么都不用做:  

1
2
3
nav, aside, .content {
   flex:  1 ;
}

如果你需要.content占据<nav>和<aside>的两倍宽,那么就把.content设为flex:2,让其他两个为1。

例子在flexbox-demo-7.html

那仅仅是对flex属性最简单的应用。同样可以设置flex-grow,flex-shrink和flex-basis这些值,不过那超出本文的话题范围了。

进一步的资源

如果你准备好继续前进,并想学着精通flexbox的更多东西,请查看下面的资源:

总结

如你所见,如果我们想控制元素在网页中的布局,flexbox可以让我们的生活更加轻松。它非常稳固和可靠,让以前那些我们每天使用的诸如使 让容器坍缩之类的奇技淫巧,成为了过去。

像我们说的,如今,在针对整个页面进行布局时,CSS网格是更好的方案,但我们仍然值得去了解flexbox能做的那些事情。flexbox的最佳应用场景,体现在对元素的一维排列上,但如果有需要,它也能在稍老旧的浏览器中,为CSS网格布局提供方便的替代方案。

 

推荐

【年末促销】葡萄城 2018 岁末福利火热放送中 



本文是由葡萄城技术开发团队发布,转载请注明出处:葡萄城官网

了解开放易用的 Web 生成平台,请前往活字格Web应用生成平台

了解可嵌入您系统的在线 Excel,请前往SpreadJS纯前端表格控件

 

 

 

浅谈Quartz定时任务调度

 

一  开发概述

       对于具有一定规模的大多数企业来说,存在着这样一种需求:存在某个或某些任务,需要系统定期,自动地执行,然而,对大多数企业来说,该技术的实现,却是他们面临的一大难点和挑战。

 对于大部分企业来说,实现如上功能,挑战在哪里?

挑战一:如何做一个自动服务的系统?

      是从0到1开发(费时费力花钱,还不一定开发成功,即使开发成功,也未必好用),还是购买第三方服务(花钱)。

挑战二:如何实现复杂的“定期规则”?

     对于简单的定期规则,可以借助于windows自带的执行计划来执行,但若是复杂的定期规则,windows执行计划未必可行,然而,Quartz的cron却很好地解决了该问题,

(可以说,cron在表达时间规则方面,无所不能),除此之外,Quartz能很好地配合windows执行计划,实现系统的定期,自动执行任务。

      通过如上概述,我们知道Quartz能很好地解决该问题,那么,什么是Quartz呢?

      简言之,Quartz就是一种任务调度计划。

  • 它是由OpenSymphony提供的、开源的、java编写的强大任务调度框架
  • 几乎可以集成到任何规模的运用程序中,如简单的控制台程序,复杂的大规模分布式电子商务系统
  • 可用于创建简单的或复杂的计划任务
  • 包含很多企业级功能,如支持JTA和集群等

      本篇文章,主要从Quartz框架核心组件,Quartz基本运行原理,Quartz核心概念和Quartz基本功能实现(代码)等方面来介绍Quartz。

二  Quartz

当要深入研究一个技术时,研究它的体系结构和内部运行原理,不失为一种较好的方式。同理,我们在研究Quartz时,也采用类似的方法,

下图为Quartz的大致结构图。

 

(一)Quartz关键组件

Quartz比较关键的两个核心组件分别为Job和Trigger

  • job--表示任务是什么
  • trigger--表示何时触发任务

 

(二)Quartz几个关键概念

1.IJob

IJob表示一个接口,该接口只有一个方法签名

1
2
3
4
public  interface  IJob
     {
         void  Execute(JobExecutionContext context);
     }

在Quartz中,所有的job任务,必须实现该接口

1
2
3
4
5
6
7
public  class  MyJob : IJob
     {
         public  void  Execute(JobExecutionContext context)
         {
             Console.WriteLine( "Quartz基本功能测试。" );
         }
     }

2.JobDetail

 JobDetail,顾名思义,就是表示关于每个Job的相关信息,它主要包括两个核心组件,即Job Task和JobData Map

3.Trigger

Trigger,表示触发器,根据配置规则来触发执行计划调度job,它主要包括两个核心组件,即SimpleTrigger和CronTrigger

4.IJobStore

IJobStore,表述任务存储器,主要存储job和trigger相关信息。

5.ISchedulerFactory

ISchedulerFactory,表示任务计划工厂,用来管理任务计划IScheduler。

6.IScheduler

IScheduler,表述任务计划,它相当于一个容器,具体job和job相关trigger就能够被注入其中,从而实现任务计划调度。其主要常用的方法:

  • Start --启动执行计划
  • Shutdowm --关闭执行计划

接口Code:

复制代码
namespace Quartz
{
    public interface IScheduler
    {
        bool IsStarted { get; }
        string SchedulerName { get; }
        string SchedulerInstanceId { get; }
        bool InStandbyMode { get; }
        bool IsShutdown { get; }
        IJobFactory JobFactory { set; }
        string[] JobGroupNames { get; }
        string[] TriggerGroupNames { get; }
        SchedulerContext Context { get; }
        IList GlobalJobListeners { get; }
        string[] CalendarNames { get; }
        IList GlobalTriggerListeners { get; }
        ISet TriggerListenerNames { get; }
        ISet JobListenerNames { get; }
        IList SchedulerListeners { get; }

        void AddCalendar(string calName, ICalendar calendar, bool replace, bool updateTriggers);
        void AddGlobalJobListener(IJobListener jobListener);
        void AddGlobalTriggerListener(ITriggerListener triggerListener);
        void AddJob(JobDetail jobDetail, bool replace);
        void AddJobListener(IJobListener jobListener);
        void AddSchedulerListener(ISchedulerListener schedulerListener);
        void AddTriggerListener(ITriggerListener triggerListener);
        bool DeleteCalendar(string calName);
        bool DeleteJob(string jobName, string groupName);
        ICalendar GetCalendar(string calName);
        string[] GetCalendarNames();
        IList GetCurrentlyExecutingJobs();
        IJobListener GetGlobalJobListener(string name);
        ITriggerListener GetGlobalTriggerListener(string name);
        JobDetail GetJobDetail(string jobName, string jobGroup);
        IJobListener GetJobListener(string name);
        string[] GetJobNames(string groupName);
        SchedulerMetaData GetMetaData();
        ISet GetPausedTriggerGroups();
        Trigger GetTrigger(string triggerName, string triggerGroup);
        ITriggerListener GetTriggerListener(string name);
        string[] GetTriggerNames(string groupName);
        Trigger[] GetTriggersOfJob(string jobName, string groupName);
        TriggerState GetTriggerState(string triggerName, string triggerGroup);
        bool Interrupt(string jobName, string groupName);
        bool IsJobGroupPaused(string groupName);
        bool IsTriggerGroupPaused(string groupName);
        void PauseAll();
        void PauseJob(string jobName, string groupName);
        void PauseJobGroup(string groupName);
        void PauseTrigger(string triggerName, string groupName);
        void PauseTriggerGroup(string groupName);
        bool RemoveGlobalJobListener(IJobListener jobListener);
        bool RemoveGlobalJobListener(string name);
        bool RemoveGlobalTriggerListener(ITriggerListener triggerListener);
        bool RemoveGlobalTriggerListener(string name);
        bool RemoveJobListener(string name);
        bool RemoveSchedulerListener(ISchedulerListener schedulerListener);
        bool RemoveTriggerListener(string name);
        DateTime? RescheduleJob(string triggerName, string groupName, Trigger newTrigger);
        void ResumeAll();
        void ResumeJob(string jobName, string groupName);
        void ResumeJobGroup(string groupName);
        void ResumeTrigger(string triggerName, string groupName);
        void ResumeTriggerGroup(string groupName);
        DateTime ScheduleJob(Trigger trigger);
        DateTime ScheduleJob(JobDetail jobDetail, Trigger trigger);
        void Shutdown(bool waitForJobsToComplete);
        void Shutdown();
        void Standby();
        void Start();
        void StartDelayed(TimeSpan delay);
        void TriggerJob(string jobName, string groupName);
        void TriggerJob(string jobName, string groupName, JobDataMap data);
        void TriggerJobWithVolatileTrigger(string jobName, string groupName);
        void TriggerJobWithVolatileTrigger(string jobName, string groupName, JobDataMap data);
        bool UnscheduleJob(string triggerName, string groupName);
    }
}
复制代码

 (三)核心UML图

1.命名空间

不同版本的Quartz命名空间有所区别,但差别不大,如下为版本1.0.3命名空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using  Quartz;
using  Quartz.Core;
using  Quartz.Impl;
using  Quartz.Impl.AdoJobStore;
using  Quartz.Impl.AdoJobStore.Common;
using  Quartz.Impl.Calendar;
using  Quartz.Impl.Matchers;
using  Quartz.Impl.Triggers;
using  Quartz.Listener;
using  Quartz.Logging;
using  Quartz.Logging.LogProviders;
using  Quartz.Simpl;
using  Quartz.Spi;
using  Quartz.Util;
using  Quartz.Xml;
using  Quartz.Xml.JobSchedulingData20;
using  System;

2.关键组件继承关系

    在Quartz中,许多组件是可以通过配置来促使作业执行的,如线程程序(Tread Procedure)决定如何执行计划任务线程(Quartz Scheduler Thread)

 

三  代码

本示例,我们将使用.net 控制台程序,基于VS2017来使用Quartz建立一个任务:

任务要求:要求在控制台每隔2秒输出:Quartz基本功能测试。

1.首先使用Nuget下载Quartz

本示例使用的Quartz版本为1.0.3

2.按照如下步骤操作

代码:

第一阶段:创建实现IJob接口的MyJob类

1
2
3
4
5
6
7
public  class  MyJob : IJob
{
     public  void  Execute(JobExecutionContext context)
     {
         Console.WriteLine( "Quartz基本功能测试。" );
     }
}

第二阶段:按规则调用Quartz组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static  void  Main( string [] args)
         {
             //每个2秒执行一次
             string  cronParam =  "*/2 * * * * ?" ;
             //创建计划任务抽象工厂
             ISchedulerFactory sf =  new  StdSchedulerFactory();
             //创建计划任务
             IScheduler sched = sf.GetScheduler();
             //创建job
             JobDetail job =  new  JobDetail( "myJob" , "group" typeof (MyJob));
             //创建触发器
             Trigger trigger =  new  CronTrigger( "myTrigger" , "group" ,cronParam);
             //将job和trigger注入到计划任务中
             sched.ScheduleJob(job, trigger);
             //启动计划任务
             sched.Start();
             //关闭计划任务
             //sched.Shutdown();
 
             Console.Read();
         }

3.测试结果

 

四  参考文献

【01】http://www.quartz-scheduler.org/

【02】https://www.ibm.com/developerworks/library/j-quartz/index.html

【03】https://www.w3cschool.cn/quartz_doc/

 

 

 

浅谈JavaScript之Event(上篇)

 

一  简述JavaScript及其在浏览器中的地位

(一)  浏览器主要构成

虽然不同浏览器之间存在差异(如Google Chrome,Firefox,Safari和IE等),但单从浏览器构成来说,大同小异,大致可归结为如下几类:

1.User Interface(用户界面):所谓用户界面,就是通过浏览器渲染出来,让用户可见的界面,如地址栏,书签菜单栏等;

2.Browser Engine(浏览器引擎):主要操作呈现的引擎界面;

3.Rendering Engine(渲染引擎):负责渲染响应请求内容,如负责解析HTML和CSS;

4.Networking(网络):负责网络呼叫处理,如http请求;

5.JS Interpreter(JavaScript 解释器):负责解析和执行javascript代码;

6.UI Back(UI后端):用于绘制组合框和窗口等基本组建;

7.Data Persistence(数据持久):通常用来持久化存储少量数据,如cookie等;

 

(二)JavaScript在浏览器中的地位

 如上图,javascript处于浏览器中的核心位置,负责解释和执行js脚本,内置于浏览器中,通过浏览器提供的API来访问。

(三)JavaScript构成

关于javascript的构成,大致可归结为三个部分:ECMAScript,DOM和BOM。

1.ECMAScript是对js的约束和规范,如基本语法结构;

2.DOM就是文档对象模型,是交互的资源,如html文档;

3.BOM主要是对浏览器本身描述,如浏览器名称,版本号等;

 

(四)JavaScript基本执行原理

 这里不深入谈及javascript的深层次执行原理,只是大致描述一下,关于更深层次的,在后续文章推出,与大家分享。

 JS的执行原理,用一句话来归结之:单线程异步。下图很好地表述该过程。

所有的执行函数统一放在队列中进行排队。

 

二  事件流

所谓事件流,也可理解为事件的轨迹。一般地,将事件流分为三个阶段:捕获阶段,目标阶段和冒泡阶段。

下图为三个阶段的大致流程图。

 (一)捕获阶段

捕获阶段处于事件流的第一阶段,该阶段的主要作用是捕获截取事件。在DOM中,该阶段始于Document,结束于body(当然,在现在的很

多高版本浏览器中,该过程结束于目标元素,只不过不执行目标元素而已,这也体现了目标元素具有双重范围)。

 

 (二)目标阶段

目标阶段处于事件流的第二阶段,该阶段的主要作用是执行绑定事件。一般地,该阶段具有双重范围,即捕获阶段的结束,冒泡阶段的开始;

(三)冒泡阶段

冒泡阶段处于事件流的第三阶段,该阶段的主要作用是将目标元素绑定事件执行的结果返回给浏览器,处理不同浏览器之间的差异,主要在该阶段完成。

(四)三阶段在Dom中的完整流程

 

三   事件处理程序

js事件处理程序按照种类来划分,大致可分为五大类:HTML事件处理程序,DOM0级事件处理程序,DOM2级事件处理程序,IE事件处理程序和跨浏览器事件处理程序。

尤其是DOM0,DOM2和IE事件处理程序,利用它们之间的差异化有效地解决浏览器差异问题,从而实现跨浏览器的兼容性问题。

 

(一)html事件处理程序

所谓html事件处理程序,就是在dom结构中嵌套js代码。在html中,元素支持的所有事件,都可以使用与相应事件处理程序同名的html特性来指定,这个特性的值应该是能执行的js代码。

如点击事件。

<body>
    <!--html事件处理程序-->
    <input type="button" value="请点击" οnclick="alert('测试html事件处理程序!!')"/>
</body>

当然,一般不采用如上方法,常规的做法是,将js代码定义在目标元素外部。因此,与如上相同功能的定义为:

目标元素

<body>
    <!--html事件处理程序-->
    <input type="button" value="请点击" οnclick="HtmlEventHandlerProc()"/>
</body>

外部js

<script>
    function HtmlEventHandlerProc() {
        alert('测试html事件处理程序!!');
    }
</script>

Tip:

1.事件处理程序中的代码,能够访问全局作用域中的任何变量;

2.每个function()存在一个局部变量,即事件对象event,通过event变量,可以直接访问事件对象。

<body>
   <input type="button" value="请点击" οnclick="alert(event.type)"/>
</body>

执行结果

3.在函数内部,this值等于事件的目标元素。

<body>
    <input type="button" value="请点击" οnclick="alert(this.value)"/>
</body>

执行结果

 this具有扩展作用域的功能,其功能相当于

复制代码
function myfunction() {
        with (document) {
            with (this) {
                //add your logic
           }
      }
 }
复制代码

如果当前元素是一个表单元素,则作用域还会包含访问表单元素(父元素)的入口。

复制代码
  function myfunction() {
        with (document) {
            with (this.form) {
                with (this) {
                    //add your logic
                }
            }
        }
    }
复制代码

这样做,有什么本质意义呢?当然是想让事件处理程序更快捷访问表单其他字段(无需引用表单元素就能访问)

 <form>
         <input type="text" name="userName" value="Alan_beijing" />
         <input type="button" value="测试表单元素" οnclick="alert(userName.value)" />
     </form>

执行结果

 4.html事件处理程序存在哪些缺点?

   缺点一:时差问题

   缺点二:扩展的作用域链在不同浏览器中会导致不同结果

   缺点三:html代码与js代码高度耦合

(二)DOM0级事件处理程序

DOM0级事件很好地解决了html和js代码强耦合的问题。

1.为元素绑定事件

1
2
3
4
var  btn = document.getElementById( 'myBtn' );
btn.onclick =  function  () {
     alert( 'Clicked' );
}

2.为元素解除事件

btn.onclick = null;

(三)DOM2级事件处理程序

DOM2级事件定义了两个方法来为目标元素绑定事件处理程序(addEventListener())和解除事件处理程序(removeEventListener()),所有节点中都包含这两个方法,并且他们都接收三个参数:

要处理的事件名,事件处理程序和一个布尔值(true表示是在捕获阶段进行,false表示在冒泡阶段进行)

1.为事件添加click事件处理程序(冒泡阶段进行)

var btn = document.getElementById("myBtn");
btn.addEventListener("click", function () {
     alert(this.id);
}, false);

执行结果:

2.添加多个事件处理程序

复制代码
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function () {
   alert(this.id);
}, false);

btn.addEventListener("click", function myfunction() { alert("添加的第二个事件处理程序"); }, false);
复制代码

执行结果:

 3 移除事件处理程序

通过addEventListener()添加的事件处理陈旭只能使用removeEventListener()来移除,移除时,传入的参数与添加的程序时使用的参数相同

(这意味着匿名函数不能通过removeEventListener()来删除)

匿名函数不能删除

复制代码
var btn = document.getElementById("myBtn");
    btn.addEventListener("click", function () {
        alert(this.id);
    }, false);

    //不能删除,因为是匿名函数
    btn.removeEventListener("click", function () {
        alert(this.id);
    }, false);
复制代码

非匿名函数能删除

复制代码
    var btn = document.getElementById("myBtn");
    var handler = function () {
        alert(this.id);
    };

    btn.addEventListener("click", handler, false);
    //删除事件处理程序
    btn.removeEventListener("click", handler, false);
复制代码

(四)IE事件处理程序

IE提供了两个方法来绑定和卸载事件处理程序,attachEvent()和detachEvent(),这两个方法均接收两个参数,即事件处理程序名称和事件处理程

序函数,并且在冒泡阶段添加(IE8及更早版本只支持冒泡)

1.为目标按钮添加绑定事件

var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function () {
   alert("IE事件处理程序!!");
});

2.为目标按钮添加绑定事件

多个执行绑定事件的结果是倒过来的。

复制代码
 var btn = document.getElementById("myBtn");
 btn.attachEvent("onclick", function () {
       alert("IE事件处理程序1!!");
 });

 btn.attachEvent("onclick", function () {
      alert("IE事件处理程序2!!");
 });
复制代码

3.为目标元素移除事件处理程序

注意:匿名事件处理程序是不能够移除的

复制代码
    var btn = document.getElementById("myBtn");
    var handler = function () {
        alert("IE事件处理程序");
    };
    //绑定事件
    btn.attach("onclick", handler);
    //移除事件
    btn.detachEvent("onclick", handler);
复制代码

(五)跨浏览器事件处理程序

在跨浏览器中,无非就是三种选择:DOM0级选择(不常用,基本被废弃),DOM2级选择和IE选择(不常用,IE8及以下版本)。

复制代码
//定义EventUtil
    var EventUtil = {
        addHandler: function (element, type, handler) {
            if (element.addEventListener) {
                element.addEventListener(type, handler, false);
            } else if (element.attachEvent) {
                element.attachEvent("on" + type, handler);
            }
        },
        removeHandler: function (element, type, handler) {
            if (element.removeEventListener) {
                element.removeEventListener(type, handler, false);
            } else if (element.detachEvent) {
                element.detachEvent("on" + type, handler);
            } else {
                element["on" + type] = null;
            }
        }
    }

    //调用
    var btn = document.getElementById("myBtn");
    var handler = function () {
        alert("事件处理程序跨浏览器!!");
    };
    //绑定事件处理程序
    EventUtil.addHandler(btn, "click", handler);
    //移除事件处理程序
    EventUtil.removeHandler(btn, "click", handler);
复制代码

四  事件类型

内容相对比较简单,这里就暂且列举主要内容,若有需要,会在下篇文章论述。

五  事件对象及其事件委托

介于文章篇幅有限,暂且列举主要内容,代码部分将在下篇文章论述。

 

 

 

探索JavaScript数组奥秘

 

 一  概述

     JavaScript数组同后端语言一样,具有它自己的数据结构,归根结底,这种数据结构,本质就是一种集合。

     在后端语言中(如java,.net等),数组是这样定义的:数组是用来存储相同数据类型的集合。这个定义,“相同数据类型”6个字限制了数据只能存储相同的数据类型,如int[]数组只能存储数字,而不能存储字符串,如下定义方式,是错误的,因为string

不属于整型

int[] arr = { 10, 20,"string" };

   然而,在JavaScript中,数组的定义却是非常宽松的,这也决定了其能存储一切数据的特点。JavaScript数组具有如下特点

   特点1:存储相同基本数据类型的数据;

   特点2:存储不同基本数据类型的数据;

   特点3:存储对象

   这三个特点,我们可归结为一句话:JavaScript存储一切对象,而不像后端语言那样,只能存储相同数据类型。除此之外,JavaScript数组还提供了很多丰富的操作方法。如下为常用的操作方法。

 本篇文章将主要结合代码来论述JavaScript数组。

二  对数组的基本操作

(一)创建数组

第一种方式:构造函数方式

   //第一种创建数组的方式:构造函数方式
    var colors = new Array();//未知数组长度
    //var colors = new Array(4);//已知数组长度
    //var colors = new Array('green', 'yellow', 'white', 'red');//创建数组同时,给数组赋值
    //var colors = Array();//在创建时,可以省略new关键字

第二种方式:字面量方式

   //第二种创建数组方式:使用数组字面量
    var colors = ['green', 'yellow', 'white', 'red'];
    var name = [];//创建空数组

(二)访问数组

访问数组,通过数组的下标来访问

   //创建数组
    var colors = ['green', 'yellow', 'white', 'red'];
    //输出索引值
    for (var i = 0; i < colors.length; i++) {
        alert(colors[i]);//green,yellow,white,red
    }

提示:for...in...访问数组属性(索引),而不是数组属性值

    //创建数组
    var colors = ['green', 'yellow', 'white', 'red'];
    //输出数组索引
    for (var propAttr in colors) {
        alert(propAttr);//0,1,2,3
    }

(三)为数组添加元素

第一种方式:栈方式(后进先出,从数组尾部加入数据)

复制代码
   //创建数组
    var colors = ['green', 'yellow', 'white', 'red'];
    //第一种方式:栈方式
    colors.push("orange");
    for (var i = 0; i < colors.length; i++) {
        alert(colors[i]);//green,yellow,white,red,orange
    }
复制代码

第二种方式:队列方式(先进先出,从数组头部加入数据)

var colors = ['green', 'yellow', 'white', 'red'];
colors.unshift('orange');
    for (var i = 0; i < colors.length; i++) {
        alert(colors[i]);//orange,green,yellow,white,red
    }

(四)移除数组元素

第一种方式:栈方式(后进先出,从数组尾部移除数据)

复制代码
    //创建数组
    var colors = ['green', 'yellow', 'white', 'red'];
    //从尾部弹出数据
    colors.pop();
    for (var i = 0; i < colors.length; i++) {
        alert(colors[i]);//green,yellow,white
    }
复制代码

第二种方式:队列方式(先进先出,从数据头部移除数据)

复制代码
   //创建数组
    var colors = ['green', 'yellow', 'white', 'red'];
    //从头部移除数据
    colors.shift();
    for (var i = 0; i < colors.length; i++) {
        alert(colors[i]);//yellow,white,red
    }
复制代码

第三种方式:length方式(借助length属性可访问性来操作)

复制代码
    //创建数组
    var colors = ['green', 'yellow', 'white', 'red'];
    //从尾部移除数据,与pop()一样
    colors.length = 3;
    for (var i = 0; i < colors.length; i++) {
        alert(colors[i]);//green,yellow,white
    }
复制代码

三  数组方法

(一)检测方法

数组检测方式,可通过typeof,instranceof和Array.isArray()来检测。

(二)转换方法

所有对象都具有toLocaleString(),toString()和valueOf()三个方法,数组也如此。

1.toString()

toString()将数据的每个属性值转化为对应的字符串,然后再输出转换后的字符串值。

    var colors = ['red','green','yellow'];
    alert(colors.toString());//red,green,yellow

而下列代码与如上代码是一样的,因为alert()接收的是字符串,所以会在后台调用toString()方法

    var colors = ['red','green','yellow'];
    alert(colors);//red,green,yellow

2.valueOf()

valueOf()方法同toString()方法一样,也是返回数组的字符串

    var colors = ['red', 'green', 'yellow'];
    alert(colors.valueOf());//red,green,yellow

3.toLocaleString()

toLocaleString()返回数组的字符串形式。

    var colors = ['red', 'green', 'yellow'];
    alert(colors.toLocaleString());//red,green,yellow

4 三者之间关系

关系1:当不显示指出调用哪个方法时(toString(),toLocaleString()和valueOf()),默认调用每个属性的toString();

关系2:当显示地指出调用哪个方法时,就调用每个属性的该方法;

关系3:关于valueOf问题,暂留

复制代码
   var person1 = {
        toString: function () {
            return "Alan";
        },
        toLocaleString: function () {
            return "Alan_beijing";
        },
        valueOf: function () {
            return "valueOf1";
        }
    };

    var person2 = {
        toString: function () {
            return "Alan1";
        },
        toLocaleString: function () {
            return "Alan_beijing1";
        }
    }

    var people = [person1, person2];
    alert(people.toString());//Alan,Alan1
    alert(people.toLocaleString());//Alan_beijing,Alan_beijing1
    alert(people.valueOf());//Alan,Alan1
    alert(people);//Alan,Alan1
复制代码

(三)栈方法

栈是一种数据结构,其算法为:LIFO(Last input First out)后进先出,其两个核心方法为push()和pop();

1.push()

push()表示将数据压入栈中,且放在栈的最后位置,即从栈的尾部压入数据。对于数组,则在数组的尾部加入数据,操作的顺序为:先把数组length+1,再压入数据。

    var arr = [10, 20, 30];
    arr.push('新加入的数据');
    alert(arr.toString());//10,20,30,新加入的数据

2.pop()

push()表示将数据从栈中弹出,且从栈的最后位置弹出。对于数组,则在数组的尾部弹出数据,操作的顺序为:先弹出数据,再数组length-1

    var arr = [10, 20, 30];
    arr.pop();
    alert(arr.toString());//10,20

(四)队列

队列是一种数据结构,其算法为:FIFO(First input First out)后进先出,其两个核心方法为unshift()()和shift();

1.unshift()

unshift()表示从队列头部加入数据。对于数组,则从数组索引最小位置加入数据,操作顺序为:先将数length+1,再将当前数组属性值往后移动1个位置,最后将新数据添加到索引0处。

    var arr = [10, 20, 30];
    arr.unshift(40);
    alert(arr.toString());//40,10,20,30

2.shift()

shift()表示从队列头部移除数据。对于数组,则从数组索引最小位置移除数据。

    var arr = [20, 30];
    arr.shift();
    alert(arr.toString());//30

(五)排序方法

在js数组中,两个重要的重排序方法:reverse()和sort()

1.reverse()

reverse(),顾名思义,逆序方法。

    var arr = [1,2,3,4,5];
    alert(arr.reverse());//5,4,3,2,1

2.sort()

sort()是比较灵活的排序方法了,支持自定义排序规则,默认排序为升序

默认为升序

    var arr = [3, 1, 2, 5, 4];
    alert(arr.sort());//1,2,3,4,5

自定义排序规则

复制代码
    var arr = [3, 1, 2, 5, 4];
    alert(arr.sort(Compare));//1,2,3,4,5
    //自定义排序规则:正序
    function Compare(value1, value2) {
        if (value1 > value2) {
            return 1;
        } else if (value1 < value2) {
            return -1;
        } else {
            return 0;
        }
    }
复制代码

 (六)位置方法

js数组提供了两个位置方法:indexof()和lastIndexOf()

indexOf()表示首次满足条件的位置;而lastIndexOf()则表示最后满足条件的位置

    var arr = [20, 30,20,40,10];
    alert(arr.indexOf(20)); //0
    alert(arr.lastIndexOf(20));//2

(七)迭代方法

ECMAScript5提供了5个迭代方法:every(),filter(),forEach(),map()和some()

这个五个方法,均接收2个参数。

1.every()

对数组中的每项运行给定函数,如果该函数对每一项都返回ture,则返回true,否则返回false.

复制代码
    //every
    var num = [1, 2, 3, 4, 5, 4, 3, 2, 1];
    var everyResult = num.every(function (item, index,array) {
        return item>2
    });

    alert(everyResult);//fasle
复制代码

2.some

对数组中的每项运行给定函数,如果该函数对任意一项返回ture,则返回true,否则返回false

复制代码
    //some
    var num = [1, 2, 3, 4, 5, 4, 3, 2, 1];
    var someResult = num.some(function (item, index, array) {
        return item > 2;
    });

    alert(someResult);//true
复制代码

3.filter

对数组中的每项运行给定函数,返回该函数会返回true的项组成的数组

复制代码
    //filter
    var num = [1, 2, 3, 4, 5, 4, 3, 2, 1];
    var filterResult = num.filter(function (item, index, array) {
        return item > 2;
    });

    alert(filterResult);//3,4,5,4,3
复制代码

4.map

对数组中的每项运行给定函数,返回每次函数调用的结果组成的数组

复制代码
    //map
    var num = [1, 2, 3, 4, 5, 4, 3, 2, 1];
    var mapResult = num.map(function (item, index, array) {
        return item * 2;
    });

    alert(mapResult);//2,4,6,8,10,8,6,4,2
复制代码

 

5.forEach

对数组中的每项运行给定函数。注意,该方法没返回值

    //forEach
    num.forEach(function (item, index, array) {
        //执行想要执行的操作
    });

(八)求和方法

ECMAScript提供了2个缩减方法:reduce()和reduceRight()

reduce和reduceRight,相当于.net的斐波拉列数列,计算算法为:f(n)=f(n-1)+f(n-2);

1.reduce

reduce计算数组时,按照从左到右的顺序

    var values = [1, 2, 3, 4, 5];
    var sum = values.reduce(function (prev, cur, index, array) {
        return prev + cur;
    });

    alert(sum);//15

2.reduceRight

reduceTight计算数组时,从右到左顺序

复制代码
    var values = [1, 2, 3, 4, 5];

    var sum1 = values.reduceRight(function (prev, cur, index, array) {
        return prev + cur;
    });

    alert(sum1);//15
复制代码

 

(九)其他方法

ECMAScript为数组操作提供了很多方法,如concat()和slice()

1.concat()

concat()方法是将两个对象合并,从而生成新对象,合并同时,不会改变原来对象的值,只是做简单的拷贝

复制代码
    var color1 = ['red', 'green', 'yellow'];
    var color2 = ['white', 'blue'];
    //添加数组
    var concatColor = color1.concat(color2);
    alert(concatColor);//red,green,yellow,white,blue

    //添加单个值
    var concatSingelColor = color1.concat('orange');
    alert(concatSingelColor);//red,green,yellow,orange

    //不添加值,只是简单copy

    var concatColor1 = color1.concat();
    alert(concatColor1);//red,green,yellow
复制代码

2.slice

slice()方法用于从目标数据中截取新数据,不会改变被截取对象的值。

复制代码
    var color1 = ['red', 'green', 'yellow'];

    //不传递参数:表示截取整个数组
    var color2 = color1.slice();
    alert(color2);//red,green,yellow

    //传递一个参数:表示从该位置处开始截取数据,直到数组最后一个数
    var color3 = color1.slice(1);
    alert(color3);//green,yellow

    //传递2个参数:第一个参数表示从该位置开始截取,第二个参数减1表示所截数据的最后位置
    var color4 = color1.slice(1, color1.length);
    alert(color4);//green,yellow
复制代码

 

 

【详解JavaScript系列】JavaScript之函数(一)

 

       在编程语言中,无论是面向过程的C,兼备面过程和对象的c++,还是面向对象的编程语言,如java,.net,php等,函数均扮演着重要的角色。当然,在面向对象编程语言JavaScript中(严格来说,JS属于弱面向对象编程语言),函数(function)更扮演着极其重要的角色和占有极其重要的地位。在本篇文章中,不论述什么是JS,JS解决什么问题等之类问题,而是重点阐述JS中的函数(function)。

一  JavaScript函数

(一)何为函数

关于函数的定义,我们先从两个角度来思考:数学角度和编程语言角度。

1.数学角度:在数学领域,关于“函数”二字,再熟悉不过,如三角函数,反三角函数,幂函数,对数函数,指数函数,微积分函数等;

2.编程角度:在编程领域,大家最熟悉且最先接触的应该是"Main函数"了,除此外,如日期函数(Date),数学函数(Math)等,当然除了内置函数外,还包括用户自定义函数;

      综合1,2点,可以将函数定义如下:

函数是解决某类问题的集合,是某类问题的高度抽象,它具有一定的通用性和复用性。

(二)定义函数

在Javascript中,存在三种经典的函数定义方式:函数声明式,函数表达式和函数构造式

1.函数声明式

1 //定义两个数相加函数
2     function AddNum(num1, num2) {
3         return num1 + num2;
4     }

注意:函数声明式,不能用匿名函数,如下方式定义错误的

1 function (num1, num2) {
2         return num1 + num2;
3     }

2.函数表达式

1  //定义两个数相加函数
2     var AddFun=function AddNum(num1, num2) {
3         return num1 + num2;
4     }

注意:函数表达式一般用匿名函数,因为调用时,不用函数名,因此,如上调用也可写成如下:

1 var AddFun = function(num1, num2) {
2         return num1 + num2;
3     }

3.函数构造式

1 var sum = new Function('num1', 'num2', 'return num1 + num2')
2  console.log(sum(10,20));//30

 (三)函数调用

 在JavaScript中,函数的调用采用“函数名(参数)”的格式来进行调用。

需要注意的是,JavaScript函数调用与后端语言函数调用(如java,.net等)存在细微差别,即JavaScript没有函数重载,参数的参数个数,由实参决定,而不是由形参决定。

1.声明式调用

 调用方式一:一般调用

复制代码
1 //定义两个数相加函数
2 function AddNum(num1, num2) {
3     return num1 + num2;
4 }
5 
6 
7 console.log(Add(10,20));//30
复制代码

调用方式二:自调用

1 (function AddNum(num1, num2) {
2       console.log(num1 + num2);
3   })(10,20);//30

注意:自调用一般用匿名函数,因为在调用时,不需要函数名,因此,也可写成如下方式:

1  (function (num1, num2) {
2         return num1 + num2;
3     })(10, 20);//30

2.表达式调用

1 //定义两个数相加函数
2  var AddFun = function (num1, num2){
3            return num1 + num2;
4  }
5      
6  console.log(AddFun(10, 20));//30

二 函数变量

在JavaScript编程语言中,变量的定义是通过var关键字来定义的(若变量不通过var定义,则为全局变量,但不推荐这么做),与其他编程语言一样,变量也分为两大类,即局部变量和全局变量。

(1)局部变量:作用域为其所在的函数;

(2)全局变量:作用域为整个过程;

(3)变量作用域:JS中的变量作用域是通过this指针,从当前的作用域开始,从当前作用域由内向外查找,直到找到位置,这里分为几个逻辑:

a.从当前作用域由内向外查找,若找到,就停止查找,否则,继续查找,直到查到window全局作用域为止;

b.当内部作用域变量名与外部作用域变量名相同时,内部作用域的覆盖外部作用域。

我们来看一个例子:

复制代码
 1 var dateTime='2018-09-16';
 2 function GetUserInfo(){
 3     var age=120;
 4     var name="Alan_beijing";
 5     function Say(){
 6        var name="老王";
 7        var address="shanghai";
 8        console.log(address+"-"+name+"-"+age+"-"+dateTime);//shanghai-老王-2018-06-05
 9     }
10    return Say();
11 }
12 
13 
14 GetUserInfo();//shanghai-老王-120-2018-09-16
复制代码

来分析一下变量及其作用域:

如上图,有4个作用域,当函数执行如下语句时,发生如下过程:

1 console.log(address+"-"+name+"-"+age+"-"+dateTime);

a.js当前this环境作用域为4作用域;

b.this指针寻找变量:addresss,name,age,dateTime,从当前作用域向外作用域逐层寻找,直到寻找到变量为止,若寻找到最外层作用域任然没找到,则会出错,提示该变量未声明;

c.当内外层变量相同时,内层变量覆盖外层变量,如4作用域的name覆盖3作用域的name;

 

三   函数声明式定义存在的问题

 在js中,存在声明提前问题,看看如下例子。

1 var globleName="Alan_beijing";
2 function Say(){
3    console.log(localName);  // undefined,不报错,是因为变量声明提前
4    var localName="Alan";
5    console.log(localName);// Alan
6 }

看过如上代码,你可能会问,函数执行到console.log(localName); 时,应该报错,因为localName未定义。

如果在后端语言,如java,.net中,可能会报错,但是在js中,却不会,不报错的原因是:在js中存在声明提前。

如上代码相当于如下代码:

复制代码
1 var globleName="Alan_beijing";
2 function Say(){
3    var localName;
4    console.log(localName);
5    localName="Alan";
6    console.log(localName);
7 }
复制代码

 

四  函数几大关键点

1.匿名函数

匿名函数,顾名思义,就是没名字的的函数,我们来看看如下两个例子:

函数表达式

1 //定义两个数相加函数
2     var AddFun=function (num1, num2) {
3         return num1 + num2;
4     }

立即执行函数

(function AddNum(num1, num2) {
      console.log(num1 + num2);
   })(10,20);//30

从如上,不难看出,匿名函数主要用域函数表达式和立即执行函数。

2.闭包

闭包的根源在于变量的作用域问题。

我们先来考虑这样一个问题,假设在面向对象编程语言中,某个方法的变量被定义为私有变量,其他函数要获取访问该变量,.net怎么处理?

方法一:构造函数

方法二:单例模式

同样地,在js中,同样存在外部函数调用内部函数变量问题,js运用的技术就叫做闭包。

所谓闭包,就是将不可访问的变量作为函数返回值的形式返回来,从而实现函数外部访问函数内部变量目的。

复制代码
1 //闭包
2     function GetName() {
3         var name = "Alan_beijing";
4         var age = function () {
5             var age = 30;
6             return age;
7         }
8         return name + age;
9     }
复制代码

3.js多态问题(重载问题)

在面向对象编程语言,如.net中,实现多态的方式大致有如下:

a.接口

b.抽象类

c.虚方法

d.方法重载

然而,在js中,没有面向对象之说(OO),那么js是如何实现多态的呢?根据方法实际传递的参数来决定。

1  //重载
2 function SetUserInfo(userName, age, address, tel, sex) {
3     console.log(arguments.length);//4
4 }
5 
6 SetUserInfo('Alan_beijing',44,'china-shanghai','xxxx');

从如上可以看出,传递多少个参数,就接收多个参数,如果在现象对象编程语言中实现该功能,至少需要写一堆代码,这也是体现js强大之一。

4.递归

来看看一个递归阶乘函数

复制代码
1 //递归
2     function factorial(num) {
3         if (num < 1) {
4             return 1;
5         } else {
6             return num * arguments.callee(num-1);
7         }
8     }
复制代码

如果是.net,我们一般会这样写

复制代码
1 //递归
2     function factorial(num) {
3         if (num < 1) {
4             return 1;
5         } else {
6             return num * factorial(num-1);
7         }
8     }
复制代码

然而,这样写,却会存在异常情况

1  var factorial1 = factorial;
2     factorial = null;//将factorial变量设置为null
3     console.log(factorial1(4));//出错

 5.原型和原型链

面向对象编程语言的显著特征之一是面向对象,然而,在js中,没有对象,那么js是如何面向对象的功能的呢(封装,继承,多态)?当然是通过原型和原型链来实现的。

 

 

 

C# Oracle.ManagedDataAccess 批量更新表数据

    这是我第一次发表博客。以前经常到博客园查找相关技术和代码,今天在写一段小程序时出现了问题,

但在网上没能找到理想的解决方法。故注册了博客园,想与新手分享(因为本人也不是什么高手)。

    vb.net和C#操作Oracle数据库已经用了N多年了。由于是做工程自动化项目的,业主只对软件的功能和

界面是否友好来判定成果的好坏。所以一直都是采用直接OracleCommand.ExecuteNonQuery(sqlString,conn)

的方式很直白的Insert、update和delete数据库表的。由于工程项目并没有很高的实时性,所以......

    最近手头没太多事情,就在博客园逛逛。看到了ODP.NET,发现了自己有点落伍了,于是照猫画虎的练练。

在Insert时顺风顺水的,但Update时出现了“ORA-01722: 无效数字”。各种找问题,网上查资料无果。

测试表只有两个字段,varchar2和number。问题代码如下:

using Oracle.ManagedDataAccess.Client;

 

复制代码
private void UpdateTable()
        {
            int valueStart = (int)NudStartValue.Value;
            int valueCount = (int)NudValueCount.Value;
            int returnValue = 0;
            int[] columnValue = new int[valueCount];
            string[] columnStr = new string[valueCount];
            string sql = string.Empty;

            OracleParameter[] parameters = new OracleParameter[]
            {
                new OracleParameter(":sname", OracleDbType.Varchar2),
                new OracleParameter(":svalue",OracleDbType.Int32)
                
            };
            parameters[0].Direction = ParameterDirection.Input;
            parameters[1].Direction = ParameterDirection.Input;

            for ( int i = 0 ; i < valueCount ; i++ )
            {
                columnStr[i] = "No:" + ( i + valueStart ).ToString();
                columnValue[i] = i + valueStart + 100;
            }
            
            parameters[0].Value = columnStr;
            parameters[1].Value = columnValue;
            sql = "update DIST_TEST set SVALUE=:svalue where SNAME=:sname";
            returnValue = db.RunUpdateSQL(sql, parameters, valueCount); 
MessageBox.Show(returnValue.ToString());
}
复制代码

 

复制代码
        public int RunUpdateSQL(string sqlStr,OracleParameter[] parameters,int paraCount)
        {
            int returnValue = 0;            
            try
            {
                Open();
                OracleCommand cmd = new OracleCommand()
                {
                    Connection = Conn,
                    ArrayBindCount=paraCount,
                    CommandText=sqlStr,
                    CommandTimeout=240
                };                
                cmd.Parameters.AddRange(parameters);                
                returnValue=cmd.ExecuteNonQuery();                
                cmd.Dispose();
            }
            catch(Exception ex)
            {
                Console.WriteLine(ex.ToString());                
            }
            return returnValue;
        }
复制代码

解决方法:将parameters的元素按sqlStr的顺序更改一下OK了。

 

复制代码
private void UpdateTable()
        {
            int valueStart = (int)NudStartValue.Value;
            int valueCount = (int)NudValueCount.Value;
            int returnValue = 0;
            int[] columnValue = new int[valueCount];
            string[] columnStr = new string[valueCount];
            string sql = string.Empty;

            OracleParameter[] parameters = new OracleParameter[]
            {                
                new OracleParameter(":svalue",OracleDbType.Int32),
                new OracleParameter(":sname", OracleDbType.Varchar2)

            };
            parameters[0].Direction = ParameterDirection.Input;
            parameters[1].Direction = ParameterDirection.Input;

            for ( int i = 0 ; i < valueCount ; i++ )
            {
                columnStr[i] = "No:" + ( i + valueStart ).ToString();
                columnValue[i] = i + valueStart + 100;
            }
            parameters[0].Value = columnValue;
            parameters[1].Value = columnStr;
            sql = "update DIST_TEST set SVALUE=:svalue where SNAME=:sname";
            returnValue = db.RunUpdateSQL(sql, parameters, valueCount); 
MessageBox.Show(returnValue.ToString());
}
复制代码

    注意上面的代码,第一个出现的是:svalue,第二个出现的是:sname。OracleParameter[]按这个顺序添加就OK了。

其实现在问题是解决了,但还没能理解,:sname和:svalue是对应的parameter.value的,为何一定要按照update语句中

变量的顺序呢。希望高手提示一下,多谢!

    希望能帮到遇到同样问题的童鞋们。

 

 

VS2015常用快捷键总结

 

生成解决方案 F6,生成项目Shift+F6

调试执行F5,终止调试执行Shift+F5

执行调试Ctrl+F5

查找下一个F3,查找上一个Shift+F3

附加到进程Ctrl+Alt+P,逐过程F10,逐语句执行F11

切换断点F9(添加或取消断点)

运行至光标处Ctrl+F10

跳出当前方法Shift+F11

新建解决方案:Ctrl+Shift+N

打开解决方案:Ctrl+Shift+O

保存文件Ctrl+S,保存所有文件Ctrl+Shift+S

查看解决方案窗口Ctrl+W,Ctrl+S

查看属性窗口Ctrl+W,Ctrl+P

错误列表显示Ctrl+W,Ctrl+E

输出列表显示Ctrl+W,Ctrl+O

书签窗口Ctrl+W,B

切换书签Ctrl+B,T

切换到下一个书签Ctrl+B,N

切换到上一个书签Ctrl+B,P

清除书签Ctrl+B,C

活动窗口切换Ctrl+Tab

浏览器窗口Ctrl+W,W

断点窗口Ctrl+D,B

即使窗口Ctrl+D,I

工具箱Ctrl+W,Ctrl+X

全屏切换Shift+Alt+Enter,向后导航Ctrl+-,向前导航Ctrl+Shift+-

项目中添加类Shift+Alt+C

项目中添加新项Ctrl+Shift+A

项目中添加现有项Shift+Alt+A

查找Ctrl+F,在文件中查找Ctrl+Shift+F

渐进式搜索Ctrl+I,反式渐进式搜索Ctrl+Shift+I

替换Ctrl+H,在文件中替换Ctrl+Shift+H

转到行号Ctrl+G

剪切板循环Ctrl+Shift+V 注:在剪贴板中可以循环保存20项,您可以任意的调用你剪切过的内容,只要不断的按Ctrl+Shift+V键直到找到需要的那一项

游标移动一个单词 Ctrl+左右箭头

滚动代码屏幕,但不移动光标位置Ctrl+上下箭头

删除当前行Ctrl+Shift+L

隐藏或展开当前嵌套的折叠状态 Ctrl+M,M

所有隐藏或展开嵌套Ctrl+M,L

折叠到定义Ctrl+M,O

切换显示空白Ctrl+E,S

选择矩形文本Shift+Alt+方向键

全变为大写:Ctrl+Shift+U 全变为小写Ctrl+U

强制智能感知 Ctrl+J

查看方法参数信息Ctrl+Shift+空格

查看当前代码快速信息Ctrl+K,I

注释选中行Ctrl+K,C或Ctrl+E,C

取消注释行Ctrl+K,U或者Ctrl+E,U

插入代码段Ctrl+K,X

插入外侧代码段Ctrl+K,S

转到定义F12

生成方法存根,光标定位在调用的方法上,按Shift+Alt+F10

显示查看实现接口方法,光标定位在类要实现的接口上,按Shift+Alt+F10

查看所有引用,Ctrl+K,R

查看调用层次Ctrl+K,T

除上述这些常用的快捷键外,如果开发人员想查看系统快捷或扩展自己的快捷键,可以打开vs-【工具】-【选项】

在窗口中选中【环境】-【键盘】

搜索想要添加的快捷键功能。

 

出处:http://www.cnblogs.com/xuwendong/ 

转载于:https://www.cnblogs.com/cjm123/p/10033778.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值