从javascript到python

        python以其简洁优雅闻名,我从学习javascript到学习python的过程中,充分感受到python作者想将一件复杂的事情简单化,或者说,将一段别人长长的语句用一个单词来表达。

        它有很多方便的语法和函数,可以很简便地实现我们需要的做法,然而一些基础的语法,还是和其他语言不同的,所以这里面可能就要硬记一下了。举个例子:

        在javascript中的字符串加数字是这样的,将数字转换为字符串然后直接拼接:

var a = '123'
var b = 321;
console.log(a + b);   //'123321'

        在python中就会报错,说不能隐式地转换整形为字符串:

a = '123'
b = 321;

#TypeError: Can't convert 'int' object to str implicitly
print(a + b);   

        而一些乘法的地方,在javascript中是这样的,先将字符串转换为数字再计算:

var a = '8'
var b = 8;

console.log(a * b);   // 64

        而在python中是这样的,直接将字符串重复8次:

a = '8'
b = 8;

print(a * b);   # '88888888' 

        当然了,这些基本的语法还是和这门语言本身设计的理念有很大关系的,所以这些细小的地方,还是要适应不同语言的理念。

        准备之后一段时间开始写一下关于pyhon的一些文章,当分享也好,笔记也好,都对自己成长有好处的。

        不过写文章之前,还是得好好潜心学习掌握透这门语言,看多点资料和别人的观点,努力弄懂这门语言的真理,加油!

再说一下javascript中的this

        QQ好友问了我一个问题,如下:

//例子一
var a = document.write;
a('hello');

//例子二
var b = console.log;
b('hello world');

        他问我,为什么例子一会报错,而例子二却是正常输出的呢?

        其实我一看到这样的写法,就知道这是一个坑,你将别的对象的方法赋值给自己,那如果这个方法里面有this的指向,那这样调用的话,this指向谁了,里面又继续调用了什么方法了,所以肯定会报错的。虽然我没看到源代码的这个方法是怎样写的,但是可以写几个例子就可以验证了,这样:

//第一种写法
var a = document;
a.write('hello');

//第二种写法
var a = document.write.bind(document);
a('hello');

//第三种写法
var a = document.write;
a.call(document,'hello');

        上面三种写法都可以达到想要的效果,运行没有问题。所以很容易知道这是this指向的问题,虽然不知道内部机制如何实现这个方法,但是我们在借用某个对象的方法的时候,记得绑定this的指向,不然容易出错。

        好了,对于上面的例子一中document.write方法会出错,那例子二中console.log为什么不会出错呢?

        我认为就是例子二中console的log方法里面没有用到this的指向,可以举个例子:

var example = {
    test:function(str){
        return str+' what?';
    }
}

var a = console.log.bind(example);
a('123');

        上面这个例子中,我将log方法中的this指向改为指向了一个乱来的example,它仍然可以打印出123,所以可以认为log中没有用到this的指向,所以上面例子二是妥妥的。

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

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

关于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 中国大陆许可协议进行许可。

关于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 中国大陆许可协议进行许可。

关于javascript中的toString( )和valueOf( )

        关于javascript中的toString( )和valueOf( )两种方法,其实早在开始读红宝书(JavaScript高级程序设计)的时候已经有点困惑了,怎么搞出来这两个这么相似的东西,重点是很多时候它们得到的结果都是一样的,虽然之后不了了之觉得对应用没什么大影响就不管了,直到现在开始写博客的时候才回头看看这个问题。

        好了,开始正文了。

        toString( ) 和 valueOf( ) 是对象的两个方法,你在浏览器后台输入Object.protototype就可以看到了它们是其中的两个。

        先说一下两个东西的用途:

        toString( ):返回对象的字符串表示。

        valueOf( ):返回对象的字符串、数值或布尔值表示。

        好了,写几个例子就明白返回结果了(undefined  和 null  的值就不举例了,因为它们都没有这两个方法,所以肯定会报错的):

//先看看toString()方法的结果
var a = 3;
var b = '3';
var c = true;
var d = {test:'123',example:123}
var e = function(){console.log('example');}
var f = ['test','example'];

a.toString();// "3"
b.toString();// "3"
c.toString();// "true"
d.toString();// "[object Object]"
e.toString();// "function (){console.log('example');}"
f.toString();// "test,example"
//再看看valueOf()方法的结果
var a = 3;
var b = '3';
var c = true;
var d = {test:'123',example:123}
var e = function(){console.log('example');}
var f = ['test','example'];

