竹磬网-邵珠庆の日记 生命只有一次,你可以用它来做些更多伟大的事情–Make the world a little better and easier


207月/170

javascript中的五种基本数据类型

发布在 邵珠庆

[0]5种数据类型:

[0.1]基本数据类型:Undefined、Null、Boolean、Number、String

[0.1.1]基本类型值是指简单的数据段,5种基本类型是按值访问的,因为可以操作保存在变量中的实际值

[0.1.2]基本类型的值在内存中占据固定大小的空间,被保存在栈内存中。从一个变量向另一个变量复制基本类型的值,会创建这个值的一个副本。

[0.1.3]不能给基本类型的值添加属性

[0.2]引用数据类型:Object

[0.2.1]引用类型值是指那些可以由多个值构成的对象。js不允许直接访问内存中的位置,也就是不能直接访问操作对象的内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象。

[0.2.2]引用类型的值是对象,保存在堆内存中,包含引用类型值的变量实际上包含的并不是对象本身,而是一个指向该对象的指针。从一个变量向另一个变量复制引用类型的值,复制的其实是指针,因此两个变量最终都指向同一个对象。

[0.2.3]对于引用类型的值,可以为其添加属性和方法,也可以改变和删除其属性和方法

[1]Undefined

[1.1]Undefined类型只有一个值,就是undefined

[1.2]var a <=> var a = undefined;

[1.3]对于尚未声明过的变量,只能执行一项操作,就是使用typeof操作符检测其数据类型【但在严格模式下会导致错误】

[1.4]出现场景:

[1.4.1]已声明未赋值的变量

[1.4.2]获取对象不存在的属性

[1.4.3]无返回值的函数的执行结果

[1.4.4]函数的参数没有传入

[1.4.5]void(expression)

[1.5]类型转换

Boolean(undefined):false

Number(undefined):NaN

String(undefined):'undefined'

[2]Null

[2.1]Null类型只有一个值,就是null,逻辑角度看,null值表示一个空对象指针

[2.2]如果定义的变量将用于保存对象,最好将该变量初始化为null

[2.3]实际上undefined值是派生自null值的,所以undefined == null

[2.4]出现场景:对象不存在时

[2.5]类型转换

Boolean(null):false

Number(null):0

String(null):'null'

[注意1]null是空对象指针,而[]是空数组,{}是空对象,三者不相同

[注意2]null不能添加自定义属性

[3]Boolean

[3.1]Boolean类型只有两个值:true 和 false

[3.2]出现场景:

[3.2.1]条件语句导致系统执行的隐士类型转换

[3.2.2]字面量或变量定义

[3.3]类型转换

Number(true): 1 || Number(false) : 0

String(true):'true' || String(false):'false'

[3.4]Boolean()

Boolean(undefined):false

Boolean(null):false

Boolean(非空对象包括空数组[]和空对象{}):true

Boolean(非0): true || Boolean(0和NaN):false

Boolean(非空包括空格字符串):true || Boolean(''):false

[注意]true不一定等于1,false也不一定等于0

 [4]Number

[4.1]Number类型使用IEEE754格式来表示整数和浮点数值

[注意]可以用一个值-0来将其转换成一个数字

[4.2]三种字面量格式是十进制、八进制、十六进制

[4.2.1]八进制字面值的第一位必须是0,然后是八进制数字序列(0-7),如果字面值中的数值超出了范围,那么前导0将被忽略,后面的数值被当作十进制数解析

[4.2.2]八进制字面量在严格模式下是无效的,会导致js抛出错误

[4.2.3]十六进制字面值的前两位必须是0x,后跟十六进制数字序列,字母可大写可小写

[4.2.4]十六进制中字面值中的数值走出范围,如出现g,h等会报错

[4.2.5]在进行算术计算时,所有以八进制和十六进制表示的数值最终都将被转换成十进制数值

[4.3]数值表示:

[4.3.1]js中可以保存正0和负0,且被认为相等

[4.3.2]浮点数值:该数值中必须包含一个小数点,并且小数点后面必须至少有一位数字。

[4.3.2.1]由于浮点型数值需要的内存空间是保存整数值的两倍,因此js会不失时机地将浮点数值转换成整数值,若小数点后没有跟任何数字或者浮点值本身表示的就是一个整数,这个数值会作为整数值来保存。

[4.3.2.2]浮点数值的最高精度是17位小数

[4.3.2.3]对于极大或者极小的数,可以用科学计数法e来表示的浮点数值来表示

[4.3.2.4]默认情况下,js会将小数点后面带有6个0以上的浮点数值转换为以e表示法表示的数值

[4.3.2.5]基于IEEE754数值的浮点计算的通病是舍入误差的问题。如:0.1+0.2 === 0.3(15个0)4

[4.3.3]js中的数值范围是Number.MIN_VALUE(5e-324) —— Number.MAX_VALUE(1.7976931348623157e+308)

[4.3.3.1]如果超出正数范围,输出Infinity(正无穷大),超出负数范围,输出-Infinity(负无穷大)

[4.3.3.2]+-Infinity不能参与数值计算

[4.3.3.3]Number.MAX_VALUE+1 != Infinity,因为计算机最多保存52位尾数位,保存不了1000多位,早就失去精度,即小数位全为0,所以相加不变

[4.3.3.4]Number.MIN_VALUE - 1 != -Infinity,也是同样的原因,所以结果为-1

[4.3.3.5]可以用isFinite()来确定一个数值是不是有穷的,包含着隐式类型转换Number()

[4.3.3.6]isFinite(NaN) //false

[4.3.4]NaN

[4.3.4.1]NaN与任何值都不相等,包括NaN本身

[4.3.4.2]任何涉及NaN的操作都会返回NaN

[4.3.4.3]isNaN()来判断这个数字是不是NaN,包含着隐式类型转换Number()

