Javascript模式(第二章基本技巧)------读书笔记

时间:2024-08-09 00:07:38

本章主要帮助大家写出高质量的JS代码的方法,模式和习惯,例如:避免使用全局变量,使用单个的var变量声明,缓存for循环的长度变量length等

一、尽量避免使用全局变量

  1 每一个js环境都有一个全局对象,通过this可以访问,创建的每一个全局变量都归这个全局对象所有,在浏览器中,这个全局对象this等于window(在其他环境中,this对象不一定是window对象) 

var sss="sss";
this.sss;//"sss"
window.sss;//"sss"

  1.1全局变量导致的问题

    1,与第三方JS库发生命名冲突;2,与广告合作伙伴的脚本发生命名冲突;3,与来自第三方的统计脚本或者分析脚本发生命名冲突;4,代码移植,如果你的代码换个环境执行,可能与另外一个环境的代码相冲突

    2, 全局变量总是出现的原因:1,JS有暗示全局变量的概念,即任何变量,如果没有声明过,那么就是全局变量;2,隐式的创建了全局变量反模式--使用var声明的链式赋值

var a=b=0;
//这一切的原因是操作符的优先级,=操作符的优先级是从右向左,所以上面的例子,实际上相当于
//var a=(b=0);因此相当于隐式地创建了全局变量b,
/*正确的写法:
*var a,b,c;
*a=b=c=0;
*/

  1.2 变量释放(即delete删除变量)时的副作用

    1,隐含的全局变量(即不声明而直接使用的变量)与明确定义的全局变量(即使用var定义的全局变量)的不同之处在于,能否使用delete操作符删除该变量

      首先我们理解一下delete操作符,delete操作符是用来删除对象的属性的,使用var定义的全局变量,不能被删除,而隐含的全局变量是可以删除的,因此隐含的全局变量其实并不是真正意义上的变量,而是作为全局对象的属性存在的

var m="111";
delete m;//false
console.log(m);//
fff="d";
delete fff;//true
console.log(fff);//Uncaught ReferenceError: fff is not defined

  1.3 访问全局对象

    按照以下方式获取全局对象,因为函数的调用(这里不包括使用new操作符执行的函数)一般都指向全局对象    

    但是在ECMAScript5的严格模式下不能如此使用,严格模式下对全局对象的访问:将库代码打包到一个直接函数,然后传递一个引用给this,即把this看成传递到直接函数的一个参数。

var global=(function(){
  return this;
})();

  1.4 单一var模式:只使用一个var,在函数的顶部对该函数中所有的变量进行声明

  优点:

    1、在一个位置可以查找到函数所需要的所有局部变量;

    2、防止变量未声明就使用;

    3、更少编码;

    4、声明的时候进行初始化,防止在后期使用时出现逻辑错误(数字变量当成字符串这样低级的错误)

    5、将DOM引用赋值给局部变量,不需要每次都去重新进行DOM搜索,可大量节约时间

function fun(){
var a=1,
     b=2,
     my={},
    i,
    j;
//函数体
}
function fun(){
var element=document.getElementById("result"),
   style=element.style;
}

  1.5、提升:无论在js函数内的任意位置声明的变量,效果都等同于在函数顶部进行声明

    JS允许在函数中的任意位置声明变量,但无论在哪里声明最终都会被提升到函数的顶部,因此先使用后声明可能会导致逻辑问题

    js分为预编译阶段与执行阶段,

    预编译阶段:

      1 对使用function语句声明的函数进行处理,不仅按照函数名按照变量标识符进行索引,对函数体也进行处理(即对函数名(也可以认为是变量名)进行赋值),如果出现同名函数,则会用后者覆盖前者(即后者的赋值覆盖前者)

        2 对匿名函数在此阶段视而不见

     3 对使用var声明的变量进行索引,但是对变量的初始化忽略掉,

      在预编译阶段遇到var声明的变量,如果前面没有做过初始化,就是undefined,如果该变量在前面是函数名,那么其值就是函数体,这里不做覆盖

