Extreme Thinking
JavaScript语言基础

2015-04-09


学习JS之前可以看看JavaScript: 世界上最被误解的语言, 英文原版: JavaScript: The World's Most Misunderstood Programming Language, javascript入门: w3schools 或者 mozilla tutorials

JS语言特性

  • 单线程, 事件驱动
    • webworker子线程完全受主线程控制,且不得操作DOM,所以,HTML5并没有改变js单线程的本质
  • 解释执行
  • 非常灵活, 函数参数可变, 数组长度可变, 数组元素可以为任意类型, 对象成员可变
  • 没有块级作用域的, 函数是js中唯一拥有自身作用域的结构, 要注意全局变量如染

Debug输出

调试输出时有如下几种方式:

  • console.log()
  • window.alert()
  • 显示调试信息在页面上: document.write(), innerHTML

JS数据类型

JS包括两类数据类型: primitive and reference,即基本数据类型和引用类型, 引用类型就是对象(object)。基本数据类型(primitive)有如下5种:

primitive value typeof comments
布尔型 true, false 'boolean'
数值型 100, 99.23 'number' JS只有单一的数字类型,内部表示为64位浮点数,integer, float, double等都属于数值型
字符型 "rain", 'bella' 'string' 单引号'与双引号"通用, 可以用 + 连接字符串, txt.length 返回长度
null null 'object' 判断null不能使用typeof,应该使用 if (value === null)
undefined undefined 'undefined' undefined和null是不同的类型

除了上面5种基本数据类型,JS里其他都是object,数组/函数也是object类型,object是JS语言的核心,一切皆对象。

作为参数传递和变量赋值时,primitive是值传递,object是引用传递

判断数据类型

使用typeof和其他一些方法可以判断出变量数据类型:

  • boolean, number, string, undefined - 使用typeof判断
  • null - typeof返回'object',需要使用 if (value === null)
  • array - instanceof 或者 Array.isArray()
  • function - typeof 或者 instanceof
  • 其他对象类型 - instanceof

typeof判断数据类型的不足 - 基本数据类型的包装类

前一节介绍了如何判断数据类型,但对于基本数据类型的包装类,String, Number, Boolean, 上面的方法无法判断,以String为例:

var str1 = 'hello world';
var str2 = new String('hello world');
console.log(typeof str1);  // "string"
console.log(typeof str2);  // "object", typeof无法判断出str2是一个string

应该采用下面的方法判断str2:

var str1 = 'hello world';
var str2 = new String('hello world');
console.log(Object.prototype.toString.call(str1)); //"[object String]",可以正确判断str1
console.log(Object.prototype.toString.call(str2)); //"[object String]",可以正确判断str2

string/number/boolean都有这个问题,所以,应该采用下面的方法判断string/number/boolean类型:

var isBoolean = function (obj) {
    return Object.prototype.toString.call(obj) === "[object Boolean]";
};

var isString = function (obj) {
    return Object.prototype.toString.call(obj) === "[object String]";
};

var isNumber = function (obj) {
    return Object.prototype.toString.call(obj) === "[object Number]";
};

注意,function/array没有这个问题。

各种数据类型示例代码:

// strings
var name = "Rain Zhao";  //可以使用双引号
var gender = 'male';     //可以使用单引号
console.log(typeof name);  //'string'

// numbers
var count = 25;
var cost = 1.51;
console.log(typeof count);  //'number'

// boolean
var found = true;
console.log(typeof found); //'boolean'

// null
var object = null;
console.log(typeof object);  //'object', 无法判断null

// undefined
var flag = undefined;
var ref;                  //未赋值的变量就是undefined
console.log(typeof ref);  //'undefined'

//function
var foo = function(){};
console.log(typeof foo);  //'function'
console.log(foo instanceof Function);  //true
console.log(foo instanceof Object);    //true, 函数也是对象

// array
var arr = ["rain", 3.14, {'name': 'bella'}];  //数组里可以放不同类型的元素
console.log(typeof arr);           //'object', 无法判断array
console.log(Array.isArray(arr));   //true, 判断数组的最好方法
console.log(arr instanceof Array); //true,如果arr在不同frame间传递,这个方法可能不work
                         //因为不同frame的Array对象可能不一样,建议使用Array.isArray()
console.log(arr instanceof Object); //true, 数组也是对象

