读《JavaScript权威指南》笔记(四)--数组、函数
一、数组
1.数组是值的有序集合。每个值叫做一个元素,而每个元素在数组中有一个位置,以数字表示,称为索引。JavaScript数组是无类型的:数组元素可以是任意类型,并且同一个数组中的不同元素也可能有不同的类型。数组的元素甚至也可能是对象或其他数组,这允许创建复杂的数据结构,如对象的数组和数组的数组。JavaScript数组的索引是基于零的32位数值:第一个元素的索引为0,最大可能的索引为4294967294(232-2),数组最大能容纳4294967295个元素。
所有的索引都是属性名,但只有在0~232-2之间的整数属性名才是索引。所有的数组都是对象,可以为其创建任意名字的属性。但如果使用的属性是数组的索引,数组的特殊行为就是将根据需要更新它们的length属性值。
注意,可以使用负数或非整数来索引数组。这种情况下,数值转换为字符串,字符串作为属性名来用。既然名字不是非负整数,它就只能当做常规的对象属性,而非数组的索引。同样,如果凑巧使用了是非负整数的字符串,它就当做数组索引,而非对象属性。当使用的一个浮点数和一个整数相等时情况也是一样的:
a[-1.23] = true; // 这将创建一个名为"-1.23"的属性 a["1000"] = 0; // 这是数组的第1001个元素 a[1.000] // 和a[1]相等
a = new Array(5); // 数组没有元素,但是a.length是5 a = []; // 创建一个空数组,length = 0 a[1000] = 0; // 赋值添加一个元素,但是设置length为1001
2.数组的实现是经过优化的,用数字索引来访问数组元素一般来说比访问常规的对象属性要快很多。
var isArray = Function.isArray || function(o){ return typeof o === "object" && Object.prototype.toString.call(o)=== "[object Array]"; };
3.indexOf()和lastIndexOf()方法不接收一个函数作为其参数。第一个参数是需要搜索的值,第二个参数是可选的:它指定数组中的一个索引,从那里开始搜索。如果省略该参数,indexOf()从头开始搜索,而lastIndexOf()从末尾开始搜索。第二个参数也可以是负数,它代表相对数组末尾的偏移量,对于splice()方法:例如,-1指定数组的最后一个元素。
4.reduce()时只有一个参数:没有指定初始值。当不指定初始值调用reduce()时,它将使用数组的第一个元素作为其初始值。这意味着第一次调用化简函数就使用了第一个和第二个数组元素作为其第一个和第二个参数
5.一旦every()和some()确认该返回什么值它们就会停止遍历数组元素。some()在判定函数第一次返回true后就返回true,但如果判定函数一直返回false,它将会遍历整个数组。every()恰好相反:它在判定函数第一次返回false后就返回false,但如果判定函数一直返回true,它将会遍历整个数组。注意,根据数学上的惯例,在空数组上调用时,every()返回true,some()返回false。
6.Array.sort()方法将数组中的元素排序并返回排序后的数组。当不带参数调用sort()时,数组元素以字母表顺序排序。如果数组包含undefined元素,它们会被排到数组的尾部。为了按照其他方式而非字母表顺序进行数组排序,必须给sort()方法传递一个比较函数。
7.字符串是不可变值,故当把它们作为数组看待时,它们是只读的。如push()、sort()、reverse()和splice()等数组方法会修改数组,它们在字符串上是无效的。
二、函数
1.函数声明语句的语法如下:
function funcname([arg1[, arg2[..., argn]]]){
statements
}
funcname是要声明的函数的名称的标识符。函数名之后的圆括号中是参数列表,参数之间使用逗号分隔。当调用函数时,这些标识符则指代传入函数的实参。
2.函数声明语句并非真正的语句,ECMAScript规范只是允许它们作为顶级语句。它们可以出现在全局代码里,或者内嵌在其他函数中,但它们不能出现在循环、条件判断,或者try/cache/finally以及with语句中。注意,此限制仅适用于以语句声明形式定义的函数。函数定义表达式可以出现在JavaScript代码的任何地方。
3.一个典型的函数定义表达式包含关键字function,跟随其后的是一对圆括号,括号内是一个以逗号分割的列表,列表含有0个或多个标识符(参数名),然后再跟随一个由花括号包裹的JavaScript代码段(函数体),例如:
// 这个函数返回传入参数值的平方 var square = function(x){ return x * x; }
4.用于初始化一个新创建的对象的函数称为构造函数。构造函数的命名规则(首字母大写)和普通函数是如此不同还有另外一个原因,构造函数调用和普通函数调用是不尽相同的。构造函数就是用来“构造新对象”的,它必须通过关键字new调用,如果将构造函数用做普通函数的话,往往不会正常工作。如果构造函数调用在圆括号内包含一组实参列表,先计算这些实参表达式,然后传入函数内,这和函数调用和方法调用是一致的。但如果构造函数没有形参,JavaScript构造函数调用的语法是允许省略实参列表和圆括号的。凡是没有形参的构造函数调用都可以省略圆括号。
5.如果函数挂载在一个对象上,作为对象的一个属性,就称它为对象的方法。当通过这个对象来调用函数时,该对象就是此次调用的上下文(context),也就是该函数的this的值。一个方法无非是个保存在一个对象的属性里的JavaScript函数。
6.this是一个关键字,不是变量,也不是属性名。JavaScript的语法不允许给this赋值。和变量不同,关键字this没有作用域的限制,嵌套的函数不会从调用它的函数中继承this。如果嵌套函数作为方法调用,其this的值指向调用它的对象。如果嵌套函数作为函数调用,其this值不是全局对象(非严格模式下)就是undefined(严格模式下)。很多人误以为调用嵌套函数时this会指向调用外层函数的上下文。如果你想访问这个外部函数的this值,需要将this的值保存在一个变量里,这个变量和内部函数都同在一个作用域内。通常使用变量self来保存this。
7.arguments.callee 代表函数名,多用于递归调用
8.大多数(非全部)的toString()方法的实现都返回函数的完整源码。内置函数往往返回一个类似“[native code]”的字符串作为函数体。
9.不完全函数编程是一种函数编程技巧,即把一次完整的函数调用拆成多次函数调用,每次传入的参数都是完成参数的一部分,每次才分开的函数就叫做不完全函数。每次函数调用就叫做不完全调用。
10.
(function(){ // mymodule()函数重写为匿名的函数表达式 // 模块代码 }()); // 结束函数定义并立即调用它
这种定义匿名函数并立即在单个表达式中调用它的写法非常常见,已经成为一种惯用法了。注意上面代码的圆括号的用法,function之前的左圆括号是必需的,因为如果不写这个左圆括号,JavaScript解释器会试图将关键字function解析为函数声明语句。使用圆括号JavaScript解释器才会正确地将其解析为函数定义表达式。使用圆括号是习惯用法,尽管有些时候没有必要也不应当省略。这里定义的函数会立即调用。
11.每个JavaScript函数(ECMAScript 5中的Function.bind()方法返回的函数除外)都自动拥有一个prototype属性。这个属性的值是一个对象,这个对象包含唯一一个不可枚举属性constructor。constructor属性的值是一个函数对象:
var F = function(){}; // 这是一个函数对象 var p = F.prototype; // 这是F相关联的原型对象 var c = p.constructor; // 这是与原型相关联的函数 c === F // => true: 对于任意函数F.prototype.constructor==F
可以看到构造函数的原型中存在预先定义好的constructor属性,这意味着对象通常继承的constructor均指代它们的构造函数。由于构造函数是类的“公共标识”,因此这个constructor属性为对象提供了类。
var o = new F(); //创建类F的一个对象 o.constructor === F // => true,constructor属性指代这个类
12.构造函数是类的公有标识。在JavaScript中也可以定义对象的类,让每个对象都共享某些属性,这种“共享”的特性是非常有用的。类的成员或实例都包含一些属性,用以存放或定义它们的状态,其中有些属性定义了它们的行为(通常称为方法)。这些行为通常是由类定义的,而且为所有实例所共享。例如,假设有一个名为Complex的类用来表示复数,同时还定义了一些复数运算。一个Complex实例应当包含复数的实部和虚部(状态),同样Complex类还会定义复数的加法和乘法操作(行为)。在JavaScript中,类的实现是基于其原型继承机制的。如果两个实例都从同一个原型对象上继承了属性,我们说它们是同一个类的实例。
13.判断是否是函数
function isFunction(x){ return Object.prototype.toString.call(x)=== "[object Function]"; }
14.bind
var sum = function(x,y){ return x + y }; //返回两个实参的和值 // 创建一个类似sum的新函数,但this的值绑定到null // 并且第一个参数绑定到1,这个新的函数期望只传入一个实参 var succ = sum.bind(null, 1); succ(2)// => 3: x绑定到1,并传入2作为实参y function f(y,z){ return this.x + y + z }; //另外一个做累加计算的函数 var g = f.bind({x:1}, 2); //绑定this和y g(3) // => 6: this.x绑定到1,y绑定到2,z绑定到3
if(!Function.prototype.bind){ Function.prototype.bind = function(o /*, args */){ // 将this和arguments的值保存至变量中 // 以便在后面嵌套的函数中可以使用它们 var self = this, boundArgs = arguments; // bind()方法的返回值是一个函数 return function(){ // 创建一个实参列表,将传入bind()的第二个及后续的实参都传入这个函数 var args = [], i; for(i = 1; i < boundArgs.length; i++)args.push(boundArgs[i]); for(i = 0; i < arguments.length; i++)args.push(arguments[i]); // 现在将self作为o的方法来调用,传入这些实参 return self.apply(o, args); }; }; }
15.try/catch语句已经可以使用多catch从句了,在catch从句的参数中加入关键字if以及一个条件判断表达式:
try { // 这里可能会抛出多种类型的异常 throw 1; } catch(e if e instanceof ReferenceError){ // 这里处理引用错误 } catch(e if e === "quit"){ // 这里处理抛出的字符串是"quit"的情况 } catch(e if typeof e === "string"){ // 处理其他字符串的情况 } catch(e){ // 处理余下的异常情况 } finally { // finally从句正常执行 }
16.如果函数只计算一个表达式并返回它的值,关键字return和花括号都可以省略
let succ=function(x)x+1, yes=function()true, no=function()false;
17.内联命名函数
var func1 = function func2(){ console.log(func1 === func2) } func1() // true func2() // Uncaught ReferenceError: func2 is not defined
尽管可以给内联函数进行命名,但这些名称只能在自身函数内部可见。内联函数的名称和变量名称有点像,它们的作用域仅限于声明它们的函数。