Follow Us

Copyright 2014 Brand Exponents All Rights Reserved

专注网站建设

我们专注于高品质网站建设,从事高端网站建设,移动互联网,网站制作、响应式设计、网站优化。提供全方位互联网产品服务。

移动互联网业务

随着随身电子产品日益普及,人们的移动性在日益增强,对位置信息的需求也日益高涨,市场对移动定位服务需求快速增加。

mockup-22

精于网站设计

DBestech致力于响应式网站开发,发挥最大限度的创造力,设计出独一无二的网站,根据企业网站展示内容的需要,定制设计独特的内容展示区创意。

Responsive Web design

响应式设计

一次实施,全网应用,国内领先的Webapp技术,是高质量实施的保障,桌面,平板,手机三屏浏览自适应。

showcase_llorix_odx3nb

在进行网站建设的时候,也可以去网上参考优秀网站的案例,研究一番,也是有益无害呢!

网站建设

html代码
首先要制作我们的页面,用到的是html5的新标签canvas;其实canvas就是我们需要用javascript脚本语言来绘图的“画布”,只是相当于一个容器呈现我们画图的结果,所以我们在页面中需要创建一个充满屏幕的canvas

<body> <canvas id=”canvas”></canvas>

<div class=”text”>仿知乎动态粒子效果背景</div> </body>
是的,body中只有这两行代码就可以了,甚至可以只有一行代码

css样式
css样式也没有什么好说的,只是要让canvas充满屏幕就可以了