function fun(){}
/*变量名fun在预编译阶段,不仅对fun这个变量名进行索引,对其函数体也进行了处理,即fun的值为函数体*/
console.log(fun);//function()
var fun="";
/*使用var声明的变量名fun在预编译阶段,只对其变量名进行索引,不对其进行赋值,所以这fun的初始化值不会覆盖上面的函数体,但是在执行阶段会覆盖*/
console.log(fun);//

    执行阶段:

      1 对匿名函数按表达式逐行进行解释执行

      2 为预编译阶段索引的变量读取初始值,由于执行阶段是逐行解释执行的,所以如果在赋值语句前面进行调用的话,值应该为预编译阶段的

function fun(){
alert(myName);//undefined
var myName="local";
alert(myName);//local
}
fun();

二、for循环

  for循环一般用于遍历数组或者类数组对象(arguments,html容器等)

  优化点:1 对数组的长度进行缓存,var len=arr.length

      2 使用单一的var模式,把所有的变量都提到函数体的开始,使用一个var进行声明

      3 i++替换掉i=i+1与i+=1

      4 在没有要求的时候,递减到0,即i--

  1 一般情况下的for循环

for(var i=0;i<arr.length;i++){
//对数组或者类数组对象中的元素的操作
}

  缺点:显然每次循环都会计算一下要访问数据的长度,这样效率就会降低,尤其是当访问的是HTML容器对象时

  改进:将要访问数据的长度缓存起来,对访问速度的提升相当显著,ie可提高170倍

for(var i=0,max=ayy.length;i<max;i++){
}

  2  进一步改进:结合我们前面提到单var变量模式

function fun(){
var i=0,ayy=[],max;//将所有该函数中用到的变量都声明在函数的顶部
for(i=0,max=ayy.length;i<max;i++){
}
}

  该模式的缺点:复制粘贴的时候,要保证将所需变量的声明全部复制进去

  3 JSLint推荐使用++与--,即逐步递增或者递减(这个有不同的意见,可以保留)

  4 更进一步改进:从最后一个元素,逐个遍历到第一个元素,将i与0比较,比i与非0的max比较效率要高

var a,b,c,d,e,f;
a=+new Date();
for(i=10000000;i>=0;i--){
}
b=+new Date();
console.log(b-a);
// c=+new Date();
for(j=0;j<=10000000;j++){
}
d=+new Date();
console.log(d-c);
//
e=+new Date();
for(k=10000000;k--;){
}
f=+new Date();
console.log(f-e);//

三、for-in循环

  for-in主要用于遍历非数组对象,称之为枚举

1 当要遍历对象属性,并过滤掉原型中的属性和方法时,使用hasOwnProperty()方法

var man={
a:1,
b:2,
c:3
},i; for(i in man){
if(man.hasOwnProperty(i)){//当确定不了对象属性和原型中的内容时,使用hasOwnProperty方法加以判断,如果可以确定则可以省略该判断,提高效率
console.log(i+":"+man[i]);
}
}

  2 在上面我们提到对原型链中的属性和方法进行过滤时,使用hasOwnProperty方法,由于hasOwnProperty方法是属于Object原型的方法,所以我们可以这样使用,这样避免命名冲突(即man对象也有一个hasOwnProperty方法,那么就与我们想要过滤使用的hasOwnProperty方法冲突了)

var i,hasOwn=Object.prototype.hasOwnProperty,man={
"name":"jim",
"age":12
};
for(i in man){
if(hasOwn.call(man,i)){
console.log(i+":"+man[i]);
}
} var i,hasOwn=Object.prototype.hasOwnProperty,man={
"name":"jim",
"age":12,
  hasOwnProperty:function(){console.log("man's hasownproperty")}
};
for(i in man){
  if(man.hasOwnProperty(i)){
    console.log(i+":"+man[i]);
  }
}

