[高级程序设计]从高级程序设计中搬来的一些值得注意的地方

1、默认情况下只有表单字段可以获得焦点,但是如果对于其它的元素首先把tabindex设置为-1,然后调用focus方法也能获取焦点,只有opera不支持这个技术

      var elem=document.getElementById("elem");
         elem.tabIndex=-1;
	  elem.focus();
2、HTML5的autofocus属性

window.onload=function(e)
	{
	    var elem=document.getElementById("autofocus");
         if(elem.autofocus)//支持HTML5属性的autofocus值为true
		 {
		   elem.focus();
		 }
  }
HTML部分

<input type="text" autofocus id="autofocus">
3、change事件具有特殊性,如果是input/textarea元素,在他们失去焦点同时value值改变的时候就会触发。但是select元素只要选项改变,即使没有失去焦点也会触发!
	<textarea id="t" onchange="console.log('change')"></textarea>
	<input type="text" onchange="console.log('input change')"/>
	<select onchange="console.log('select change')">
注意:blur和change事件的触发顺序没有明确的规定,不同的浏览器结果不一样,不能假定两个事件总会按照顺序进行
4、建议使用value属性读取和设置文本框的值,不建议使用标准的DOM方法,换句话说不要使用setAttribute设置input的value,也不要修改textarea元素的第一个子节点,原因很简单:对value属性所做的修改不一定会反映到DOM中,因此在处理文本框的值的时候最好不要用DOM方法

          var ta=document.getElementById("t");
		console.log(ta.value);
		ta.value='qinliang';
5、防止表单重复提交的方法。通过disabled属性;检测onsubmit防止重复提交;利用token机制(本质是通过session和表单的隐藏域来完成的)
6、select事件用于 确定用户选择的文本

function getSelection(textbox)
	{
	  if(typeof textbox.selectionStart =='number')
	  {
	    return textbox.value.substring(textbox.selectionStart,textbox.selectionEnd);
	  }else if(document.selection)
	  //IE8-中用document.selection对象保存了用户在整个页面范围中选择的文本信息,虽然无法
	  //确定页面那个部分的文本,不过结合select一起使用可以假定用户选择了文本域中的文本!
	  {
	    return document.selection.createRange().text();
	  }
	}
	//select事件冒泡,所以绑定到window对象上了!
	window.onselect=function(e)
	{
	    var ta=document.getElementById("t");
		var result=getSelection(ta);
		console.log(result);
  }
在IE9+和其它浏览器中只要用户选择了文本同时释放了鼠标就会触发select事件,而在IE8前的版本中只要选择了一个字母不必释放鼠标就会触发select,同时在调用select方法时候也会触发select事件。
如果要选择部分文本,我们在IE9和其它浏览器中可以使用如下方法

	window.onload=function(e)
	{
	      var ta=document.getElementById("t");
		ta.setSelectionRange(0,3);
		ta.focus();
		//要看到选择的文本,必须在调用setSelectionRange之前或者之后立即将焦点设置到文本框
          }

兼容IE8的方法如下:

	//可以用于自动完成建议的文本框!
	function selectText(textbox,startIndex,stopIndex)
	{
	  if(textbox.setSelectionRange)
	  {
	    textbox.setSelectionRange(startIndex,stopIndex);
	  }else if(textbox.createTextRange)
	  //IE8一下浏览器
	  {
	    var range=textbox.createTextRange();
		//调用moveStart和moveEnd方法之前必须调用collapse把范围折叠到文本框的开头!
		//这时候开始和结束都移动到了文本框的开始位置
         range.collapse(true);
		 range.moveStart("character",startIndex);
		 //结束位置移动的字符数目!
		 range.moveEnd("character",stopIndex-startIndex);
		 //最后一步调用select选择文本
		 range.select();
	  }
     textbox.focus();
	 //为了让文本框有被选择的效果必须让文本框获取到焦点!
	}
	
	window.onload=function(e)
	{
	    var ta=document.getElementById("t");
		selectText(ta,2,5);
		//选择第二到第4个字符,是断尾的
  }

7、javascript中那些断尾的方法的总结

通过上面的例子我们可以看到setSelectionRange和createTextRange是断尾的,那么我们回忆一下javascript中那些方法是断尾(不包括最后一个下标)的。
substring方法:对源字符串无影响;断尾;如果第一个参数为负数,那么设置为0,如果第二个参数是负数,那么两个参数互换(前后互换)

slice方法:断尾;如果有字符串是负数,那么加上length后得到新的字符串(前后不换)