html{height: 100%}
body{margin: 0;height: 100%;
background: #fff;}
canvas{display: block;width: 100%;height: 100%;}
.text{
width: 100%;
background: transparent;
display: flex;
justify-content: center;
height: 100%;
line-height: 100%;
top: 0;
position: absolute;
top: 50%;
font-size: 50px;
}
写法不唯一,只要要你的canvas是充满整个屏幕的就好,当然,你要是不需要充满屏幕也可以~

js代码
说完了html和css,那么就剩js了,主要是通过js脚本来创建每个线段和粒子的~github上的例子中使用的是es6编写的,不过在demo中也使用了gulp安装babel可以将es6代码转化为es5(所以索demo中同时有es6的代码也有es5的代码,大家按需下载~)主要的思路如下

设置单个粒子的随机x,y坐标和圆圈的半径
使用canvas的api进行绘制粒子(圆圈)和粒子之前连线,设置一个范围,在此范围内的粒子圆心到圆心通过直线连接
让粒子在屏幕范围内移动
设置鼠标的交互事件,相当于以鼠标位置的x,y坐标为圆心,固定或随机值为半径重新创建了一个粒子,并且也在一定范围内也设置和其他粒子的连线(同第二步)
其实思路就以上五点,只不过我们需要了解canvas的api才能绘出我们想要的结果
设置单个粒子的随机x,y坐标和圆圈的半径

//创建对象
//以一个圆为对象
//设置随机的 x,y坐标,r半径,_mx,_my移动的距离
//this.r是创建圆的半径,参数越大半径越大
//this._mx,this._my是移动的距离,参数越大移动
constructor(x, y) {
this.x = x;
this.y = y;
this.r = Math.random() * 10 ;
this._mx = Math.random() ;
this._my = Math.random() ;

}
canvas 画圆和画直线

//canvas 画圆和画直线
//画圆就是正常的用canvas画一个圆
//画直线是两个圆连线,为了避免直线过多,给圆圈距离设置了一个值,距离很远的圆圈,就不做连线处理
drawCircle(ctx) {
// beginPath() 方法开始一条路径,或重置当前的路径
ctx.beginPath();
//arc() 方法使用一个中心点和半径,为一个画布的当前子路径添加一条弧。
ctx.arc(this.x, this.y, this.r, 0, 360)
//closePath() 方法创建从当前点到开始点的路径。
ctx.closePath();
//fillStyle()方法设置或返回用于填充绘画的颜色、渐变或模式。
ctx.fillStyle = ‘rgba(204, 204, 204, 0.3)’;
//fill()方法 填充当前绘图(路径)
ctx.fill();
}

drawLine(ctx, _circle) {
let dx = this.x – _circle.x;
let dy = this.y – _circle.y;
let d = Math.sqrt(dx * dx + dy * dy)
//设置粒子圆心之间连线的范围为150
if (d < 150) {
ctx.beginPath();
//开始一条路径,移动到位置 this.x,this.y。创建到达位置 _circle.x,_circle.y 的一条线:
ctx.moveTo(this.x, this.y); //起始点
ctx.lineTo(_circle.x, _circle.y); //终点
ctx.closePath();
ctx.strokeStyle = ‘rgba(204, 204, 204, 0.3)’;
ctx.stroke();
}
}
粒子移动

// 粒子移动
// 圆圈移动的距离必须在屏幕范围内
move(w, h) {

this._mx = (this.x < w && this.x > 0) ? this._mx : (-this._mx);
this._my = (this.y < h && this.y > 0) ? this._my : (-this._my);
this.x += this._mx / 2;
this.y += this._my / 2;
}
完整js

class Circle {
//创建对象
//以一个圆为对象
//设置随机的 x,y坐标,r半径,_mx,_my移动的距离
//this.r是创建圆的半径,参数越大半径越大
//this._mx,this._my是移动的距离,参数越大移动
constructor(x, y) {
this.x = x;
this.y = y;
this.r = Math.random() * 10 ;
this._mx = Math.random() ;
this._my = Math.random() ;

}

//canvas 画圆和画直线
//画圆就是正常的用canvas画一个圆
//画直线是两个圆连线,为了避免直线过多,给圆圈距离设置了一个值,距离很远的圆圈,就不做连线处理
drawCircle(ctx) {
ctx.beginPath();
//arc() 方法使用一个中心点和半径,为一个画布的当前子路径添加一条弧。
ctx.arc(this.x, this.y, this.r, 0, 360)
ctx.closePath();
ctx.fillStyle = ‘rgba(204, 204, 204, 0.3)’;
ctx.fill();
}

drawLine(ctx, _circle) {
let dx = this.x – _circle.x;
let dy = this.y – _circle.y;
let d = Math.sqrt(dx * dx + dy * dy)
if (d < 150) {
ctx.beginPath();
//开始一条路径,移动到位置 this.x,this.y。创建到达位置 _circle.x,_circle.y 的一条线:
ctx.moveTo(this.x, this.y); //起始点
ctx.lineTo(_circle.x, _circle.y); //终点
ctx.closePath();
ctx.strokeStyle = ‘rgba(204, 204, 204, 0.3)’;
ctx.stroke();
}
}

// 圆圈移动
// 圆圈移动的距离必须在屏幕范围内
move(w, h) {
this._mx = (this.x < w && this.x > 0) ? this._mx : (-this._mx);
this._my = (this.y < h && this.y > 0) ? this._my : (-this._my);
this.x += this._mx / 2;
this.y += this._my / 2;
}
}
//鼠标点画圆闪烁变动
class currentCirle extends Circle {
constructor(x, y) {
super(x, y)
}

drawCircle(ctx) {
ctx.beginPath();
//注释内容为鼠标焦点的地方圆圈半径变化
//this.r = (this.r < 14 && this.r > 1) ? this.r + (Math.random() * 2 – 1) : 2;
this.r = 8;
ctx.arc(this.x, this.y, this.r, 0, 360);
ctx.closePath();
//ctx.fillStyle = ‘rgba(0,0,0,’ + (parseInt(Math.random() * 100) / 100) + ‘)’
ctx.fillStyle = ‘rgba(255, 77, 54, 0.6)’
ctx.fill();

}
}
//更新页面用requestAnimationFrame替代setTimeout
window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;

let canvas = document.getElementById(‘canvas’);
let ctx = canvas.getContext(‘2d’);
let w = canvas.width = canvas.offsetWidth;
let h = canvas.height = canvas.offsetHeight;
let circles = [];
let current_circle = new currentCirle(0, 0)

let draw = function () {
ctx.clearRect(0, 0, w, h);
for (let i = 0; i < circles.length; i++) {
circles[i].move(w, h);
circles[i].drawCircle(ctx);
for (j = i + 1; j < circles.length; j++) {
circles[i].drawLine(ctx, circles[j])
}
}
if (current_circle.x) {
current_circle.drawCircle(ctx);
for (var k = 1; k < circles.length; k++) {
current_circle.drawLine(ctx, circles[k])
}
}
requestAnimationFrame(draw)
}

let init = function (num) {
for (var i = 0; i < num; i++) {
circles.push(new Circle(Math.random() * w, Math.random() * h));
}
draw();
}
window.addEventListener(‘load’, init(60));
window.onmousemove = function (e) {
e = e || window.event;
current_circle.x = e.clientX;
current_circle.y = e.clientY;
}
window.onmouseout = function () {
current_circle.x = null;
current_circle.y = null;

};

网站建设中,JavaScript中那些不可避免的“陷阱”可要小心了!

types & grammer

  1. 判断以下结果
var s = 'abc';
s[1] = 'B';

console.log(s);

var l = new String('abc');
l[1] = 'B';
console.log(l);

string 及其包装对象 (Boxed Object) 是不可变 (immutable) 类型,因此不能改变它本身(modify in place),所以 String的所有方法都是返回一个新的字符串,而不会改变自身。

source

  1. 如何逆序一个字符串?

s.split('').reverse().join('')

  1. 接上,为什么不能直接使用 Array.prototype.reverse.call(s) 逆序字符串?

当一个数组逆序时 l.reverse() 会改变 l 本身。正如第一题,string 不能改变自身。

  1. 判断以下结果,为什么会出现这样的情况,如何做出正确的比较?
0.1 + 0.2 === 0.3;
0.8 - 0.6 === 0.2;

浮点数根据 IEEE 754 标准存储64 bit 双精度,能够表示 2^64 个数,而浮点数是无穷的,代表有些浮点数必会有精度的损失,0.1,0.2 表示为二进制会有精度的损失。比较时引入一个很小的数值 Number.EPSILON 容忍误差,其值为 2^-52

function equal (a, b) {
  return Math.abs(a, b) < Number.EPSILON
}

source zhihu

  1. 如何判断一个数值为整数?
# ES6
Number.isInteger(num);

# ES5
if (!Number.isInteger) {
  Number.isInteger = function(num) {
    return typeof num == "number" && num % 1 == 0;
  };
}

source

  1. 如何判断一个数值为 +0?
function isPosZero (n) {
  return n === 0 && n / 0 === -Infinity
}
  1. 'abc'.toUpperCase() 中 ‘abc’ 作为 primitive value,如何访问 toUpperCase 方法

primitive value 访问属性或者方法时,会自动转化为它的包装对象。另外也可以使用Object.prototype.valueOf() 解包装(Unboxing)。

source

  1. 判断以下结果 (Boxing Wrappers)
function foo() {
  console.log(this)
}

foo.call(3);

Number(3)。理由如上。

  1. 判断以下结果
Array.isArray(Array.prototype)

内置对象的 prototype 都不是纯对象,比如 Date.prototype 是 Date,Set.prototype 是 Set。

source

  1. 判断以下结果
Boolean(new Boolean(false));
Boolean(document.all);

[] == '';
[3] == 3;
[] == false;
42 == true;

new Boolean() 返回 object,为 true document.all。

Falsy value 指会被强制转化为 false 的值,有以下五种。除此之外全部会转化为 true

  • undefined
  • null
  • false
  • +0, -0, and NaN
  • “”

source

  1. 找出以下代码问题 (TDZ)
var a = 3;
let a;

这是暂时性死域(Temporal Dead Zone)的问题,let a 声明之前,不能使用 a。

  1. 找出以下代码问题 (TDZ)
var x = 3;

function foo (x=x) {
    // ..
}

foo()

同样,在函数默认参数中,也有 TDZ。

scope & closures

  1. var a = 2 中,EngineScopeCompiler 做了什么工作
  2. 判断以下结果 (Lexical Scope)
var scope = 'global scope';
function checkscope () {
  var scope = 'local scope';
  function f() {
    return scope; 
  }
  return f;
}

checkScope()();

local scope。由于 js 为词法作用域(Lexical Scope),访问某个变量时,先在当前作用域中查找,如果查找不到则在嵌套作用域中查找,直到找到。如果找不到,则报 ReferenceError

  1. 判断以下结果 (Hoisting)
console.log(a);
var a = 3;

undefined。会被编译器理解为

var a;
console.log(a);
a = 3;
  1. 判断以下结果 (Function First)
var foo = 1;
function foo () {

}
console.log(foo);

1。函数也会有提升,所以会被赋值覆盖。

  1. 判断以下结果 (IIFE & Function First)
var foo = 1;
(function () {
  foo = 2;
  function foo () {
     
  }

  console.log(foo);
})()

console.log(foo);

2,1。会被编译器理解为如下形式

var foo = 1;
(function () {
  var foo;
  function foo () {
     
  }

  foo = 2;
  console.log(foo);
})()

console.log(foo);
  1. 判断以下结果,如何按序输出 (Closure)
for (var i = 0; i < 10; i++) {
  setTimeout(function () {
    console.log(i);
  }, i * 1000)
}

1s 之后连续输出 10 个 10。因为没有块级作用域,可以把 var 改成 let,也可以给 setTimeout 包装一层 IIFE。

this & object prototypes

以下均为浏览器环境

  1. 判断以下结果 (Default Binding)
function foo() {
  "use strict";
  console.log( this.a );
}

var a = 2;

foo();

会报错,在函数的严格模式下,默认绑定其中的 this 指向 undefined。

  1. 判断以下结果
"use strict";
var a = 2;
let b = 3;

console.log(this.a, this.b);

2 在浏览器环境中 this 指向 window,而 var 声明的变量会被挂在 window 上。而 let 声明的变量不会挂在 window 上。

  1. 判断以下结果 (Strict Mode & Default Binding)
function foo() {
	console.log( this.a );
}

var a = 2;

(function(){
	"use strict";

	foo();
})();

2 只有存在 this 的函数中设置严格模式,this 为 undefined。因此会正常输出。

  1. 判断以下结果 (Hard Binding)
function foo () {
  console.log(this.a);
}

const o1 = { a: 3 };
const o2 = { a: 4 };

foo.bind(o1).bind(o2)();

3 bind 为硬绑定,只会在第一次绑定有效

  1. 如何实现 Function.prototype.bindFunction.prototype.softBind
  2. new 的过程中发生了什么,判断以下结果 (new)
function F () {
  this.a = 3;
  return {
    a: 4;
  }
}

const f = new F();
console.log(f.a);
  1. 什么是 data descriptoraccessor descriptor
  2. 如何访问一个对象的属性与如何对一个对象的属性赋值 ([[Get]] & [[Put]])
  3. 如何遍历一个对象 ($$iterator)
  4. 如何实现一个继承 (Object.create & call)
  5. 如何实现 __proto__
  6. 如何实现 Object.create

当然,除了这些“陷阱”还有很多难点,毕竟作为一门强大的语言,没有这些“陷阱”怎么突出它的特别呢?所以,好好加油吧!

什么是哈希

在记录的关键字与记录的存储地址之间建立的一种对应关系叫哈希函数。
哈希函数就是一种映射,是从关键字到存储地址的映射。
通常,包含哈希函数的算法的算法复杂度都假设为O(1),这就是为什么在哈希表中搜索数据的时间复杂度会被认为是”平均为O(1)的复杂度”.

基本概念

在讲解具体内容钱,首先我们要清楚以下几个概念:
1. 冲突(碰撞)
对于不同的关键字ki、kj,若ki != kj,但H(ki) = H(kj)的现象叫冲突(collision) ,即不同的输入却有相同的输出。我们应该尽量避免冲突,因为冲突不仅会使我们在查找的时候效率变慢,还甚至会被攻击者利用从而大量消耗系统资源。

哈希函数的应用

哈希算法广泛应用于很多场景,例如安全加密和数据结构中哈希表的查找,布隆过滤器和负载均衡(一致性哈希)等等。
下面介绍几个常用的哈希算法。

加密哈希算法

在安全方面应用主要体现在以下三个方面:
(1) 文件校验
(2) 数字签名
(3) 鉴权协议

在nodejs中我们可以使用原生crypto模块对数据进行加密,crypto.getHashes()查看支持的哈希算法。

除了我们常用的md5,sha-1,sha-2族外,还有像DSA-SHA1,RSA-SHA1,sha1WithRSAEncryption,其中sha1WithRSAEncryption和RSA-SHA1等价,DSA和RSA都是加密算法,DSA和RSA的区别在于,DSA用于签名,而RSA可用于签名和加密。

下面简单介绍下几种比较常用的加密哈希算法:

1、 MD5
MD5即Message-Digest Algorithm 5(信息-摘要算法5),用于确保信息传输完整一致。是计算机广泛使用的杂凑算法之一,主流编程语言普遍已有MD5实现。将数据(如汉字)运算为另一固定长度值,是杂凑算法的基础原理,MD5的前身有MD2、MD3和MD4。
MD5是输入不定长度信息,输出固定长度128-bits的算法。经过程序流程,生成四个32位数据,最后联合起来成为一个128-bits散列。基本方式为,求余、取余、调整长度、与链接变量进行循环运算。得出结果。
NodeJS中使用MD5:

MD5一度被广泛应用于安全领域。但是在2004年王小云教授公布了MD5、MD4、HAVAL-128、RIPEMD-128几个 Hash函数的碰撞。这是近年来密码学领域最具实质性的研究进展。使用他们的技术,在数个小时内就可以找到MD5碰撞。使本算法不再适合当前的安全环境。目前,MD5计算广泛应用于错误检查。例如在一些BitTorrent下载中,软件通过计算MD5和检验下载到的碎片的完整性。

2、SHA-1
SHA-1曾经在许多安全协议中广为使用,包括TLS和SSL、PGP、SSH、S/MIME和IPsec,曾被视为是MD5的后继者。
SHA-1是如今很常见的一种加密哈希算法,HTTPS传输和软件签名认证都很喜欢它,但它毕竟是诞生于1995年的老技术了(出自美国国安局NSA),已经渐渐跟不上时代,被破解的速度也是越来越快。
微软在2013年的Windows 8系统里就改用了SHA-2,Google、Mozilla则宣布2017年1月1日起放弃SHA-1。
当然了,在普通民用场合,SHA-1还是可以继续用的,比如校验下载软件之类的,就像早已经被淘汰的MD5。

3、SHA-2
SHA-224、SHA-256、SHA-384,和SHA-512并称为SHA-2。
新的哈希函数并没有接受像SHA-1一样的公众密码社区做详细的检验,所以它们的密码安全性还不被大家广泛的信任。
虽然至今尚未出现对SHA-2有效的攻击,它的算法跟SHA-1基本上仍然相似;因此有些人开始发展其他替代的哈希算法。

4、SHA-3
SHA-3,之前名为Keccak算法,是一个加密杂凑算法。
由于对MD5出现成功的破解,以及对SHA-0和SHA-1出现理论上破解的方法,NIST感觉需要一个与之前算法不同的,可替换的加密杂凑算法,也就是现在的SHA-3。

5、RIPEMD-160
RIPEMD-160 是一个 160 位加密哈希函数。
它旨在用于替代 128 位哈希函数 MD4、MD5 和 RIPEMD。
RIPEMD-160没有输入大小限制,在处理速度方面比SHA2慢。
安全性也没SHA-256和SHA-512好。

其它加密算法相关

nodejs除了提供常用加密算法,还提供了HMAC(密钥相关的哈希运算消息认证,类似加盐处理),对称加密算法Cipher(加密)和Decipher(解密),非对称加密算法Signer(签名)和Verify(验证),这里篇幅太长,详细可以参考这几篇讲解得很详细的文章:
Node.js加密算法库Crypto
浅谈nodejs中的Crypto模块
浅谈nodejs中的Crypto模块(补完)

查找哈希算法

下面列举几个目前在查找方面比较快的哈希算法(不区分先后),比较老的或者慢的就没举例了,毕竟篇幅有限。

1、lookup3
Bob Jenkins在1997年发表了一篇关于哈希函数的文章《A hash function for hash Table lookup》,这篇文章自从发表以后现在网上有更多的扩展内容。这篇文章中,Bob广泛收录了很多已有的哈希函数,这其中也包括了他自己所谓的“lookup2”。随后在2006年,Bob发布了lookup3。
Bob很好的实现了散列的均匀分布,但是相对来说比较耗时,它有两个特性,1是具有抗篡改性,既更改输入参数的任何一位都将带来一半以上的位发生变化,2是具有可逆性,但是在逆运算时,它非常耗时。

2、Murmur3
murmurhash是 Austin Appleby于2008年创立的一种非加密哈希算法,适用于基于哈希进行查找的场景。murmurhash最新版本是MurMurHash3,支持32位、64位及128位值的产生。
MurMur经常用在分布式环境中,比如Hadoop,其特点是高效快速,但是缺点是分布不是很均匀。

3、FNV-1a
FNV又称Fowler/Noll/Vo,来自3位算法设计者的名字(Glenn Fowler、Landon Curt Noll和Phong Vo)。FNV有3种:FNV-0(已过时)、FNV-1、FNV-1a,后两者的差别极小。FNV-1a生成的哈希值有几个特点:无符号整形;哈希值的bits数,是2的n次方(32, 64, 128, 256, 512, 1024),通常32 bits就能满足大多数应用。

4、CityHash
2011年,google发布CityHash(由Geoff Pike 和Jyrki Alakuijala编写),其性能好于MurmurHash。
但后来CityHash的哈希算法被发现容易受到针对算法漏洞的攻击,该漏洞允许多个哈希冲突发生。

5、SpookyHash
又是Bob Jenkins哈希牛人的一巨作,于2011年发布的新哈希函数性能优于MurmurHash,但是只给出了128位的输出,后面发布了SpookyHash V2,提供了64位输出。

6、FarmHash
FarmHash也是google发布的,FarmHash从CityHash继承了许多技巧和技术,是它的后继。FarmHash声称从多个方面改进了CityHash。

7、xxhash
xxhash由Yann Collet发表,http://cyan4973.github.io/xxHash/ 这是它的官网,据说性能很好,似乎被很多开源项目使用,Bloom Filter的首选。

查找哈希函数的性能比较

一般性能好的哈希算法都会根据不同系统做优化。
这里有一篇文章详细介绍了各种非加密哈希的性能比较(http://aras-p.info/blog/2016/08/09/More-Hash-Function-Tests/ )。

文章详细列出了各个平台(甚至包括手机和XBOXOne以及asm.js)之间的哈希算法性能比较,同时也对不同输入数据量做了对比。
似乎在跨平台使用方面CityHash64在64位系统性能最佳,而xxHash32在32位系统性能最好。
在数据量大方面,不同平台也有不同的选择
Intel CPU:总体来说xxhash64更好,随之FarmHash64(如果使用了SSE4.2),xxHash32更适合32位系统。
苹果手机CPU(A9):CityHash64在64位系统性能最佳,而xxHash32在32位系统性能最好。
SpookyV2更适合在XBOXOne中使用。
而短字符串输入使用FNV-1a性能最优(在pc,手机和XBOX中少于8字节,在asm.js中少于20字节),而且它的实现很简单。

哈希函数的分类

介绍了那么多哈希函数,实际上哈希函数主要分为以下几类:

1、加法Hash;
所谓的加法Hash就是把输入元素一个一个的加起来构成最后的结果,prime是素数。

2、位运算Hash;
这类型Hash函数通过利用各种位运算(常见的是移位和异或)来充分的混合输入元素。

3、乘法Hash;
这样的类型的Hash函数利用了乘法的不相关性.乘法哈希里最有名的就是adler32,reactJS的checksum校验就是使用的adler32的改良版。
https://github.com/facebook/react/blob/b1b4a2fb252f26fe10d29ba60d85ff89a85ff3ec/src/renderers/dom/stack/server/ReactMarkupChecksum.js
https://github.com/facebook/react/blob/b1768b5a48d1f82e4ef4150e0036c5f846d3758a/src/renderers/shared/utils/adler32.js

4、除法Hash;
和乘法一样用了不相关性,但性能不好。

5、查表Hash;
查表Hash最有名的样例莫过于CRC系列算法。尽管CRC系列算法本身并非查表,可是,查表是它的一种最快的实现方式。以下是CRC32的实现:

查表Hash中有名的样例有:Universal Hashing和Zobrist Hashing。他们的表格都是随机生成的。

6、混合Hash;
混合Hash算法利用了以上各种方式。各种常见的Hash算法,比方MD5、Tiger都属于这个范围。它们一般非常少在面向查找的Hash函数里面使用。

哈希函数的选择

那么多种哈希函数,我们究竟该选择哪种呢?
不同的应用场景对哈希的算法设计要求也不一样,但一个好的哈希函数应该具备以下三点:
1. 抗碰撞性,尽量避免冲突。
2. 抗篡改性,只要改动一个字节,其哈希值也会很大不同。
3. 查找效率。

在加密方面,哈希函数应该对抗碰撞性和抗篡改性要求很高,而会牺牲查找效率。
而且随着时代的变化我们最好还是选择更现代化的哈希函数。
目前来说我们可以选择SHA-2族用于安全加密中,SHA-3更安全但对性能损耗更大。更保险的做法是加盐后混合加密。
在nodejs中我们可以很方便地用crypto.pbkdf2()函数加盐加密,默认会调用hmac算法,用sha-1进行加密,并且可以设置迭代次数和密文长度。常用于用户注册和登录校验流程中。下面的例子我们用伪随机函数randomBytes()生成16字节的盐(更安全的做法是多于16字节)

而在查找方面,哈希函数更追求查找的效率和良好的抗碰撞性。
短字符串输入使用FNV-1a,数量大的在32位系统使用xxhash,64位系统使用FarmHash

专业共识

这里的专业共识是指设计师和前端都需了解的知识点,双方在这些知识点上达成共识,有助于能更加畅通无阻的进行沟通交流。

网站建设中,页面的设计是最重要的存在,赶紧get起来这些专业共识。

 1

设计基础

 尺寸            字体           排版

色彩            布局           动画

网站建设

1. 尺寸

一个网页的尺寸设置与屏幕分辨率和浏览器相关,我们不可能满足所有用户的最佳尺寸,但我们能做的是让绝大多数用户感觉是最佳的。

我们可以通过统计分析工具获取用户屏幕分辨率数据,从而为设计提供参考。例如使用Google Analytics 来获取用户屏幕分辨率数据。

目前常见页面尺寸

PC 端:960px / 980px / 1000px / 1190px / 1200px

手机端:750px

移动端适配

移动端可根据业务需求和产品特点选择响应式适配,还是独立移动版设计,对于页面结构和功能简单的站点,适用响应式,对于大型业务复杂的站点,移动端建议独立版本设计。移动端字体大小使用 rem 单位,以适配不同手机分辨率,保持整体缩放。移动端适配原则:文字流式、控件弹性、图片等比缩放。

Retina 屏适配

设计师将 logo、icon、固定位图片等图片元素生成 2 倍大小提供给前端,前端利用 Media Query 实现 Retina 高清屏适配。

2. 字体

进行跨平台的字体设定,力求在各个操作系统下都有最佳展示效果。

字体家族
中文字体:PingFang SC(iOS 优先),Hiragino Sans GB(备用字体),Microsoft YaHei(次级备用字体)。

英文字体:Helvetica Neue(优先字体),Helvetica(备用字体),Arial(次级备用字体)。

CSS Fonts

font-family:”Helvetica Neue”,Helvetica,”PingFang SC”,”Hiragino Sans GB”, “Microsoft YaHei”,”微软雅黑”,Arial,sans-serif;

字体使用规范

设定字体使用规范,如主标题、次标题、小标题、正文、辅助文字、失效文字、链接文字等字号、颜色、样式等。

补充

字号不得小于 12px,标准 icon 可转成字体图标,特殊字体可以使用 WebFont。

3. 排版

注意行高和段落、标点和空格、对齐、文案长度、层级引导。

行高和段落

行高默认为字号的 1.5 倍。段落间距一般为字号的一倍宽。
CSS 设定:

.paragraph {line-height:1.5;}

.paragraph p {margin:1em 0;}
PS 设定:

上海网站建设
标点和空格

•  使用全角中文标点;

•  遇到完整的英文整句、特殊名词,其内容使用半角标点;

•  数字使用半角字符;

•  不重复使用标点符号;

•  中文和英文间需要空格;

•  数字与单位之间需要增加空格。 例外:度、百分比与数字之间不需要增加空格;

•  中文链接之间增加空格。

对齐

•  中文/英文居左对齐;

•  数字/小数点对齐;

•  冒号对齐。

层级引导

使用对比手法区分信息层次感,让用户第一眼获取所需信息。

4. 色彩

设计中对色彩的运用首要应考虑到品牌层面的表达,另外很重要的一点是色彩的运用应达到信息传递,动作指引,交互反馈,或是强化和凸现某一个元素的目的。任何颜色的选取和使用应该是有意义的。

色彩和交互

在交互行为中需对色彩做变化时,建议采取在颜色上添加黑色或者白色并按照 n+5% 的规律递增的方式来实现。以下图为例,当鼠标 hover 某个特定元素,就视为浮起,对应颜色就相应增加白色叠加,相反点击的行为可以理解为按下去,在颜色上就相应的增加黑色的叠加。

上海网站建设
5. 布局

在布局时遵循尺寸规则、交互和视觉原则,建议使用删格系统进行布局。

栅格系统

最常见的是 12 列栅格系统,例如 Bootstrap 的栅格系统,支持将一行分成 1 列、2 列、3 列、4 列、6 列等,并提供了强大的响应式布局方案。

网站建设
6. 动画

设计需了解一些常见动画效果、实现手法、和 js 动效库,例如:渐隐、缩放、移动、伸缩、摇摆等,便于和前端沟通。

 2

十大原则

 亲密性           对齐           对比           重复

直接了当           足不出户           简化交互

提供邀请           巧用过渡           即时反应

1. 亲密性

如果信息之间关联性越高,它们之间的距离就应该越接近,也越像一个视觉单元。反之,则它们的距离就应该越远,也越像多个视觉单元。亲密性的根本目的是实现组织性,让用户对页面结构和信息层次一目了然。

纵向设定小、中、大三类间距,它们的关系 Y=10+10*N,横向间距视具体情况而定。

2. 对齐

文案类对齐
如果页面的字段或段落较短、较散时,需要确定一个统一的视觉起点,一般文案左对齐。

表单类对齐

冒号对齐(右对齐)能让内容锁定在一定范围内,让用户眼球顺着冒号的视觉流,就能找到所有填写项,从而提高填写效率。

数字类对齐

为了快速对比数值大小,建议所有数值取相同有效位数,并且右对齐。

3. 对比

主次关系对比

为了让用户能在操作上(类似表单、弹出框等场景)快速做出判断, 来突出其中一项相对更重要或者更高频的操作。

总分关系对比

通过调整排版、字体、大小等方式来突出层次感,区分总分关系,使得页面更具张力和节奏感。

状态关系对比

通过改变颜色、增加辅助形状等方法来实现状态关系的对比,以便用户更好的区分信息。常见类型有「静态对比」、「动态对比」。

4. 重复

相同的元素在整个界面中不断重复,不仅可以有效降低用户的学习成本,也可以帮助用户识别出这些元素之间的关联性。

重复元素可以是一条粗线、一种线框,某种相同的颜色、设计要素、设计风格,某种格式、空间关系等。

5. 直截了当

需要在哪里输出,就要允许在哪里输入,不要为了编辑内容而打开另一个页面,应该直接在上下文中实现编辑。常见方法有单字段行内编辑、多字段行内编辑、直接拖放、直接选择等。

单字段行内编辑
当「易读性」远比「易编辑性」重要时,可以使用「单击编辑」。当「易读性」为主,同时又要突出操作行的「易编辑性」时,可使用「文字链/图标编辑」。

多字段行内编辑

编辑模式在不破坏整体性的前提下,可扩大空间,以便放下「输入框」等表单元素;其中,在 Table 中进行编辑模式切换时,需要保证每列的不跳动。

6. 足不出户

能在这个页面解决的问题,就不要去其它页面解决。

覆盖层

二次确认覆盖层:避免滥用 Modal 进行二次确认,应该勇敢的让用户去尝试,给用户机会「撤消」即可。

嵌入层
列表嵌入层:在列表中,显示某条列表项的详情信息,保持上下文不中断。

流程处理
渐进式展现:基于用户的操作/某种特定规则,渐进式展现不同的操作选项。
配置程序:通过提供一系列的配置项,帮助用户完成任务或者产品组装。
弹出框覆盖层:虽然弹出框的出现会打断用户的心流,但是有时候在弹出框中使用「步骤条」来管理复杂流程也是可行的。

7. 简化交互

费茨法则:如果用户鼠标移动距离越少、对象相对目标越大,那么用户越容易操作。

实时可见工具

如果某个操作非常重要,就应该把它放在界面中,并实时可见。

悬停即现工具

如果某个操作不那么重要,或者使用「实时可见工具」过于啰嗦会影响用户阅读时,可以在悬停在该对象上时展示操作项。

开关显示工具

如果某些操作只需要在特定模式时显示,可以通过开关来实现。

交互中的工具

如果操作不重要或者可以通过其他途径完成时,可以将工具放置在用户的操作流程中,减少界面元素,降低认知负担,给用户小惊喜。

可视区域 ≠ 可点击区域
在使用 Table 时,文字链的点击范围受到文字长短影响,可以设置整个单元格为热区,以便用户触发。当需要增强按钮的响应性时,可以通过增加用户点击热区的范围,而不是增大按钮形状,从而增强响应性,又不缺失美感。

8. 提供邀请

邀请就是引导用户进入下一个交互层次的提醒和暗示,以表明在下一个界面可以做什么。

静态邀请

引导操作邀请:一般以静态说明形式出现在页面上,不过它们在视觉上也可以表现出多种不同样式。 常见类型:「文本邀请」、「白板式邀请」、「未完成邀请」。

漫游探索邀请:是向用户介绍新功能的好方法,尤其是对于那些设计优良的界面。但是它不是「创口贴」,仅通过它不能解决界面交互的真正问题。

动态邀请

悬停邀请:在鼠标悬停期间提供邀请。
推论邀请:用于交互期间,合理推断用户可能产生的需求。
更多内容邀请:用于邀请用户查看更多内容。

9. 巧用过渡

在界面中,适当的加入一些过渡效果,能让界面保持生动,同时也能增强用户和界面的沟通。

在视图变化时保持上下文

滑入与滑出:可以有效构建虚拟空间。
传送带:可极大地扩展虚拟空间。
折叠窗口:在视图切换时,有助于保持上下文,同时也能拓展虚拟空间。

解释刚刚发生了什么
对象增加:在列表/表格中,新增了一个对象。
对象删除:在列表/表格中,删除了一个对象。
对象更改:在列表/表格中,更改了一个对象。
对象呼出:点击页面中元素,呼出一个新对象。

改善感知性能
当无法有效提升「实际性能」时,可以考虑适当转移用户的注意力,来缩短某项操作的感知时间,改善感知性能。

10. 即时反应

「提供邀请」的强大体现在交互之前给出反馈,解决易发现性问题;「巧用过渡」的有用体现在它能够在交互期间为用户提供视觉反馈;「即时反应」的重要性体现在交互之后立即给出反馈。

查询模式
自动完成:用户输入时,下拉列表会随着输入的关键词显示匹配项。 根据查询结果分类的多少,可以分为「确定类目」、「不确定类目」两种类型。
实时搜索:随着用户输入,实时显示搜索结果。「自动完成」、「实时建议」的近亲。
微调搜索:随着用户调整搜索条件,实时调整搜索结构。

反馈模式
实时预览:在用户提交输入之前,让他先行了解系统将如何处理他的输入。
渐进式展现:在必要的时候提供必要的提示,而不是一股脑儿显示所有提示,导致界面混乱,增加认知负担。
进度指示:当一个操作需要一定时间完成时,就需要即时告知进度,保持与用户的沟通。
点击刷新:告知用户有新内容,并提供按钮等工具帮助用户查看新内容。
定时刷新:无需用户介入,定时展示新内容。

 3

设计规范

遵循设计环境规范

按设计环境规范进行设计,如:Web、iOS、Android 设计规范。遵循文件命名及画板图层命名规范。

建立设计规范

例如:

配色系统(主色调一般为品牌色、确定产品定位,整体风格,字体颜色等);

信息系统(字体排版,图标,层级区分等);

布局系统(栅格系统,间距等);

控件系统(按钮,表单控件等)。

生成设计文档

Style Guide(PDF 格式,风格指南);

UI Kit(PSD 格式,PS 调取);

标注图(PNG 格式,前端参考)。

有效沟通

 1

前期 · 多方确认

设计前审核原型,与产品和技术人员做好交流,进行功能评估,确认实现难度。视觉定稿前,与产品和技术人员进行确认,确保视觉稿是可实现的。有必要的话,提供前端效果插件。

 2

中期 · 视觉规范

设计生成视觉规范和标注说明,提供给技术人员做参考,确保首次还原效果至少在 80% 以上。

 3

后期 · 还原跟进

技术人员初步完成页面实现后,设计师需要主动跟进还原,并反复验收跟进。

效率工具

好工具能优化工作流程,解放生产力,提升工作效率,这里列举一些常用工具和插件。

设计工具:Sketch

快捷键:Cheat Sheet

PS 插件:栅格布局 Velositey、切图 Cutterman

标注工具:Markman、Size Marks、Zeplin、Sketch Measure

Chrome 插件:
WhatFont  —  识别网页所使用的字体
Page Ruler  — 获取网页中元素大小、位置信息
Browser Resize  — 模拟各种分辨率
Wappalyzer  — 用于识别网站使用的库和框架
PerfectPixel  — 叠加插件,与设计稿进行对比
ColorZilla — 拾色器

作为一个网站建设的人来说,JavaScript的变化防不胜防,到底是方便了开发人员,还是有了更大的难度?

Async/Await简介

对于从未听说过async/await的朋友,下面是简介:

  • async/await是写异步代码的新方式,以前的方法有回调函数Promise
  • async/await是基于Promise实现的,它不能用于普通的回调函数。
  • async/await与Promise一样,是非阻塞的。
  • async/await使得异步代码看起来像同步代码,这正是它的魔力所在。

Async/Await语法

示例中,getJSON函数返回一个promise,这个promise成功resolve时会返回一个json对象。我们只是调用这个函数,打印返回的JSON对象,然后返回”done”。

使用Promise是这样的:

const makeRequest = () =>
getJSON()
.then(data => {
console.log(data)
return “done”
})
makeRequest()

使用Async/Await是这样的:

const makeRequest = async () => {
console.log(await getJSON())
return “done”
}
makeRequest()

它们有一些细微不同:

  • 函数前面多了一个aync关键字。await关键字只能用在aync定义的函数内。async函数会隐式地返回一个promise,该promise的reosolve值就是函数return的值。(示例中reosolve值就是字符串”done”)
  • 第1点暗示我们不能在最外层代码中使用await,因为不在async函数内。
// 不能在最外层代码中使用await
await makeRequest()
// 这是会出事情的
makeRequest().then((result) => {
// 代码
})

await getJSON()表示console.log会等到getJSON的promise成功reosolve之后再执行。

为什么Async/Await更好?

1. 简洁

由示例可知,使用Async/Await明显节约了不少代码。我们不需要写.then,不需要写匿名函数处理Promise的resolve值,也不需要定义多余的data变量,还避免了嵌套代码。这些小的优点会迅速累计起来,这在之后的代码示例中会更加明显。

2. 错误处理

Async/Await让try/catch可以同时处理同步和异步错误。在下面的promise示例中,try/catch不能处理JSON.parse的错误,因为它在Promise中。我们需要使用.catch,这样错误处理代码非常冗余。并且,在我们的实际生产代码会更加复杂。

const makeRequest = () => {
try {
getJSON()
.then(result => {
// JSON.parse可能会出错
const data = JSON.parse(result)
console.log(data)
})
// 取消注释,处理异步代码的错误
// .catch((err) => {
// console.log(err)
// })
} catch (err) {
console.log(err)
}
}

使用aync/await的话,catch能处理JSON.parse错误:

const makeRequest = async () => {
try {
// this parse may fail
const data = JSON.parse(await getJSON())
console.log(data)
} catch (err) {
console.log(err)
}
}

3. 条件语句

下面示例中,需要获取数据,然后根据返回数据决定是直接返回,还是继续获取更多的数据。

const makeRequest = () => {
return getJSON()
.then(data => {
if (data.needsAnotherRequest) {
return makeAnotherRequest(data)
.then(moreData => {
console.log(moreData)
return moreData
})
} else {
console.log(data)
return data
}
})
}

这些代码看着就头痛。嵌套(6层),括号,return语句很容易让人感到迷茫,而它们只是需要将最终结果传递到最外层的Promise。

上面的代码使用async/await编写可以大大地提高可读性:

const makeRequest = async () => {
const data = await getJSON()
if (data.needsAnotherRequest) {
const moreData = await makeAnotherRequest(data);
console.log(moreData)
return moreData
} else {
console.log(data)
return data
}
}

4. 中间值

你很可能遇到过这样的场景,调用promise1,使用promise1返回的结果去调用promise2,然后使用两者的结果去调用promise3。你的代码很可能是这样的:

const makeRequest = () => {
return promise1()
.then(value1 => {
return promise2(value1)
.then(value2 => {
return promise3(value1, value2)
})
})
}

如果promise3不需要value1,可以很简单地将promise嵌套铺平。如果你忍受不了嵌套,你可以将value 1 & 2 放进Promise.all来避免深层嵌套:

const makeRequest = () => {
return promise1()
.then(value1 => {
return Promise.all([value1, promise2(value1)])
})
.then(([value1, value2]) => {
return promise3(value1, value2)
})
}

这种方法为了可读性牺牲了语义。除了避免嵌套,并没有其他理由将value1和value2放在一个数组中。

使用async/await的话,代码会变得异常简单和直观。

const makeRequest = async () => {
const value1 = await promise1()
const value2 = await promise2(value1)
return promise3(value1, value2)
}

5. 错误栈

下面示例中调用了多个Promise,假设Promise链中某个地方抛出了一个错误:

const makeRequest = () => {
return callAPromise()
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => {
throw new Error(“oops”);
})
}
makeRequest()
.catch(err => {
console.log(err);
// output
// Error: oops at callAPromise.then.then.then.then.then (index.js:8:13)
})

Promise链中返回的错误栈没有给出错误发生位置的线索。更糟糕的是,它会误导我们;错误栈中唯一的函数名为callAPromise,然而它和错误没有关系。(文件名和行号还是有用的)。

然而,async/await中的错误栈会指向错误所在的函数:

const makeRequest = async () => {
await callAPromise()
await callAPromise()
await callAPromise()
await callAPromise()
await callAPromise()
throw new Error(“oops”);
}
makeRequest()
.catch(err => {
console.log(err);
// output
// Error: oops at makeRequest (index.js:7:9)
})

在开发环境中,这一点优势并不大。但是,当你分析生产环境的错误日志时,它将非常有用。这时,知道错误发生在makeRequest比知道错误发生在then链中要好。

6. 调试

最后一点,也是非常重要的一点在于,async/await能够使得代码调试更简单。2个理由使得调试Promise变得非常痛苦:

  • 不能在返回表达式的箭头函数中设置断点
网站建设
  • 如果你在.then代码块中设置断点,使用Step Over快捷键,调试器不会跳到下一个.then,因为它只会跳过异步代码。

使用await/async时,你不再需要那么多箭头函数,这样你就可以像调试同步代码一样跳过await语句。

网站建设

结论

Async/Await是近年来JavaScript添加的最革命性的的特性之一。它会让你发现Promise的语法有多糟糕,而且提供了一个直观的替代方法。

相信很多开发人员,尤其是Android开发者都会或多或少听说过Kotlin,当然如果没有听过或者不熟悉也没有关系。因为本篇文章以及博客后期的内容会涉及到很多关于Kotlin的知识分享。

在写这篇文章前的一个多月,Flipboard中国的Android项目确定了正式将Kotlin作为项目开发语言,这就意味着新增的代码文件将以Kotlin代码格式出现,而且同时旧的Java代码也将会陆陆续续翻译成Kotlin代码。在使用Kotlin的这段时间,被它的简洁,高效,快捷等等特点震撼,所以有必要写一篇文章来谈一谈Kotlin的特性,如若能取得推广Kotlin的效果则倍感欣慰。

Kotlin的“简历”

  • 来自于著名的IDE IntelliJ IDEA(Android Studio基于此开发) 软件开发公司 JetBrains(位于东欧捷克)
  • 起源来自JetBrains的圣彼得堡团队,名称取自圣彼得堡附近的一个小岛(Kotlin Island)
  • 一种基于JVM的静态类型编程语言

来自知名的工具开发商JetBrains,也就决定了Kotlin的基因中必然包含实用与高效等特征。那我们接下来看一看Kotlin的特点,当然这也是我改用Kotlin的重要原因。

语法简单,不啰嗦

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//variables and constants
var currentVersionCode = 1   //变量当前的版本号,类型Int可以根据值推断出来
var currentVersionName : String = "1.0" //显式标明类型
val APPNAME = "droidyue.com" //常量APPNAME 类型(String)可以根据值推断出来

//methods
fun main(args: Array<String>) {
    println(args)
}

// class
class MainActivity : AppCompatActivity() {
  
}

// data class  自动生成getter,setting,hashcode和equals等方法
data class Book(var name: String, val price: Float, var author: String)

//支持默认参数值,减少方法重载
fun Context.showToast(message: String, duration:Int = Toast.LENGTH_LONG) {
    Toast.makeText(this, message, duration).show()
}
  • Kotlin支持类型推断,没有Java那样的啰嗦。
  • 另外用var表示变量,val表示常量更加的简洁
  • 方法也很简单,连function都缩写成了fun,平添了几分双关之意。
  • 类的继承和实现很简单,使用:即可
  • Kotlin每个句子都不需要加分号(;)

空指针安全

空指针(NullPointerException或NPE)是我们使用Java开发程序中最常见的崩溃了。因为在Java中我们不得不写很多防御性的代码,比如这样

1
2
3
4
5
6
7
8
public void test(String string) {
    if (string != null) {
        char[] chars = string.toCharArray();
        if (chars.length > 10) {
            System.out.println(((Character)chars[10]).hashCode());
        }
    }
}

在Kotlin中空指针异常得到了很好的解决。

  • 在类型上的处理,即在类型后面加上?,即表示这个变量或参数以及返回值可以为null,否则不允许为变量参数赋值为null或者返回null
  • 对于一个可能是null的变量或者参数,在调用对象方法或者属性之前,需要加上?,否则编译无法通过。

如下面的代码就是Kotlin实现空指针安全的一个例子,而且相对Java实现而言,简直是一行代码搞定的。

1
2
3
4
5
6
7
8
9
10
11
12
fun testNullSafeOperator(string: String?) {
    System.out.println(string?.toCharArray()?.getOrNull(10)?.hashCode())
}

testNullSafeOperator(null)
testNullSafeOperator("12345678901")
testNullSafeOperator("123")

//result
null
49
null

关于空指针安全的原理,可以参考这篇文章研究学习Kotlin的一些方法

支持方法扩展

很多时候,Framework提供给我们的API往往都时比较原子的,调用时需要我们进行组合处理,因为就会产生了一些Util类,一个简单的例子,我们想要更快捷的展示Toast信息,在Java中我们可以这样做。

1
2
3
public static void longToast(Context context, String message) {
    Toast.makeText(context, message, Toast.LENGTH_LONG).show();
}

但是Kotlin的实现却让人惊奇,我们只需要重写扩展方法就可以了,比如这个longToast方法扩展到所有的Context对象中,如果不去追根溯源,可能无法区分是Framework提供的还是自行扩展的。

1
2
3
4
fun Context.longToast(message: String) {
    Toast.makeText(this, message, Toast.LENGTH_LONG).show()
}
applicationContext.longToast("hello world")

注意:Kotlin的方法扩展并不是真正修改了对应的类文件,而是在编译器和IDE方面做得处理。使我们看起来像是扩展了方法。

Lambda, 高阶函数,Streams API, 函数式编程支持

所谓的Lambda表达式是匿名函数,这使得我们的代码会更加的简单。比如下面的代码就是lambda的应用。

1
2
3
findViewById(R.id.content).setOnClickListener {
    Log.d("MainActivity", "$it was clicked")
}

所谓的高阶函数就是

  • 可以接受函数作为参数
  • 也可以返回函数作为结果

举一个接受函数作为参数的例子。在Android开发中,我们经常使用SharedPreference来存储数据,如果忘记调用apply或者commit则数据修改不能应用。利用Kotlin中的高阶函数的功能,我们能更好的解决这个问题

1
2
3
4
5
6
7
8
9
10
fun SharedPreferences.editor(f: (SharedPreferences.Editor) -> Unit) {
    val editor = edit()
    f(editor)
    editor.apply()
}

//实际调用
PreferenceManager.getDefaultSharedPreferences(this).editor {
    it.putBoolean("installed", true)
}

当然这上面的例子中我们也同时使用了方法扩展这个特性。

Kotlin支持了Streams API和方法引用,这样函数式编程更加方便。比如下面的代码就是我们结合Jsoup,来抓取某个proxy网站的数据,代码更加简单,实现起来也快速。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
fun parse(url: String): Unit {
    Jsoup.parse(URL(url), PARSE_URL_TIMEOUT).getElementsByClass("table table-sm")
        .first().children()
        .filter { "tbody".equals(it.tagName().toLowerCase()) }
        .flatMap(Element::children).forEach {
            trElement ->
            ProxyItem().apply {
                trElement.children().forEachIndexed { index, element ->
                    when (index) {
                        0 -> {
                            host = element.text().split(":")[0]
                            port = element.text().split(":")[1].toInt()
                        }
                        1 -> protocol = element.text()
                        5 -> country = element.text()
                    }
                }
            }.let(::println)
        }
}

字符串模板

无论是Java还是Android开发,我们都会用到字符串拼接,比如进行日志输出等等。在Kotlin中,字符串模板是支持的,我们可以很轻松的完成一个字符串数组的组成

1
2
3
val book = Book("Thinking In Java", 59.0f, "Unknown")
val extraValue = "extra"
Log.d("MainActivity", "book.name = ${book.name}; book.price=${book.price};extraValue=$extraValue")

注意:关于字符串拼接可以参考这篇文章Java细节:字符串的拼接

与Java交互性好

Kotlin和Java都属于基于JVM的编程语言。Kotlin和Java的交互性很好,可以说是无缝连接。这表现在

  • Kotlin可以自由的引用Java的代码,反之亦然。
  • Kotlin可以现有的全部的Java框架和库
  • Java文件可以很轻松的借助IntelliJ的插件转成kotlin

Kotlin应用广泛

Kotlin对Android应用开发支持广泛,诸多工具,比如kotterknife(ButterKnife Kotlin版),RxKotlin,Anko等等,当然还有已经存在的很多Java的库都是可以使用的。

除此之外,Kotlin也可以编译成Javascript。最近使用Kotlin写了一段抓取proxy的代码,实现起来非常快捷。甚至比纯JavaScript实现起来要快很多。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
fun handle(): Unit {
        window.onload = {
            document.getElementsByClassName("table table-sm").asList().first()
                    .children.asList().filter { "TBODY".equals(it.tagName.toUpperCase()) }
                    .flatMap { it.children.asList() }.forEach {
                var proxyItem = ProxyItem()
                it.children.asList().forEachIndexed { index, element ->
                    when (index) {
                        0 -> {
                            proxyItem.host = element.trimedTextContent()?.split(":")?.get(0) ?: ""
                            proxyItem.port = element.trimedTextContent()?.split(":")?.get(1)?.trim()?.toInt() ?: -1
                        }
                        1 -> proxyItem.protocol = element.trimedTextContent() ?: ""
                        5 -> proxyItem.country = element.trimedTextContent() ?: ""
                    }
                }.run {
                    console.info("proxyItem $proxyItem")
                }

            }
        }
    }

关于性能

Kotlin的执行效率和Java代码的执行效率理论上一致的。有时候Kotlin可能会显得高一些,比如Kotlin提供了方法的inline设置,可以设置某些高频方法进行inline操作,减少了运行时的进栈出栈和保存状态的开销。

读到这里,是不是想要尝试一下Kotlin呢,它简洁的语法,汇集诸多特性,高效率实现等等,已经在国外风生水起,国外的Pintereset, Square, Flipboard等公司已经开始应用到生产中。

关于转向Kotlin

其实,我在做决定之前(当时Kotlin还没有被钦定)也曾有过考虑,是不是选择了Kotlin就意味着放弃Java呢,冷静下来想一想,其实并不是那么回事,因为Kotlin与Java语法太相近,以及在Kotlin中无时无刻不在和Java相关的东西打交道,所以这点顾虑不是问题的。

对于个人的项目来转向Kotlin,通常不是很难的选择,毕竟Kotlin是那么优秀的语言,相信很多人还是愿意尝试并使用这个事半功倍的语言的。

而比较难抉择的情况是如果如何让团队转用Kotlin,个人认为团队难以转用的原因有很多,比如学习成本,历史包袱等等。但其实根本原因还是思维方式的问题,歪果仁喜欢用工具来提升开发效率,因为人力成本很高。而国内团队提高效率的办法通常是增加成员。好在Flipboard 美国团队自2015年(可能更早)就引入了Kotlin,因此中国团队这边选用Kotlin也更加顺水推舟。当然更主要的是目前团队规模不大,成员一致认可Kotlin的优点。

关于团队转用Kotlin的方法,一般比较行得通的办法是自上而下的推行。这就意味着要么直接的技术负责人比较开明要么就是需要有人来不断推介来影响团队。

做个比较现实的比拟,Java就像是一趟从我的家乡保定开往北京西的耗时将近2个小时甚至更长的普通列车,而Kotlin则是那趟仅需40分钟就能到达的高铁。通常的人都会选择高铁,因为它节省了时间和提高了体验。这个时间和体验对应编程中的,我想应该是高效率和高可读性,可维护性的代码。

现在好了,有了Google的支持,Kotlin转Android相信在不久的将来就会全面展开。篡改Python的一句名言“人生苦短,我用Kotlin”,这样一个高效实用的语言应该会被越来越多的团队所接受,并应用到开发生产中。当然也希望在国内环境下大放异彩。

了解更多

客户案例