首页 > 代码库 > JavaScript大杂烩1 - 理解JavaScript的类型系统

JavaScript大杂烩1 - 理解JavaScript的类型系统

  随着硬件水平的逐渐提高,浏览器的处理能力越来越强大,本人坚信,客户端会越来越瘦,瘦到只用浏览器就够了,服务端会越来越丰满;虽然很多大型的程序,比如3D软件,客户端仍然会存在,但是未来的主流必将是浏览器,也就是Web程序/网站。

 

Web前端开发模式:Thinking in "DIV + CSS + JS (JavaScript)"

  任何面向用户的程序,最终都表现为3个部分:界面,逻辑,数据。而经过几十年的编程实践,大家都发现,当把这3个部分以弱耦合的形式结合起来的时候,开发的灵活性和效率最好,而且这种形式的程序应对变化的能力也比较强。这条原则在Web前端开发中就体现为"DIV + CSS + JS"的分离与开发。

  对照上面的3个部分,我们很容易找到对应的关系:以DIV为代表的HTML构成网页上面的数据,CSS控制界面的显示效果,JavaScript执行网页的逻辑。这3个部分可以互不干扰,独立进行开发,为Web的程序设计和团队合作带来了无与伦比的灵活性。

  这个系列是以总结JavaScript开发为主,涉及到另外两个方面的内容都是简单的提及一下。

 

浏览器的兼容性

  JavaScript是运行在浏览器中的,但是由于各家软件公司,像微软,并不按照规定的套路出牌,不去执行相关的标准去实现浏览器的行为,所以JavaScript从诞生那天起,在不同的浏览器中就充满着兼容性问题。不过还好,渐渐的大家都服从相同的ECMAScript标准了,兼容性的问题得到了一定的缓解,不过在很多时候还是值得我们去注意。
  从某种意义上说,各种JavaScript类库的出现一方面是为了重用更多的功能,另一方面也是为了更好的屏蔽浏览器的兼容性问题,本质上其实都还是重用,这是计算机技术向前发展的动力。
  所以很多时候,我们进行Web开发的时候,选择合适的类库不仅能快速开发出我们想要的功能,还能够最大程度的减少浏览器的兼容性问题。

 

JavaScript的组成部分

  简单的来说,JavaScript就是这样的:

JavaScript = ECMAScript (语言标准) + 
      DOM (Document Object Model,文档内容的建模对象) + 
      BOM (Browser Object Model,浏览器的建模对象)

ECMAScript规范: 这个不用多说,就是语法规范,这个是JavaScript编程的基石。

DOM: 这个是JavaScript的重点,我们学了那么多,就是为了要为HTML提供逻辑功能,从HTML元素抽象出来的DOM自然是重中之重。

BOM: JavaScript是运行在浏览器中的,与浏览器打交道是必须的,但是通常不会很频繁。

 

基本类型

  从语言的类型来说,JavaScript是一门脚本语言,是动态语言,特点就是不需要编译,直接解释执行。
  动态语言也有基本的类型系统,而且通常比较简单,这一点在JavaScript中体现最为明显。
  通常来说,JavaScript定义了如下6种基本类型:
1. number
  这是一切数字类数据的类型,包括浮点数(如0.1),整数(如10),还有像NaN这样的特殊值。
  NaN代表非法的数字,这个值通常是在将一个类型转换成数字失败以后返回的数值。
  此外,与别的编程语言一样,也可以使用十六进制或八进制表示数字,比如0xFF, 012等。
2. string

   这个类的重要性就不需重复了,不过需要注意,在JavaScript中,既可以用双引号""定义字符串,也可以用单引号‘‘定义字符串。但是考虑到HTML中的属性大多用双引号定义属性,所以在JavaScript中习惯上只用单引号定义字符串。
3. boolean
  这个类型很简单,只有true和false两个值。
4. object
  这个类型是JavaScript的基石,因为JavaScript是面向对象的语言,object就是整个类型系统的根,其他的所有类型都是对象,并且都是直接或者间接的从object继承而来。
  object类型有一个特殊的值null,代表对象的值是空值。
5. function
  这个类型时JavaScript的第一等成员,它构成了JavaScript编程的主旋律,function类型作为一种基元类型,可以使用在任何数据类型可以出现的场合,比如变量,参数,返回值等等。不用怀疑,function是从object继承来的。