substr方法:第一个参数是开始下标,第二个参数是个数;如果第一个参数为负数那么加上length,如果后面为负数不变(前后不换)

          var str="hello world";
	 console.log(str.substring(-3));//第一个参数是负数那么设为0!打印"hello world"
	 console.log(str.substring(3,-4));//第二个参数为负数那么前后对调!打印"hel"
	 console.log(str.slice(-3));//如果有负数那么加上length!打印"rld"
	 console.log(str.slice(3,-4));//打印"lo w"
	 console.log(str.substr(3));//打印"lo world"
	 console.log(str.substr(3,-4));//如果第一个参数为负数加上length,第二个参数为负数设置为0,打印""!
总结:slice是切割,如果 切割不了(负数)就加上length再次切割;substr重视第一个参数( 重男轻女),如果他是负数那么加上length,第二个参数如果是负数让他自生自灭(设置为0);substring最人道,前面是负数那么设置为0,后面是负数 前后调换就可以!
8、如何屏蔽用户无效输入

var elem=document.getElementById('in');
	elem.onkeypress=function(e)
	{
	  var event=e?e:window.event;
	  //获取charCode,其值是输入元素的ASCII值,而且charCode只有在keypress事件时候才会
	  //包含值,此时的keyCode通常为0或者也可能等于所按键的键码,IE8之前和Opera是保存ASCII
	  var charcode=(typeof event.charCode=="number")?event.charCode:event.keyCode;
	 //FF和Safari3.1对向上键,向下键,退格键,删除键也会触发keypress事件,但是在FF中所有非字符键
	 //触发的keypress事件对应的字符编码为0,而在Safari3.1之前的版本中对应的字符编码全部是8
	 //因此我们需要避免屏蔽这些常用的键
	 //为了不屏蔽ctrl+c,ctrl+v所以我们还要确保用户没有按下ctrl键!
	  if(!/\d/.test(String.fromCharCode(charcode)) && charcode>9 && !event.ctrlKey)
	  {
	     console.log("keypress");
	    if(event.preventDefault)
		{
		 event.preventDefault();
		}else
		{
		  e.returnValue=false;
		}
	  }
	}

9、hashchange(为event提供了oldURL和newURL属性,但是只有FF6+,Chrome,Opera支持,因此最好用location.hash确定当前的参数列表)和pageshow,pageHide(提供了persisted属性)事件必须绑定到window对象上面。对于pageShow事件如果页面从bfcache中加载persisted就是true,对于pageHide来说如果隐藏后会被添加到bfcache中persisted就是true!因此第一次触发pageShow其persisted一定为false!指定了onunload事件处理程序的页面会被自动排除在bfcache之外,即使事件处理程序是空的,原因在于onunload常用语撤销在onload中所执行的操作,而跳过onload后再次显示页面很可能导致页面不正常!注意:bfcache实际上将整个页面都保存到了内存中,如果页面在bfcache中,那么再次打开页面就不会触发onload事件,因此为了形象说明bfcache行为ff提供了pageshow/pageHide事件!

                 var support=("onhashchange" in window);
		  //这种检测支持hashchange的方式有问题,如果IE8在IE7文档模式下运行,即使功能无效也会返回true!
		  //documentMode是IE8+增加的属性获取获取文档兼容性模式,下面这种方式更加稳妥!
		  var support1=("onhashchange" in window)&&(document.documentMode===undefined||document.documentMode>7)

有一个容易混淆的compateMode属性,在html文档中没有指定DOCTYPE,在ie下就是怪异模式

//document.compatMode属性,如果是标准模式,则document.compatMode的值等于”CSS1Compat“,如果是怪异模式,则document.compatMode的值等于”BackCompat“
if (document.compatMode == "CSS1Compat") {
    alert("Standards mode");
} else {
    alert("Quirks mode");
}

10、defer,async和DOMContentLoaded的执行顺序

defer:来源于HTML4.01;html5规范要求脚本按照他们出现的顺序先后执行,一次前面的延迟脚本会在后面的延迟脚本之前执行,而这两个脚本会先于DOMContentLoaded事件执行;但是在现实中,延迟脚本不一定会按照顺序执行,而且也不一定会在DOMContentedLoaded事件之前执行,因此最好只包含一个延迟脚本;HTML5中规定defer只适用于外部脚本,因此支持HTML5实现忽略给内联脚本添加的defer,虽然IE4-7支持内联的defer,但是在IE8以后支持HTML5规范!总之:只适用外部脚本+DOMContentLoaded没有顺序

async:来源于 HTML5;只是适用于外部脚本;标记为async的脚本不保证他们的执行顺序';一定在load之前,但可能会在DOMContentLoaded之前或者之后执行!

