js编程容易犯的错误笔记

原文资料:http://www.36ria.com/4479

看了这篇文章后,发现有些错误自己确实需要多加注意,写篇文章加深下记忆。

错误1-使用全局变量

//解决方法,使用封转
(function(){
    var total = 0, tax = 0.05;
    //other code
}())

这样做后,在函数外部是绝对不能访问到函数内部定义的变量,形成了个人的代码空间。但是这样就不能公开部分方法或属性。例如,想要创建一个购物车,定义一个总价的变量,作为公共属性,这种情况下可以采用模块式编程。

var cartTotaler = (function(){
    var total = 0,tax = 0.05;
    //other code
    return{
        addItem:function(item){},
        removeItem:function(item){}
    }
}());
//demo2
var dog = (function(){
    var name = 'brow',age = 18;
	return{
		speak:function(){
		console.log('my name is:' + name);
		console.log('my age is:' + age);
	        }	
            };	
    }());	
    dog.speak();

关于全局变量有一点值得注意,如果不用关键词var来声明创建变量,那么javascript引擎会默认将该变量定义为全局变量。

(function(){
    tax = 0.05;
}());
var totalPrice = 100 + (100*tax); //105

错误3-使用==

比较下面的代码

if(1=='1'){
    console.log('it's true!');
}
if(1==='1'){
    console.log('it's true!');
}

错误5-使用for-in时不对属性检查

var pro,obj = {name:'Joe',job:'Coder',age:25};
for(pro in obj){
    console.log(pro+':' + obj[pro]);
}

但是,浏览器中for-in遍历对象属性和方法时会包括对象原型链上的所有属性和方法,但是绝大多数属性是不希望被枚举出来的。可以使用hasOwnProperties方法来检查属性是否属于对象。

Function Dog(name){
    this.name = name;
}
Dog.prototype.legs = 4;
Dog.prototype.speak = function(){
    return 'woof';
};
var d = new Dog('Bowser');
for(var prop in d){
    console.log(pro + ':' + d[prop]);
}
console.log('======');
for(var prop in d){
    if(d.hasOwnProperty(prop)){
        console.log(prop+":" +d[prop]);
    }
}

有时,只希望枚举出对象的属性,不包括方法,可以使用typeof方法过滤

for(var prop in d){
    if(typeof d[prop] !== 'function'){
        console.log(prop + ':' + d[prop]);
    }
}

不管如何,在使用for-in循环时要确保对属性进行检查,以避免得到你意料之外的结果。

在使用parseInt时不用基数

parseInt('200');//200
parseInt('043');//35
parseInt('043',10);//43
parseInt('aaa333',10);//NaN
parseInt('100',2);//4

错误9-单个单个的插入dom元素

DOM操作会使浏览器重绘页面,所以如果有一连串的元素一个接一个的插入页面中,这会急剧增加浏览器渲染页面的负担。所以推荐使用Document fragments,如下

var list = document.getElementById('list'),
    frag = document.createDocumentFragment(),
    items = ['one','two','three','four'],el;
    for(var i = 0; items[i];i++){
        el = document.createElement('li');
        el.appendChild(document.createTextNode(item[i]));
        frag.appendChild(el);//best way
    }
    list.appendChild(frag);

html5 data dataset

HTML5引入了一新的特性,自定义属性,但是必须以’data-’开头,后面加上任意字符即可定义一个自定义属性。看代码:

<div id='userInfo' data-name='alex' data-age='24' data-sex='male'></div>

以上是一个可以通过验证的HTML5代码,HTML5 Javascript API也提供了访问这些自定义属性的方法(除了set/getAttribute之外)

var userInfo = document.querySelector('#userInfo'),
phrases = [{
    name:'name',
    prefix:'我的名字叫'
},{
    name:'age',
    prefix:'我的年纪'
},{
    name:'sex',
    prefix:'我的性别'
}],
pos = 0;
document.body.addEventListener('click',function(e){
    var phrase = phrases[pos++];
    console.log(phrase.prefix + userInfo.dataset[phrase.name]);
});

或者可以使用getAttribute,如

console.log(phrase.prefix + userInfo.getAttribute('data-' + phrase.name));

获取某个属性的值,可以像下面这样使用dataset对象:

var userInfo = document.getElementById('userInfo');
var name = userInfo.dataset.name;

需要注意的是带连字符(-)连接的名称在使用的时候需要命名驼峰化,如data-meal-time,获取值的时候就要dataset.mealTime.
为啥我们需要dataset,传统的方法获取属性值是使用element.getAttribute(‘propertity’); 使用dataset,获取的是DOMStringMap对象,DomStringMap是HTML5一种新的含有多个名值对的交互变量。你可以 像如下一样使用操作

var arr = [];
for(var item in dataset){
    arr.push(dataset[item]);
}

上面的几行代码作用是让所有的自定义的属性值塞到一个数组中。如果你想删除一个data属性,可以使用delete element.dataset.propertity,或者添加一个属性,element.dataset.propertity = ‘test’. 因为是HTML5的产物,所以在使用的时候有必要进行下特征检测,看是否支持dataset,如下

if(userInfo.dataset){
    userInfo.dataset.dessert = 'ice';
} else {
    userInfo.setAttribute('data-dassert','ice');
}

最后是data属性在css中的应用,HTML代码

<div class='div' data-border='blue'></div>
<div class='div' data-border='red'></div>

css代码如下

.div{width:100px; height:100px;}
.div[data-border='blue']{border:1px solid #00f;}
.div[data-border='red']{border:1px solid #f00;}

可惜万恶的IE6不支持属性选择器

参考资料:http://qiqicartoon.com/?p=691

call and apply

js中经常需要改变this的指向,这里就要用到call和apply方法了,他们都属于function的扩展方法,所有的函数对象都可以调用。区分call,apply其实就一句话:

fn.call(this,arg1,arg2,arg3) == fn.apply(this,arguments) == this.fn(arg1,arg2,arg3)

相同点:这两个方法产生的结果完全一样 不同点:参数传递方式

在js里,代码总是会有一个上下文对象,代码处理在该对象之内。上下文对象是通过this变量来体现的,这个this变量永远指向当前代码所处的对象中。跟定义时无关,看代码执行的时候所处的上下文。看例子:

//创建一个A类
function A(){
    //此时的执行上下文对象就是this,就是当前实例对象
    this.message = 'message of a';
    this.getMessage = function(){
        return this.message;
    }
}
//创建一个A类实例对象
var a = new A();
//调用类实例getMessage方法获得message值
alert(a.getMessage());
//创建一个B类
function B(){
    this.message = 'message of b';
    this.setMessage = function(msg){
        this.message = msg;
    }
}
var b = new B();
//用a对象调b对象的方法
b.setMessage.call(a,'a的消息');
alert(a.getMessage()); //output 'a的消息'

所以b.setMessage.call(a,’a的消息’)就等于用a作执行时上下文对象调用b对象的setMessage方法,这过程和b无关系,等效于a.setMessage(‘a的消息’);
call,apply的作用就是借用别人的方法来调用,就像调用自己的一样。
再来看看apply的例子:

function print(a,b,c,d){
    alert(a+b+c+d);
}
function example(a,b,c,d){
    //用call方法借用print,参数显式打散传递
    print.call(this,a,b,c,d);
    //用apply方法借用print,参数作为一个数组传递,这里直接调用js的arguments数组
    print.apply(this,arguments);
    //或者封装成数组
    print.apply(this,[a,b,c,d]);
}
//下面将显示'圣诞快乐'
example('圣','诞','快','乐');

最后的example方法的上下文对象是window对象,如果call,apply内没有指定对象,默认window对象。

应用区别: 当参数明确时用call,不明确时用apply结合arguments.

print.call(window,'圣','诞','快','乐');
//foo参数可能为多个
function foo(){
    print.apply(window,arguments);
}
foo('1','2','3','4','5')    //output ???

三栏等高 固定像素

闲着蛋疼,写写固定像素的三栏布局,学艺不精还望多多指教,上代码:
html结构

<body>
	<div class="header">我是头部</div>
    <div class="wrap">
    	<div class="contentWrap">
        	<div class="content">
            	<p>我是正文内容</p>
                <a class="addContent" href="javascript:;">点我添加内容</a>
            </div>
        </div>
        <div class="slider">
        	<p>我是侧边导航栏</p>
            <a class="addContent" href="javascript:;">点我添加内容</a>
        </div>
        <div class="extra">
        	<p>我是附加栏</p>
            <a class="addContent" href="javascript:;">点我添加内容</a>
        </div>
    </div>
    <div class="footer">我是底部</div>
</body>

wrap层主要是想固定内容的宽度,水平居中。不知道有没更好的方法可以去掉这层结构。

<style type="text/css">
	*{margin:0; padding:0; font-size:12px; font-family:Arial, Helvetica, sans-serif;}
	.header{width:950px; background-color:#06F; margin:0 auto;}
	.footer{width:950px; margin:0 auto; background-color:#C36;}
	.wrap{width:950px; margin:0 auto; background-color:#fff; overflow:hidden;}
	.contentWrap{float:left; width:100%; background-color:#C63; padding-bottom:9999px; margin-bottom:-9999px;}
	.content{margin:0 150px 0 170px; min-height:100px; height:auto!important; height:100px;}
	.slider{float:left; width:170px; margin-left:-950px; background-color:#3FF; padding-bottom:9999px; margin-bottom:-9999px;}
	.extra{float:left; width:150px; margin-left:-150px; background-color:#C30; padding-bottom:9999px; margin-bottom:-9999px;}
</style>

用负边距加浮动来排列主次内容,再用padding-bottom和margin-bottom实现等高布局。

<script type="text/javascript">
	$(".addContent").click(function(){
			$('<p>新加的内容</p>').insertBefore($(this));
		}
	);
</script>

最后是动态加内容的js代码,偷懒直接用了调用了JQ库
更新:修复IE6下最小高度和高度自适应的问题,给子列添加最小高度

.content{margin:0 150px 0 170px; min-height:100px; height:auto!important; height:100px;}

演示页面

Function prototype bind

看到bind,用过JQ的童鞋应该都会知道bind是用来绑定事件的,但是在JavaScript 1.8.5中,bind是用来改变运行环境的this值的,跟call和apply类似,返回一个新的函数。IE9支持,Opare和safari不支持。想要实现,必须通过以下代码:

if (!Function.prototype.bind) {  
      Function.prototype.bind = function (oThis) {  
        if (typeof this !== "function") {  
          // closest thing possible to the ECMAScript 5 internal IsCallable function  
          throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");  
        }  

        var fSlice = Array.prototype.slice,  

			//获取参数,转换成数组
            aArgs = fSlice.call(arguments, 1),  

			//var that = this; 
            fToBind = this,   

			//Function
            fNOP = function () {}, 

            fBound = function () {  

			//fToBind就是调用这个bind方法的函数
              return fToBind.apply(this instanceof fNOP ? this : oThis || window,  
                                   aArgs.concat(fSlice.call(arguments))); 
								  // alert(arguments.length);
            };  

        fNOP.prototype = this.prototype;  
        fBound.prototype = new fNOP();  

        return fBound;  
      };  
    }  

上面有处不明白this instanceof fNOP ? this,为什么要做这个判断呢,希望明白的童鞋可以告知,谢谢。
简单版的写法:

if(!Function.prototype.bind){
    Function.prototype.bind = function(oThis){
        var mothod = this,
            fslice = Array.prototype.slice,
            args = fslice.call(arguments,1);
        return function(){
            return method.apply(oThis,args.concat(fslice.call(arguments)));
        }
    }
}

相关资料:https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind

Array map function

array.map([callback[,thisArg])这个是属于js1.6里的方法,现在各主流浏览器都支持了,IE9以下不支持。作用就是创建返回一个新数组,新数组里的每个元素是经过回调函数处理过的元素。PS:不会改变原来的数组。

如果要在低版本的IE浏览器下实现该方法,可以用过以下代码:

if(!Array.prototype.map){
		Array.prototype.map = function(callback /*,thisArg*/){
				var len = this.length;
				if(typeof callback != 'function')
				throw new TypeError();

				var res = new Array(len);
				var thisArg = arguments[1];
				for(var i =0;i<len;i++){
						if(i in this){
								res[i] = callback.call(thisArg,this[i],i,this);
							}
					}
				return res;
			}
	}

下面看个例子:

    function fuzzyPlural(single) {  
      return single.replace(/o/g, 'e');  
    }  

    var words = ["foot", "goose", "moose"];  
    console.log(words.map(fuzzyPlural));  

    // ["feet", "geese", "meese"]  

思考下

["1", "2", "3"].map(parseInt);  //[1,NaN,NaN]

jQuery.extend详解

这个函数在写JQ插件的时候会经常用到,写一写加深点印象。主要的用法有这么几个 一、jQuery的扩展方法原型是:

extend(dest,src1,src2...);

将src1,src2…合并到dest中,返回值为合并后的dest,会改变dest的结构,如果不想改变原先的结构,可以使用

var newSrc = $.extend({},src1,src2....); //用空对象代替dest

这样newSrc就返回合并后的结构了,如

var result = $.extend({},{name:'tom',age:'21'},{name:'jerry',sex:'boy'});

那么合并后的结果就是:

result = {name:'jerry',age:'21',sex:'boy'};

如果参数名称相同,后面的会覆盖前面的值

二、省略dest参数
1、$.extend(src); 该方法是将src合并到jquery全局对象中
2、$.fn.extend(src);该方法是将src合并到实例对象中
下面介绍几个常用的扩展实例:

$.extend({net:{}}); //在jq全局对象中扩展一个net命名空间
$.extend($.net,{hello:function(){alert('hello');}} //将hello方法扩展到之前的net命名空间中

三、extend(boolean,dest,src1,src2…);
第一个参数bool代表是否进行深度拷贝,其余的同前面的一致。看例子

var result = $.extend(true,{},{name:'john',location:{city:'B',country:'us'}},{last:'C',location:{state:'Ma',country:'china'}});

可以看到src1中有location,src2中也有location,所以如果是true那么结果是

result = {name:'john',last:'C',location:{city:'B',state:'Ma',country:'china'}};

如果是false的话,结果就是

result = {name:'john',last:'C',location:{state:'Ma',country:'china'}};

HTML Canvas 基础知识学习笔记(六)

今天来学习下canvas上对于形状的支持,首先是自定义形状,也可以叫不规则的形状。可以理解为闭合的路径,只要调用函数closePath(),路径就闭合了。所以说自定义的形状其实就是闭合的路劲,调用的方法都是在绘制路径时候用到的,只要最后调用closePath()闭合就行了,看代码:

context.beginPath();
context.moveTo(170, 80);
context.bezierCurveTo(130, 100, 130, 150, 230, 150);
context.bezierCurveTo(250, 180, 320, 180, 340, 150);
context.bezierCurveTo(420, 150, 420, 120, 390, 100);
context.bezierCurveTo(430, 40, 370, 30, 340, 50);
context.bezierCurveTo(320, 5, 250, 20, 250, 50);
context.bezierCurveTo(200, 5, 150, 20, 170, 80);
context.closePath(); // complete custom shape
context.lineWidth = 5;
context.strokeStyle = "#0000ff";
context.stroke();

如何给形状填充颜色呢,canvas提供了fillStyle属性和fill()方法,调用方式:

context.fillStyle=[value];
context.fill();

区分与strokeStyle属性和stroke()方法
canvas还提供了对规则形状的支持,首先是rect()绘制矩形。调用方式:

context.rect(topLeftCornerX,topLeftCornerY,width,height);

效果代码:

var topLeftCornerX = 188;
var topLeftCornerY = 50;
var width = 200;
var height = 100;

context.beginPath();
context.rect(topLeftCornerX, topLeftCornerY, width, height);

context.fillStyle = "#8ED6FF";
context.fill();
context.lineWidth = 5;
context.strokeStyle = "black";
context.stroke();

调用规则形状的方法,就不需要手动去colsePath了
还有就是绘制圆形,其实这个方法之前我们讲过,就是

context.arc(centerX,centerY,radius,0,2*Math.PI,false);

这次还要调用fill()的方法填充里面。
只要改变结束的弧度,就可以变成半圆形了:

context.arc(centerX,centerY,radius,0,Math.PI,false);

最后是裁剪图片clip()函数,只要调用

context.clip()

就可以裁剪一块规定的区域,区域外的就不能显示了。
演示地址

HTML Canvas 基础知识学习笔记(五)

今天来学习下画布对路径的支持,其实我的理解路径就是直线,曲线的组合。所以我们要用到的函数就是之前讲过的lineTo,quadraticCurveTo,bezierCurveTo,只要把这些函数画出来的线条组合在一起就是一个路径了。可以运行以下代码看效果:

var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');

context.beginPath();
context.moveTo(100,20);
context.lineTo(200,160);//line1
context.quadraticCurveTo(230,200,250,120);//quadratic curve
context.bezierCurveTo(290,-40,300,200,400,150);//bezier curve
context.lineTo(500,90);//line2
context.lineWidth = 5;
context.strokeStyle = '#00f';
context.stroke();

除了线条外,线条与线条的连接处也是路径的一部分,可以通过属性lineJoin来设置样式,这里有三种样式:miter, round,和bevel,默认的就是miter。设置连接处样式的关键代码:

// round line join (middle)
context.beginPath();
context.moveTo(canvas.width / 2 - 50, canvas.height - 50); // line 1
context.lineTo(canvas.width / 2, 50); // line 1
context.lineTo(canvas.width / 2 + 50, canvas.height - 50); // line 1
context.lineWidth = 25;
context.lineJoin = "round";
context.stroke();

最后要说下路径中如何画出圆弧形的拐角,可以使用arcTo(controlX,controlY,endX,endY,radius)来画出弧形的拐角。关键代码:

var rectWidth = 200;
var rectHeight = 100;
var rectX = canvas.width / 2 - rectWidth / 2;
var rectY = canvas.height / 2 - rectHeight / 2;

var cornerRadius = 50;

context.beginPath();
context.moveTo(rectX, rectY);
context.lineTo(rectX + rectWidth - cornerRadius, rectY);
context.arcTo(rectX + rectWidth, rectY, rectX + rectWidth, 
rectY + cornerRadius, cornerRadius);
context.lineTo(rectX + rectWidth, rectY + rectHeight);

context.lineWidth = 5;
context.stroke();

演示地址

简单美化input file元素

工作中经常需要自定义input的file类型元素,这里采用了一种比较简单的自定义方法。 HTML结构:

	<div class="upfileDiv">
    	<input id="J_upfile" class="upfile" type="file" onchange="document.getElementById('J_upfileResult').innerHTML = this.value; alert(this.value);" />
        <input class="upFileBtn" type="button" onclick="document.getElementById('J_upfile').click()" />
    </div>
    <span id="J_upfileResult">仅支持JPG、GIF、PNG图片文件,且文件小于5M</span>

CSS:

	*{margin:0; padding:0;}
	.upfileDiv{margin:100px 100px 0;width:119px; height:27px; background:url(btnBg.jpg) no-repeat 0 0; overflow:hidden; position:relative;}
	.upfile{position:absolute; top:-100px;}
	.upFileBtn{opacity:0; width:119px; height:27px; cursor:pointer;}

简单的就是通过点击隐藏的BUTTON来JS调用flie元素的click事件,用自定义的图片做为外框DIV的背景图片,达到自定义的效果。 演示地址

PS:FF下无法取到上传文件的完整路径。据说是安全原因,暂时没有很好的解决办法,有好办法可以联系我:)