6. undefined
  这个类型比较特别,它只有一个值,就是undefined。它代表已经声明,但还没有赋值的数据。任何的数据类型,只要声明了,没赋值,那么这个变量的类型就是undefined,值就是undefined。

  动态语言的变量不要求像静态语言那样,严格的定义类型,然后使用;在JavaScript中,一切类型都由var来定义,不需要指定类型。没有指定类型,就决定了定义的变量的类型取决于赋值的数据的类型。下面例子给出了常见的用法:

var age = 10; // number
var name = ‘Frank‘; // string
var married = false; // boolean
var obj = new object(); // object
var fun = function() { alert(‘Hi‘);}; // function
var extend; // undefined

  这些类型定义很简单,使用起来也与别的语言没什么区别,但是有几点还是需要注意的:

1. 空串,NaN,null,undefined的辨析
  空串很简单,其类型string类型,值为‘‘。
  NaN也很简单,其类型为number类型,值为NaN。只不过NaN与任何值都不相等,即使是与NaN自身也不相等,判断一个值是否是NaN的唯一方式就是使用全局的isNaN()方法。
  null也很简单的,其类型为object类型,值为null。
  undefined最简单,其类型为undefined类型,值为undefined。
  这些相似的东西很多时候是相似的,比如在if语句的条件表达式中,不过只要我们记住了它们的类型,那么在下面的全等运算符(===)面前,它们是无所遁形的
2. 类型之间的转换
  类型转换的方式主要有下面几种:
  1). 使用全局方法:String(),Number(),parseInt(),parseDouble()等等。
  2). 使用对象的方法:比如所有对象转成string类型都可以使用object内置的toString()方法。
  3). 使用运算符: 比如所有对象转成string类型的间接方式: var s = ‘‘ + obj;其本质上与第二个方法是一样的。
  4). 使用new操作: 比如new String(),new Number()等等。
  从上面我们看到了两种类型,以字符串类型为例,一种是string类型,一种是String类型,因为JavaScript是区分大小写的,这两种不是同一种类型。如何理解这种现象呢?有C#背景的同学可能比较熟悉值类型与引用类型的区别,实际上在JavaScript中也存在类似情况,string作为基本的类型,算是一种与值类型相当的类型,而String是作为string的包装类型存在,是完整的与引用类型相当的类型。
  所以使用new String()的方式得到的字符串会与普通的使用‘‘定义的字符串会有那么一点不同,比如在命令行(浏览器的调试工具)中本别输入:

String(10) 输出的结果是"10"
new String(10) 输出的结果是String {0: "1", 1: "0", length: 2}

  通常我们不会使用第四种转换方式去强行构造一个字符串对象。

  此外需要注意Number()与parseInt()处理的过程是不一样的,Number(‘10a‘)会返回NaN,而parseInt(‘10a‘)会返回10。
3. 其它类型到boolean类型的转换
  boolean类型是比较特殊的,因为很多的语句(比如if,while等等)都要使用boolean值。其他类型转换成boolean类型的显式方式是Boolean()方法。但是我们还是要了解一下,哪些值会转成true,哪些值会转成false。
  除了‘‘,null,undefined,0,NaN这几个值会转成false以外,其它的所有值都会转成true。

 

基本运算
  看完基本类型,我们再看看基本的运算,这个和其他语言没什么区别,基本的数学运算(+,-,*,/,+=,-=,*=,/=),取模(%,%=),赋值(=),判等(<,>,==,!=),取反(-),自增自减(++,--),唯一的三元条件运算符(?:)等等都是一样的。
  但是,在JavaScript中,有几个运算符与别的语言是有些区别:
1. 全等(===),不全等(!===)
  全等代表着类型和值都相等,不全等代表着类型或值不相等,细细辨别下面这些语句的不同:

alert(null == undefined); // true
alert(null === undefined); // false

  结论是我们需要判等的时候,尽量使用全等运算===,而不是普通的相等运算==。

2. 逻辑运算符(&&,||,!)
  这些运算符的第一层用法就是判断一组boolean类型的逻辑运算结果,当然其他的类型的值可以先转成boolean后参与运算。
  这个运算符的第二层用法就是像下面这种常见的写法:

var Common = Common || {};

  这种用法是确保Common被初始化了,然后可以作为一种namespace使用,这个在后面对象的总结中还会提及到。

  结合起来,其实可以简单总结为:

||是这样运算的:从第一个操作数开始,遇到有意义的操作数就返回,否则返回最后一个表达式(不一定是boolean值); 
&&是这样运算的:从第一个操作数开始,遇到无意义的操作数返回,否则返回最后一个表达式(注意同上); 
!是这样运算的:对表达式的值取非(注意不是对表达式)。

  无意义的值指的就是‘‘,null,undefined,0,NaN这几个与false等价的值。