总结:async和defer和DOMContentLoaded之间没有固定的关系。但是使用async使得js不再是关键路径的一部分了,同时也不再是解析器阻塞了,于是DOMContentLoaded可以越早触发了(还要注意浏览器并发下载资源的个数,而且在执行JS时候会阻塞浏览器默认的行为,也就是会阻塞页面的解析)!

11、剪切板操作

IE中剪切板对象是window对象,SF/Chrome/FF中是event对象

   function getClipBoard(event)
		{
		//getData用户从剪切板中获取数据,他接受一个参数也就是数据类型,在IE中
		//可以是"text"/"URL",但是在FF,SF,Chrome中这个参数是mime类型,不过可以用
		//'text'代替'text/plain'
		  var clipBoardData=event.clipboardData||window.clipboardData;
		//在IE中clipboardData是window对象,SF/chrome/FF中是event对象,在IE中随时可以获取到该对象
		//而SF/Chrome/FF只有在处理剪切板事件期间才能访问他!
		  return clipBoardData.getData('text');
		}
设置剪切板的时候对于SF/Chrome不能用"text"代替"text/pain"

 function setClipBoard(event,value)
	   {
	     if(event.clipboarData)
		 {
		 //setData第一个参数也是数据类型,第二个是放在剪切本中的文本,IE是"text"/'URL'
		 //而SF,chrome仍然只支持mime类型,但是与getData不同的是SF,chrome不能识别text类型!
		   return event.clipboardData.setData('text/pain',value);
		   //对于safari/chrome浏览器是"text/plain",对于IE浏览器是"text"
		 }else if(window.clipboardData)
		 {
		   return window.clipboardData.setData('text',value);
		 }
	   }
FF/SF/Chrome只允许在onpaste中访问getData方法,所以oncopy中无法访问getData方法

	   $(document).ready(function()
	   {
	   //FF,Safari,Chrome只允许在onpaste中访问getData方法!
	      $('input')[0].onpaste=function(e)
		  {
		    var e=e?e:window.event;
			var text=getClipBoard(e);
			//必须是数字才行!
			if(!(/^\d*$/.test(text)))
			{
			  if(e.preventDefault)
			  {
			    e.preventDefault();
			  }else
			  {
			    e.returnValue='false';
			  }
			}
		  }
	//在oncopy中无法访问getData方法,所以即使复制了也是打印空字符串!
        $('input')[0].oncopy=function(e)
		{
		  var e=e?e:window.event;
		  var text=getClipBoard(e);
		  console.log(text);
		}
	   })
注意:Oprea不支持javascript访问剪切板
至于beforecut/beforepaste/beforecopy

 //在SF/Chrome/FF中beforecopy/beforecut/beforepaste事件只会在显示针对文本框的上下文
		  //(预计发生剪切板事件时候)的情况下才会触发(Chrome47中div也会触发,只是都是无法获取到数据的)
		  //在IE中则会在触发copy/cut/paste事件之前先行触发这些事件,至于copy/cut/paste只要在上下文
		  //菜单中选择了相应的项,或者使用了相应的组合都会触发他们
		  //在实际的事件发生之前通过beforecopy/beforecut/beforepaste可以在剪切板中发送数据或者从剪切板中取得数据
		  //之前修改他们,不过取消这些事件不会取消对剪切板的操作,只有取消copy/pasete/cut才会阻止相应的操作!
        $('div')[0].onbeforecopy=function(e)
		{
		  var e=e?e:window.event;
		  var text=getClipBoard(e);
		  console.log(text);
		}
12,检验用户的输入是否有效
通过点击按钮检测文本框输入的值是否正确,其中文本框的值是必须输入的

 var input=document.getElementById('input1');
	  function check(input)
	  {
	      //每一个表单元素都有一个checkValidity方法,但是也可以直接在form中调用
		  //相比于checkValidty而言,validity属性可以告诉你为什么字段有效或者无效
		  //可以是:PatternMismatch,rangeOverflow,rangeUnderflow,stepMistach,tooLong
		  //TypeMismatch,valie,valueMissing等错误!
		   if(input.validity&&!input.validity.valid)
		   {
			 if(input.validity.valueMissing)
			 {
			   alert('请输入值');
			 }else if(input.validity.typeMismatch)
			 {
			   alert('输入的数据类型不匹配');
			 }else
			 {
			   alert('输入的数据无效');
			 }
		   }
	  }
	  input1.onclick=check(document.getElementById('input'));
13、禁止表单验证

