python——有趣的缩进

        最近在学python,它的一大表现特点就是缩进,其实之前用pug(以前的jade)这个模板引擎的时候,已经领教过缩进的招式,缩进其实隐藏着坑的,有很多人都不习惯,很多人都习惯了看花括号,见到缩进感觉很别扭的感觉。

        不过尝试写多两行代码,就会发觉缩进也是挺微妙的,会省很多功夫,而且代码简洁优雅,可能就是python出名的原因之一,很著名的一句话“life is short , you need python”或者“life is short , use python”

        所以又有另外一种问法:你用tab键还是用space键,一个tab顶你4个space喔,当然很多还是推荐用space,不过我觉得就是将tab键设置为4个space键来使用tab键比较好。之前就是有个格式问题报错,大概是:you can use tab or space, but not both,就是说你不要混用,所以如果格式有问题,这也是一个坑。

        通常刚接触的人来说,不习惯的通常在于if语句,或者for循环的地方,跳出了if或者for循环应该写在哪个缩进位置,例如:

def example(x):
    if x < 0:
        return -x
    return x

print(example(-3))  #3

        注意到我写了两个return ,而第二个return是跳出了if语句的,跟它同一缩进位置,证明是平行的关系,当多几个for循环,多几个if和else之后,这种缩进就显得有点微妙了。

        其实缩进减少了很大工作量,而且不要写分号,因为缩进就是要解决掉分号和花括号的,某程度上缩进还是一目了然的,写多两遍,发觉又是另一种景象。

一个例子

        突然想起了一个生活例子,和思维方式很有关系的一个例子。

        早前洗手间的洗手盆的水龙喉开关坏了,我爸买个新的来换,换的时候下面有两个接口,我知道一边进热水口,一边进冷水口,但是我们的配到手洗盆的两个出水口都是接在普通的冷水管上,所以我叫我爸不用装左边的热水进口,装右边的就行了,因为左边装了也是进冷水的。

        用后才发觉,开关把手转到中间的时候下面漏水了,原来是从没有接的热水进口漏出去了。

        想后才发觉,两边联通的才能混合出不同温度的水,所以其实两边可以相通的,所以下面一定要接紧热水进口,不接就会漏出去,再回想一下,难怪以前即使两个冷水出口,也要将热水进口接进去了。

        这是一种思维导向,从进水口出发,理所当然地认为都是冷水,就无需将冷水接到热水进口了,用后才发觉热水进口会是一个bug,所以其实解决一个问题的时候,应该从完成的问题之后的情形出发,去思考它的实际意义,存在价值,再回过头来想如何去做,做什么事情,才能解决这个问题。

        有些启发。

 

 

关于javascript中的深拷贝和浅拷贝

        关于javascript中的深拷贝和浅拷贝,有很多种说法:深克隆浅克隆、深复制浅复制,其实说的都是那么一回事。在这里我就说深拷贝和浅拷贝。

        什么东西要做这个拷贝操作呢?就是对象。因为我们在声明变量并初始化变量为对象的时候,实质上给它的值是一个指向实际对象的指针,一个地址而已,例如:

var a;
a = {test:123};

        这个a,实际上是一个对象指针,它指向的是{ “test”:123 }这个对象,那如果我想多拷贝一份b呢?

var a;
a = {test:123};
var b = a;
console.log(b);   //{test:123}

        好了,你也拷贝了一份给b了,那如果你改变一下b对象的内容,a对象又会怎样呢?

var a;
a = {test:123};
var b = a;
b.test = 321;
console.log(b);   //{test:321}
console.log(a);   //{test:321}

        我们可以看到,你改变b对象内容,a的对象也会被改变了,因为你只是复制了a的地址给b,而这个地址,都同时指向了同一个对象,所以你通过b地址改变对象,实际上a地址对应的对象就改变了。

        这个拷贝,就是我们说的浅拷贝,你拷贝的只是一个指针,它指向的还是这个对象。这个是否和我们有时候写的函数有点像:

var a = {test:123};
function example(b){
    b.test = 321;
    return b;
}
console.log(example(a));   //{test:321}
console.log(a);   //{test:321}

        这个example中的形参b,实际上就是拷贝了一份a的指针过来,然后操作,再返回一个对象,其实返回的这个对象,就是a指向的那个对象,但是通过b地址改变了。

        所以有时候我们在写函数的时候,特别对于传入的对象,如果单纯以为传入的是整个对象那就很危险了,因为你的操作很可能造成对原对象的影响,所以不要忽视这一个问题。

        好了,我们知道了这样是浅拷贝,那如何是深拷贝呢?

        其实很简单,就是不仅拷贝多一份地址,还要拷贝多一份对象,整个对象。这里有一个简单粗暴的方法:

var a = {test:123};
var b = JSON.parse(JSON.stringify(a));
b.test = 321;
console.log(a); //{test:123}
console.log(b); //{test:321}

        通过先用JSON.stringify方法转为字符串,然后再用JSON.parse方法转为对象,当然了,更妥当的方法,就是采用遍历的方法,将每一个属性都复制给一个新的对象,或者可以采用lodash模块中的克隆方法,可以选择不同程度的克隆深度,也是非常好的方法。

        关于javascript中的深拷贝和浅拷贝,重点是无论任何情况,都不要轻易直接将对象地址复制给新的变量,因为你操作的这个地址指向的对象,很可能被你改变了,到你回过头来再用原来对象的时候,发觉已经被你改变了。

文章在我的github上的地址:点击跳转

原创文章,转载请注明出处!

知识共享许可协议
本文章采用知识共享署名-非商业性使用-相同方式共享 3.0 中国大陆许可协议进行许可。

Laziest——利用es6的模板字符串功能封装的模板引擎

        当然了,模板引擎有很多,而且功能也很强大。

        这里就用es6的模板字符串功能封装了一个弱版模板引擎,只有常量取值和引入js代码功能,但感觉也够用了。

        模块在我的github上,地址:https://github.com/ershing/laziest

        安装方法:

npm i laziest

        使用方法:

//引入laziest模块
const laziest = require('laziest');
//使用compile方法编译模板
var view = laziest.compile(templatePath);
//引入数据内容
var data = require(dataPath);
//生成内容
var output = view(data);
//将内容写入文件
const fs = require('fs');
fs.writeFileSync(filePath,output);

        模板语法:

<% javascript 代码 %>  //插入javascript代码
#{ 变量名称 }  //插入变量值

详细看我的github,地址:https://github.com/ershing/laziest

原创文章,转载请注明出处!

知识共享许可协议
本文章采用知识共享署名-非商业性使用-相同方式共享 3.0 中国大陆许可协议进行许可。

学如逆水行舟

        学如逆水行舟,不进则退。这是小学时候常用的语句。

        想想,确是那么一回事。

        很多人说编程就是一门手艺,不是一项知识,也别将它用知识的角度来对待,只要你不停地练习,你就能掌握得这门手艺掌握得很好。

        有时候想想,也对,很多东西你要不断练习,太久不练习,回过头看代码,你只剩下那么一丁点记忆了,又要花点时间去熟练起来。

        其实生活中很多东西都只是一门手艺,需要你不断去练习,冒出来新的东西,就要练习新的东西,这就是一个学习的过程,人的智商其实一出生就已经定了,你是这么高就这么高,至于我们看到很多的天才,只不过是不停练习练习得来的,而且是从小就不停练习练习得来的,而他的智商,只不过决定他练多少次能够掌握得好罢了,只要你坚持,就能一步一步地进步,慢慢成为别人敬仰的大神。

        学如逆水行舟,不进则退,说的就是要不要停下自己前进的脚步,多加练习,磨练自己,不要荒废光阴,努力向前,否则就会在这潮流的大海中被淹没了。

