现在随着javascript日趋流行,编写可维护的js也变得重要,根据工作中遇到的问题,今天写下这遍文章供大家参考(会持续更新),大家如有问题可以沟通!
1,正确的检测数据类型
(1)大家都知道typeof可以返回一个用于识别其运算数类型的字符串;对于任何变量来说,使用typeof可以返回以下6中类型之一:
number,string,boolean;object;function;undefined;
不过,对于null来说,typeof返回 的却是object;因此我们进行简单封装下:
function type(o){
return (o===null) ? 'null' : typeof o;
}
但是,一定要注意,typeof不能检测复杂的数据类型,以及各种特殊的用途的对象;如正则表达式对象;date对象;数学对象等;
如果,对于对象或数组,我们可以用constructor属性,该属性值引用的是原来构造该对象的函数,如果结合typeof和constructor,基本上可以检测数据类型;
(2)当然,使用constructor可以判断大部分的数据类型,但是对于null和undefined特殊值,就不能使用constrictor,因为使用javascript解释器会跑出异常,此时可以,先把值转为布尔值,如果为true,则说明不是null和undefined;然后再调用constructor
var val = undefined;
console.log(typeof val);//"undefined"
console.log(val && val.constructor);//undefined
var vals = null;
console.log(typeof vals);//object
console.log(vals && vals.constructor);//null
对于数值直接量,也不能直接使用constrctor,不过可以用小括号括起来,因为小括号运算符可以把树枝转换为对象;
console.log((10).constructor);//function Number() { [native code] }
(3)使用toString()方法检测对象类型是最安全/最准确的;调用其方法,把对象转换为字符串,然后通过检测字符串中是否包含数组所特有的标志可以确定对象的数据类型;
其方法返回的字符串如下:
【object class】
其中objce是对象的通用类型。class表示对象内部类型,内部类型的名称与该对象的构造函数名对应;例如Array对象的class是“Array”;
但是,要获取对象的class值的唯一方法是调用Obect对象定义的toString()方法,因为不同对象都会预定一自己的toString(),所以不能直接调用,例如:
var data = new Date();
console.log(data.toString());//返回utc时间
要调用Object的toString()方法,可以先调用Object.prototype.toString对象的默认的toString()方法,再调用该函数的apply()方法在想要检测的对象上执行;
var data = new Date();
console.log(Object.prototype.toString.apply(data));//[object Date]
下面是一个完整的检测数据类型的方法:
function typeOf(o){
var _toString = Object.prototype.toString;
var _type = {
'undefiend':'undefiend',
'number':'number',
'boolean':'boolean',
'string':'string',
'[object Function]':'function',
'[object RegExp]':'regexp',
'[object Array]':'array',
'[object Date]':'date',
'[object Error]':'error'
};
return _type[typeof o] || _type[_toString.call(o)] || (o ? 'object':'null');
}
var a = Math.abs();
console.log(typeOf(a));//number
上面的方法对于自定义的对象是无效的;这是因为自定义对象转换字符串后,返回的值时没有规律的;
2,正确处理javascript特殊值
(1)正确使用NaN和Infinity
为了方便检测NaN值,js提供了静态方法isNaN,以辨数字和NaN的区别;
其实判断一个值是否可以用数字的最佳方法是使用isFinite函数,因为它会先刷掉NaN和Infinity(无穷大);使用inFinite函数能够检测NaN,正负无穷大;不幸的是,它会试图把它的运算数转换为一个数字,因此,如果值不是一个数字,使用isFinite不是一个有效的检测方法,不过,我们可以封装下isNumber函数
var isNumber = function(val){
return typeof val === 'number' && isFinite(val);
};
console.log(isNumber('11'));//false
(2)谨慎使用伪数组
要判断一个值是否为数组,必须使用constructor属性
var value = [];
if(value && typeof value === 'object' && value.constructor === Array){
console.log('true');//true
}else{
console.log('false')
}
3,推荐提高条件性能的策略
if(a){
if(b){
if(c){
if(d){
alert('所有条件都成立!');
}else{
alert('条件d不成立!')
}
}else{
alert('条件c不成立!');
}
}else{
alert('条件b不成立!');
}
}else {
alert('条件a不成立!');
}
从思维方向上来说,这种结构嵌套并没有错误;但是,如果这种多重结构嵌套,就会出现另外一种可能:a条件不成立,直接退出,而不管b,c,d条件是否成立,狠武断,给测试带来伤害;未来避免上述情况,我们可以采用排除法,即对每个条件进行判断,条件成立再执行特定的操作;
var flag = true;
if(!a){
alert('条件a不成立');
flag = false;
}
if(!b){
alert('条件b不成立');
flag = false;
}
if(!c){
alert('条件c不成立');
flag = false;
}
if(!d){
alert('条件d不成立');
flag = false;
}
if(flag){
alert('所有条件都成立!');
}
(2)获取字节长度问题
String.prototype.sumLength = function(){
var _b = 0,_l = this.length;
if(_l){
for(var i= 0;i<_l;i++){
if(this.charCodeAt(i) > 255){
_b +=2;
}else{
_b +=1;
}
}
return _b;
}else{
return 0;
}
}
var s = '我是andy';
console.log(s.sumLength());//8
(3)正确的检测数组类型
var isArray = function(val){
return Object.prototype.toString.apply(val) === '[object Array]';
}
4,使用arguments模拟重载
function sayHello(){
switch (arguments.length){
case 0:
return 'Hello';
case 1:
return 'Hello,'+arguments[0];
case 2:
return (arguments[1] == 'cn' ? '你好,':'Hello, ') + arguments[0];
}
}
console.log(sayHello());
console.log(sayHello('andy'));
console.log(sayHello('andy','cn'));
4,比较函数调用模式
var obj = {
value:0,
increment:function(inc){
this.value += typeof inc === 'number' ? inc :1;
}
};
obj.increment();
console.log(obj.value);//1
obj.increment(2);
console.log(obj.value);//3
(2)函数调用模式
当函数以次模式调用时,this被绑定到全局对象.这无非是语言设计上的错误.当若语言设计正确,当内部函数被调用时, this应该仍绑定到外部函数的this变量.不过,有一个狠容易的解决方法:如果改办法定义一个变量并将它赋值为this, 那么内部函数就可以通过这个变量访问this
(3)构造器调用模式var obj = { value:1, doub:function(){ var that = this; var helper = function(){ that.value = that.value *2; }; helper(); } } obj.doub(); console.log(obj.value);//2
js是一门基于原型继承的语言,改语言是无类别的,对象可以直接从其它对象继承属性. 当今大多数语言都是基于类的语言,虽然原型继承有着强大的表现力,但它偏离来主流用法,不被广泛理解; js为了能够兼容基于类语言的编写风格,提供来一套基于类语言的对象构建语法; 如果在一个函数前面加上new运算符来进行调用,那么将创建一个隐藏链接到该函数的prototype原型对象的新实例对象, 同时this将会被绑定到这个新实例对象上,注意,new前缀也会改变return语句的行为;var F = function(string){ this.status = string; }; F.prototype.getNum = function(){ return this.status; }; var f = new F('andy'); console.log(f.getNum());//andy
(4)apply调用模式js是函数式的面向对象编程语言,函数可以拥有方法.apply就是函数的一个基本方法, 使用这个方法可以调用函数,并修改函数体内的this值; apply方法包括两个参数:第一个参数设置绑定给this的值;第二个参数是包涵参数的数组;
var array = [5,4]; var add = function(){ var i,sum = 0; for(i=0;i<arguments.length;i+=1){ sum += arguments[i]; } return sum; }; var sum = add.apply({},array); console.log(sum);//9
上面代码构建一个包涵两个数字的数组,然后使用apply方法调用add()函数,将数组array中的元素值相加.
var F = function(string){ this.status = string; }; F.prototype.get = function(){ return this.status; }; var obj = { status :'objh' }; var status = F.prototype.get.apply(obj); console.log(status);//objh
上面代码构建了一个构造函数F,为该函数定义了一个原型方法get,该方法能够读取当前对象当status属性的值. 然后定义了一个obj对象,改对象包涵了一个status属性,使用apply方法在obj对象上调用构造函数F的get方法, 返回obj对象的status属性值5,使用闭包跨域开发
闭包是指词法表示包括不必计算的变量的函数,必包函数能够使用函数外定义的变量;闭包结构有两个比较鲜明的特征:(1)封闭性(2)持久性对于一般函数来说,调用后会注销掉,而对于闭包来说,在外部函数被调用后,闭包依然保存在系统中,闭包的函数依然存在,从而实现对数据对持久使用。function bar(x){ var a = x; var b = function(){ return a; }; return b; } var c1 = bar(1); console.log(c1());//1---调用闭包函数 //在上面实例中,首先在函数bar结构体内定义两个变量,分别存储参数和必报结构,而闭包结构中 //寄存着参数值.当调用函数bar之后,函数结构被注销,它对局部变量也会跟着注销掉,因此变量a中 //存储的参数值也会随之丢失.但是由于变量b存储着必报结构,因此闭包结构内部的参数值并没有释放, //在调用函数之后,依然能够从闭包中读取到参数值 function f(x){//外部函数 var a = x;//外部函数的局部变量,并把参数值传递给它 var b = function(){//内部函数 return a;//访问外部函数中的局部变量 }; a++;//访问后,动态更新外部函数的变量 return b;//内部函数 } var c = f(5);//调用外部函数,并赋值 console.log(c(5));//调用内部函数,返回外部函数更新后的值6 //如果没有闭包函数的作用,那么这种数据寄存和传递就无法得以实施: function f2(x){ var a = x; var b = a; a++; return b; } var c2 = f2(5); console.log(c2);//5
6,推荐链式调用方法
在js中,很多方法没有返回值,一些设置或修改对象的某个状态却不返回任何值的方法就是典型的例子; 如果让这些方法返回this,而不是undefiend,那么就要启用级联功能,即所谓的链式语法. 在一个级联中,单独一条语句可以连续调用同一个对象的很多方法 如下面扩展String的3个方法Function.prototype.method = function(name,func){ if(!this.prototype[name]){ this.prototype[name] = func; return this; } }; String.method('trim',function(){ return this.replace(/^\s+|\s+$/g,''); }); String.method('writeIn',function(){ console.log(this); return this; }); String.method('alert',function(){ window.alert(this); return this; }); var str = ' abc '; str.trim().writeIn().alert(); //延伸 Function.prototype.method = function(name,func){ if(!this.prototype[name]){ this.prototype[name] = func; return this; } }; function Person(name){ this.name = name; } Person.method('hide',function(){ console.log(this.name); return this; }); Person.method('writeIn',function(){ console.log(2); return this; }); Person.method('alert',function(){ console.log(3); return this; }); var p = new Person('andy'); p.trim().writeIn().alert();
7,用局部变量访问集合元素
一般来说,访问任何类型的dom,当同一个dom属性或方法被访问一次以上,最好使用一个局部变量 缓存改dom成员.当遍历一个集合时,第一个要优化的是将集合引用存储与局部变量,并在循环之外 缓存length属性.然后,如果在循环体中多次访问同一个集合元素,那么使用局部变量缓存它. 下面例子,循环访问每个元素的3个属性.执行最慢的方法是每次都要访问全局变量document, 优化后的代码缓存了一个指向集合的引用,执行最快的方法是将集合的当前元素存入局部变量.//较慢 function collGlobal(){ var coll = document.getElementsByTagName('b'),len = coll.length,name = ''; for(var i = 0;i<len;i++){ name = document.getElementsByTagName('b')[i].nodeName; name = document.getElementsByTagName('b')[i].nodeType; name = document.getElementsByTagName('b')[i].tagName; } return name; } //较快 function collLocal(){ var coll = document.getElementsByTagName('b'),len = coll.length,name = ''; for(var i = 0;i<len;i++){ name = coll[i].nodeName; name = coll[i].nodeType; name = coll[i].tagName; } return name; } //最快 function collNodesLocal(){ var coll = document.getElementsByTagName('b'),len = coll.length,name = '',el =null; for(var i = 0;i<len;i++){ el = coll[i]; name = el.nodeName; name = el.nodeType; name = el.tagName; } return name; }
8,推荐使用css选择器
使用css选择器是一个便捷的确定节点的方法,这是因为大家已经对css狠熟悉了; 许多js库为此提供了API,而且,最新对浏览器提供了一个名为querySelectorAll()原生浏览器DOM函数. 显然这种方法比使用js和dom迭代并缩小元素列表对方法要快;var b = document.querySelectorAll('.name b'); //如果不使用querySelectorAll,达到同样对目的,代码会沉长些 var b = document.getElementsByClassName('names')[0].getElementsByTagName('b'); console.log(b); //当需要联合查询时,使用querySelectorAll()更加遍历 var errs = document.querySelectorAll('div.uzais,div.nums');