如果是在表单中添加了novalidata那么在提交的时候不会验证,但是如果在submit按钮中添加了formnovalidate那么提交表单根本不会验证

 <form novalidate>
      <!--直接在表单上设置表示不用验证,但是如果有多个submit那么给submit设置formnovalidate那么不会验证表单而直接提交,
	  如论文投稿系统是否要验证参考文献格式等-->
	  <input type="submit" formnovalidate/>
	  <input type="submit">
	  <!--第一个submit会验证表单,第二个不会验证而是直接提交-->
	 </form>
14、关于select选项option的值

var select=document.forms[0].elements['location'];
	//推荐使用text/value获取文本和选项的值
	var text=select.options[0].text;
	var value=select.options[0].value;
   //不推荐下面的DOM方法
   var text=select.options[0].firstChild.nodeValue;
   var value=select.options[0].getAttribute('value');
注意:选择框的change事件和其它表单的change事件触发的条件不一样,其它控件change只有在值被修改而且焦点离开了当前字段时候触发,而select的change事件只有选中了选项就会触发;在未指定value特性的情况下,IE8会返回空字符串,而IF9+等其它会返回和text属性一样的值;选择框的type只能是select-one/select-multiple一种,取决于是否有multiple属性
如果是单选的select,我们可以用selectbox.selectIndex获取到选项,如下:

<form>
	 <select name="location">
         <option>1</option>
		  <option value="name">2</option>
	 </select>
</form>  
单选我们可以用selectedIndex获取到我们选择的option的所有的信息,如text/value值

 $('select')[0].onchange=function(e)
  {
	var select=document.forms[0].elements['location'];
	//获取到了select对象了
	var option=select.options[select.selectedIndex];
	//通过selectIndex获取到选项
	console.log(option.text+"="+option.value);
	}
对于可以选择多项的选择框selectedIndex属性就好像只允许选择一项一样,设置selectedIndex的值会导致取消以前所有的选项并选择指定的哪一项,而读取时候只会返回选中项的第一项,所以多项选择我们采用这种方式:取得对某一项的引用,然后将selected属性设置为true!与selectedIndex不一样的是,在允许多项选择的对象中,设置selected属性不会取消对于其它选项的选择,因为可以动态选中多项,但是在单选中如果修改某一个选项的selected属性会取消对其它选项的选择,同时将selected设置为false对单选选择框没有效果!

<form>
	 <select name="location" multiple>
        <span style="white-space:pre">	</span>  <option>1</option>
		  <option value="name">2</option>
		    <option>3</option>
		  <option value="name">4</option>
	 </select>
</form>  
<input type="button"/>
下面获取所有的选中的options

function getSelectedOptions(select)
 {
   var result=new Array();
   var option=null;
   for(var i=0,len=select.options.length;i<len;i++)
   {
     option=select.options[i];
	 if(option.selected)
	 {
	   result.push(option);
	 }
   }
   return result;
 }
//这时候当选择select的多个项目时候再次点击按钮
//就会打印选中的所有的options集合!
 $('input')[0].onclick=function(e)
 {  
   var result=getSelectedOptions($('select')[0]);
   console.log(result);
}
总之:如果是多选select这时候会给每一个option默认一个selected为true,所以我们遍历所有的options集合然后选择selected为true的option就可以了!而对于单选的option我们通过获取selectedIndex就可以获取到了!
15、为select对象添加option对象和移除option对象
添加option第一种方式是DOM方法:

 var newOption=document.createElement("option");
   newOption.appendChild(document.createTextNode("1"));
   newOption.setAttribute("value","qinliang");
   $('select')[0].appendChild(newOption);
第二种方式是Option的构造方式,但是该方式不支持IE8-
var newOption=new Option("text","value");
   //第二个为vaue值,但是在IE8一下的浏览器有问题
   $('select')[0].appendChild(newOption);
第三种方式是通过add方法

  var newOption=new Option("text","value");
  //DOM规定这个方法有两个参数,要添加的新选项和位于新选项之后的选项
  //如果想在最后添加选项第二个参数是null,IE对add方法的实现第二个参数可选
  //如果指定该参数必须是新选项之后的索引,兼容DOM的浏览器必须指定第二个参数
  //因此如果要兼容浏览器就不能只传入一个参数!
  //如下面会在所有浏览器中把新选项添加到列表最后!
   $('select')[0].add(newOption,undefind);
如果想把新选项添加到其它位置那么就应该用标准DOM技术和insertBefore方法!
如果是移除选项:

方式1:调用select的removeChild方法就可以

方式2:调用select的remove方法传入下标就可以了

方式3:就是把相应的选项设置为null

var select=$('select')[0];
select.options[0]=null;//设置为空
当然我们如果要移除所有的options对象,那么我们可以不断移除第一个选项就可以了:

 function clearSelect(select)
 {
   for(var i=0,len=select.options.length;i<len;i++)
   {
     select.remove(0);
	 //看清除:这里是0,而不是i,否则肯定移除不掉所有元素!
   }
 }
 clearSelect($('select')[0]);