关于Node.js的Buffer对象

        关于Node.js的Buffer对象,其实和前面写的文章(关于Node.js的字符编码)有很大关系。

        因为在最初写Node.js,用到Buffer的时候,感觉这个东西有点飘渺,它是怎样的一种存在呢?这篇文章会好好说一说。

        在说Buffer之前,想先说一下Web Api 的一些内容先,其实也是密切相关的。

        先说一下File对象和Blob对象。

        File对象通常来自一些类似选择文件等操作返回的FileList 对象,这个File存放着对应文件的一些信息,包括文件名称、文件大小、文件类型、文件位置、最后修改时间等等的信息,File对象继承了Blob对象的方法,是一个特殊的Blob。

        而Blob对象,是通过Blob( ) 构造函数得来的,例如下面这样创建Blob对象:

var blob = new Blob([JSON.stringify(debug, null, 2)],
  {type : 'application/json'});

        Blob对象表示一个不可变的、原始数据的类似文件对象,也有文件大小、类型等属性,其实可以说File对象就是关于一个完整文件的信息,而Blob对象是关于一段文件的信息,无论文件是否被切割了很多块(slice( )方法)。

        好了,既然有了这两个对象,那么我们如何读取这两个对象(File对象和Blob对象)对应的文件内容呢?

        Web Api又有一个FileReader 对象给我们读取文件,它有那么的几种方法:readAsArrayBuffer( )(读取文件结果中包含ArrayBuffer对象)、readAsBinaryString( )(读取文件结果中包含文件的原始二进制数据)、readAsDataURL( )(读取文件结果中包含一个URL格式的字符串以表示所读取文件的内容)、readAsText( )(读取文件结果中包含一个字符串以表示所读取的文件内容)。

        后面两种方法容易理解,而readAsBinaryString( )在W3C中已经被废除,生产环境中不推荐使用,这里就不说了,这里就说readAsArrayBuffer( )结果中包含的这个ArrayBuffer对象。

        通俗来讲,ArrayBuffer对象代表一个有固定长度的二进制数据数组,你创建一个ArrayBuffer对象的时候,就好像下面这样:

var buffer = new ArrayBuffer(8);

        这样你就创建了一个长度为8byte的缓冲区,不给参数是默认用0来填充数据的。

        其实,你创建的这个buffer只是代表某个数据块的对象,并没有提供方法来操作其中的数据内容,在 ECMAScript 2015 (ES6) 引入 TypedArray 之前,JavaScript 语言没有读取或操作二进制数据流的机制。

        好了,怎么又来个TypedArray 了,它其实不是一般的Array,它是类型化数组,它用来创建操作二进制数据的视图(DataView也可以做到,类似TypedArray),包含很多构造方法:Int8Array 、Uint8Array、Int16Array、Uint16Array、Int32Array、Uint32Array、Float32Array、Float64Array 等。所以如果我们想创建一个缓冲区,并建立一个队缓冲区操作的视图的话,可以这样写:

var buffer = new ArrayBuffer(8); //创建缓冲区
var view = new Int32Array(buffer); //创建操作视图
view[0] = 38; //操作数据
console.log(view[0]); //打印结果为38

        好了,该回过头来说说Node.js的Buffer对象了,

        其实Buffer 实例对象也是 Uint8Array 实例对象,但有一些不同,尚且不谈论不同的地方,Buffer 类被引入使其可以在 TCP 流或文件系统操作等场景中处理二进制数据流,所以它就是一个操作二进制数据流的工具。

        它可以这样创建缓冲区:

// 创建一个长度为 10、且用 0 填充的 Buffer。
const buffer = Buffer.alloc(10);

        可以这样直接创建含有数据的缓冲区:

// 创建一个包含 [0x1, 0x2, 0x3] 的 Buffer。
const buffer = Buffer.from([1, 2, 3]);

