八、JavaScript面向对象的支持
6. JavaScript面向对象的支持的补充内容
1). 类型系统
我们前面已经完整地描述过JavaScript的两种类型系统。包括:
- 基础类型系统:由typeof()返回值的六种基础类型
- 对象类型系统:由new()返回值的、构造器和原型继承组织起来的类型系统
JavaScript是弱类型语言,因此类型自动转换是它语言特性的一个重要组成部分。但对于一个指定的变量而言,(在某一时刻,)它总是有确定的数据类型的。“运算”是导致类型转换的方法(但不是根源),因此“运算结果的类型”的确定就非常重要。关于这一部分的内容,推荐大家阅读一份资料:
类型系统中还有一个特殊的组成部分,就是“直接量”声明。下面的代码简述各种直接量声明的方法,但不再详述具体细节:
//
// 各种直接量声明(一些错误格式或特例请查看JScript手册)
//
// 1. Number
var n1 = 11; // 普通十进制数
var n2 = 013; // 八进制数
var n3 = 0xB; // 十六进制数
var n4 = 1.2; // 浮点值
var n5 = .2; // 浮点值
var n6 = 1.0e-4; // (或1e-4)浮点值
// 2. String
var s1 = 'test'; // (或"test")字符串
var s2 = "test/n";// 带转义符的字符串(转义符规则参见手册)
var s3 = "'test'";// 用""、''以在字符串中使用引号
var s4 = "/xD"; // 用转义符来声明不可键入的字符
// 3. Boolean
var b1 = true;
var b2 = false;
// 4. Function
function foo1() {}; // 利用编译器特性直接声明
var foo2 = function() {}; // 声明匿名函数
// 5. Object
// * 请留意声明中对分隔符“,”的使用
var obj1 = null; // 空对象是可以被直接声明的
var obj2 = {
value1 : 'value', // 对象属性
foo1 : function() {}, // 利用匿名函数来直接声明对象方法
foo2 : foo2 // 使方法指向已声明过的函数
}
// 6. RegExp
var r1 = /^[O|o]n/; // 使用一对"/../"表达的即是正则表达式
var r2 = /^./gim; // (注意,) gim为正则表达式的三个参数
// 7. Array
var arr1 = [1,,,1]; // 直接声明, 包括一些"未定义(undefined)"值
var arr2 = [1,[1,'a']]; // 异质(非单一类型)的数组声明
var arr3 = [[1],[2]]; // 多维数组(其实是从上一个概念衍生下来的
// 8. undefined
var u1 = undefined; // 可以直接声明, 这里的undefined是Global的属性
有些时候,我们可以“即声明即使用”一个直接量,下面的代码演示这一特性:
//
// 直接量的“即声明即使用”
//
var obj = function () { // 1. 声明了一个匿名函数
return { // 2. 函数执行的结果是返回一个直接声明的"对象"
value: 'test',
method: function(){}
}
}(); // 3. 使匿名函数执行并返回结果,以完成obj变量的声明
在这个例子中,很多处用到了直接量的声明。这其中函数直接声明(并可以立即执行)的特性很有价值,例如在一个.js文件中试图执行一些代码,但不希望这些代码中的变量声明对全局代码导致影响,因此可以在外层包装一个匿名函数并使之执行,例如:
//
// 匿名函数的执行
// (注:void用于使后面的函数会被执行, 否则解释器会认为仅是声明函数)
//
void function() {
if (isIE()) {
// do something...
}
}();
2). 对象系统
对象系统中一个未被提及的重要内容是delete运算。它用于删除数组元素、对象属性和已声明的变量。
由于delete运算不能删除用var来声明的变量,也就意味着它只能删除在函数内/外声明的全局变量。——这个说法有点别扭,但事实上的确如此。那么我们可以更深层地透视一个真相:delete运算删除变量的实质,是删除用户在window对象的上下文环境中声明的属性。
回到前面有关“上下文环境”的讨论,我们注意到(在函数外)声明全局变量的三种形式:
var global_1 = '全局变量1';
global_2 = '全局变量2';
function foo() {
global_3 = '全局变量3';
}
全局变量2和3都是“不用var声明的变量”,这其实是在window对象的上下文环境中的属性声明。也就是说可以用window.global_2和window.global_3来存取它们。这三种声明window对象的属性的方法,与直接指定“window.global_value = <值>”这种方法的唯一区别,是在“for .. in”运算时,这三种方法声明的属性/方法都会被隐藏。如下例所示:
//
// 全局变量上下文环境的一些特点:属性名隐藏
//
var global_1 = '全局变量1';
global_2 = '全局变量2';
void function foo() {
global_3 = '全局变量3';
}();
window.global_4 = '全局变量4';
for (var i in window) {
document.writeln(i, '<br>');
}
document.writeln('<HR>');
document.writeln(window.global_1, '<BR>');
document.writeln(window.global_2, '<BR>');
document.writeln(window.global_3, '<BR>');
我们注意到在返回的结果中不会出现全局变量1/2/3的属性名。但使用window.xxxx这种方式仍可以存取到它们。
在window上下文环境中,global_1实质是该上下文中的私有变量,我们在其它代码中能存取到它,只是因为其它(所有的)代码都在该上下文之内。global_2/3则被(隐含地)声明成window的属性,而global_4则显式地声明为window的属性。
因此我们回到前面的结论:
- 删除(不用var声明的)变量的实质,是删除window对象的属性。
此外,我们也得到另外三条推论(最重要的是第一条):
- delete能删除数组元素,实质上是因为数组下标也是数组对象的隐含属性。
- 在复杂的系统中,为减少变量名冲突,应尽量避免全局变量(和声明)的使用,或采用delete运算来清理window对象的属性。
- window对象是唯一可以让用户声明“隐含的属性”的对象。——注意这只是表面的现象,因为事实上这只是JavaScript规范带来的一个“附加效果”。:)
delete清除window对象、系统对象、用户对象等的“用户声明属性”,但不能清除如prototype、constructor这样的系统属性。此外,delete也可以清除数组中的元素(但不会因为清除元素而使数组长度发生变化)。例如:
//
// delete运算的一些示例
//
var arr = [1, 2, 3];
var obj = {v1:1, v2:2};
global_variant = 3;
delete arr[2];
document.writeln('1' in arr, '<BR>'); // 数组下标事实上也是数组对象的隐含属性
document.writeln(arr.length, '<BR>'); // 数组长度不会因delete而改变
delete obj.v2;
document.writeln('v2' in obj, '<BR>');
document.writeln('global_variant' in window, '<BR>');
delete global_variant;
// 以下的代码不能正常执行,这是IE的一个bug
if ('global_variant' in window) {
document.writeln('bug test:', global_variant, '<BR>');
}
最后这行代码错误的根源,在于IE错误地检测了'global_variant'在window的对象属性中是否仍然存在。因为在同样的位置,“('global_variant' in window)”表达式的返回结果居然为true!——firefox中没有这个bug。
delete清除掉属性或数组元素,并不表明脚本引擎会对于该属性/元素执行析构。对象的析构操作是不确定的,关于这一点请查看更前面的内容。