3. 位运算符(&,|,^, >>,<<,>>>)
  这几个运算符中前4个没什么可说的,关键就是最后这个个无符号右移位操作:

expression1 >>> expression2

  >>> 运算符将 expression1 的位右移 expression2 中指定的位数,用零填充右移后左边空出的位,这是它与>>最大的不同(>>会保持符号,如果第一位是1的话,右移还是用1填充左边), 右移的位被丢弃。

4. undefined参与运算
  这是一个特殊的类型,导致参与运算的结果也比较特别,品味下面的例子:

undefined + 1; // 结果为NaN
undefined + ‘‘; // 结果为‘undefined‘

  所以一旦程序中出现undefined的话,往往会出现一些奇怪的问题,所以程序中要特别注意出现undefined的情况。

  此外,JavaScript的基本语句与别的C系语言也相同,比如if,switch,return,for,while,break,continue,包括注释(//,/*...*/)等等,这里就不多说了。

 

自动垃圾回收机制
  JavaScript是使用自动垃圾回收机制的,这个有C#或者Java这些语言背景的同学还是比较清楚的。原理也是差不多,没有再被有效引用的变量会自动被当做垃圾回收掉。如何辨别一个变量是不是垃圾有很多方法,常用的是标记清楚和引用计数两种方法,“标记清除”是目前主流的垃圾回收算法,这种算法的思想是给当前不使用的值加上标记,然后再回收其内存。“引用计数”算法的思想是跟踪记录所有值被引用的次数。JavaScript引擎不再使用“引用计数”这种算法;但在较低版本的IE(较高版本中据说已修复,不过我不用IE久矣)中访问非原生JavaScript对象(如DOM元素),还是使用这种垃圾收集的。
  当代码中存在循环引用现象时,“引用计数”算法就会导致内存泄露问题。比如:

var a = {name:‘a‘};
var b = {name:‘b‘};
a.bname = b
b.aname = a;

  这种和数据库死锁差不多的的情况还好,至少说程序员不会有意为之。但是下面这种情况在IE中却是普遍存在的:

$(document).ready(function() {
    var div = document.getElementById("mydiv");
    div.onclick = function(){
      console.log("div");
    };
});

  这个是网上某位仁兄给出的例子,据说当指定的单击事件处理程序时,创建了一个在其封闭的环境中包含div变量的闭包环境。而div也包含一个指向闭包的引用(onclick属性自身),这就导致了内存都不能得到释放。当然解决方法很简单:

$(document).ready(function() {
  var div = document.getElementById("mydiv");
  div.onclick = divonclicked;
});

function divonclicked() {
  console.log("div");
};

  此时因为divonclicked函数不在包含div的引用,所以没有形成循环,内存可以得到释放。

  可能很多人都知道将一个对象置为null,那么它的内存就会回收。这是因为变量的指向了一个null,那么它原来指向的那块内存空间就会因为没有被指向,或者说没有被引用,而被垃圾回收掉,这种做法在JavaScript中仍然适用。
  上面的例子姑且不管现在存不存在,总的来说,为了避免这些内存泄露的情况,方法就是少用IE,然后是多注意闭包中引用HTML对象的情况,在大部分情况下自动垃圾回收是没有太大问题的。

 

Client端JavaScript与Server端JavaScript
  JavaScript发展至今,已经不再仅仅满足于活动在浏览器端了,它把触角已经伸向了后面的服务器端,这对程序猿来说应该是好事,因为只要学好JavaScript,就可以前后通吃了。不过大家还是要注意,后端的JavaScript完全可以称之为全新的编程语言,不能再以老的对待浏览器中的JavaScript的眼光去看待它了。
  浏览器端的JavaScript由于运行在浏览器的这个沙箱中,所以它不可能有多么大的权限。它的主要职责就是操作浏览器中的对象,完成相应的逻辑功能。即使存在各种类库,但是本质上还是没有脱离浏览器的限制。
  但是服务器端的JavaScript中,例如在NodeJs环境中,JavaScript是作为一门独立的语言存在的,它与C#等语言一样,拥有各种丰富的各种类库,去完成丰富的功能,比如读取文件,开启线程等等,这些特性与浏览器端的JavaScript已经完全不可同日而语了。

  这个系列的重点是在Client端的JavaScript,这里的基础知识对Server端的JavaScript完全是适用的。