// 创建一个包含 UTF-8 字节 [0x74, 0xc3, 0xa9, 0x73, 0x74] 的 Buffer。
const buffer = Buffer.from('tést');

// 创建一个包含 Latin-1 字节 [0x74, 0xe9, 0x73, 0x74] 的 Buffer。
const buffer = Buffer.from('tést', 'latin1');

        好像前面那样,将ArrayBuffer 对象放进去也是可以的:

const arrayBuffer = new ArrayBuffer(16);
const buffer = Buffer.from(arrayBuffer);

console.log(buffer.buffer === arrayBuffer); //trued

        当然了,拷贝TypedArray 实例内容也是可以的:

const arr = new Uint16Array(2);
arr[0] = 5000;
arr[1] = 4000;

// 拷贝 `arr` 的内容
const buf1 = Buffer.from(arr);

        上面这些例子基本摘抄自Node.js官网文档,其实说到底,Node.js中的Buffer对象是一个大类,包括很多方法来操作二进制数据,就是这样的。

文章在我的github上的地址:点击跳转

原创文章,转载请注明出处!

知识共享许可协议
本文章采用知识共享署名-非商业性使用-相同方式共享 3.0 中国大陆许可协议进行许可。

关于javascript中的操作符

        javascript的操作符(或者叫运算符)有很多个,关于它们具体的介绍和优先级排序可以参考MDN文档

        这篇文章更像是一篇笔记,记录一些操作符的操作效果。

        好了,用一道题开始这篇文章:

console.log([] == ![]);

        这道题很经典,打印结果是什么?是true,这就有违我们平常的思维逻辑了,它本身和它取反的值还能相等的呢?好了,开始分析一下:

        逻辑操作符比比较操作符优先级高,首先会进行![ ]这个操作,这个取反的逻辑非操作符有个规则,对象取反都是返回false,所以实则这条题目是:

console.log([] == false);

        好了,到了比较操作符了,比较操作符又有个规则,如果比较的其中一个是对象,则要先调用它的valueOf( )方法或toString( )方法进行转换,之后再比较,关于valueOf( )和toString( )这两种方法,可以看前面的文章(关于javascript中的toString( )和valueOf( )),由于数组继承Array.prototype ,Array.prototype有toString方法,但是没有valueOf方法,而valueOf方法需要再上一级找Object.prototype才有,所以即使valueOf优先级高也不会调用,在这里就会调用toString方法,而空数组调用这个方法得到空字符串,所以又变成这样:

console.log('' == false);

        到这里了,比较操作符又有规则了,布尔值在比较前会先转换为数值,而字符串和一个数值比较,则会转换字符串为数值再比较(比较拗口),所以又变成这样:

console.log(0 == 0);

        答案显而易见,就是ture。

        好了,其实结合我前面的文章(关于javascript中的toString( )和valueOf( ))来看,其实也不难,在这里又再列举下其他一些规则吧:

        数值和字符串相加,会将数字转换为字符串再拼一起,例如:

console.log(5 + '5'); // '55'

        数值和字符串相减,会将字符串转换为数值再进行相减运算,例如:

console.log(5 - '5'); // 0

        比较操作符时候,字符串和数字比较,会转换字符串为数字再比较,例如:

console.log(3 > '5'); // false

        在这里总结上面:

        相加减操作时候,有字符串可以拼肯定将数字转为字符串来拼,拼不到了(减法怎么拼)就将字符串转为数字去运算。

        比较操作时候,有数字比较,你肯定要转换为数值去比较啊。(至于两个字符串比较,就是按照字符编码去比较,实则比较的也是数值)。

        至于其他情况,待续……

文章在我的github上的地址:点击跳转

原创文章,转载请注明出处!

知识共享许可协议
本文章采用知识共享署名-非商业性使用-相同方式共享 3.0 中国大陆许可协议进行许可。