[4.4]数值转换:Number()可用于任何类型,parseInt()和parseFloat专门用于把字符串转换成数值

[注意1]Number()、parseInt()、parseFloat()可以接受各种进制的数字,但对于含数字的字符串并不适用

[注意2]Number()、parseInt()、parseFloat()中数字为1.2. 会报错,但字符串为'1.2.'则不会报错

[4.4.1]Number()

Number(true):1 || Number(false):0

Number(各种进制的数字):运算后的十进制的数字,如1.0或1.或01会以1输出

Number(undefined):NaN

Number(null):0

Number(字符串):

Number(只包含数字的十进制和十六进制的字符串):运算后的十进制的数字

[注意]字符串中不识别八进制,按照十进制数字处理

Number(''和' '):0

Number(其他情况的字符串):NaN

Number(对象):

Number([]和[0]和[-0]):0

Number([数字]):运算后的数字

Number([1,2]和{}和其他对象):NaN

[4.4.2]parseInt():在转换字符串时,会忽略字符串前面的空格,直到找到第一个非空格字符。如果第一个字符不是数字字符或者负号,parseInt()就会返回NaN。如果是,则继续解析,直到解析完成或者遇到非数字字符。

[4.4.2.1]parseInt()可以识别出各种进制的整数,但在解析八进制字面量的字符串,ECMAScript3会解析八进制,但ECMAScript5没有解析八进制的能力

[4.4.2.2]parseInt()函数提供第二个参数,表示多少进制,如:parseInt('123',16或10或2)

[4.4.2.3]parseInt(各种进制的数字):运算后的十进制的数字,如1.0或1.或01会以1输出

[4.4.2.4]因为parseInt()是专门用来处理字符串转换数字的,所以parseInt(其他类型包括'')//NaN

[4.4.3]parseFloat():类似于parseInt(),会忽略字符串前面的空格,直到找到第一个非空格字符

[4.4.3.1]parseFloat()只能解析十进制字符串

[4.4.3.2]parseFloat(各种进制的数字):运算后的十进制的数字,如1.0或1.或01会以1输出

 [5]String:由单引号或双引号括起来的字符序列,任何字符串的长度都可以通过访问length属性获得

[5.1]字符字面量,也叫转义序列

\n 换行

\t 制表

\b 空格

\r 回车

\f 进纸

\\ 斜杠

\' 单引号

\" 双引号

\xnn 以十六进制nn表示一个字符(n为0-f),如\x41表示'A'

\unnnn 以十六进制nnnn表示一个Unicode字符(n为0-f),如\u03a3表示希腊字符ε

[5.2]ECMAScript中的字符串是不可变的

[5.3]字符串连接需要先创建一个新字符串,然后在新字符串中填充两个需要拼接的字符串,最后再销毁原来的字符串。这个过程在后台发生,也是在某些旧版本浏览器(IE6)拼接字符串速度慢的原因,但后来已经解决了这个低效率问题

[5.4]字符串转换

[5.4.1]toString()

Null和Undefined没有该方法

Boolean、Object、String有该方法

Number使用该方法可以传递基数2、8、10、16,如var num = 10;num.toString(2);//1010

但10.toString(2)会报错,因为数字后面不能跟标识符

[5.4.2]String()

有toString()方法,使用toString()方法

String(null);//'null'

String(undefined);//'undefined'

[5.4.3]要把某个值转换为字符串,可以使用加号操作符把它与一个空字符串''加在一起

[5.4.4]如果数组中的某一项的值是null或者undefined,那么该值在join()、toLocaleString()、toString()和valueOf()方法返回的结果中以空字符串表示

最后给大家一个简单的例子说明下这五种基本类型的不同

var testString = "Hello"; 
var testBoobean = true; 
var testUndefined = undefined; 
var testUndefined1; 
var testNull = null; 
var testObject = {a:1}; 
var testFunction = function(){return;}; 
 
alert(testString);//"string" 
alert(testBoobean);//"boolean" 
alert(testUndefined);//"undefined" 
alert(testUndefined1);//"undefined" 
alert(testUndefined2);//"undefined" 
alert(testNull);//"object" 
alert(testObject);//"object" 
alert(testFunction);//"function"
125月/140

JavaScript的开发规范要求

发布在 邵珠庆

作为一名开发人员(We前端JavaScript开发),不规范的开发不仅使日后代码维护变的困难,同时也不利于团队的合作,通常还会带来代码安全以及执行效率上的问题。本人在开发工作中就曾与不按规范来开发的同事合作过,与他合作就不能用“愉快”来形容了。现在本人撰写此文的目的除了与大家分享一点点经验外,更多的是希望对未来的合作伙伴能够起到一定的借鉴作用。当然,如果我说的有不科学的地方还希望各路前辈多多指教。下面分条目列出各种规范要求,这些要求都是针对同事编码毛病提出来的,好些行业约定的其它规范可能不会再提及。

1、保证代码压缩后不出错

对于大型的JavaScript项目,一般会在产品发布时对项目包含的所有JavaScript文件进行压缩处理,比如可以利用Google Closure Compiler Service对代码进行压缩,新版jQuery已改用这一工具对代码进行压缩,这一般会去掉开发时写的注释,除去所有空格和换行,甚至可以把原来较长的变量名替换成短且无意义的变量名,这样做的目的是加快文件的下载速度,同时也减小网站访问带来的额外数据流量,另外在代码保护上也起到了一点点作用,至少压缩后的代码即使被还原还是没那么容易一下读懂的。要想代码能正确通过压缩,一般要求语句都要以分号正常结束,大括号也要严格结束等,具体还要看压缩工具的要求。所以如果一开始没有按标准来做,等压缩出错后再回去找错误那是浪费时间。

