全局变量
全局变量在所有作用域都可见,因此可以在任意地方被修改。这一点导致对于大型项目而言,代码会变得极其复杂且不稳定。除此之外全局变量还有可能会与子程序的某个变量命名冲突,导致程序难以调试。
定义全局变量的三种方法:
- 在函数外通过
var
定义:var foo = value;
。 - 在全局对象添加属性:
window.foo = value;
。 - 未声明变量:
foo = value;
。
作用域
JavaScript有块语法,但没有块级作用域(块内部定义的变量外部不可见)。相对的JavaScript提供了函数作用域,在函数内定义的变量任何位置都可见。因此在JavaScript中所有变量的声明最好放置在函数内最开始的位置(在其他语言中,好的习惯是变量声明最好放在第一次使用它的地方,JavaScript因为声明提前的机制,导致这里有所不同)。
自动插入分号
Javascript用分号表示一句代码的结束,如果语句独占一行可以省略分号。在没有分号会影响代码解析的情况下,Javascript会补填分号。 但是return,break,continue三个关键字不能单独占用一行,因为这会导致JavaScript对其补充分号使语句出现歧义。
//错误,JavaScript会解析为return;会返回undefined。
return
{
status: true
}
//应该写作
return{
status: true
}
字符长度
JavaScript采用UCS-2编码(早期有UCS和Unicode两个团队做字符集统一,UCS-2编码规范要比UTF-16规范早,后来UCS-2并入UTF-16,另外在设计JavaScript时还没有UTF-16,所以采用了UCS-2)。UCS-2编码采用两个字节即16位表示一个字符,而Unicode用两个字节表示基本平面字符(Unicode字符是分区定义的,每个区存放65536即216个字符,称为一个平面。共17个平面即25,前面的65536个字符是最常见的字符,这个平面叫做基本平面),用四个字节表示其他字符。这样JavaScript对于基本平面外的字符采用Unicode-16规范用四个字节表示,但是对于JavaScript而言字符长度就是16位值(两个字节,一个字节占八位)的个数,所以这些字符在JavaScript中长度为2。
typeof
typeof返回一个字符串表示的运算数类型,不过typeof null
返回的字符串是"object"
。也就是说typeof是不能识别null和对象的。所以可以通过如下表达式判断是否为对象。
if(my_value && typeof my_value === 'object'){
//my_value is a object
}
因为null值为假,object值为真,所以可以通过值判断是否为null,再判断是否为object。
typeof也无法辨认数字与NaN。JavaScript提供了isNaN函数来辨别数字与NaN。
typeof NaN; //=>"number"
isNaN(12); //=>false
isNaN(NaN); //=>true
isNaN("12"); //=>false
判断是否为数字的最好方法是通过isFinite方法,它会把运算数转换为一个数字,同时排除NaN和Infinity。
var isNumber = function isNumber(val){
return typeof val === 'number' && isFinite(val);
}
另外typeof也无法辨别数组和对象,要判断是否为数组要通过constructor属性,或者通过原型。
if(Object.prototype.toString.apply(val)==='[object Array]'){
//val is a array.
}
parseInt
parseInt会把字符串转换为整数,对于传入的参数遇到非数字后会停止解析。
parseInt("16"); //=>16
parseInt("16 tons"); //=>16
parseInt("16 tons 32"); //=>16
如果字符串第一个数字是0,那么会按照八进制转换,这里要注意在八进制中8和9不作为数字处理。
parseInt("08"); //=>0
parseInt("09"); //=>0
parseInt("10"); //=>8
parseInt的第二个参数用于区分采用哪种进制转换字符串。
parseInt("10", 8); //=>8
parseInt("10", 10); //=>10
15.1.2.2: The specification of the function parseInt no longer allows implementations to treat Strings beginning with a 0 character as octal values.
parseInt中不再将以0开头的字符串作为八进制转换,在新版本浏览器中已经根据ES5新规范实现。在旧版本浏览器中可以始终指定第二个参数为10避免这个错误。
parseInt("8", 10); //=>8
parseInt("9", 10); //=>9
浮点数
JavaScript的Number类型以双精度(64位)存储,同时JavaScript遵循二进制浮点数算数标准(IEEE754),这个算数标准计算的结果本身是不准确的。在JavaScript中0.1+0.2=0.30000000000000004
,而PHP中0.1+0.2=0.3
。是因为PHP默认的小数值精度导致的计算结果如此,如果对其做格式转换printf("%.17f", 0.1 + 0.2);
那么PHP中取得的结果也是0.30000000000000004
。这是二进制计算标准导致的与语言无关。
对于这种计算可以转换大单位,比如将角转换为元,克转换为千克。
NaN
NaN是IEEE754定义的特殊数量值,表示非数字(Not a Number)。typeof无法区分NaN和数字,同时NaN也不等于它自己。
NaN === NaN; //=>false
伪数组
JavaScript中没有真正的数组,JavaScript中数组是key值为数字的对象。因此JavaScript数组不需要特别声明维度,也不会有越界问题,但是相对的其性能要比真正的数组低。 另外arguments也不是数组,它只是一个有length属性的对象,因此它无法使用Javascript中Array定义的方法。
hasOwnProperty
hasOwnProperty常用作过滤器以避开for in语句的隐患,但是hasOwnProperty不是一个 运算符(关键字)而是一个方法,所以它有可能被同名函数或者变量替换。
var a = {};
a.hasOwnProperty = {};
a.hasOwnProperty('hasOwnProperty');
//=>TypeError: a.hasOwnProperty is not a function(…)
Object.prototype.hasOwnProperty.apply(a, ['hasOwnProperty']);
//=>true
==
JavaScript有两组相等运算符==,!==
和===,!===
,其中==,!==
在做比较时会做数值转换。
'' == '0'; //=>false
'' == 0; //=>true
false == 'false'; //=>false
false == '0'; //=>true
false == undefined; //=>false
false == null; //=>false
null == undefined; //=>true
因此建议不要使用==,!==
,只用===,!===
即可。
with
with语句用来快速访问对象属性,但是它本身阻断了变量名词法作用域,影响了JavaScript处理器的速度,同时结果无法预测。
with(obj){
a = b;
}
//等价于
if(obj.a === undefined){
//如果obj中不存在a属性,赋值给全局变量a,否则赋值给obj.a。
//obj中不存在b属性,则将全局变量b赋值给a,否则将obj.b赋值给a。
a = obj.b === undefined ? b : obj.b;
}else{
obj.a = obj.b === undefined ? b : obj.b;
}
eval
eval传递一个字符串给JavaScript编译器,并执行其结果,使用eval会使代码难以阅读,并且运行效率降低,同时它也会降低程序的安全性。 Function构造器也是eval的一种形式,也应该避免使用。 在setTimeout和setInterval中传入字符串参数,也会同eval一样处理,也要避免。
位运算
JavaScript位运算符和Java一样,Java的位运算符用来处理整数,但是JavaScript没有整数,所以它要将双精度转位整数然后运算再转回去。在多数语言中位运算是接近硬件处理的,但一般情况下JavaScript执行环境一般接触不到硬件,所以位运算反而很慢。
函数语句与函数表达式
JavaScript中除了函数构造器外还可以通过函数语句和函数表达式创建函数对象,两者之间是有区别的。 函数语句在解析时会声明提前,在条件语句中禁止使用函数语句。而函数声明语句允许在任何地方调用,但是调用要在定义之后,否则会报错。
console.log(a()); //=>a, 声明提前
console.log(b()); //=>TypeError: b is not a function(…)
//函数语句
function a(){
return "a";
}
//函数表达式
var b = function(){
return b;
}
类型包装对象
JavaScript有一套类型包装对象,通过valueOf可以获取到这个包装对象的值。
var b = new Boolean(false);
console.log(b); //=>Boolean {[[PrimitiveValue]]: false}
console.log(b.valueOf()); //=>false
这一些列特性会让人困惑而且也是没有价值的,通过字面量声明变量更加灵活和简洁。
new
JavaScript中new运算符会创建一个继承于其运算数(构造函数)的原型对象的对象,然后调用这个运算数,把新创建的对象绑定给this。如果忘记使用new关键字,那么函数将作为普通函数调用,并不会绑定一个新的对象给this,this会指向全局,这样会造成全局变量的污染。所以作为构造器函数的运算数一般都以大写字母开头命名。
function Pet(name){
this.name = name;
}
c = new Pet("cat");
d = Pet("duck"); //this绑定到全局变量
console.log(c.name);
console.log(name); //全局变量被污染