//object
var man = {
    'name':'rain',  //属性名可以加引号,也可以不加引号,如果属性名有空格,则需要加引号
    birth: '1978',  //不加引号看起来会比较直观
    age: 37         //最后一个属性后不用加逗号
}
console.log(typeof man);  //'object', 所有对象类型都返回'object'
console.log(man instanceof Object);  //true

JS对象组成

JS对象由3部分组成:

  • 原生对象 - 由js engine实现
  • BOM对象 - Browser Object Model, 用于与宿主对象(浏览器)交互, 由browser提供, 有Window, Navigator, Screen, History, Location等
  • DOM对象 - Document Object Model, 用于操作HTML页面, 由browser提供, 主要对象为Document

在NodeJS里,没有浏览器里的BOM对象和DOM对象,Node里的global对象类似window,另外node实现了file, process等native开发相关的对象。Node没有DOM对象实现, jsdom可以提供node里的DOM支持。

JS原生对象和方法

原生对象 Comments
与primitive对应的对象 String, Number, Boolean 不要new,应该直接使用基本数据类型
包装类 Array, Object, Function 建议不要new,应使用字面量来创建对象
Date, RegExp, JSON, Error
内置对象 Math 不要new,直接使用
全局属性 Infinity, NaN -Infinity表示负无穷大
全局函数 eval(), isFinite(), isNaN(), String(), Number(), parseInt(), parseFloat(), encodeURI(), decodeURI()

更多阅读: http://www.w3schools.com/jsref/default.asp

JS hoisting

javascript的变量声明具有hoisting机制,JavaScript引擎在执行的时候,会把所有变量的声明都提升到当前作用域的最前面, 变量看起来好像可以先使用再声明, 下面的代码是合法的:

x = 5;          // Assign 5 to x
console.log(x); // 5
var x;          // Declare x

变量申明会提前,但变量初始化不会提前:

var x = 5;  // Initialize x
console.log("x is", x, "and y is", y); //"x is 5 and y is undefined", 可以使用变量y但y并没有初始化
var y = 7;  // Initialize y

函数申明也会被提前.

注意:为了提高代码可读性,最好把所有变量都声明在当前作用域的最前面。

单线程,异步,事件驱动

JavaScript既是单线程又是异步的,这种「既是单线程又是异步」的语言有一个共同特点:它们是 event-driven 的。

驱动js event 的来自一个异构的平台,即浏览器或者nodejs,browser或nodejs都是C++的,C++是多线程的,event的产生和触发,异步机制的实现都是在C++里,譬如浏览器里的DOM事件,nodejs里的各种异步事件,这些事件都是在native code里实现和管理的,js里是一组 event-handler,js引擎会保证所有 event-handler 都在同一个线程内执行,但是它们被调用的时机是由那个驱动平台决定的。

所以js engine是多线程的,但js本身是单线程的。

除了平台提供的异步事件,怎么把我们的js代码变成异步的?一个方法是使用setTimeout(),另一个办法就是使用一些现成的类库,譬如when.js, jQuery的deferred.

自动垃圾回收

和Java一样JavaScript有自动垃圾回收机制,也就是说js解释器会负责管理代码执行过程中使用的内存,写代码时无需考虑内存分配及无用内存的回收问题。

早期IE使用引用计数,但可能有交叉引用问题,闭包也导致变量作用域难以管理,浏览器容易有内存泄漏问题,目前的浏览器多数采用进入环境和离开环境来记录变量,内存泄漏问题已经有很多改善。

js编程建议

  • 使用 'use strict';
  • 使用JSLint/JSHint/JSCS等static tool扫描代码
  • 不要修改和扩展原生对象,会引起混淆并降低代码可读性
  • 减少全局变量污染:
    • 单全局变量: var myModule = {}; myModule.name = "rain";
    • 闭包: (function(){ your code here })()
  • 对成员的销毁应通过将其置为null,而不是用delete,因为重新赋值方式性能比用delete好
  • 使用单引号定义字符串,因为字符串里有双引号的几率大
  • 使用 === 代替 ==

JavaScript的问题

  • 数值计算精度丢失
  • JavaScript内部支持Unicode,但只支持2字节的UFT-16编码,即UCS-2编码,Unicode辅助平面上的字符无法正确处理