移动选项基于这样一个事实:如果把处于DOM中的元素通过appendChild添加到页面的其它部分,那么元素会从原来的位置上消失

 function reArrange(select)
 {
   var optionToMove=select.options[1];
   //把第二个移动到第1个前面!
   select.insertBefore(optionToMove,select.options[optionToMove.index-1]);
 }
 reArrange($('select')[0]);

16、如何判断select元素的option元素是否有值value!

if(option.selected)
{
  if(option.hasAttribute)
  {
    var optValue=(option.hasAttribute('value')?option.value:option.text);
	//找到选中项的时候需要确定什么值,如果不存在value特性,或者存在该特效
	//但是值为空字符串,都要用选项的文本代替,在DOM兼容的浏览器中用hasAttribute方法
	//在IE中需要用specified属性!
  }else
  {
    optValue=(option.attribute('value').specified?option.value:option.text);
  }
}
17、富文本操作的部分

主要是使用document.execCommand,这个方法可以对文档执行预定义的命令,可以为该函数传递三个参数,要执行的命令,表示浏览器是否需要为命令提供一个界面的布尔值和执行命令必须的值(如果不需要值要传递null)。为了确保浏览器兼容性,第二个参数应该始终是false,因为在FF中即使设置了true也会抛出错误!与剪切板有关的命令在不同浏览器中差异大,Opera没有实现任何剪切板命令,而FF默认禁止他们(必须修改用户的首选项来启用他们),SF/Chrome实现了cut/copy却没有实现paste,不过即使不能通过execCommand来执行命令,但是可以通过相应的快捷键达到同样的操作!

  frames['design'].document.execCommand('bold',false,null);
   //同样的方法也适用于页面contenteditable为true的区域,只要把对
   //框架的引用替换为当前页面的document对象集合
执行bold命令的时候,IE/Opera会使用strong包裹,SF/Chrome使用b标签,FF使用span标签。因此不能指望富文本编辑器会产生一致的HTML!

通过下列的方法可以判断当前文本是否合适执行相应的命令:

frames['design'].document.queryCommandEnabled('bold')
但是仅仅是确定是否合适,如FF在默认情况下会禁用剪切操作,但是仍然返回true!
下面方法确定是否已经把命令应用到了选择的文本:

 frames['design'].document.queryCommandState('bold')//可以利用这个方式更换粗体,斜体等按钮状态
下面的方法用于获取执行命令时候传入的值,即document.execCommand的第三个参数
frames['design'].document.queryCommandValue('fontsize');
   //其中fontsize是命令名称,这里是获取到值!

通过getSelection方法可以获取到富文本选取,然后对该选取进行操作:

<div style='width:1000px;height:100px;border:1px solid red;' contenteditable id='test'>我是你老公</div>
<iframe name="design" src="blank.html"></iframe>
<input type="button" value='Click'/>
点击按钮我们可以把元素的背景颜色变成红色

window.onload=function()
{
  frames['design'].document.designMode='on';
}
 $('input')[0].onclick=function(e)
 {
     var selection=frames['design'].getSelection();
     console.log(selection);
     //通过getSelection获取选择的文本,这个方法是window和document对象的属性!
	  var selectText=selection.toString();
	  //获得代表选区的范围!
	  var range=selection.getRangeAt(0);
	  //突出选择的文本,当选择了iframe中的文本,然后点击按钮的时候就会添加
	  //红色的背景!
	  var span=frames['design'].document.createElement('span');
	  span.style.backgroundColor='red';
	  range.surroundContents(span);
   }
HTML5将getSelection方法纳入了标准,但是FF3.6+中调用document.getSelection会返回一个字符串,为此在FF3.6+中改作调用window.getSelection可以返回selection对象,FF8修改了这个bug!同时IE8-浏览器不支持DOM,但是我们可以通过她支持的selection对象操作文本,IE中的selection对象是document的属性:

在IE中我们通过htmlText和pasteHTML实现了选择文本后背景色变成红色的情况:

$('input')[0].onclick=function(e)
{
	var  range=frames['design'].document.selection.createRange();
	var selectText=range.text;//获取文本
	//但是如果要高亮显示文本可以用htmlText/pasteHTML
	range.pasteHTML("<span style=\"background-color:red\"> " + range.htmlText+ "</span>");
}

