代码改变世界

《JavaScript语言精粹》小记

2016-05-08 20:56  猴子猿  阅读(1151)  评论(0编辑  收藏  举报
一、前言

以下内容均摘自《JavaScript语言精粹》一书,本人在读这本书时,发现作者诠释JavaScript很犀利,特别是数组部分,固记录下来,想和大家分享下。

随笔主要包含两大部分:

  1、数组阐述部分;

  2、其他片段。

二、JavaScript数组

数组是一段线性分配的内存,它通过整数计算偏移并访问其中的元素。数组是一种性能出色的数据结构。不幸的是,JavaScript没有像此类数组一样的数据结构。

作为替代,JavaScript提供了一种拥有一些类数组(array-like)特性的对象。它把数组的下标转变成字符串,用其作为属性。它明显地比一个真正的数组慢,但它使用起来更方便。它的属性的检索和更新的方式和对象一模一样,只不过多一个可以用整数作为属性名的特性。数组有自己的字面量格式。

数组--字面量:

数组字面量提供了一种非常方便地创建数组的表示法。一个数组字面量是在一对方括号中包围零个或多个用逗号分隔的值的表达式。数组字面量允许出现在任何表达式可以出现的地方。数组的第一个值将获得属性名’0’,第二值将获得属性名’1’,依次类推:

var empty = [];
var numbers = [
    'zero', 'one', 'two', 'three', 'four',
    'five', 'six', 'seven', 'eight', 'nine'
];
empty[1]          //undefined
numbers[1]        //'one'

empty.length      //0
numbers.length    //10

对象字面量:

var numbers_object = {
    '0': 'zero', '1': 'one', '2': 'two',
    '3': 'three', '4': 'four', '5': 'five',
    '6': 'six', '7': 'seven', '8': 'eight',
    '9': 'nine'
};

两者产生的结果相似。numbers和numbers_object都是包含10个属性的对象,并且那些属性刚好有相同的名字和值。但是它们也有一些显著的不同。numbers继承自Array.prototype,而numbers_object继承自Object.prototype,所以numbers继承了大量有用的用法。同时,numbers也有一个诡异的length属性,而numbers_object则没有。

在大多数语言中,一个数组的所有元素都要求是相同的类型。JavaScript允许数组包含任意混合类型的值。

var misc = [
    'string', 98.6, true, false, null, undefined,
    ['nested', 'array'], {object: true}, NaN,
    Infinity
];
misc.length    //10

数组--长度:

每个数组都有一个length属性。和大多数其他语言不通,JavaScript数组的length是没有上界的。如果你用大于或等于当前length的数字作为下标来存储一个元素,那么length值会被增大以容纳新元素,不会发生数组越界错误。

length属性的值是这个数组的最大整数属性名加上1.它不一定等于数组里的属性的个数:

var myArray = [];
myArray.length    //0

myArray[1000000] = true;
myArray.length    //1000001
//myArray只包含一个属性

[ ]后置下标运算符把它所含的表达式转换成一个字符串,如果该表达式有toString方法,就使用该方法的值。这个字符串将被用作属性名。如果这个字符串看起来像一个大于等于这个数组当前的length且小于4294967295的正整数,那么这个数组的length就会被重新设置为新的下标加1。

你可以直接设置length的值。设置更大的length不会给数组分配更多的空间。而把length设小将导致所有下标大于等于新length的属性被删除:

numbers.length = 3;
//numbers是['zero', 'one', 'two']

通过把下标指定为一个数组的当前length,可以附加一个新元素到该数组的尾部:

numbers[numbers.length] = 'shi';
//numbers是['zero', 'one', 'two', 'shi']

有时用push方法可以更方便地完成同样的事情:

numbers.push('go');
//numbers是['zero', 'one', 'two', 'shi', 'go']

数组--删除:

由于JavaScript的数组其实就是对象,所以delete运算符可以用来从数组中移除元素:

delete numbers[2];
//numbers是['zero', 'one', undefined, 'shi', 'go']

不幸的是,那样会在数组中留下一个空洞。这是因为排在被删除元素之后的元素保留着它们最初的属性。而你通常想要的是递减后面每个元素的属性。

幸运的是,JavaScript数组有一个splice方法。它可以对数组做个手术,删除一些元素并将它们替换为其他的元素。第1个参数是数组中的一个序号,第2个参数是要删除的元素个数。任何额外的参数会在序号那个点的位置被插入到数组中:

numbers.splice(2, 1);
//numbers是['zero', 'one', 'shi', 'go']

值为’shi’的属性的键值从’3’变到’2’。因为被删除属性后面的每个属性必须被移除,并且以一个新的键值重新插入,这对于大型数组来说可能会效率不高。

数组--枚举:

因为JavaScript的数组其实就是对象,所以for in语句可以用来遍历一个数组的所有属性。遗憾的是,for in无法保证属性的顺序,而大多数要遍历数组的场合都期望按照阿拉伯数字来产生元素。此外,可能从原型链中得到意外属性的问题依旧存在。

幸运的是,常规for语句可以避免这些问题。JavaScript的for语句和大多数类C(C-like)语言相似。它被3个从句控制—第1个初始化循环,第2个执行条件检测,第3个执行增量运算:

var i;
for(i = 0; i < myArray.length; i += 1){
    document.writeln(myArray[i]);
}

数组--容易混淆的地方:

在JavaScript编程中,一个常见的错误是在必须使用数组时使用了对象,或者在必须使用对象时使用了数组。其实规则很简单:当属性名是小而连续的整数时,你应该使用数组。否则,使用对象。

JavaScript本身对于数组和对象的区别是混乱的。typeof运算符报告数组的类型是’object’,这没有任何意义。

JavaScript没有一个好的机制来区别数组和对象。我们可以通过定义自己的is_array函数来弥补这个缺陷:

var is_array = function(value){
    return value &&
        typeof value === 'object' &&
        value.constructor === Array;
};

遗憾的是,它在识别从不同的窗口(window)或帧(frame)里构造的数组时会失败。有一个更好的方式去判断一个对象是否为数组:

var is_array = function(value){
    return Object.prototype.toString.apply(value) === '[object Array]';
};

数组--小结:

JavaScript没有真正的数组。这也不全是坏事。JavaScript的数组确实非常容易使用。你不必给它们设置维度,而且它们永远不会产生越界(out-of-bounds)错误。但它们的性能相比真正的数组可能相当糟糕。

typeof运算符不能辨别数组和对象。要判断一个值是否为数组,你还需要检查它的constructor属性:

if(my_value && typeof my_value === 'object' && my_value.constructor === Array){
    //my_value是一个数组
}

上面的检测对于在不同帧或窗口创建的数组将会给出false。当数组有可能在其他的帧中被创建时,下面的检测更为可靠:

if(Object.prototype.toString.apply(my_value) === '[object Array]'){
    //my_value确实是一个数组
}

arguments数组不是一个数组,它只是一个有着length成员属性的对象。上面的检测会分辨出arguments并不是一个数组。

三、花絮

1、  JavaScript是弱类型,固不需要进行类型转换;

2、  一个编译单元包含一组可执行的语句。在Web浏览器中,每个<script>标签提供一个被编译且立即执行的编译单元。因为缺少链接器,JavaScript把它们一起抛到一个公共的全局名字空间中。

3、  词法作用域:当定义了一个函数后,当前的作用域就会被保存下来,并且成为函数内部状态的一部分。

4、  在if判断中,下列值被当做假:

   False    null    undefined    空字符串’ ’    数字0    NaN

5、  函数包含一组语句,它们是JavaScript的基础模块单元,用于代码复用、信息隐藏和组合调用。函数也是对象,与众不同之处在于它们可以被调用。因为函数是对象,所以它们可以像任何其他的值一样被使用。函数可以保存在变量、对象和数组中。函数可以被当做参数传递给其他函数,函数也可以再返回函数。而且函数是对象,所以函数可以拥有方法。当一个函数被保存为一个对象属性时,我们称它为一个方法;当一个函数并非一个对象的属性时,那么它就是被当做一个函数来调用;当一个函数,如果创建的目的就是希望结合new前缀来调用,那它就被称为构造函数。

6、  对象字面量是一种可以方便地按指定规格创建新对象的表示法。属性名可以死标识符或字符串。这些名字被当做字面量而不是变量名来对待,所以对象的属性名在编译时才能知道。

7、  柯里化:允许我们把函数与传递给它的参数相结合,产生出一个新的函数。通过创建一个保存着原始函数和要被套用的参数的闭包工作。它返回另一个函数,该函数被调用时,会返回调用原始函数的结果,并传递调用curry时的参数加上当前调用的参数。它使用Array的concat方法连接两个参数数组。