2、保证代码能通过特定IDE的自动格式化功能

一般较为完善的开发工具(比如Aptana Studio)都有代码“自动格式”化功能,这一功能帮助实现统一换行、缩进、空格等代码编排,你可以设置自己喜欢的格式标准,比如左大括号{是否另起一行。达到这个要求的目的在于方便你的开发团队成员拿你代码的一个副本用IDE自动格式化成他喜欢或熟悉的风格进行阅读。你同事需要阅读你的代码,可能是因为你写的是通用方法,他在其它模块开发过程中也要使用到,阅读你的代码能最深入了解方法调用和实现的细节,这是简单API文档不能达到的效果。

3、使用标准的文档注释

这一要求算是最基本的,这有利于在方法调用处看到方法的具体传参提示,也可以利用配套文档工具生成html或其它格式的开发文档供其他团队成员阅读,你可以尝试使用jsdoc-toolkit。如果你自动生成的API是出自一个开放平台,就像facebook.com应用,那么你的文档是给天下所有开发者看的。另外编写完整注释,也更方便团队成员阅读你的代码,通过你的参数描述,团队成员可以很容易知道你编写的方法传参与实现细节。当然也方便日后代码维护,这样即使再大的项目,过了很长时间后,回去改点东西也就不至于自己都忘记了当时自己写的代码是怎么一回事了。

4、使用规范有意义的变量名

使用规范有意义的变量名可以提高代码的可读性,作为大项目开发成员,自己写的代码不仅仅要让别人容易看懂。开发大项目,其实每个人写的代码量可能都比较大,规范命名,日后自己看回自己的代码也显的清晰易懂,比如日后系统升级或新增功能,修改起代码来也轻松多了。如果到头发现自己当初写的代码现在看不太懂了,那还真是天大的笑话了。

当然,使用有意义的变量名也尽量使用标准的命名,比如像这里:var me = this也许没有var self = this好,因为self是Python中的关键字,在Python中self就是通常语言this的用法。再看下面一个例子,加s显然比没有加来的科学些,这样可以知道这个变量名存的是复数,可能是数组等:

var li = document.getElementsByTagName('li')var lis = document.getElementsByTagName('li')

5、不使用生偏语法

JavaScript作为一门动态脚本语言,灵活性既是优点也是缺点,众所周知,动态语言不同层次开发人员对实现同样一个功能写出来的代码在规范或语法上会存在较大的差别。不管怎么样,规范编码少搞怪,不把简单问题复杂化,不违反代码易读性原则才是大家应该做的。

比如这语句:typeof(b) == 'string' && alert(b) 应该改为:if (typeof(b) == 'string') alert(b),像前面那种用法,利用了&&运算符解析机制:如果检测到&&前语句返回false就不再检测后面语句,在代码优化方面也有提到把最可能出现的情况首先判断,像这种写法如果条件少还好,如果条件较多而且语句也长,那代码可读性就相当差。

又比如:+function(a){var p = a;}( 'a') 应该改为:(function(a){var p = a;})( 'a'),其实function前面的+号与包含function的()括号作用是一样的,都是起运算优先作用,后者是常见且容易看明白的防止变量污染的做法,比如好些流行JavaScript框架就是采用后面这种方式。

再说个降低代码可读性的例子,如:function getPostionTxt(type){return type == 2 ? "野外" : (type == 3 ? "商城" : (type == 4 ? "副本" : null));} 应该改成:function getPostionTxt(type){var typeData={"2":"野外","3":"商城","4":"副本"};if (typeData[type]) return typeData[type]; else return null;}。如果type是从0开始不间断的整数,那么直接使用数组还更简单,这种结果看起来就清晰多了,看到前面那种多层三元表达式嵌套头不晕吗。

6、不在语句非赋值地方出现中文

语句中不应该出现中文我想一般人都知道,虽然这样做不影响程序运行,但是显然有背行业标准要求,当然我们也不是在使用“易语言”做开发。关于这一个问题,我本来不想把它拿出来说的,但我确实遇到有人这样做的,也不知道是不是因为他的英语实在太烂了,至少还可以用拼音吧,另外寻求翻译工具帮忙也不错的选择。我举例如下,像以下写法出现在教学中倒还可以理解:

this.user['名字'] = '张三' 或者 this.user.名字 = '张三'

7、明确定义函数固定数量的参数

固定数量参数的函数内部不使用arguments去获取参数,因为这样,你定义的方法如果包含较多的脚本,就不能一眼看到这个方法接受些什么参数以及参数的个数是多少。比如像下面:

var $ = function(){return document.getElementById(arguments[0]);} 应该改成:var $ = function(elemID){return document.getElementById(elemID);}

8、不必热衷动态事件绑定

虽然知道事件可以动态绑定,比如使用addEventListener或者使用jQuery的bind方法,也知道采用动态事件绑定可以让XHTML更干净,但是一般情况下我还是建议直接把事件写在DOM节点上,我认为这样可以使代码变得更容易维护,因为这样做,我们在查看源代码的时候就可以容易地知道什么Element绑定了什么方法,简单说这样更容易知道一个按钮或链接点击时调了什么方法脚本。

9、降低代码与XHTML的耦合性

不要过于依赖DOM的一些内容特征来调用不同的脚本代码,而应该定义不同功能的方法,然后在DOM上调用,这样不管DOM是按钮还是链接,方法的调用都是一样的,比如像下面的实现显然会存在问题:

function myBtnClick(obj)
{
 if (/确定/.test(obj.innerHTML))
  alert('OK');
 else if (/取消/.test(obj.innerHTML))
  alert('Cancel');
 else  alert('Other');
}<a herf="javascript:;">确定</a><a herf="javascript:;">取消</a>

上面例子其实在一个函数内处理了两件事情,应该分成两个函数,像上面的写法,如果把链接换成按钮,比如改成这样:<input type="button" value="确定" />,那么myBtnClick函数内部的obj.innerHTML就出问题了,因为此时应该obj.value才对,另外如果把按钮名称由中文改为英文也会出问题,所以这种做法问题太多了。

10、一个函数应该返回统一的数据类型

因为JavaScrip是弱类型的,在编写函数的时候有些人对于返回类型的处理显得比较随便,我觉得应该像强类型语言那样返回,看看下面的两个例子:

function getUserName(userID)
{
 if (data[userID])
  return data[userID];
 else  return false;
}
应该改为:function getUserName(userID)
{
 if (data[userID])
  return data[userID];
 else  return "";
}

这个方法如果在C#中定义,我们知道它准备返回的数据类型应该是字符串,所以如果没有找到这个数据我们就应该返回空的字符串,而不是返回布尔值或其它不合适的类型。这并没有影响到函数将来的调用,因为返回的空字符串在逻辑判断上可被认作“非”,即与false一样,除非我们使用全等于“===”或typeof进行判断。

11、规范定义JSON对象,补全双引号

使用标准肯定是有好处的,那么为什么还是有人不使用标准呢?我想这可能是懒或习惯问题。也许还会有人跟我说,少写引号可以减轻文件体积,我认为这有道理但不是重点。对于服务器返回的JSON数据,使用标准结构可以利用Firefox浏览器的JSONView插件方便查看(像查看XML那样树形显示),另外你如果使用jQuery做开发,最新版本jQuery1.4+是对JSON格式有更高要求的,具体的可以自己查阅jQuery更新文档。比如:{name:"Tom"}或{'name':'Tom'}都应该改成{"name":"Tom"}。

12、不在文件中留下未来确定不再使用的代码片段

当代码调整或重构后,之前编写的不再使用的代码应该及时删除,如果认为这些代码还有一定利用价值可以把它们剪切到临时文件中。留在项目中不仅增加了文件体积,这对团队其它成员甚至自己都起到一定干扰作用,怕将来自己看回代码都搞不懂这方法是干什么的,是否有使用过。当然可以用文档注释标签@deprecated把这个方法标识为不推荐的。

13、不重复定义其他团队成员已经实现的方法

对于大型项目,一般会有部分开发成员实现一些通用方法,而另外一些开发成员则要去熟悉这些通用方法,然后在自己编写模块遇到有调用的需要就直接调用,而不是像有些开发者喜欢“单干”,根本不会阅读这些通用方法文档,在自己代码中又写了一遍实现,这不仅产生多余的代码量,当然也是会影响团队开发效率的,这是没有团队合作精神的表现,是重复造轮子的悲剧。

比如在通用类文件Common.js有定义function $(elemID){return document.getElementById(elemID)}那么就不应该在Mail.js中再次出现这一功能函数的重复定义,对于一些复杂的方法更应该如此。

14、调用合适的方法

当有几个方法都可以实现同类功能的时候,我们还是要根据场景选择使用最合适的方法。下面拿jQuery框架的两个AJAX方法来说明。如果确定服务器返回的数据是JSON应该直接使用$.getJSON,而不是使用$.get得到数据再用eval函数转成JSON对象。如果因为本次请求要传输大量的数据而不得以使用$.post也应该采用指定返回数据类型(设置dataType参数)的做法。如果使用$.getJSON,在代码中我们一眼能看出本次请求服务器返回的是JSON。

温馨提示:jQuery1.4后,如果服务器有设置数据输出的ContentType,比如ASP.NET C#设置 Response.ContentType = "application/json",那么$.get将与$.getJSON的使用没有什么区别。

15、使用合适的控件存储合适的数据

曾发现有人利用DIV来保存JSON数据,以待页面下载后将来使用,像这样:<div >{ "name":"Tom"}</div>,显然这个DIV不是用来界面显示的,如果非要这样做,达到使用HTML文件进行数据缓存的作用,至少改成用隐藏域来存这数据更合理,比如改成:<input type="hidden" value=" { "name":"Tom"}" />。

其实也可以利用window对象来保存一些数据,像上面的例子,我们可以在AJAX请求页直接包含这样的脚本块:<script>window.userData = { "name":"Tom"};</script>,当在AJAX请求回调函数中执行完$( "#MyDiv ").html(data)后,在window上就马上有了这一变量。如果采用第一种方法,将不可避免eval(document.getElementById("UserData").innerHTML)。如果在window对象存放大量数据的话,这些数据不用时要及时手动清理它们,它们是要等浏览器刷新或重启后才会消失的,这就会增加内存开销。

16、永远不要忽略代码优化工作

代码最优化是每个程序员应该努力达到的目标,也应该成为程序员永远的追求。写代码的时候,不应该急着把功能实现出来,要想一下如何写代码,代码的执行效率才是较好的。

举个例子:假设有定义getElementById的快捷方法functoin $(elemID){return document.getElementById(elemID)},那么有人可能会写出这样的代码$("MyDiv").parentNode.removeChild($("MyDiv")),其实这里执行了两次getElementById DOM查找,如果改成这样将更好:var myDiv = $("MyDiv"); myDiv.parentNode.removeChild(myDiv)。还好getElementById的DOM查找算比较快,如果换成getElementsByTagName则更应该注重优化了。jQuery开发团队也有提醒大家要注意这方面的问题。

当然,代码优化技巧也是需要个人不断积累的。曾有朋友跟我说他写网站后台代码从来不用考虑优化的,因为他们网站用的是至强四核服务器,我觉得这是很可笑的。

17、会分析策划文档,能用面向对象方法进行接口定义和代码组织

这一能力对于每一个程序员来说都是非常重要的,这也是决定一个程序员水平高低的一个重要因素。能够把需求细化并抽象出不同的类,然后有条理地编写代码,使代码结构清晰,可读性高,代码易于维护,不至于太过程化而且杂乱无章,这样才算是一个优秀的程序员。

411月/131

浅析JavaScript执行顺序

发布在 邵珠庆

JavaScript执行顺序

之前从JavaScript引擎的解析机制来探索JavaScript的工作原理,下面我们以更形象的示例来说明JavaScript代码在页面中的执行顺序。如果说,JavaScript引擎的工作机制比较深奥是因为它属于底层行为,那么JavaScript代码执行顺序就比较形象了,因为我们可以直观感觉到这种执行顺序,当然JavaScript代码的执行顺序是比较复杂的,所以在深入JavaScript语言之前也有必要对其进行剖析。

1.1  按HTML文档流顺序执行JavaScript代码

首先,读者应该清楚,HTML文档在浏览器中的解析过程是这样的:浏览器是按着文档流从上到下逐步解析页面结构和信息的。JavaScript代码作为嵌入的脚本应该也算做HTML文档的组成部分,所以JavaScript代码在装载时的执行顺序也是根据脚本标签<script>的出现顺序来确定的。例如,浏览下面文档页面,你会看到代码是从上到下逐步被解析的。

<script>

alert("顶部脚本");

</script>

<html><head>

<script>

alert("头部脚本");

</script>

<title></title>

</head>

<body>

<script>

alert("页面脚本");

</script>

</body></html>

<script>

alert("底部脚本");

</script>

如果通过脚本标签<script>的src属性导入外部JavaScript文件脚本,那么它也将按照其语句出现的顺序来执行,而且执行过程是文档装载的一部分。不会因为是外部JavaScript文件而延期执行。例如,把上面文档中的头部和主体区域的脚本移到外部JavaScript文件中,然后通过src属性导入。继续预览页面文档,你会看到相同的执行顺序。

<script>

alert("顶部脚本");

</script>

<html>

<head>

<script src="head.js"></script>

<title></title>

</head>

<body>

<script src="body.js"></script>

</body>

</html>

<script>

alert("底部脚本");

</script>

1.2  预编译与执行顺序的关系

在Javascript中,function才是Javascript的第一型。当我们写下一段函数时,其实不过是建立了一个function类型的实体。

就像我们可以写成这样的形式一样:

functionHello() {

alert("Hello");

}

Hello();

varHello = function() {

alert("Hello");

}

Hello();

其实都是一样的。

但是当我们对其中的函数进行修改时,会发现很奇怪的问题。

   <scripttype="text/javascript">

        functionHello() {

            alert("Hello");

        }

        Hello();

        functionHello() {

            alert("Hello World");

        }

        Hello();

    </script>

我们会看到这样的结果:连续输出了两次Hello World。而非我们想象中的Hello和Hello World。

这是因为Javascript并非完全的按顺序解释执行,而是在解释之前会对Javascript进行一次“预编译”,在预编译的过程中,会把定义式的函数优先执行,也会把所有var变量创建,默认值为undefined,以提高程序的执行效率。也就是说上面的一段代码其实被JS引擎预编译为这样的形式:

    <scripttype="text/javascript">

        varHello = function() {

            alert("Hello");

        }

        Hello = function() {

            alert("Hello World");

        }

        Hello();

        Hello();

    </script>

我们可以通过上面的代码很清晰地看到,其实函数也是数据,也是变量,我们也可以对“函数“进行赋值(重赋值)。当然,我们为了防止这样的情况,也可以这样:

 <scripttype="text/javascript">

        functionHello() {

            alert("Hello");

        }

        Hello();

    </script>

    <scripttype="text/javascript">

        functionHello() {

            alert("Hello World");

        }

        Hello();

    </script>

这样,程序被分成了两段,JS引擎也就不会把他们放到一起了。

 

 

当JavaScript引擎解析脚本时,它会在预编译期对所有声明的变量和函数进行处理。

做如下处理:

1. 在执行前会进行类似“预编译”的操作:首先会创建一个当前执行环境下的活动对象,并将那些用var申明的变量设置为活动对象的属性,但是此时这些变量的赋值都是undefined,并将那些以function定义的函数也添加为活动对象的属性,而且它们的值正是函数的定义。

2. 在解释执行阶段,遇到变量需要解析时,会首先从当前执行环境的活动对象中查找,如果没有找到而且该执行环境的拥有者有prototype属性时则会从prototype链中查找,否则将会按照作用域链查找。遇到var a = ...这样的语句时会给相应的变量进行赋值(注意:变量的赋值是在解释执行阶段完成的,如果在这之前使用变量,它的值会是undefined)
所以,就会出现当JavaScript解释器执行下面脚本时不会报错:

alert(a);                            // 返回值undefined

var a =1;

alert(a);                            // 返回值1

由于变量声明是在预编译期被处理的,所以在执行期间对于所有代码来说,都是可见的。但是,你也会看到,执行上面代码,提示的值是undefined,而不是1。这是因为,变量初始化过程发生在执行期,而不是预编译期。在执行期,JavaScript解释器是按着代码先后顺序进行解析的,如果在前面代码行中没有为变量赋值,则JavaScript解释器会使用默认值undefined。由于在第二行中为变量a赋值了,所以在第三行代码中会提示变量a的值为1,而不是undefined。

同理,下面示例在函数声明前调用函数也是合法的,并能够被正确解析,所以返回值为1。

f();                                 // 调用函数,返回值1

function f(){

    alert(1);

}

但是,如果按下面方式定义函数,则JavaScript解释器会提示语法错误。

f();                                 // 调用函数,返回语法错误

var f = function(){

    alert(1);

}

这是因为,上面示例中定义的函数仅作为值赋值给变量f,所以在预编译期,JavaScript解释器只能够为声明变量f进行处理,而对于变量f的值,只能等到执行期时按顺序进行赋值,自然就会出现语法错误,提示找不到对象f。

再见一些例子:

 

<script type="text/javascript">

/*在预编译过程中func是window环境下的活动对象中的一个属性,值是一个函数,覆盖了undefined值*/

alert(func); //function func(){alert("hello!")}

var func = "this is a variable"

function func(){

alert("hello!")

}

/*在执行过程中遇到了var重新赋值为"this is a variable"*/

alert(func);  //this is a variable

</script>

 

<script type="text/javascript">
var name = "feng";
function func(){
/*首先,在func环境内先把name赋值为undefined,然后在执行过程中先寻找func环境下的活动对象的name属性,此时之前已经预编译值为undefined,所以输出是undefined,而不是feng*/
alert(name);  //undefined
var name = "JSF";
alert(name);  //JSF
}
func();
alert(name); //feng
</script>

虽然变量和函数声明可以在文档任意位置,但是良好的习惯应该是在所有JavaScript代码之前声明全局变量和函数,并对变量进行初始化赋值。在函数内部也是先声明变量,然后再引用。

 

1.3  按块执行JavaScript代码

所谓代码块就是使用<script>标签分隔的代码段。例如,下面两个<script>标签分别代表两个JavaScript代码块。

<script>

// JavaScript代码块1

var a =1;

</script>

<script>

// JavaScript代码块2

function f(){

    alert(1);

}

</script>

JavaScript解释器在执行脚本时,是按块来执行的。通俗地说,就是浏览器在解析HTML文档流时,如果遇到一个<script>标签,则JavaScript解释器会等到这个代码块都加载完后,先对代码块进行预编译,然后再执行。执行完毕后,浏览器会继续解析下面的HTML文档流,同时JavaScript解释器也准备好处理下一个代码块。

由于JavaScript是按块执行的,所以如果在一个JavaScript块中调用后面块中声明的变量或函数就会提示语法错误。例如,当JavaScript解释器执行下面代码时就会提示语法错误,显示变量a未定义,对象f找不到。

<script>

// JavaScript代码块1

alert(a);

f();

</script>

<script>

// JavaScript代码块2

var a =1;

function f(){

    alert(1);

}

</script>

虽然说,JavaScript是按块执行的,但是不同块都属于同一个全局作用域,也就是说,块之间的变量和函数是可以共享的。

1.4  借助事件机制改变JavaScript执行顺序

由于JavaScript是按块处理代码,同时又遵循HTML文档流的解析顺序,所以在上面示例中会看到这样的语法错误。但是当文档流加载完毕,如果再次访问就不会出现这样的错误。例如,把访问第2块代码中的变量和函数的代码放在页面初始化事件函数中,就不会出现语法错误了。

<script>

// JavaScript代码块1

window.onload = function(){        // 页面初始化事件处理函数

    alert(a);

    f();

}

</script>

<script>

// JavaScript代码块2

var a =1;

function f(){

    alert(1);

}

</script>

为了安全起见,我们一般在页面初始化完毕之后才允许JavaScript代码执行,这样可以避免网速对JavaScript执行的影响,同时也避开了HTML文档流对于JavaScript执行的限制。

注意

如果在一个页面中存在多个windows.onload事件处理函数,则只有最后一个才是有效的,为了解决这个问题,可以把所有脚本或调用函数都放在同一个onload事件处理函数中,例如:

window.onload = function(){

    f1();

    f2();

    f3();

}

而且通过这种方式可以改变函数的执行顺序,方法是:简单地调整onload事件处理函数中调用函数的排列顺序。

除了页面初始化事件外,我们还可以通过各种交互事件来改变JavaScript代码的执行顺序,如鼠标事件、键盘事件及时钟触发器等方法,详细讲解请参阅第14章的内容。

1.5  JavaScript输出脚本的执行顺序

在JavaScript开发中,经常会使用document对象的write()方法输出JavaScript脚本。那么这些动态输出的脚本是如何执行的呢?例如:

document.write('<script type="text/javascript">');

document.write('f();');

document.write('function f(){');

document.write('alert(1);');

document.write('}');

document.write('</script>');

运行上面代码,我们会发现:document.write()方法先把输出的脚本字符串写入到脚本所在的文档位置,浏览器在解析完document.write()所在文档内容后,继续解析document.write()输出的内容,然后才按顺序解析后面的HTML文档。也就是说,JavaScript脚本输出的代码字符串会在输出后马上被执行。

请注意,使用document.write()方法输出的JavaScript脚本字符串必须放在同时被输出的<script>标签中,否则JavaScript解释器因为不能够识别这些合法的JavaScript代码,而作为普通的字符串显示在页面文档中。例如,下面的代码就会把JavaScript代码显示出来,而不是执行它。

document.write('f();');

document.write('function f(){');

document.write('alert(1);');

document.write(');');

但是,通过document.write()方法输出脚本并执行也存在一定的风险,因为不同JavaScript引擎对其执行顺序不同,同时不同浏览器在解析时也会出现Bug。

Ø 问题一,找不到通过document.write()方法导入的外部JavaScript文件中声明的变量或函数。例如,看下面示例代码。

document.write('<script type="text/javascript" src="test.js">

</script>');

document.write('<script type="text/javascript">');

document.write('alert(n);');  // IE提示找不到变量n

document.write('</script>');

alert(n+1);                          // 所有浏览器都会提示找不到变量n

外部JavaScript文件(test.js)的代码如下:

var n = 1;

分别在不同浏览器中进行测试,会发现提示语法错误,找不到变量n。也就是说,如果在JavaScript代码块中访问本代码块中使用document.write()方法输出的脚本中导入的外部JavaScript文件所包含的变量,会显示语法错误。同时,如果在IE浏览器中,不仅在脚本中,而且在输出的脚本中也会提示找不到输出的导入外部JavaScript文件的变量(表述有点长和绕,不懂的读者可以尝试运行上面代码即可明白)。

Ø 问题二,不同JavaScript引擎对输出的外部导入脚本的执行顺序略有不同。例如,看下面示例代码。

<script type="text/javascript">

document.write('<script type="text/javascript" src="test1.js">

</script>');

document.write('<script type="text/javascript">');

document.write('alert(2);')

document.write('alert(n+2);');

document.write('</script>');

</script>

<script type="text/javascript">

alert(n+3);

</script>

外部JavaScript文件(test1.js)的代码如下所示。

var n = 1;

alert(n);

在IE浏览器中的执行顺序如图1-6所示。

 

图1-6  IE 7浏览器的执行顺序和提示的语法错误

在符合DOM标准的浏览器中的执行顺序与IE浏览器不同,且没有语法错误,如图1-7所示的是在Firefox 3.0浏览器中的执行顺序。

 

图1-7  Firefox 3浏览器的执行顺序和提示的语法错误

解决不同浏览器存在的不同执行顺序,以及可能存在Bug。我们可以把凡是使用输出脚本导入的外部文件,都放在独立的代码块中,这样根据上面介绍的JavaScript代码块执行顺序,就可以避免这个问题。例如,针对上面示例,可以这样设计:

<script type="text/javascript">

document.write('<script type="text/javascript" src="test1.js"></script>');

</script>

<script type="text/javascript">

document.write('<script type="text/javascript">');

document.write('alert(2);') ; // 提示2

document.write('alert(n+2);'); // 提示3

document.write('</script>');

alert(n+3); // 提示4

</script>

<script type="text/javascript">

alert(n+4); // 提示5

</script>

这样在不同浏览器中都能够按顺序执行上面代码,且输出顺序都是1、2、3、4和5。存在问题的原因是:输出导入的脚本与当前JavaScript代码块之间的矛盾。如果单独输出就不会发生冲突了。

 

197月/130

JavaScript 语言基础知识点总结(思维导图)

发布在 邵珠庆

温故而知新 ———— 最近温习了一遍Javascript 语言,故把一些基础、概念性的东西分享一下。

(下面内容大都为条目、索引,是对知识点的概括,帮助梳理知识点,具体内容需要查阅资料)

 

 

JavaScript 数组
JavaScript-array ,JavaScript 数组
JavaScript 函数基础
JavaScript-function-base
Javascript 运算符
Javascript-operational-character
JavaScript 流程控制
JavaScript-process-statement
JavaScript 正则表达式
JavaScript-regular-expressions,JavaScript 正则表达式
JavaScript 字符串函数
JavaScript-string-function
JavaScript 数据类型
The-JavaScript-data-type,The-JavaScript 数据类型
JavaScript 变量
The-JavaScript-variable
Window 对象
Window-object,Window 对象
DOM 基本操作
DOM-operation,DOM 基本操作

1.DOM基础操作

 

2.数组基础

3.函数基础

4.运算符

5.流程控制语句

6.正则表达式

 

7.字符串函数

8.数据类型

9.变量

10.window对象

 

念念不忘,必有回响。技术成就梦想!
184月/130

JavaScript跨域总结与解决办法

发布在 邵珠庆

本文来自网络(http://f2e.me/200904/cross-scripting/,该网址已不能访问),仅作个人读书笔记之用,并稍作修改和补充。

什么是跨域

JavaScript出于安全方面的考虑,不允许跨域调用其他页面的对象。但在安全限制的同时也给注入iframe或是ajax应用上带来了不少麻烦。这里把涉及到跨域的一些问题简单地整理一下:

首先什么是跨域,简单地理解就是因为JavaScript同源策略的限制,a.com 域名下的js无法操作b.com或是c.a.com域名下的对象。更详细的说明可以看下表:

URL 说明 是否允许通信
http://www.a.com/a.js
http://www.a.com/b.js
同一域名下 允许
http://www.a.com/lab/a.js
http://www.a.com/script/b.js
同一域名下不同文件夹 允许
http://www.a.com:8000/a.js
http://www.a.com/b.js
同一域名,不同端口 不允许
http://www.a.com/a.js
https://www.a.com/b.js
同一域名,不同协议 不允许
http://www.a.com/a.js
http://70.32.92.74/b.js
域名和域名对应ip 不允许
http://www.a.com/a.js
http://script.a.com/b.js
主域相同,子域不同 不允许
http://www.a.com/a.js
http://a.com/b.js
同一域名,不同二级域名(同上) 不允许(cookie这种情况下也不允许访问)
http://www.cnblogs.com/a.js
http://www.a.com/b.js
不同域名 不允许
特别注意两点:
第一,如果是协议和端口造成的跨域问题“前台”是无能为力的,
第二:在跨域问题上,域仅仅是通过“URL的首部”来识别而不会去尝试判断相同的ip地址对应着两个域或两个域是否在同一个ip上。
“URL的首部”指window.location.protocol +window.location.host,也可以理解为“Domains, protocols and ports must match”。

接下来简单地总结一下在“前台”一般处理跨域的办法,后台proxy这种方案牵涉到后台配置,这里就不阐述了,有兴趣的可以看看yahoo的这篇文章:《JavaScript: Use a Web Proxy for Cross-Domain XMLHttpRequest Calls

1、document.domain+iframe的设置

对于主域相同而子域不同的例子,可以通过设置document.domain的办法来解决。具体的做法是可以在http://www.a.com/a.html和http://script.a.com/b.html两个文件中分别加上document.domain = ‘a.com’;然后通过a.html文件中创建一个iframe,去控制iframe的contentDocument,这样两个js文件之间就可以“交互”了。当然这种办法只能解决主域相同而二级域名不同的情况,如果你异想天开的把script.a.com的domian设为alibaba.com那显然是会报错地!代码如下:

www.a.com上的a.html

document.domain = 'a.com';
var ifr = document.createElement('iframe');
ifr.src = 'http://script.a.com/b.html';
ifr.style.display = 'none';
document.body.appendChild(ifr);
ifr.onload = function(){
    var doc = ifr.contentDocument || ifr.contentWindow.document;
    // 在这里操纵b.html
    alert(doc.getElementsByTagName("h1")[0].childNodes[0].nodeValue);
};

script.a.com上的b.html

document.domain = 'a.com';

这种方式适用于{www.kuqin.com, kuqin.com, script.kuqin.com, css.kuqin.com}中的任何页面相互通信。

备注:某一页面的domain默认等于window.location.hostname。主域名是不带www的域名,例如a.com,主域名前面带前缀的通常都为二级域名或多级域名,例如www.a.com其实是二级域名。 domain只能设置为主域名,不可以在b.a.com中将domain设置为c.a.com。

问题:
1、安全性,当一个站点(b.a.com)被攻击后,另一个站点(c.a.com)会引起安全漏洞。
2、如果一个页面中引入多个iframe,要想能够操作所有iframe,必须都得设置相同domain。

2、动态创建script

虽然浏览器默认禁止了跨域访问,但并不禁止在页面中引用其他域的JS文件,并可以自由执行引入的JS文件中的function(包括操作cookie、Dom等等)。根据这一点,可以方便地通过创建script节点的方法来实现完全跨域的通信。具体的做法可以参考YUI的Get Utility

这里判断script节点加载完毕还是蛮有意思的:ie只能通过script的readystatechange属性,其它浏览器是script的load事件。以下是部分判断script加载完毕的方法。

js.onload = js.onreadystatechange = function() {
    if (!this.readyState || this.readyState === 'loaded' || this.readyState === 'complete') {
        // callback在此处执行
        js.onload = js.onreadystatechange = null;
    }
};

3、利用iframe和location.hash

这个办法比较绕,但是可以解决完全跨域情况下的脚步置换问题。原理是利用location.hash来进行传值。在url: http://a.com#helloword中的‘#helloworld’就是location.hash,改变hash并不会导致页面刷新,所以可以利用hash值来进行数据传递,当然数据容量是有限的。假设域名a.com下的文件cs1.html要和cnblogs.com域名下的cs2.html传递信息,cs1.html首先创建自动创建一个隐藏的iframe,iframe的src指向cnblogs.com域名下的cs2.html页面,这时的hash值可以做参数传递用。cs2.html响应请求后再将通过修改cs1.html的hash值来传递数据(由于两个页面不在同一个域下IE、Chrome不允许修改parent.location.hash的值,所以要借助于a.com域名下的一个代理iframe;Firefox可以修改)。同时在cs1.html上加一个定时器,隔一段时间来判断location.hash的值有没有变化,一点有变化则获取获取hash值。代码如下:

先是a.com下的文件cs1.html文件:

function startRequest(){
    var ifr = document.createElement('iframe');
    ifr.style.display = 'none';
    ifr.src = 'http://www.cnblogs.com/lab/cscript/cs2.html#paramdo';
    document.body.appendChild(ifr);
}

function checkHash() {
    try {
        var data = location.hash ? location.hash.substring(1) : '';
        if (console.log) {
            console.log('Now the data is '+data);
        }
    } catch(e) {};
}
setInterval(checkHash, 2000);

cnblogs.com域名下的cs2.html:

//模拟一个简单的参数处理操作
switch(location.hash){
    case '#paramdo':
        callBack();
        break;
    case '#paramset':
        //do something……
        break;
}

function callBack(){
    try {
        parent.location.hash = 'somedata';
    } catch (e) {
        // ie、chrome的安全机制无法修改parent.location.hash,
        // 所以要利用一个中间的cnblogs域下的代理iframe
        var ifrproxy = document.createElement('iframe');
        ifrproxy.style.display = 'none';
        ifrproxy.src = 'http://a.com/test/cscript/cs3.html#somedata';    // 注意该文件在"a.com"域下
        document.body.appendChild(ifrproxy);
    }
}

a.com下的域名cs3.html

//因为parent.parent和自身属于同一个域,所以可以改变其location.hash的值
parent.parent.location.hash = self.location.hash.substring(1);

当然这样做也存在很多缺点,诸如数据直接暴露在了url中,数据容量和类型都有限等……

4、window.name实现的跨域数据传输

文章较长列在此处不便于阅读,详细请看 window.name实现的跨域数据传输

5、使用HTML5 postMessage

HTML5中最酷的新功能之一就是 跨文档消息传输Cross Document Messaging。下一代浏览器都将支持这个功能:Chrome 2.0+、Internet Explorer 8.0+, Firefox 3.0+, Opera 9.6+, 和 Safari 4.0+ 。 Facebook已经使用了这个功能,用postMessage支持基于web的实时消息传递。

otherWindow.postMessage(message, targetOrigin);
otherWindow: 对接收信息页面的window的引用。可以是页面中iframe的contentWindow属性;window.open的返回值;通过name或下标从window.frames取到的值。
message: 所要发送的数据,string类型。
targetOrigin: 用于限制otherWindow,“*”表示不作限制

a.com/index.html中的代码:

b.com/index.html中的代码:

参考文章:《精通HTML5编程》第五章——跨文档消息机制https://developer.mozilla.org/en/dom/window.postmessage

6、利用flash

这是从YUI3的IO组件中看到的办法,具体可见http://wiht.link/YUI-intro
可以看在Adobe Developer Connection看到更多的跨域代理文件规范:ross-Domain Policy File SpecificationsHTTP Headers Blacklist