当我们需要把富文本内容提交给服务器的时候就需要特殊的处理,我想这也是xhEditor的原理把(xhrEditor也需要在submit中才能访问富文本内容)

 $('form')[0].onsubmit=function(e)
 {
  var event=e?e:window.event;
  var target=event.target?event.target:event.srcElement;
  target.elements['comments'].value=frames['design'].document.body.innerHTML;
  //富文本不属于表单空间,不会自动提交给服务器,而需要我们手动提交html
  //为此我们添加一个隐藏的表单控件,让它的值等于从iframe中提取的值,从而
  //确保在提交表单之前填充comments字段,特别是当要调用submit方法来提交表单时候
  //一定不要忘记!
 }

18、Canvas绘图部分注意事项

<canvas id="drawing" width="500" height="500" style="border:1px solid #ccc;">浏览器不支持canvas</canvas>
注意:如果把宽度和高度放置在style里面这时候画出来的就是长方形而不是正方形!

可以通过lineWidth设置描边线条,通过lineCap控制线条末端的形状是平头,圆头,还是方头(butt,round,square);通过lineJoin控制线条相交的方式是圆交,斜交,还是斜接(round,bevel,miter)!

 var drawing=document.getElementById('drawing');
	 if(drawing.getContext)
	 {
	   var context=drawing.getContext('2d');
	     context.strokeStyle="#ff0000";
	      //在strokeRect前设置
		context.lineWidth="1";
		//控制线条相交的方式时候是圆角相交!
		context.lineJoin="round";
		//控制线条末端的形状是圆角!
		context.lineCap="round";
	      context.strokeRect(10,10,50,50);
	 }
其中上面的lineCap,lineJoin在绘制线条的时候特别清晰

var drawing=document.getElementById('drawing');
	 if(drawing.getContext)
	 {
	   var context=drawing.getContext('2d');
	   //开始路径
	   context.beginPath();
	   context.lineWidth='0.05';
	   context.arc(100,100,99,0,2*Math.PI,false);
	   //必须移动到内圆上的某一点防止多余的线条出现!
	   context.moveTo(194,100);
	  context.arc(100,100,94,0,2*Math.PI,false);
	  var result=context.isPointInPath(195,100);
	 //该函数接受x,y参数作为参数,用于在路径被关闭之前
	 //确定画布上某一点是否在路径上!
	   context.stroke();
	   //描边
	 }
已知上面的画布是宽度和高度都是100,所以外圆的最大宽度是100,但是这时候如果lineWidth特别大,就会产生不明显的效果,所以用了lineWidth为0.05!
利用canvas也可以绘制文本

	 context.font='bold 14px Arial';
	 context.textAlign="center";
	 //建议使用start/end而不是left/right,还可能是center
	 context.textBaseline="middle";
	 //可能是top/hanging,middle,alphabetic,bottom等
	 var fontSize=100;
	 context.font=fontSize+"px Arial";
	 //measureText方法接受一个参数,也就是要绘制的文本,返回一个TextMetrics
	 //对象,返回的对象目前只有一个width属性,该方法利用font,textAlign,textBaseline
	 //的当前值计算指定文本的大小!
	 while(context.measureText('Hello World').width>140)
	 //140表示目标临界宽度
	 {
	   fontSize--;
	   context.font=fontSize+"px Arial";
	 }
	 context.fillText("12",100,20);
	 //fillText使用fillStyle绘制文本,strokeText使用strokeStyle为文字描边
	 //大多数情况下使用fillText
	   context.stroke();
	   //描边
	 }
fillText,strokeText可以接受第四个参数,也就是文本的最大像素宽度,提供这个参数后调用fillText/strokeText如果传入的字符串大于最大宽度,那么文本字符的高度正确,但是宽度会收缩以适应最大宽度!

可以用context的save和restore保存对绘图上下文的设置和变换

var context=drawing.getContext('2d');
	   context.fillStyle="#ff0000";
	   context.save();
	   //保存绘图上下文的设置和变换
	   context.fillStyle="#00ff00";
	   context.save();
	   //保存绘图上下文的设置
	   context.moveTo(0,0);
	   context.lineTo(100,100);
	   //这时候是黑色,因为默认是黑色填充
	   context.stroke();
	   context.restore();
	   //得到绿色
	   context.fillRect(0,0,50,50);
	   //得到红色
	   context.restore();
	   context.fillRect(100,100,50,50);
可以通过drawImage把图片绘制到画布上

      var context=drawing.getContext('2d');
	  var img=document.querySelector('img');
	  //把图像的部分的绘制到画布上,原图片是[0,10]到[50,50]的部分
	  //drawImage还可以传递一个canvas作为第一个参数,这样就可以把
	  //另一个画布绘制到当前画布上,但是绘制的图像必须来自于同一个域
	  //否则抛出错误!
	  context.drawImage(img,0,10,50,50,0,100,40,60);
