以前经常由于不注意,习惯用写PHP或者Java的方式来写nodejs,产生了了一些错误,这里总结一些小小的trick,以便于展现nodejs的不一样,和平时须要注意的地方。node
var variable = 'global'; console.log(variable); function fn () { console.log(variable); var variable = 'local'; console.log(variable); } fn();
你可能觉得这段代码执行结果为:数组
global global local
但实际上结果是闭包
global undefined local
缘由就是函数做用域致使局部变量在整个函数体内部可见,因此执行起来就成了:app
function fn () { var variable console.log(variable); variable = 'local'; console.log(variable); }
函数内部的console.log出于就近原则读取的是内部variable,亦即局部variable覆盖了全局variable,而后局部variable是整个函数体内可见,因此至关于提高了变量声明,亦即变量声明放在了函数开头,可是变量初始化仍是在原来的位置,因此就是上面展现的顺序。
写Java的时候咱们倾向于在最开始使用一个局部变量以前声明它,这样帮咱们清晰它的做用域以及生命周期;可是JavaScript没有块级做用域,因此局部变量最好写在函数开始,这样才能更清晰的展现它的做用域(整个函数内部)和生命周期,避免产生误解。函数
有点须要注意的是:声明写var与不写var是有区别的:ui
console.log(a); a = 1;
会报错,而下面这个:this
console.log(a); var a = 1;
结果是 undefined ,也就是没有var的声明不会提高。.net
js中建立函数有两种方式:函数声明式和函数字面量式。只有函数声明才存在函数提高:prototype
console.log(f1); console.log(f2); function f1() {} var f2 = function() {}
结果:code
[Function: f1] undefined
就是函数提高致使顺序变为:
function f1() {} console.log(f1); console.log(f2); var f2 = function() {}
JavaScript 没有 提供对象继承的语言级别特性,而是经过原型复制来实现的。
var util = require('util') function Superclass(){ this.a = 'a'; } Superclass.prototype.d = 'd'; function Subclass(){ this.b = 'b'; } util.inherits(Subclass, Superclass); var superC = new Superclass(); var subC = new Subclass(); console.log(superC.a); console.log(subC.a); subC.a = 'suba'; console.log(superC.a); console.log(subC.a); subC.cc = 'cc'; console.log(superC.cc); console.log(subC.cc); console.log(superC.d); console.log(subC.d);
结果:
a undefined a suba undefined cc d d Superclass { a: 'a' }
subC仅仅继承了superC在prototype中定义的属性d,而构造函数内部创造的a属性没有被subC继承。同时,在原型中定义的属性不会被console.log做为对象的属性输出。在subC中修改属性a并不会修改superC的属性a,可是能获取superC的属性d,并且设置了一个属性cc也不会影响superC。因此对于set操做并不会修改原型链,只有get操做才会体会到原型链(继承)的存在。
var util = require('util') function Superclass(){ this.a = 'a'; } Superclass.prototype.d = 'd'; function Subclass(){ this.b = 'b'; } util.inherits(Subclass, Superclass); var superC = new Superclass(); var subC = new Subclass(); for(x in subC){ console.log(x); }
结果是
b d
也就是说 in 关键字能检测到自有属性和继承熟性,这个能够用 !==来代替
for(x in subC){ if(subC[x] !== undefined) console.log(x); }
结果一致, 可是in能够区分属性不存在 和 属性存在且为undefined 两种状况,而!==不能区分。
再看下面的:
for(x in subC){ if(subC.hasOwnProperty(x)) console.log(x); }
结果是
b
也就是说hasOwnProperty能检测到自身属性,不包含继承属性。总结一下:
superC.hasOwnProperty(); //自有属性为真 superC.propertyIsEnumerable(superC); //可枚举属性为真 Object.keys(superC); //全部可枚举自有属性 Object.getOwnPropertyNames(superC); //全部自有属性 for x in superC //自有及其其原型链上继承到的可枚举属性
看看这段代码:
for(var i = 0; i < 5; i++){ setTimeout(function(){ console.log(i); },100*i) }
你可能会认为结果是
0 1 2 3 4
可是结果是
5 5 5 5 5
缘由就是 settimeout的回调函数执行时,for循环已经执行完毕。i变成了5,而回调函数最近的原型做用域上的i(此处也就是全局做用域)就是5,天然就是5了。要达到想要的效果正确的作法是:
for(var i = 0; i < 5; i++){ (function(i){setTimeout(function(){ console.log(i); },100*i)})(i) }
即用 (function(i){})(i);来产生一个当即做用域,保证settimeout回调函数执行的时候最近的原型做用域的i就是当时循环的i。
说到这个就得谈谈闭包:所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(一般是一个函数),于是这些变量也是该表达式的一部分。用大白话:闭包的做用就是在一个函数执行完并返回后,并不回收该函数所占用的资源,由于该函数的内部函数(或属性)的执行须要依赖该函数中的属性。
function outF() { var count = 0; return function inF(){ count++; console.log(count); } } var inF= outF(); inF(); // 1 inF(); // 2
可见outF执行事后,其属性count并未回收。回到上面那个错误的循环,for建立了若干个闭包,每一个闭包共享上下文环境 i。由于for(很大可能)会先跑完,因此运行回调函数的时候 i 已经变成了5。而正确的循环中,也经过匿名函数建立了闭包,这个匿名函数做为外部函数,经过当即调用,使得settimeout不须要共享循环中的i,而是独享每一次循环不一样的i。
做用域真的能够说是JavaScript的一个问题,var 声明是具备整个函数内部的可见性,而js1.7以后的let的出现算是弥补了这个缺陷(至因而不是缺陷就见仁见智了),let 声明的变量只属于就近的花括号内部,看下面的代码
for(let i = 0; i < 5; i++){ setTimeout(function(){ console.log(i); },100*i) }
结果就是
0 1 2 3 4
区别就在于使用了 var 和 let 来生明变量i,let 使得每次程序进入花括号就产生了一个块级做用域,也就是说settimeout的回调函数做用域链中最近的i再也不是全局的i,而是块级做用域的i,也就是每一次不一样的0,1,2,3,4而不是全局i最后是5。let产生了和上面当即做用域相同的效果。
很是奇怪,在Javascript中没有很是简单的获取一个对象的类别的方法,instanceof 是要检查原型链的,相似于isPropertypeOf,因此没法一步到位得到最精确地的对象类型,通常用下面这个 classof能够得到最精确的类型
var a = new Date(); function classof(o){ if(o===null) return "Null"; if(o===undefined) return "Undefined"; return Object.prototype.toString.call(o).slice(8,-1); } console.log(classof(a));//Date
之因此不直接用Object.prototype.toString,是由于好多类型重写了这个方法,不能保证它输出是
[object class],因此使用Function.call方法。
nodejs中分号; 是可选的,这个有必定程度的便利,但是在我看来它更多的是形成了混乱,js会在必要的时候帮助咱们添加分号,它有本身的添加规则(咱们天然都懒得去记)。
var a a = 3 console.log(a)
这个会解析成
var a; a = 3;console.log(a);
没啥毛病。
但是
var equa = function(a,b){ if(a===b){ return true; } return false; } console.log(equa(5,5));//undefined
就没有按预期执行,由于它解析成了 return;true;返回的天然是undefined。
因此避免混乱最简单的作法就是老老实实的给每一句都加上 ;
a = [];a[1000]=5; //a.length=1001,虽然a只有一个元素 a1 = [,,,]; // [undefined,undefined,undefined] a2 = new Array(3); //数组根本没有元素 0 in a1;// true ,如上所说,in 能够区分元素不存在和元素值存在且为undefined的状况 0 in a2;// false
filter():“过滤”功能,数组中的每一项运行给定函数,返回知足过滤条件组成的数组。
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; var arr2 = arr.filter(function(x, index) { return index % 3 === 0 || x >= 8; }); console.log(arr2); //[1, 4, 7, 8, 9, 10]
every():判断数组中每一项都是否知足条件,只有全部项都知足条件,才会返回true。
var arr = [1, 2, 3, 4, 5]; var arr2 = arr.every(function(x) { return x < 10; }); console.log(arr2); //true var arr3 = arr.every(function(x) { return x < 3; }); console.log(arr3); // false
some():判断数组中是否存在知足条件的项,只要有一项知足条件,就会返回true。
reduce() 和 reduceRight(),这两个方法都会实现迭代数组的全部项,而后构建一个最终返回的值。reduce()方法从数组的第一项开始,逐个遍历到最后。而 reduceRight()则从数组的最后一项开始,向前遍历到第一项。
这两个方法都接收两个参数:一个在每一项上调用的函数和(可选的)做为归并基础的初始值。
传给 reduce()和 reduceRight()的函数接收 4 个参数:前一个值、当前值、项的索引和数组对象。这个函数返回的任何值都会做为第一个参数自动传给下一项。第一次迭代发生在数组的第二项上,所以第一个参数是数组的第一项,第二个参数就是数组的第二项。
下面代码用reduce()实现数组求。
var values = [1,2,3,4,5]; var sum = values.reduceRight(function(prev, cur, index, array){ return prev + cur; },0); console.log(sum); //15
调用函数有4种方式,不一样之处就在于调用上下文,也就是关键字(不是变量,不是属性名)this的值
JavaScript 权威指南 第六版;
JavaScript 语言精粹;
深刻浅出 nodejs;
http://blog.csdn.net/u0146071...
https://developer.mozilla.org...