关于Node.js的字符编码

        关于字符编码,其实是非常基础的东西,不过,对于很多人来说,也没有弄清楚它是怎样来的一个东西,就只知道有很多不同的字符编码,常用的是utf-8,所以这里还是要说一下字符编码。

        对于计算机来说,它可以处理这么多不同数据(视频、图片、程序等等),是怎样做到的呢?其实对硬件来说,它能感受到的就是高低电平,低电平表示0,高电平表示1,那么它就能处理0和1这两种数据(实际远不止这么简单),所以可以说,它实质处理的是由1和0组合的二进制数据。

        由于我们对计算机的输入远远不止1和0两个数字,所以必须利用它们的组合来表示其他的数字或字符,我们都知道,一个字节(byte) 有8个比特(bit),就是一个8位的二进制数,所以一个字节可以用0-255这么多个整数来表示其他东西。

        大家都知道,美国人发明计算机时候,肯定按照自己想法来搞,所以按照它们的字符来对应整数的话,只有0-127个字符被编码到计算机里,包括大小写字母、数字和其他一些符号等等,这个编码表就是著名的ASCII编码。

        那么问题就来了,你美国自己那么少字符,我们中国这么复杂的文字怎么表示呢?1个字节肯定不够用来表示了,所以我们会用两个字节来表示,制定了我们的GB2312编码。当然了,其他国家也会这样了,所以有很多不同的编码。

        那么问题又来了,如果的数据里面又有我们这个国家的东西,又有你们国家的东西,那么无论用哪个字符编码,都肯定会出现乱码的情况啊,所以,就出现了大一统的Unicode编码。

        而这个Unicode编码呢,又是两个字节的,为什么呢?因为能够表示的东西足够多啊,那如果我全部数据都是英文的话,你要我一个字母用两个字节表示,就要前面补0,相当于浪费了一倍的存储空间了,所以,就出现了可伸缩长度的UTF-8编码,又叫做万国码,现在已经标准化为RFC 3629,可以用1到6个字节编码Unicode字符,表示英文字母用一个字节,表示中文用三个字节,所以如果用的多英文的时候又可以节省空间了。

        好了,可以回过头来说一下Node.js的字符编码了。

        Node.js目前支持的字符编码有这几种(用字符串表示):

        ‘ascii’、’utf8’、’utf16le’、’ucs2’、’base64’、’latin1’、’binary’、’hex’

        前面两个都说了,而‘utf16le’是用2个 或 4 个字节的 Unicode 字符编码,相当于又是一个变种的Unicode,而‘ucs2’就是‘utf16le’的别名,一样的。

        至于‘base64’,相信大家都很熟悉了,它是网络上最常见的用于传输8Bit字节代码的编码方式之一,有时候我们见到的类似“%XX”形式的就是通过‘base64’加密的(转换),它的转换过程大概如此:先将字符转换为ascii编码,然后用二进制表示,以三个字节(每个字节有8位)为一组,然后分成4份,每份6位,当然了,每一份要补全字节,都要在前面加两个0,就变成了四个字节了(实际浪费了三分一的空间),这么有规律的转换,所以是有Base64编码表查到每一个编码对应的真实字符的。

        而’latin1’就是一字节编码,类似‘ascii’,实际上虽然‘ascii’也是一字节字符编码,但是它实际只利用了7位,最高位为0,因为它只表示0-127,而‘latin1’就表示0-255,所以它向下兼容‘ascii’的,而‘binary’就是’latin1’的别名。

        最后,’hex’就是将每个字节编码为两个十六进制字符,2的8次方和16的平方相等啦,所以它们可以转换过来的啦。

        其实字符编码,就是用一个整数来表示某一个字符的一种方式,而且不会重复,至于你选择用一个字节(二进制),两个字节(二进制),还是不同进制(十进制、十六进制)来表示,就是不同的字符编码了。

文章在我的github上的地址:点击跳转

原创文章,转载请注明出处!

知识共享许可协议
本文章采用知识共享署名-非商业性使用-相同方式共享 3.0 中国大陆许可协议进行许可。