a.valueOf();// 3
b.valueOf();// "3"
c.valueOf();// true
d.valueOf();// {test:'123',example:123}
e.valueOf();// function(){console.log('example');}
f.valueOf();// ['test','example']

        很清楚了,toString( )就是将其他东西用字符串表示,比较特殊的地方就是,表示对象的时候,变成”[object Object]”,表示数组的时候,就变成数组内容以逗号连接的字符串,相当于Array.split(‘,’)。 而valueOf( )就返回它自身了。

        至于迷惑的地方,就在于它们在什么时候被调用,举个例子:

var a = '3';
console.log(+a);// 3

        当然了,打印结果是数字3(不是字符串‘3’),因为一元加操作符接在字符串前面就将其转换为数字了(字符串转化为数字的一种方式,相当于Number( )方法),但是如果它应用在对象上,过程是怎样的呢,再举例子:

//例子一
var example = {test:'123'};
console.log(+example);// NaN

//例子二 同时改写 toString 和 valueOf 方法
var example = {
    toString:function(){
        return '23';
    },
    valueOf:function(){
        return '32';
    }
};
console.log(+example);// 32

//例子三 只改写 toString 方法
var example = {
    toString:function(){
        return '23';
    }
};
console.log(+example);// 23

        通过例子一和例子二的比较,我们可以知道,一元加操作符在操作对象的时候,会先调用对象的valueOf方法来转换,最后再用Number( )方法转换,而通过例子二和例子三的比较,我们可以知道,如果只改写了toString方法,对象则会调用toString方法,证明valueOf的优先级比toString高。上面例子是单独对对象上使用一元加操作符,但是,如果是字符串加对象呢?

console.log('test'+{});   //"test[object Object]"

       这个很明显,对象和字符串相加,肯定转换为字符串啊,所以调用了对象的toString方法,变为[object Object]了。

       好了,如果是alert呢?

//例子一
var example = {test:'123'};
alert(example);// "[object Object]"

//例子二 同时改写 toString 和 valueOf 方法
var example = {
    toString:function(){
        return '23';
    },
    valueOf:function(){
        return '32';
    }
};
alert(example);// "23"

//例子三 只改写 valueOf 方法
var example = {
    valueOf:function(){
        return '32';
    }
};
alert(example);// "[object Object]"

        虽然上面结果我用双引号了,但是你知道弹窗不会将字符串的双引号表示出来的。通过上面几个例子,我们就知道了,alert它对待对象,就和字符串和对象相加一样,就是调用它的toString( )方法,和valueOf方法无关。

        好了,总结一下,一般用操作符单独对对象进行转换的时候,如果对象存在valueOf或toString改写的话,就先调用改写的方法,valueOf更高级,如果没有被改写,则直接调用对象原型的valueOf方法。如果是弹窗的话,直接调用toString方法。至于其他情况,待续……

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

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

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

关于Node.js的单线程

        好了,开始讲Node.js了,什么是Node.js呢?

        它不是一种新的语言,它是基于googleV8引擎的javascript运行环境,特点:单线程、事件驱动、非阻塞、异步I/O……

        其实一开始的时候理解它的单线程问题还是有点困扰的,虽然不影响应用,但是还是要去理解它,在这里先说一些概念。

        先说一下进程和线程:

        进程,就是具有一个独立功能的程序的一次运行活动,说得直白点,就是一个运行的程序,你打开任务管理器就可以看到很多个进程了,而通常一个进程里面又包含若干个线程,它们都可以共享利用进程的资源,是独立运行和调度的基本单位。

        类比一下,进程就是一个正在生产的大车间,而线程就是里面的一条条生产线,都可以共享车间里面的资源。

        再说一下多线程,多线程就是多个线程并发执行,想象一个,你的生产线是一条接着一条去运行还是很多条生产线一起运行高效呢?答案可想而知。

        但是多线程有缺点,就是如果多条生产线使用独立性的公共资源就会使运行速度变慢,就等于要排队拿饭,饭点只有一个吗,轮流来。而且有可能造成线程的死锁,例如两个人要走这条路,一个人说,你让开我就能走了,另一个人也是这样想的,那大家都在等了,不用走了。

        但是对于单核CPU来说,它的多线程实际不是同一时间上运行多个线程,它同一时间只能做一件事情,就是只能运行一个线程,至于我们可以做到多线程并发,是由于CPU在不同的线程之间快速切换,以达到类似同时执行的效果,所以多线程并发不是并行的概念(不能同一时间同时执行)。

        如果问这样的多线程有什么意义,其实可以很好地解决阻塞问题,例如你要烧开水泡茶,如果是单线程的话,那么你要先等开水烧开,才能继续去准备茶杯和茶叶;如果是多线程,那么你在烧开水的时候,就可以转去准备茶杯和茶叶了,不用干等了。

        但是,并不是线程越多就越好的,因为线程之间的切换需要开销的,如果你的线程太多,CPU在线程切换的花销就太大了,你想象一下,我这个还没做完,那我就先记住做到哪里,然后做另一个,而做另外一个之前,又要先看看做到哪里了和要准备什么,这个切换太多的话,代价就显现了。

        好了,转回来说Node.js的单线程了。

        其实,Node.js实际上不是单线程的,那为什么说它是单线程,因为我们的js代码确实是处于单线程中执行的,但是如果需要调用到异步函数,例如I/O的话,它就会交由线程池的其他线程去执行,js代码会不等它的结果,跳过继续执行代码,然后最后才回头,通过轮询机制,执行回调之后的代码,但是即使多个回调,也只能排队,一个一个执行。

        举个例子,一个老太爷在做事,他只能一个事情接一个事情去做,然后遇到一件麻烦事,要叫张三还钱了,然后他叫管家去追债,然后自己继续办事,到最后搞定了。大声叫一下管家(可能管家已经追完钱回来在旁边等了),老太爷问他搞定没啊,如果管家说搞定了,老太爷就可以将钱放进钱柜里了,如果管家说搞不定啊,老太爷可能就会打他一顿了。

        当然了,基于它的单线程,Node.js是适合I/O密集型的程序,不适合计算密集型的程序,因为如果你叫老太爷计算一个东西,他一直算啊算,那其他要算的怎么办,不就在老太爷卡住了吗?I/O密集型就不一样了,老太爷没什么数要算,有麻烦事就交给下人去搞定就行了,多几个麻烦事也不怕,有的是下人,大不了他们干完回来一个接着一个汇报情况而已。

        关于Node.js的单线程,大概就是说的这样吧。

 

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

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

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

