作为一个前端新人,多读书读好书,夯实基础是十分重要的,正如盖楼房一样,底层稳固了,才能越垒越高。从开始学习到现在,基础的读了红宝书《JavaScript高级程序设计》,犀牛书《JavaScript权威指南》,特别是红宝书,是把我领进js领域的一本书。现在到了进阶阶段(可能红宝书还会回去继续翻),准备开始读《Effective JavaScript》,刚拿到手,很薄的一本书,也把读书笔记整理下来,希望养成一个良好的读书习惯。
让自己习惯JavaScript
了解JavaScript的版本
目前主流JavaScript版本任然是ES5,在ES5开始引入严格模式
use strict;//在文件头或者函数体顶部使用,就是严格模式
一般来说,我们如果想编写一个通用型插件,都需要使用严格模式来进行开发,但是在使用严格模式开发时会出现一个问题,工程先后引入两个文件file1.js和file2.js,file1.js使用的严格模式进行开发,file2是非严格模式,那么下面的代码会出问题。
<script src="file1.js" />
<script src="file2.js" />
// 这么引入两个文件,file2也会按照严格模式运行,那么如果file2.js出现了非严格模式的行为,程序就会报错
因此,在严格模式下开发通用插件的时候需要注意这个问题
解决办法:使用立即执行函数IIFE进行开发。
(function(){
'use strict';
function foo(){
//你的插件内容
}
})();
理解JavaScript浮点数
JavaScript中只有一种数值类型,无论是整数还是浮点数,都被表示为Number类型,可以使用typeof关键字查看它们。
typeof 17.2;//'number'
typeof 26;//'number'
JavaScript之中所有的数字类型都是双精度浮点数,整数也可以用双精度浮点数来表示,如JavaScript之中所能表示的整数范围是(2e-53~2e53).
JavaScript中的浮点数是不精确的
其中最著名的例子就是:0.1+0.2;//0.30000000000000004
JavaScript中的数字类型在表示浮点数上不一定精确,使用时要慎重
当心隐式的强制转换
终于到了JavaScript强大的饮食转换了,先来看一个简单的例子
(([][[]]+[])[+!![]]+([]+{})[!+[]+!![]]).toUpperCase();
猜猜上面的代码的结果是什么?答案是:NB,没错,JavaScript就是这么NB。根本毫不相关的东西,这也正是体现了JavaScript隐式转换的强大之处,感兴趣的可以百度一下上面为什么会出现这样的结果。
隐式转换的一些原则
-
布尔型与数字类型相互转换
其中,true对应1,false对应0.3+true; // 4 while(1){ //相当于while(true) //无限循环 }
-
字符串与数字相互转换
在这里不得不提的就是JavaScript对运算符加号“+”的重载,JavaScript的加号不仅可以进行数字相加,还可以进行字符串的拼接工作,还可以进行数字与字符串的隐式转换工作。例如下面的几行代码2 + 3; // 5 "hello" + " world";// "hello world" 2 + "3"; // "23" // 这里还有个十分重要的,数字字符串之前如果只存在一个加号,会将字符串隐式转换成数字类型 +"3";// 3 +"3fsdf"; //NaN 1+(+"3");// 4,必须有括号,否则JavaScript会把两个加号编程自加运算,从而报错表达式不合法 // 需要注意的是,对于加法来说,隐式转换的优先级字符串高于数字类型,而位运算和乘法除法运算数字类型高于字符串类型 "17" * 3; // 51 "8" | "1";// 9
-
强制转换的隐藏错误
null会在运算中隐式转换成0 一个未被定义的变量(undefined)会被隐式转换成浮点类型的NaN。 //关于NaN的几点注意 var x = NaN; x === NaN;//false isNaN(NaN);// true //但是isNaN对于其他不是浮点类型的数来说,也会隐式转换成NaN,因此这个API存在问题 isNaN('string');//true isNaN(undefined);//true isNaN({});//true //因此如果想判断是不是正正的NaN,我们可以写一个函数来判断 function isReallyIsNaN(number) { return number !=== number; //利用NaN不等于自身这一特性 }
-
对象的隐式转换
默认来说,对象也会被隐式转换成字符串"the Math object: "+ Math;//"the Math object: [object Math]",Math对象被隐式转换为字符串 // valueOf方法是真正为那些代表数值的对象(Number)而设计的 var obj = { toString: function() { return '[object Object]'; }, valueOf : function() { return 26; } } 'the obj true value is:'+obj;//'the obj true value is:26',
-
真值运算
JavaScript中的逻辑运算if、||和&&理论上是可以接受任何值的,因为JavaScript中
所有类型都会在逻辑运算中转换成布尔值JavaScript中有7个假值:false、0,-0、""、NaN、null、undefined
由于数字和字符串可能为假值,所以,使用针织运算检查函数参数或者对象属性是否已经存在不是绝对安全的。
function point(x){ if(!x){ return 26;//如果x为假值,不传或者传入0,函数都会返回26 } } point(0);//x:26 //但是,传入0这个值是完全有可能的,所以这种判断形势是不正确的 function point(x){ if(typeof x === 'undefined'){ return 26; } //或者使用if(x === undefined)来判断也可以 }
原始类型优于封装类型
JavaScript对象拥有六个原始值基本类型: 布尔值,数字,字符串,null,undefined和对象。
需要注意的是,使用typeof对null判断得到的结果是'object',ECMAScript标准描述null是一个独特的类型
创建原始类型与创建基本类型
//创建一个String对象,内部封装一个字符串值
var s = new String("hello");
s + ' world';// 'hello world'
//创建一个原始类型
var str = 'string';
//封装类型本质是一个对象,原始类型才是基本类型,可以使用typeof来查看
typeof s;// 'object'
typeof str;// 'string'
//由于封装类型自身是一个对象,因此即使内部封装的值相同,两个对象也是不相等的
var s1 = new String('string');
var s2 = new String('string');
s1 === s2;//false
s1 == s2;//false
总体来讲,一般不需要使用封装类型来代替基本类型,因为基本类型无论是从声明还是使用都较为方便。而且,基本类型本身是没有任何方法的,不过可以隐式转换成String对象来执行其内部方法:
var s = 'hello';//基本类型
s.toUpperCase();//HELLO,将s隐式转换成String对象
【注】隐式封装的类型有一点需要特别注意:每一次隐式封装都会产生一个新的String对象,原来的对象会被抛弃,也就是说新产生的隐式封装对象生命周期就是所在行代码执行完成。
'string'.value = 26;
'string'.value;//undefined
第二行代码隐式封装形成了新的String对象,新的String对象内部没有value这个属性值,因此是undefined
对JavaScript原始类型值设置属性是没有意义的
避免对回合类型使用 == 运算符
由于JavaScript隐式转换的存在,因此,在使用 == 运算符的时候,有时候可能不会得到我们所预期的结果。
'1.0e0' == { valueOf: function(){ return true } };
//结果是true,因为右侧valueOf结果是true,会被隐式转换成数字1,根据隐式转换法则,左侧字符串也会被隐式转换成1,因此比较是相等的。
// 使用==运算符比较时一些特殊情况
null == undefined;//true,这种情况永远是true
null/undefined ==
string/number/boolean;//false,这种情况永远是false
string/number/boolean == string/number/boolean;// 将原始类型转换为数字进行比较
string/number/boolean == Date对象;// 先将原始类型转化成数字,再将date类型转化成原始类型(优先尝试toString,其次尝试valueOf)
string/number/boolean == 非Date对象;// 先将原始类型转化成数字,再将date类型转化成原始类型(优先尝试valueOf,其次尝试toString)
// 关于Date对象比较问题
var date = new Date('1993/07/11');
date == '1993/07/11';//false
这是因为Date对象调用toString方法之后转换的不是我们所熟知的字符串形式。
在浏览器中调用date.toString(),结果是"Sun Jul 11 1993 00:00:00 GMT+0800 (中国标准时间)"
//将Date类型字符串转换成我们定义格式的字符串
function toYMD(date){
var y = date.getYear() + 1,
m = date.getMonth() + 1,
d = date.getDate();
return y + '/'
+ (m < 10 ? '0' + m : m) + '/'
+ (d < 10 ? '0' + d : d);
}
toYMD(new Date('1993/07/11')); //"1993/07/11"
在比较的时候,最好使用严格相等运算符 ===,这是一个良好的习惯
了解分号插入的局限
JavaScript语法对于分号没有硬性的规定,其语言机制自身会自动添加分号而区分开语句,了解分号插入的规则,对于提高JavaScript代码的严谨性有着巨大的帮助。
分好仅在 } 标记之前、一个或多个换行符之后和程序的输入结尾被插入
换而言之,我们只可以在一行、一个代码块和一段程序结束的地方省略分号,其他任何地方省略分好都有可能出现错误。
function foo1(r){ r += r; return r };//合法
function foo2(r){ r += r return r };//不合法
分好仅在随后的输入标记不能被解析时插入
换句话说,分好是JavaScript为我们提供的一种错误校正机制
a = b
(f());
// 上面这段代码会被解析成
a = b(f());//这是一条合法语句
a = b
f();//下面这段代码会被解析成两条独立的语句,因为 a = b f()是不合法的语句
换句话说,其实JavaScript只是为我们机械化的解决了语法问题而已,如果解析不合法,就会为我们通过插入分号来解决问题,但是,可能不是我们所预期的,因此,规范化的代码才是重中之重,尽量不要省略分号。
另一个比较重要的情况是:return语句,在return关键字和其可选参数之间一定不可以包含换行符。
return {}; //一条语句,返回一个空对象
return
{};
//上面语句等价于
return ;
{}
;
也就是会自动在return后面的换行符前插入一个分号
另外一种特殊情况就是++运算符和--运算符
a
++
b;
//会被解析成
a;
++b;
在return、throw、break、continue、++、--的参数之前不可以换行
分号不会作为分隔符在for循环空语句的头部被自动插入
也就是说,for循环体内必须显式的包含分号,否则会出错
for(var i = 0, total = 1 // 解析出错
i < n
i++)
视字符串为16位的代码单元序列
几种流行的Unicode编码:utf-8、utf-16、utf-32
JavaScript字符串是由16位代码单元组成,而不是由Unicode代码点组成
JavaScript使用两个代码单元表示2e16及以上的Unicode代码点。这两个代码单元被称为代码对。
代理对甩开了字符串元素计数,length、charAt、charCodeAt方法以及正则表达式模式受到了影响