四、不要给内置对象的原型增加方法(这里的内置对象指的是Date,Math,Array,String,Event,Object等)

  我们经常给构造函数的原型增一些方法,但是给js的内置对象的构造函数的原型增加方法会严重影响可维护性,因此这里不推荐

  下面的几种情况例外

    1,可以添加ECMAScript5中描述的确尚未实现的方法,等待ECMAScript加以实现

    2,某些浏览器的JS引擎已经实现了该方法

    3,写成文档形式,与团队充分沟通

  给内置对象添加自定义方法

if(typeof Object.prototype.MyMethod!=="function"){
Object.prototype.MyMethod=function(){
};
}  

五、switch模式

  1 每个case语句结尾都有一个break

  2 使用default来作为switch的结束

六、避免使用隐式类型转换

  1 什么是隐式类型转换:false==0、""==false

  2 为了避免出现上面的情况,我们在使用比较语句的时候尽量使用===和!==

  6.1 避免使用eval

    1 eval可以将任何的字符串当作js代码执行

    2 将ajax返回的数据字符串转换成对象时,不推荐使用eval,推荐JSON.parse或者JSON.org网站的类库,因为eval执行的字符串可能是一家被篡改过的代码

    3 setInterval/setTimeout传递参数时,也会导致类似于eval的隐患,因此尽量避免使用 

    4 new Function()与eval和相似,使用时要小心

      如果一定要使用eval,可以使用new Function来替代,因为new Function将在局部函数空间运行,因此代码中var定义的变量不会成为全局变量

      另外一个避免eval中使用var定义的变量成为全局变量,将eval放到一个即时函数中 

      new Function(或者Function),这个方法是只看到全局变量,对局部变量影响较小     

setTimeout("fun(1,2,3)",100);//反模式

//推荐模式
setTimeout(function(){
fun(1,2,3);
},100);
var jsString1='var aaaaaa=1;console.log(aaaaaa);';
var jsString2='var bbbbbbb=2;console.log(bbbbbbb);';
var jsString3='var ccccccc=3;console.log(ccccccc);';
eval(jsString1);//
new Function(jsString2)();//2new Function将在局部函数空间运行

/*另外一个避免eval中使用var定义的变量成为全局变量,将eval放到一个即时函数中*/
(function(){
eval(jsString3);
})();//
console.log(aaaaaa);//1eval代码里面var定义的变量会成为全局变量
console.log(bbbbbbb);//ReferenceError: ccccccc is not defined
console.log(ccccccc);//ReferenceError: ccccccc is not defined
 new Function(或者Function),这个方法是只看到全局变量,对局部变量影响较小

function fff(){
var bbb='bbb';
var jsString='console.log(bbb);'
new Function(jsString)();//或者Function(jsString)()
}
fff();//ReferenceError: bbb is not defined
var a="global a";
function fff(){
var a='local a';
var jsString='console.log(a);'
new Function(jsString)();//或者Function(jsString)()
}
fff();//global a

七、使用parseInt方法时,第二个参数尽量不要省略  

八、编码约定

  8.1 缩进:使用tab键进行缩进

  8.2 大括号:for与if语句最好都使用大括号

  8.3 开放大括号的位置:和语句放在同一行  

/*根据分号插入机制,也就是一行结束如果后面没有分号,会自动插入分号*/
return
{
"name":"Amy",
"age":18
}; /*相当于
return ;
{
"name":"Amy",
"age":18
};
*因此下面这种方式更合适
*/
return {
"name":"Amy",
"age":18
};

九、命名约定

  9.1 构造函数的首字母大写

  9.2 分隔单词:函数/方法名:小驼峰式;变量:小写字母,下划线分隔;常量:全部大写;私有变量/方法:下划线做前缀

十、编写注释

十一、编写API文档

十二、编写可读性强的代码

十三、同行互查

十四、在正式发布时精简代码

十五、运行JSLint