标签:
java语言里有一句很经典的话:在java的世界里,一切皆是对象。
Javascript虽然跟java没有半点毛关系,但是很多会使用javascript的朋友同样认为:在javascript的世界里,一切也皆是对象。
其实javascript语言和java语言一样变量是分为两种类型:基本数据类型和引用类型。
基本类型是指:Undefined、Null、Boolean、Number和String;而引用类型是指多个指构成的对象,所以javascript的对象指的是引用类型。在java里能说一切是对象,是因为java语言里对所有基本类型都做了对象封装,而这点在javascript语言里也是一样的,所以提在javascript世界里一切皆为对象也不为过。
Javascript里的基本变量是存放在栈区的(栈区指内存里的栈内存),它的存储结构如下图所示:
javascript里引用变量的存储就比基本类型存储要复杂多,引用类型的存储需要内存的栈区和堆区(堆区是指内存里的堆内存)共同完成,如下图所示:
在javascript里变量的存储包含三个部分:
部分一:栈区的变量标示符;
部分二:栈区变量的值;
部分三:堆区存储的对象。
JS中变量赋值和函数传参都是传递栈区的值。JS中的变量未定义不能访问,但可以不用var定义而直接出现在赋值语句中,此时相当于在全局通过var定义该变量,实际上是window的一个属性。
其实javascript里的变量和其他语言有很大的不同,javascript的变量是一个松散的类型,松散类型变量的特点是变量定义时候不需要指定变量的类型,变量在运行时候可以随便改变数据的类型,但是这种特性并不代表javascript变量没有类型,当变量类型被确定后javascript的变量也是有类型的。但是在现实中,很多程序员把javascript松散类型理解为了javascript变量是可以随意定义即你可以不用var定义,也可以使用var定义,其实在javascript语言里变量定义没有使用var,变量必须有赋值操作,只有赋值操作的变量是赋予给window,这其实是javascript语言设计者提升javascript安全性的一个做法。
此外javascript语言的松散类型的特点以及运行时候随时更改变量类型的特点,很多程序员会认为javascript变量的定义是在运行期进行的,更有甚者有些人认为javascript代码只有运行期,其实这种理解是错误的,javascript代码在运行前还有一个过程就是:预加载,预加载的目的是要事先构造运行环境例如全局环境,函数运行环境,还要构造作用域链,而环境和作用域的构造的核心内容就是指定好变量属于哪个范畴,因此在javascript语言里变量的定义是在预加载完成而非在运行时期。
var ooo = null; console.log(ooo);// 运行结果:null console.log(ooo == undefined);// 运行结果:true console.log(ooo == null);// 运行结果:true console.log(ooo === undefined);// 运行结果:false console.log(ooo === null);// 运行结果:true
运行之,结果很震惊啊,null居然可以和undefined相等,但是使用更加精确的三等号“===”,发现二者还是有点不同,其实javascript里undefined类型源自于null即null是undefined的父类,本质上null和undefined除了名字这个马甲不同,其他都是一样的,不过要让一个变量是null时候必须使用等号“=”进行赋值了。
当变量为undefined和null时候我们如果滥用它javascript语言可能就会报错,后续代码会无法正常运行,所以javascript开发规范里要求变量定义时候最好马上赋值,赋值好处就是我们后面不管怎么使用该变量,程序都很难因为变量未定义而报错从而终止程序的运行,例如上文里就算变量是string基本类型,在变量定义属性程序还是不会报错,这是提升程序健壮性的一个重要手段,由引子的例子我们还知道,变量定义最好放在变量所述作用域的最前端,这么做也是保证代码健壮性的一个重要手段。
函数传递参数的本质就是外部的变量复制到函数参数的变量里,我们看看下面的代码:
function testFtn(sNm,pObj){ console.log(sNm);// 运行结果:new Name console.log(pObj.oName);// 运行结果:new obj sNm = "change name"; pObj.oName = "change obj"; } var sNm = "new Name"; var pObj = {oName:"new obj"}; testFtn(sNm,pObj); console.log(sNm);// 运行结果:new Name console.log(pObj.oName);// 运行结果:change obj
var ftn1 = function(){ console.log("test:ftn1"); }; var ftn2 = function(){ console.log("test:ftn2"); }; function ftn(f){ f(); f = ftn2; } ftn(ftn1);// 运行结果:test:ftn1 console.log("====================华丽的分割线======================"); ftn1();// 运行结果:test:ftn1
这个代码是很早之前有位朋友考我的,我当时答对了,但是我是蒙的,问我的朋友答错了,其实当时我们两个都没搞懂其中缘由,我朋友是这么分析的他认为f是函数的参数,属于函数的局部作用域,因此更改f的值,是没法改变ftn1的值,因为到了外部作用域f就失效了,但是这种解释很难说明我上文里给出的函数传参的实例,其实这个问题答案就是函数传参的原理,只不过这里加入了个混淆因素函数,在javascript函数也是对象,局部作用域里f = ftn2操作是将f在栈区的地址改为了ftn2的地址,对外部的ftn1和ftn2没有任何改变。
记住:javascript里变量复制和函数传参都是在传递栈区的值。
栈区的值除了变量复制起作用,它在if语句里也会起到作用,当栈区的值为undefined、null、“”(空字符串)、0、false时候,if的条件判断则是为false,我们可以通过!运算符计算,因此当我们的代码如下:
var obj = {}; if (!!obj){ console.log("true"); }else{ console.log("false"); }
结果则是true,因为var obj = {}相当于var obj = new Object(),虽然对象里没什么内容,但是在堆区里,对象的内存已经分配了,而变量栈区的值已经是内存地址了,所以if语句判断就是true了。
标签:
原文地址:http://www.cnblogs.com/doit8791/p/4768149.html