关于javascript中的闭包

        其实关于闭包的定义,很多种说法,而关于闭包的解释,更是多不胜数了。很多说得非常复杂,也有很多人有着不同的理解,在这里我就从最容易理解的角度去解释闭包的概念,如有不正确的地方,请指出。

        闭包,其实就是能够读取函数内部变量的函数。

        举个例子:

//全局环境
function outer(){
    var big = 20;
}
console.log(big);

        当然了,执行上面这样的代码会报错,因为你没有在全局环境中声明big,你想访问outer函数内部的big也是不行的,因为你没有调用outer函数,压根不会创建outer的执行上下文,更不用说outer里面的big的作用域只是在outer函数内部了。

        那如何能够在外部访问到outer函数里面的big变量呢?有办法,就是闭包:

//全局环境
function outer(){
    var big = 20;
    function inner(){
        console.log(big);
    };
    return inner();
}
outer();//20

        你执行outer函数的时候,里面返回的是调用inner函数的结果,而inner函数就是访问big变量的,所以这样就能使外部可以访问到函数里面的变量。这就是所谓的闭包,没有传说中的那么复杂,不过网上的所谓闭包面试题就解释得很复杂了。

        但是闭包有一个问题,就是会使得你内部访问的变量常驻内存当中,垃圾机制又没有将其回收,如果此函数不再使用了,又没有对其进行清除,就会造成内存泄漏,如果过多使用闭包,后果可想而知。

        其实换一个角度,我们看这样的例子:

//全局环境
var bigger = 30;
function outer(){
    console.log(bigger);
}
outer();//30

        像上面的例子,全局环境下声明的outer函数,里面调用了全局环境的bigger变量,不又是一个闭包吗?其实没错,这是广义的闭包,不是常说的闭包,但是转头想想,其实这个bigger是一直存在于全局的执行上下文中的全局变量,你不清除掉不也一样一直存在内存中吗?

        一句话理解闭包类题目:如果a函数内的其他b函数用到了a函数执行上下文中的变量n,那么这个变量n的值就会一直保存在这个函数的变量对象当中,直到下一次改变它。

        举个网上的面试例子来说明我这个理解:

//全局环境
function fun(n,k) {
   console.log(k)
   return {
       fun:function(m){
           return fun(m,n);
       }
   };
}
var a = fun(0); a.fun(1); a.fun(2); a.fun(3);
var b = fun(0).fun(1).fun(2).fun(3);
var c = fun(0).fun(1); c.fun(2); c.fun(3);

        这个例子也是随便一搜的,其他例子其实也类同,所以在这里就用我个人理解的角度去分析一下这个题。

        先说var a 这一行的输出吧:

        var a = fun(0)很容易知道输出undefined了,但是返回的是一个对象,这个对象的fun属性是一个匿名函数,而这个匿名函数里面又返回了一个fun函数,这个fun函数用到了变量n。好了,用上我前面的理解,可以知道fun函数里面就保存了n=0这个值,直到下一次改变它。类似如下:

var a = {
    fun:function(m){
       return fun(m,0);
    }
};

        而从a.fun(1)、a.fun(2)、 a.fun(3)开始,大家肯定知道会执行这三个函数:

f(1,0)、f(2,0)、f(3,0)

        所以会输出undefined  0  0  0。

        可能大家会懵了,你在执行f(1,0)的时候,不是n的值改为1,不是会改变了n的值了吗?后面的n的值不是变了吗?在这里我就要说说关于我的理解中,怎样为改变这个变量的值。

        先再回顾一下我前面的理解:如果a函数内的其他b函数用到了a函数执行上下文中的变量n,那么这个变量n的值就会一直保存在这个函数的变量对象当中,直到下一次改变它。

        改变这个变量的n值,必须通过闭包,也就是这个:

{
     fun:function(m){
         return fun(m,n);
     }
};

        通过这个fun属性对应的匿名函数的执行,才会在匿名函数的返回结果中改变这个n的值。

        在上面的执行a.fun(1)中,只是又返回了一个新的对象,但是并没有执行新对象里面的fun属性对应的匿名函数喔,那就是没有改变n的值啊,所以你继续a.fun(2)、a.fun(3)也会输出同样的结果啊。

        也就是,必须有执行这样:a.fun(1).fun(1)这样,才会改变到n的值。

        对于上面例题var b这一行来说,就很容易明白var b = fun(0).fun(1).fun(2).fun(3);后面的fun(1).fun(2).fun(3)这三个中的fun其实都是属性名fun,并不是声明的函数fun,所以他们都在改变n的值啊,所以也很容易明白这一行的输出结果就是undefined  0  1  2。

        var c的那一行也很容易明白输出结果是undefined  0  1  1了。

        关于这个题的详细理解,大家再细想一下,就很清楚了。

 

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

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

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

关于javascript中的bind、call、apply等函数的用法

        前面的文章已经说到this的指向了,那么这篇文章就要说一说和this相关的三个函数的用法:bind( )、call( )和apply( )。

        其实它们都很简单,但是在处理一些与this相关的函数的时候,用来改变函数中this的指向,却是必不可少的工具,所以必须掌握好它们的用法。

        好了,三管齐下,一起比较地举例子说一下:

//先来一个对象big吧
var big = {
    name:'BaBa',
    saying:function (age){
        console.log(this.name,'age:',age);
    }
};

//再来一个small对象
var small= {
    name:'ErZi'
};

//如果想调用big的saying方法来说出‘Erzi’:
//使用bind方法
big.saying.bind(small)(20);//打印结果为ErZi age: 20
//使用call方法
big.saying.call(small,20);//打印结果为ErZi age: 20
//使用apply方法
big.saying.apply(small,[20]);//打印结果为ErZi age: 20

        所以,其实三种方法都可以达到同一个结果,至于区别就很显而易见了。

        bind只是单纯创建了一个函数,并将this的指向明确指定为small了,如果要执行函数,就要在后面加括号调用了。

        call就是直接执行一个自己指定this指向的函数,参数是一个一个传递。

        apply和call的区别就是,参数是放进一个数组中传递。

        实际上,很多时候我们在一个函数中,想用到其他的函数,但是其他函数的this指向就不明确,所以就会在自己的函数里面通过这三个方法来调用函数,例如:

//先来一个对象big吧
var big = {
    name:'BaBa',
    age:50,
    saying:function (){
        console.log(this.name,'age:',this.age);
    }
};

//再来一个SayingAge的构造函数
function SayingAge(name,age){
    this.name = name;
    this.age = age;
    this.saying = big.saying.bind(this);
}

//实例化一个对象genius
var genius = new SayingAge('tiancai',18);
//调用genius的saying方法
genius.saying();//打印结果为tiancai age: 18

        构造函数SayingAge就是将big的saying方法拿过来,并将saying函数里面的this指向改为自己的this指向(指向实例化的对象)。

        如果想不单单是bind了一个函数的this指向,还想执行它,举个call的例子(apply只是将参数放进数组):

//先来一个对象big吧
var big = {
    name:'BaBa',
    age:50,
    saying:function (sex){
        console.log(this.name,'age:',this.age,'sex:',sex);
    }
};
//再来一个small对象
var small = {
    name:'ErZi',
    age:18
};
//借用big的saying方法看看small的saying是什么
big.saying.call(small,'male');//打印结果为ErZi age: 18 sex: male

        当然了,这个方法并没有给small添加了saying方法,只不过调用了big的saying方法,并将里面的this指向small而已。

        相信大家对这三个方法的应用都很轻松掌握了。

 

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

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

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