注意:toDataURL是canvas的方法,不是context的方法
为上下文设置阴影属性

 var context=drawing.getContext('2d');
	   //设置形状或者路径x方向的偏移量
	   context.shadowOffsetX=5;
	   //形状或路径y方向的阴影偏移量默认是0
	   context.shadowOffsetY=5;
	   //模糊的像素,默认是0
	   context.shadowBlur=4;
	   //阴影颜色
	   context.shadowColor='rgba(0,0,0,0,5)';
	   //绘制红色的矩形
	   context.fillStyle="#ff0000";
	   context.fillRect(10,10,50,50);
	   //绘制蓝色矩形
	   context.fillStyle="rgba(0,0,255,1)";
	   context.fillRect(30,30,50,50);
这些属性都是通过context来修改
我们也可以通过createLinearGradient方法来创建渐变

var context=drawing.getContext('2d');
	  var gradient=context.createLinearGradient(30,30,70,70);
	  gradient.addColorStop(0,"white");
	  gradient.addColorStop(1,"black");
	  //addColorStop接受两个参数,色标位置和CSS颜色值
	  //其中色标位置从0到1
	  context.fillStyle=gradient;
	  //把渐变对象设置为fillStyle/strokeStyle的值
	  //从而让渐变绘制形状或者描边
	  context.fillRect(30,30,50,50);
通过createRadialGradient创建径向渐变

 var context=drawing.getContext('2d');
	 var gradient=context.createRadialGradient(55,55,10,55,55,30);
	 //前三个参数是起点的圆心和半径,后面三个参数也是一样的
	 //很显然这里是同心圆!
	 gradient.addColorStop(0,"white");
	 gradient.addColorStop(1,"black");
	 context.fillStyle="#ff0000";
	 context.fillRect(10,10,50,50);
	 //绘制渐变矩形
	 context.fillStyle=gradient;
	 context.fillRect(30,30,50,50);
可以通过createPattern来创建模式对象

var context=drawing.getContext('2d');
	   var img=$('img')[0];
	   var pattern=context.createPattern(img,"repeat");
	   context.fillStyle=pattern;
	   //把fillStyle设置为模式对象
	   context.fillRect(10,10,150,150);

该函数的第一个参数可以是video元素或者一个canvas元素!其中第二个参数与css的background-repeat属性相同,包括repeat,repeat-x,repeat-y,no-repeat!注意:模式和渐变一样都是从画布的原点(0,0)开始的!

通过getImageData和putImageData可以对图片原始数据进行处理

var drawing=document.getElementById('drawing');
	 if(drawing.getContext)
	 {
	  var context=drawing.getContext('2d');
	  var img=document.images[0];
	  context.drawImage(img,0,0);
	  //绘制原始图片
	  var imageData=context.getImageData(0,0,img.width,img.height);
	  //获取到图像原始数据,返回ImageData实例,该对象有width/height/data属性
	  var data=imageData.data;
	  //data属性是一个数组,保存着图像中每一个像素的数据
	  //每一个像素的数据用4个元素保存,分别是红绿蓝透明度
	  for(var i=0,len=data.length;i<len;i+=4)
	  {
	    var red=data[i];
		var green=data[i+1];
		var blue=data[i+2];
		var alpha=data[i+3];
		var average=Math.floor((red+green+blue)/3);
		data[i]=average;
		data[i+1]=average;
		data[i+2]=average;
	  }
	  imageData.data=data;
	  //回写图像数据并且显示
	  context.putImageData(imageData,0,0);
	  }

把data数组回写到imageData对象上面后,调用putImageData方法把图片数据绘制到画布上,最终得到图像的黑白版!

19、代码调试部分

如果有finally那么try/catch中的return都会被忽略

          try
	 {
		window.name="qinliang";
	 }
	 catch (error)
	 {
	 //该error对象的message属性是唯一一个保证所有浏览器都支持的
	 //属性,还有一个name用于保存错误类型,除了Opera9之前的浏览器
	 //都支持
	 }finally
	 {
	   //只要包含了finally,那么try/catch中的return语句都会被忽略
	 }
如果需要知道错误的类型可以用instanceof,可以判断是什么类型的错误如,TypeError/ReferenceError/URIError/RangeError/SyntaxError/EvalError等
自定义错误类型必须继承Error,同时指定name,message属性

 //要自定义错误必须为新创建的错误类型指定name和message
	function CustomError(name,message)
	{
	  this.name=name;
	  this.message=message;
	}
	CustomError.prototype=new Error();
	//利用原型链继承通用错误类型
	throw new CustomError('my message',"自定义错误异常");
    //throw用于随时抛出自定义错误,抛出错误的时候要给throw指定一个值
	//这个值是什么类型没有要求,遇到throw时候代码立即停止执行,仅当
	//有try..catch捕获到抛出的错误时候才会继续执行
	console.log(1);
	//不会执行,除非上面try..catch掉
