数组
数组是动态的、无类型的,数组元素可以为任意类型(表达式、数字、字符串、对象、数组…),并且同一个数组中的元素也可以为不同类型。元素下标范围:0~ 2 32 − 2 2^{32}-2 232−2
数组的基本操作
数组创建
- 使用直接量 (注意:数组直接两的语法允许有可选的结尾逗号,所以[ , , ]数组中只有两个元素);
- 使用构造函数:
var a = new Array(); //[]空数组
var b = new Array(10); //单个数字表示预分配一个长度为10的数组,数组中的索引属性还未定义
var c = new Array(1, 2, 3, "test"); //创建一个元素为参数的数组
数组读写
所有的数组都是对象的特殊形式,数组就是对象,所以JS将指定数字索引值转换为字符串,即1变为 “1”,以此作为属性名来使用。
所有的 索引都是属性名,但只有0~
2
32
−
2
2^{32}-2
232−2的属性名是索引。注意,可以用负数或非整数来索引数组, 这种情况下,数组转换为字符串,该字符串仅作为属性名来用,不能做索引。因为数组索引仅仅是对象属性的一种特殊类型,所以数组没有“越界”的错误概念,越界一律值为undefined。
a[-1.23] = true; //创建了一个名为“-1.23”的属性
a["1000"] = 2; //数组的第1001个数的值为2
a[1.000]; //和a[1]1相等
稀疏数组
是包含从0开始的不连续索引的数组。非稀疏数组即为稠密数组。
数组的length:是数组的一个属性,length属性使数组区别于常规的对象。稀疏数组的 length 大于元素的个数,稠密数组的 length 等于元素的个数。
针对 length的两个特殊行为:
- length随着索引最大值的增大而增大,而且必定大于数组的每一个索引值;
- 当 length小于该数组长度时,索引为大于等于 length的元素将从数组中删除;
var a = [1, 2, 3, 4, 5];
a.length = 0; //删除了所有的元素,a为null
a.length = 5; //长度为5的空数组,等价于new Array(5)
如果数组中某个元素不能配置,就不能删除该元素,所以 length属性不能设置为小于等于该元素的索引值。
稀疏数组的创建:a.用Arraya( )构造函数 b.指定数组的索引值大于当前数组的长度 c.用delete操作符 d.省略数组直接量中的值时
a = new Array(5);
b = [];
b[20] = 2;
var c = [, , ,]; var d = [undefined];
console.log(0 in a); //false
console.log(0 in b); //false
console.log(0 in c); //false
console.log(0 in d); //true
delete d[1];
console.log(0 in d); //false
数组遍历
- for循环
- for/in
不存在的索引不会遍历到,但是for.in循环能够枚举继承的属性名,因此要进行过滤优化:
var a = [];
for(var i in a){
if(a.hasOwnProperty(i)){ //跳过继承的属性
continue;
}
if(String(Math.floor(Math.abs(Number(i))) == i){ //跳过不是非负整数的i
continue; //因为数组的索引会自动转为字符串,所以要先转换为Number,然后再转会String
}
}
- forEach( ) 按照索引的顺序挨个传递给定义的一个函数
var a = [1, 2, 3, 4, 5];
var sum = 0; //所以数据的平方和
a.forEach(function(x){ //把每个元素传递给此函数, 传的不是索引
sum += x*x;
})
console.log(sum);
数组方法
- join( )——将元素转化为字符串并连接在一起,连接符默认为",",可自选连接字符;String.split( )方法是该方法的逆向操作,不改变调用数组;
var a = [1, 2, 3, 4, 5];
var s = a.join("#"); //以#为连接符号
console.log(a.join("#")); //1#2#3#4#5
var b = s.split("#"); //化为数组,注意:某些字符需要转义才能识别
console.log(b); //['1', '2', '3', '4', '5']
- reverse( )——将数组中的元素颠倒顺序;
- sort( )——以字母顺序排序,undefined元素会被排到数组的尾部,元素值为undefined的正常排列;自定义非字母顺序进行数组排列,设定原理c++中一致:
var a = [33, 4, 1111, 222];
a.sort();
a.sort(function(a, b){
return a-b; //按照数字大小从小到大排列
})
- concat( )——创建并返回一个新数组,数组元素为调用数组的元素和每个参数(若为数组则是数组中的元素),不改变调用数组本身,
- slice( )——返回新数组不改变调用数组,元素为截取的数组片段。指定参数为数组索引,两个(左闭右开),一个(该处到数组末尾), 负数则逆向数;
- splice( )——返回一个由删除元素组成的数组,没有删除就返回空数组;该方法实现删除、插入元素(若为数组则是插入该数组,不是数组中的元素),会改变调用函数;
var a = [1, 2, 3, 4, 5];
//第一个参数为操作起始索引,第二个参数为删除的元素个
//从第三个参数开始,如果后面跟的任意多个元素表示要插入的元素
console.log(a.splice(2, 0, ['a', 'b'])); //[] 未删除的元素
console.log(a); //[ 1, 2, ['a', 'b'], 3, 4, 5]
-
push( ) and pop( )——用数组实现先进后出的栈。push( )在数组尾部添加一个或多个元素(若为数组则是插入该数组,不是数组中的元素),返回数组新的长度;pop( )在数组尾部删除最后一个元素返回删除的值。改变调用数组;
-
unshift( ) and shift( )——
-
toString( ) and toLocaleString( )
二者的区别:
1.数字转换成字符串的时候,并没有感觉这两个方法有什么区别
2.当数字是四位及以上时,toLocaleString()会让数字三位三位一分隔,eg:1,234,456
3.如果是toLocaleString(),先判断是否指定语言环境(locale),指定的话则返回当前语言环境下的格式设置(options)的格式化字符串;没有指定语言环境(locale),则返回一个使用默认语言环境和格式设置(options)的格式化字符串。
如果是toString(),会直接返回标准的格式;
var sd=new Date();
sd
//Wed Feb 15 2017 11:21:31 GMT+0800 (CST)
sd.toLocaleString()
//"2017/2/15 上午11:21:31"
sd.toString()
//"Wed Feb 15 2017 11:21:31 GMT+0800 (CST)"
数组类型
ES5以后用Array.isArray( )来判断某对象是否为数组;原理如下:
function isArray(o){
return typeof o === "object" && Object.prototype.toString.call(o) ==="[object Array]";
}
类数组对象
一种常常完全合理的看法把拥有一个数组length属性和对应非负整数属性的对象看做一种类型的数组。
eg:给常规对象增加length属性,然后遍历生成类数组对象:
var o = {};
while(i < 10){
o[i] = i*i;
i++;
}
o,length = i;
用来检测类数组对象的函数:
function isArrayLike(o){
if(o && typeof o === "object" &&
isFinite(o.length) && //检测o.length是否为有限制
o.length>=0 && o.length===Math.floor(o.length) && o.length < 4294967296)
return true;
else
return false;
}
JS数组方法特意定义为通用,所以不仅应用在真正的数组上也可以应用在类数组对象上,但类数组对象没有继承Array.prototype,不能直接调用数组方法,可以采用Function.call( )方法调用:
var a = {"0":"b", "1":"a", "2":"c", length:3};
Array.prototype.join.call(a,"+");
函数
函数表达式:var square = function(x) {…}
函数声明式:function square(x) {…}
两种方式函数名实际都是看不到,square仅仅是变量的名字,指代函数对象,将改变量赋值给其他变量仍可以正常工作;
函数调用
- 作为方法
一个方法是保存在一个对象的属性里的JavaScript函数,方法调用和函数调用有一个重要的区别,即上下文this,此处对象成为调用上下文。任何函数作为方法调用都会传入一个隐式的实参——对象。
关于嵌套函数中的this:
嵌套的函数不继承调用它的函数中的this。如果嵌套函数作为方法调用,其this的直指向调用的对象;如果嵌套函数作为函数调用,其this非严格模式下指向全局对象,严格模式下是undefined。
var o = {
m: function(){
var self = this;
console.log(this === o); //true
f();
function f(){
console.log(this); //全局对象
console.log(self === o); //true,如果想要访问这个外部函数this值,只能将this的值存在变量里传入
}
}
}
o.m();
- 作为构造函数
凡是没有形参得构造函数调用均可以省略圆括号,构造函数被调用来创建一个新的空对象,这个新对象将继承自构造函数得prototype属性。
构造函数试图初始化这个新对象,并将这个对象作为其上下文
因此构造函数可以用this来引用这个新创建得对象,此时o.m()的this就是obj
var obj = new o.m();
注意:如果构造函数没有return或return为空,默认返回新对象,有return值自动忽略返回新对象
- 间接调用
JS中的函数也是对象,任何函数可以作为任何对象的方法来调用,哪怕这个函数不是那个对象的方法。比如call( )和apply( )都可以间接地调用函数。
函数的实参和形参
- 可选形参(省略的形参将被赋值为undefined)
function getNames(o, /*optional*/ a){ //代码的可读性
if(a === undefined) a = []; //检测是否传入该参数,未传入就新建一a[]返回
for(var property in o){
a.push(property);
}
return a;
}
将对象作为实参可以避免参数过多难以记住参数顺序的情况;
- 可变长的实参列表:实参对象,可以让函数操作任意数量的实参
实参对象是一个类数组对象,对象中的属性(数组的元素)是传入的实参,标识符arguments是指向实参对象的引用,可以通过该标识符的数字下标访问传入函数的实参值,而不一定要通过名字获取实参;
//与内置函数MAX.max()功能类似
function max(/* ... */){ //代码规范,表明传入参数的数量不定
var max = Number.NEGATIVE_INFINITY;
for(var i = 0; i<arguments.length; i++){
arguments[0] = 15; //修改实参数组的元素同样会修改x的值,因为二者指向的是同一个值
if(arguments[i] > max){
max = arguments[i];
}
}
console.log(arguments.length); //6 //类数组对象属性
console.log(x); //1
return max;
}
var largest = max(1,5,9,13,2,9); //15
实参对象的callee和caller属性
自定义函数属性
和类数组对象相似,**函数作为一种特殊的对象,可以拥有属性,且和对象调用属性方法相同。**当函数需要一个“静态”变量来保持值不变时,可以给函数定义属性,避免使用会使命名空间变得杂乱的全局变量。
如计算阶乘的函数factorial( ):
function factorial(n){
if(isFinite(n) && n>0 && n==Math.round(n)){ //判断是否为整数
if(!(n in factorial)){ //如果没有缓存结果,即没有这个属性
factorial[n] = n*factorial(n-1); //计算结果并缓存,即该函数增加了n这个属性
//此处不能是factorial[n-1],因为可能也不存在这个属性,所以用递归求取
}
return factorial[n]; //返回缓存结果
}
return NaN;
}
factorial[1] = 1;
续写中…