• HTML5简单入门系列(八)


    前言

    本篇介绍HTML5中相对复杂的一些APIs,其中的数学知识比较多。虽然如此,但是其实API使用起来还是比较方便的。

    这里说明一下,只写出API相关的JS代码,因为他们都是基于一个canvas标签进行的操作。有特殊情况,我会单独列出。

    下边是公用的canvas标签:

    <canvas height="600" width="800" id="myCanvas"></canvas>

    调用方式,也基本一致,如下:

    1 window.onload = function () {
    2             shadow();
    3             //transparent();
    4         }

    HTML5 APIs 续

    透明

    设置图形的透明度要用到 globalAlpha 属性。globalAlpha 属性的值是一个介于之间的浮点数。0表示完全透明,而1表示完全不透明。

    示例代码如下(注意画图时重新调用beginPath,否则后边的fill方法会连同之前的图形区域一起重新填充颜色): 

     1 function transparent() {
     2             var canvas = document.getElementById("myCanvas");
     3             var ctx = canvas.getContext("2d");
     4             ctx.globalAlpha = 0.7;
     5             ctx.beginPath();
     6             ctx.fillStyle = "red";
     7             ctx.rect(0, 0, 100, 100);
     8             ctx.fill();
     9 
    10             ctx.globalAlpha = 0.4;
    11             ctx.beginPath();
    12             ctx.fillStyle = "green";
    13             ctx.rect(50, 50, 150, 150);
    14             ctx.fill();
    15 
    16             ctx.globalAlpha = 0.2;
    17             ctx.fillStyle = "blue";
    18             ctx.fillText("Sample String", 40, 60);
    19         }

     效果如图:

     阴影

    要为图形(文本)添加阴影需要用到 shadowColorshadowBlurshadowOffsetX shadowOffsetY属性。

    shadowOffsetX 和 shadowOffsetY 用来设定阴影在轴的延伸距离,负值表示阴影会往上或左延伸,正值则表示会往下或右延伸,默认都是0(像素)

    shadowBlur 用于设定阴影的模糊程度,其数值并不跟像素数量挂钩,也不受变换矩阵(后边会说到,变换矩阵会影响缩放、旋转和移动)影响,默认为0(w3school定义是阴影的模糊级数,默认值#000000)

    shadowColor 用于设定阴影效果的延伸,值可以是标准的CSS 颜色值,默认是全透明的黑色。

    示例代码如下: 

     1 function shadow() {
     2             var canvas = document.getElementById("myCanvas");
     3             var ctx = canvas.getContext("2d");
     4 
     5             ctx.shadowOffsetX = 5;//指示阴影位于形状(文本) left 位置右侧的 5 像素处
     6             ctx.shadowOffsetY = 5;
     7             ctx.shadowBlur = 2;
     8             ctx.shadowColor = "rgba(0, 0, 0, 0.5)";
     9 
    10             ctx.font = "40px Times New Roman";
    11             ctx.fillStyle = "Black";
    12             ctx.fillText("Sample String", 5, 60);
    13         }

     效果如下:

    状态的保存和恢复

    save restore 方法是用来保存和恢复canvas 状态的,都没有参数。Canvas 的状态就是当前画面应用的所有样式和变形的一个快照。

    Canvas 状态是以栈(stack)的方式保存的,每一次调用save 方法,当前的状态就会被放入栈中保存起来。你可以调用任意多次save 方法。每一次调用restore 方法,上一个保存的状态就从栈中弹出,所有设定都恢复。

    看下边的示例: 

     1 var i, context, interval;
     2         window.onload = function () {
     3             //draw();//可在浏览器端 单步调试效果更佳。
     4 
     5             i = 1;
     6             context = document.getElementById('myCanvas').getContext('2d');
     7             interval = setInterval(dystate, 500);
     8         }
     9         function draw() {
    10             var ctx = document.getElementById('myCanvas').getContext('2d');
    11 
    12             ctx.fillRect(0, 0, 150, 150);   // state 1
    13             ctx.save();
    14 
    15             ctx.fillStyle = '#09F'
    16             ctx.fillRect(15, 15, 120, 120);  // state 2
    17             ctx.save();
    18 
    19             ctx.fillStyle = '#FFF'
    20             ctx.globalAlpha = 0.5;
    21             ctx.fillRect(30, 30, 90, 90);   //state 3 没有保存
    22 
    23             ctx.restore();               //恢复到 state 2
    24             ctx.fillRect(45, 45, 60, 60);
    25 
    26             ctx.restore();               // 恢复到state 1
    27             ctx.fillRect(60, 60, 30, 30);
    28         }
    29 
    30         function dystate() {
    31             var r1 = Math.floor((Math.random() * 2 + 1) * 255 % 255);
    32             var r2 = Math.floor((Math.random() * 3 + 1) * 255 % 255);
    33             var r3 = Math.floor((Math.random() * 7 + 1) * 255 % 255);
    34             context.fillStyle = "rgb(" + r1 + "," + r2 + "," + r3 + ")";
    35 
    36             if (i <= 5) {
    37                 context.fillRect(i * 10, i * 10, 150 - i * 15, 150 - i * 15);
    38                 context.save();
    39             }
    40             else {
    41                 context.restore();
    42                 context.fillRect(i * 10, i * 10, 150 - i * 15, 150 - i * 15);
    43             }
    44             i = i + 1;
    45             if (i > 10) {
    46                 clearInterval(interval);
    47             }
    48         }

     效果如下:

    该示例中,LZ使用了两个示例,第一个示例单步调试效果和第二个示例效果基本一样。

    第二个示例是随机颜色值,每次执行结果不同,请注意!

    代码中标出的红色部分rgb方法就是获取颜色值,rgba() 方法与 rgb() 方法类似,就多了一个用于设置色彩透明度的参数。它的有效范围是从 0.0(完全透明)到 1.0(完全不透明)。

    示例如下:

    1 // 这些 fillStyle 的值均为 '橙色'
    2 ctx.fillStyle = "orange";
    3 ctx.fillStyle = "#FFA500";
    4 ctx.fillStyle = "rgb(255,165,0)";
    5 ctx.fillStyle = "rgba(255,165,0,1)";

    组合

    之前的例子里面,我们总是将一个图形画在另一个之上,大多数情况下,这样是不够的。比如说,它这样受制于图形的绘制顺序(透明度那个示例可说明,后绘画的会覆盖之前的绘画)。不过,我们可以利用 globalCompositeOperation 属性来改变这些做法。 

    globalCompositeOperation 属性设置或返回如何将一个源(新的)图像绘制到目标(已有)的图像上。

    源图像 = 您打算放置到画布上的绘图。 

    目标图像 = 您已经放置在画布上的绘图。 

    默认值: source-over
    JavaScript 语法: context.globalCompositeOperation="source-in";

     摘自Mozilla开发社区

    source-over (default)

    这是默认设置,新图形会覆盖在原有内容之上。

    destination-over
    会在原有内容之下绘制新图形。

    source-in

    新图形会仅仅出现与原有内容重叠的部分。其它区域都变成透明的。

    destination-in

    原有内容中与新图形重叠的部分会被保留,其它区域都变成透明的。

    source-out

    结果是只有新图形中与原有内容不重叠的部分会被绘制出来。

    destination-out

    原有内容中与新图形不重叠的部分会被保留。

    source-atop

    新图形中与原有内容重叠的部分会被绘制,并覆盖于原有内容之上。

    destination-atop

    原有内容中与新内容重叠的部分会被保留,并会在原有内容之下绘制新图形

    lighter

    两图形中重叠部分作加色处理。

    darker

    两图形中重叠的部分作减色处理。

    xor

    重叠的部分会变成透明。

    copy

    只有新图形会被保留,其它都被清除掉。

    示例代码:

    这里需要重新定义两个canvas

    1 <canvas id="myCanvas" width="578" height="430"></canvas>
    2     <!-- 下面这个canvas就是用作内存中绘图的,样式被设为不可见 -->
    3     <canvas id="tempCanvas" width="578" height="430" style="display: none;"></canvas>

    下边的代码将会绘制如上表中的图形原型(代码来源

     1 window.onload = function () {
     2             var canvas = document.getElementById("myCanvas");
     3             var context = canvas.getContext("2d");
     4             // 注意这里创建了一个临时canvas,可以理解为内存中绘图所用,用于在正式将图形画到页面之前先把完整的图形在这个临时canvas中画完,然后再一下子拷贝到真正用于显示的 myCanvas上,而在页面中的这个临时canvas是不可见的
     5             var tempCanvas = document.getElementById("tempCanvas");
     6             var tempContext = tempCanvas.getContext("2d");
     7 
     8             var squareWidth = 55;
     9             var circleRadius = 35;
    10             var startX = 10;
    11             var startY = 30;
    12             var rectCircleDistX = 50;
    13             var rectCircleDistY = 50;
    14             var exampleDistX = 150;
    15             var exampleDistY = 140;
    16             var arr = new Array();
    17             arr.push("source-atop");
    18             arr.push("source-in");
    19             arr.push("source-out");
    20             arr.push("source-over");
    21             arr.push("destination-atop");
    22             arr.push("destination-in");
    23             arr.push("destination-out");
    24             arr.push("destination-over");
    25             arr.push("lighter");
    26             arr.push("darker");
    27             arr.push("xor");
    28             arr.push("copy");
    29             // 画出十二种操作模式
    30             for (var n = 0; n < arr.length; n++) {
    31                 var thisX; var thisY;
    32                 var thisOperation = arr[n];
    33                 // 第一行     
    34                 if (n < 4) {
    35                     thisX = startX + (n * exampleDistX);//横坐标移到下个位置
    36                     thisY = startY;
    37                 }
    38                     // 第二行          
    39                 else if (n < 8) {
    40                     thisX = startX + ((n - 4) * exampleDistX);//横坐标回到第二行起始位置,并后移
    41                     thisY = startY + exampleDistY;//纵坐标移到第二行
    42                 }
    43                     // 第三行        
    44                 else {
    45                     thisX = startX + ((n - 8) * exampleDistX);
    46                     thisY = startY + (exampleDistY * 2);
    47                 }
    48 
    49                 tempContext.clearRect(0, 0, canvas.width, canvas.height);//整个canvas被清空
    50                 // 画矩形
    51                 tempContext.beginPath();
    52                 tempContext.rect(thisX, thisY, squareWidth, squareWidth);
    53                 tempContext.fillStyle = "blue";
    54                 tempContext.fill();
    55 
    56                 // 设置全局组合模式
    57                 tempContext.globalCompositeOperation = thisOperation;
    58                 // 画圆
    59                 tempContext.beginPath();
    60                 tempContext.arc(thisX + rectCircleDistX, thisY + rectCircleDistY, circleRadius, 0, 2 * Math.PI, false);
    61                 tempContext.fillStyle = "red";
    62                 tempContext.fill();
    63                 // 恢复成默认状态
    64                 tempContext.globalCompositeOperation = "source-over";
    65                 tempContext.font = "10pt Verdana";
    66                 tempContext.fillStyle = "black";
    67                 tempContext.fillText(thisOperation, thisX, thisY + squareWidth + 45);
    68                 // 将图像从 tempCanvas 拷贝到 myCanvas 
    69                 context.drawImage(tempCanvas, 0, 0);
    70             }
    71         }

    关于为什么需要一个临时的canvas,这就和globalCompositeOperation 属性相关了,LZ单步调试了,后边的绘图(部分)会将之前的内容清空,比如最后一个copy值,会清空已有的所有图形而单独显示它自己,这是copy的本质,其他几个属性值,也有类似行为,因此需要一个临时canvas,一个一个拷贝到展示的canvas上。

    效果图如下:

    裁切路径

    clip() 方法从原始画布中剪切任意形状和尺寸。

    一旦剪切了某个区域,则所有之后的绘图都会被限制在被剪切的区域内(不能访问画布上的其他区域)。

    如果和上面介绍的 globalCompositeOperation 属性作一比较,它可以实现与source-in source-atop 差不多的效果。最重要的区别是裁切路径不会在canvas 上绘制东西,而且它不受新图形的影响。

    默认情况下,canvas 有一个与它自身一样大的裁切路径(也就是绘图只能在canvas范围内)。

    我们使用两个canvas对比使用clip和不使用clip时,对绘图效果的影响。

     1 <!DOCTYPE html>
     2 <html>
     3 <body>
     4     <p>不使用 clip():</p>
     5     <canvas id="myCanvas" width="300" height="150" style="border: 1px solid red;">Your browser does not support the HTML5 canvas tag.
     6     </canvas>
     7 
     8     <script>
     9         var c = document.getElementById("myCanvas");
    10         var ctx = c.getContext("2d");
    11         
    12         ctx.rect(50, 20, 200, 120);
    13         ctx.strokeStyle = "blue";
    14         ctx.stroke();
    15         
    16         ctx.fillStyle = "green";
    17         ctx.fillRect(0, 0, 150, 100);
    18     </script>
    19 
    20     <br />
    21 
    22     <p>使用 clip():</p>
    23     <canvas id="myCanvas2" width="300" height="150" style="border: 1px solid red;">Your browser does not support the HTML5 canvas tag.
    24     </canvas>
    25 
    26     <script>
    27         var c = document.getElementById("myCanvas2");
    28         var ctx = c.getContext("2d");
    29         // Clip a rectangular area
    30         ctx.rect(50, 20, 200, 120);
    31         ctx.strokeStyle = "blue";
    32         ctx.stroke();
    33         ctx.clip();
    34         
    35         ctx.fillStyle = "green";
    36         ctx.fillRect(0, 0, 150, 100);
    37     </script>
    38 </body>
    39 </html>

    效果如下(红色是canvas整个区域,蓝色是裁剪区,绿色是填充效果):

    移动

    translate(x, y) 它用来移动 canvas 和它的原点到一个不同的位置。

    默认初始时,canvas的原点坐标是(0,0),我们可以把canvas看做一个原点坐标是(0,0)的坐标系。使用该方法后,整个canvas位移到(x,y)为原点的坐标系。之后的画图操作数值都是相对于移动之后的坐标系的,如:

    位移之后,某坐标位置是(100,100),则相对于移动之前的坐标系,它是(100+x,100+y),原坐标系的(x,y)是当前坐标系的(0,0)原点。

    看下边的示例: 

     1 function randcircle() {
     2             var c = document.getElementById("myCanvas");
     3             var ctx = c.getContext("2d");
     4             for (var x = 5; x < 10; x++) {
     5                 for (var y = 5; y < 10; y++) {
     6                     ctx.save();
     7                     //circle(ctx, x * 20, y * 20, 10);
     8                     ctx.translate(x * 20, y * 20);
     9                     circle(ctx, 0, 0, 10);
    10                     ctx.restore();
    11                 }
    12             }
    13         }
    14         function circle(ctx, x, y, R) {
    15             var r1 = Math.floor((Math.random() * 2 + 1) * 255 % 255);
    16             var r2 = Math.floor((Math.random() * 3 + 1) * 255 % 255);
    17             var r3 = Math.floor((Math.random() * 7 + 1) * 255 % 255);
    18             ctx.beginPath();
    19             ctx.fillStyle = "rgb(" + r1 + "," + r2 + "," + r3 + ")";
    20 
    21             //var s = Math.floor((Math.random() * 10 + 5) * 10 % 10);
    22 //ctx.scale(s, s); 23 24 ctx.arc(x, y, R, 0, 2 * Math.PI, false); 25 ctx.fill(); 26 }

     看代码7-9行

    7 //circle(ctx, x * 20, y * 20, 10);
    8 ctx.translate(x * 20, y * 20);
    9 circle(ctx, 0, 0, 10);

    注释的这一句可以当做下边两句来使用,但这并没能将translate的优势展示出来。

    我们可以想象,circle方法内如果是定点画圆呢?也就是说circle如果不接收位置参数,那么先移动坐标原点再调用方法则可以在随意位置画圆了。

     效果图如下(颜色是随机的):

    值得注意的是,代码中21-22行注释掉的缩放代码,对圆心的定位影响很大,LZ还需要再研究一下。先看一下效果图吧。

    1、将21-22代码取消注释,其他不变,效果如下(效果同LZ所想):

    2、将21-22代码取消注释,修改代码如下

    7 circle(ctx, x * 20, y * 20, 10);
    8 //ctx.translate(x * 20, y * 20);
    9 //circle(ctx, 0, 0, 10);

    效果如图(LZ略晕,因为圆心位置变了):

    缩放

    scale 方法接受两个参数。x,y 分别是横轴和纵轴的缩放因子,它们都必须是正值。值比1.0 小表示缩小,比1.0 大则表示放大,值为1.0 时什么效果都没有。

    如果您对绘图进行缩放,所有之后的绘图也会被缩放。定位也会被缩放,如果您 scale(2,2),那么绘图将定位于距离画布左上角两倍远的位置。

    看到标红的文字,LZ释然了,上边的例子中的疑惑也就澄清了。让我们来看个示例再对比一下效果吧。 

     1 function scale() {
     2             var c = document.getElementById("myCanvas");
     3             var ctx = c.getContext("2d");
     4             ctx.strokeRect(5, 5, 25, 15);
     5             ctx.scale(2, 2);
     6             ctx.strokeRect(5, 5, 25, 15);
     7             ctx.scale(2, 2);
     8             ctx.strokeRect(5, 5, 25, 15);
     9             ctx.scale(2, 2);
    10             ctx.strokeRect(5, 5, 25, 15);
    11         }

     我们画了是个矩形,每次横向纵向放大2倍,效果如下:

     

    虽然每次都是从(5,5)到(25,15)的矩形,但是缩放之后,不止长宽变了,起点坐标也跟着一起变化了

    而上边translate例子中,移动原点位置之后再缩放,则只影响长度不影响定位

    旋转

    rotate(angle)这个方法只接受一个参数:旋转的弧度(angle),它是顺时针方向的,以弧度为单位的值。如果是角度,则可以使用 degrees*Math.PI/180转换成弧度。

    旋转的中心点始终是 canvas 的原点,如果要改变它,需要用到 translate 方法。该方法比较简单,看下边的示例:

     1 function rotate() {
     2             var canvas = document.getElementById("myCanvas");
     3             var context = canvas.getContext("2d");
     4             context.translate(400, 300);
     5             context.arc(0, 0, 10, 0, Math.PI * 2, false);//中心一个圆
     6             context.fill();
     7             for (var i = 1; i < 8; i++) {//层数
     8                 var x = i * 20;//x坐标
     9                 var n = i * 6;//该层圆圈数量
    10                 var s = 2 * Math.PI * x;//当前x坐标圆周长,用于计算小圆的半径
    11                 var r = Math.floor(s / n / 2);//半径
    12                 var angle = 2 * Math.PI / n;//旋转角度
    13                 for (var j = 0; j < n; j++) {//每层是6的倍数递增
    14                     context.save();
    15                     context.beginPath();
    16                     context.rotate(angle * j);
    17                     context.arc(x, 0, r, 0, 2 * Math.PI, false);
    18                     context.fillStyle = 'rgb(' + (30 * i) + ',' + (255 - 30 * i) + ',255)';
    19                     context.fill();
    20                     context.restore();
    21                 }
    22             }
    23         }

    首先我们先移动原点到(400,300),然后画了一个小圆。

    我们设定圆圈展示层数、及每层数量,然后根据当前层所在坐标及每层数量(6倍递增)计算当前层小圆的半径和旋转角度(弧度)

    最后在同一个位置画圆,再将小圆旋转到正确位置(记得每次save和restore,否则旋转角度不必angle * j,而是继承上次旋转角度;还有每次绘图之前要用beginPath哦!)

    效果如下图:

    变换矩阵

    transform(a,b,c,d,e,f) 方法以用户自定义的变换矩阵对图像坐标进行变换操作

    setTransform(a,b,c,d,e,f) 方法重置当前的变形矩阵为单位矩阵,然后以相同的参数调用 transform 方法。(反正LZ是有点晕~~~)

    这个方法需要6个参数组成一个 3 x 3 的转换矩阵,坐标由 (x, y) 到 (x', y') 的转换公式如下所示:

     这个。。计算。。呵呵。。记住下边的参数说明就行了。。 

    参数说明如下:

    参数

    描述

    a

    水平缩放绘图

    b

    水平倾斜绘图

    c

    垂直倾斜绘图

    d

    垂直缩放绘图

    e

    水平移动绘图

    f

    垂直移动绘图

    我们还是看下边的例子吧。

     1 function transform() {
     2             var canvas = document.getElementById("myCanvas");
     3             var ctx = canvas.getContext("2d");
     4 
     5             var sin = Math.sin(Math.PI / 6);
     6             var cos = Math.cos(Math.PI / 6);
     7             ctx.translate(200, 200);
     8             var c = 0;
     9             for (var i = 0; i <= 12; i++) {
    10                 c = Math.floor(255 / 12 * i);
    11                 ctx.fillStyle = "rgb(" + c + "," + c + "," + c + ")";
    12                 ctx.fillRect(0, 0, 100, 10);
    13                 ctx.transform(cos, sin, -sin, cos, 0, 0);
    14             }
    15 
    16             ctx.setTransform(-1, 0, 0, 1, 200, 200);
    17             ctx.fillStyle = "rgba(255, 128, 255, 0.5)";
    18             ctx.fillRect(0, 50, 100, 100);
    19         }

    先上效果图再解释:

    看到效果,各位园友觉得这个矩形是怎么画出来的呢?此时的坐标原点在哪里呢?

    好吧,LZ得承认最开始这个旋转的横条效果,也没有搞的很明白,虽然单步调试看到了效果。。。对于这个矩形呢,LZ也没有找到正确的坐标原点。

    所以,我添加如下代码

    ctx.strokeStyle = "green";
    ctx.arc(0, 0, 50, 0, Math.PI, false);
    ctx.stroke();

    现在的效果图如下:

    现在找到了坐标原点了吧,对,它就在绿色弧线中心。

    这里LZ要强调一下,setTransform方法会重置当前的变形矩阵为单位矩阵,即原始没有旋转、没有缩放、没有移动的原始坐标系。

    所以setTransform第5/6两个参数将作为移动(translate)参数进行原点移动,就到了上图中绿色弧线的中心了。

    那么接下来,根据绘制矩形的坐标设置,我们想想它是怎样一个效果。

    ctx.fillRect(0, 50, 100, 100);

    起点(0,50)没问题,那么左下角(100,100)的横坐标怎么偏向左边去了呢?

    这就是我们在调用setTransform是第一个参数的效果,水平缩放-1,即长度不变,方向相反。第四个参数垂直缩放1,保持原来的效果。

    我们再修改代码,看看第二个、第三个参数的效果。修改如下:

    ctx.setTransform(-1, 0.4, 0, 1, 200, 200);

    效果如下:

    我们修改了第二参数,水平倾斜0.4(倾斜和旋转都是顺时针的),但是效果图是逆时针倾斜的,这和第一个参数-1有关,我们将第一个参数改成1,效果如下,符合我们的预期:

    那么到这里,应该这几个参数都弄明白了吧。

    我们介绍的只是setTransform方法,它和transform的使用是一样的。

    区别在于,setTransform会先将坐标系重置即所谓的单位矩阵,然后在参数基础上执行transform方法。

    关于那个'米'字运行效果,就不再做解释了。

    小结

    本篇介绍了透明、阴影、状态的保存和恢复、组合、裁剪路径、移动、缩放、旋转及变换矩阵,东西略多,楼主也是搜罗了好多地方才整理好。

    参考资料

    w3school

    Mozilla开发者社区

    canvas教程 (页面左下角的文章列表)

    如果有些说明错误的地方,请园友大牛指正。

    后边还有一些关于动画的东西,但是和HTML5 APIs 没什么直接关系,都是使用现有js技术结合HTML5 APIs来实现的动态效果,有兴趣的可以自己搜索一下。


     写博小感

    通过写该系列博客,楼主真心感觉到写博的不容易,单是本篇,楼主用了整整一天时间来完成。

    虽然本系列只是楼主的学习记录,但是写出来的时候,心里总是胆战心惊,这里没弄明白写错了怎么办?让园友大牛笑话怎么办?误导其他人怎么办?

    尤其是最近两篇APIs的介绍,每个参数是做什么的、最后有什么效果,楼主都不敢妄想,从各处搜索资料,一一实验(虽然有些代码并非原创),然后将自己的想法描述出来。

    尽管耗时耗力,但是终归算是有了一个结束,楼主本人也颇有收获,也终于明白为什么前人强调要写博客的重要性,就算是很细小很简单的知识点,说不定就能解决别人在这个节骨眼上碰到麻烦。

    好了,就说这些吧,欢迎拍砖(凡是打不死我的,终将让我变得更加坚强,O(∩_∩)O哈哈~)

  • 相关阅读:
    C# 文件操作 全收录 追加、拷贝、删除、移动文件、创建目录、递归删除文件夹及文件....
    FlexPaper在线文档分享(转载)
    winform中屏蔽对标题栏的操作
    【转】海量数据查询优化
    [转帖]用Reflector和FileDisassembler配合反编译.net Windows程序
    关于中缀表达式和逆波兰表达式(终结篇)
    jQuery教程基础篇之强大的选择器(层次选择器)
    模版方法(Template Method)
    VS2008新建项目时出现“此安装不支持该项目类型”
    并行计算相关文章
  • 原文地址:https://www.cnblogs.com/cotton/p/4409715.html
Copyright © 2020-2023  润新知