或者我们也直接可以抛出一个自定义错误类new Error('Arguments must be an array');
在Chrome/IE/FF中指定全局的onerror事件处理没有try.catch的错误

//任何没有try-catch处理的错误都会触发window对象的error事件,Opera/SF不支持
//任何浏览器中都不会创建event对象,但是可以接受三个参数:错误消息,错误所在的URL
//错误的行号,大多数我们只用message,而且必须通过DOM0绑定
  window.onerror=function(message,url,line)
  {
    console.log(message);
	return false;
	//返回false可以阻止浏览器报告错误的默认行为,实际上是充当了整个文档的try..catch
	//可以捕获所有没有代码处理的运行时错误,这个事件处理程序是报告错误的最后已到防线,尽量
	//少使用。IE中即使发生error事件代码仍然正常执行,所有变量和数据都保存,因此可以在onerror中
	//访问,但是在FF中,常规代码停止执行,事件发生之前的所有变量和数据都被销毁,因此几乎就无法判断错误
	//类型
  }
图像也有一个error事件,该事件有一个event对象

var img=new Image();
  img.onerror=function(e)
  {
    console.log(e);
	//图像的onerror会返回一个以图像为目标的event对象
  }
  img.src='1.jpg';
  //这样当加载图像失败就会显示警告框,需要注意的是
  //发生error事件时,图像的下载过程已经结束也就是说
  //不能再重新下载了!
我们可以模仿CSP中常见的report-uri来回报前端的错误日志

 function logError(sev,msg)
 {
   var image=new Image();
   image.src="log.jsp?sev="+encodeURIComponent(sec)+"&msg="+encodeURIComponent(msg);
   //通过image发生get请求到服务器
 }
//模块初始化如果出错就调用他,这在seajs中都是有这种形式的,在CSP中可以设置report-uri
//指定回报的url
for(var i=0,len=mods.length;i<len;i++)
{
  try
  {
	mods[i].init();
  }
  catch (e)
  {
    logError("not fatal",'Module init failed'+e.message);
	//保存日志
  }
  //选择image对象的好处:所有浏览器都支持image,包括不支持xhr的对象
  //可以避免跨域限制,通常一台服务器负责多台服务器错误,而xhr做不到
  //在记录错误的过程中出错的机会低,因为不需要javascript库的支持!

如果要设置一个兼容浏览器的log函数,可以如下设定

 function log(message)
 {
  if(typeof console=='object')
  {
    console.log(message);
  }else if(typeof opera=='object')
  {
  //在Opera10.5之前,javascript通过opera.postError来访问javascript控制台
  //该方法可以往控制台写入任何信息
    opera.postError(message);
  }else if(typeof java=='object'&&typeof java.lang=='object')
  {
  //liveConnect也就是在javascript中运行java代码,FF/SF/Pera都支持
  //因此可以操作java控制台
    java.lang.System.out.println(message);
   }
 }
在FF/SF/Opera中可以调用java控制台开输入javascript消息!在不执行javascript控制台的IE7等浏览器中可以创建一个区域,然后在该区域里面写入log就可以了!
如果在大型项目中我们需要调试的时候就会用到assert函数,有点像jUnit测试,只是没有jUnit功能那么强大而已:

//大型引用程序都是用assert函数抛出错误
function assert(condition,message)
{
  if(!condition)
  {
   throw new Error(message);
  }
}
//用法如下
function divide(num1,num2)
{
  assert(typeof num1=='number' && typeof num2=="number",'both arguments must be number');
  //如果不是数字那么抛出错误
  return num1/num2;
}
console.log(divide(5,3));
在IE9之前所有的DOM对象都是以COM对象而不是元素的javascript实现的,所以可能会导致未找到成员错误

  //在IE中如果在对象被销毁之后又给该对象赋值就会导致未找到
   //成员的错误,而导致这个错误的原因就一定是COM对象!
   document.onclick=function(e)
   {
     var event=e?e:window.event;
	 //在chrome等浏览器中没有这种现象
	 setTimeout(function()
	 {
	   event.returnValue=false;
	 },1000)
   }
在IE9以下的浏览器中,因为在点击事件以后event被销毁,所以再次给其赋值就会导致未找到成员错误,而其它浏览器中没有这种现象!

你可能感兴趣的:([高级程序设计]从高级程序设计中搬来的一些值得注意的地方)