首页 > 代码库 > 6.13

6.13

一、数组的目录索引

Array

Array.length 是数组的长度,每个新建的数组对象都会有 length 对象,可以通过 Array.prototype 修改原型,不过数组的基本使用和操作不是今天的重点,我们来看数组方法。

 

一般情况下,数组方法在最后都会带有一个 thisArg 的参数,这个参数会指定内部 this 的指向。如果参数中有回掉函数,这个回掉函数一般接受 3 个参数,value、index 和 array,分别代表当前传入的值,当前传入值所在的索引和当前的处理的数组。

 

目录索引:

 

concat

every

filter

forEach

indexOf

join

lastIndexOf

map

reduce

reduceRight

push

pop

unshift

shift

reverse

slice

splice

some

sort

toString

copyWithin

find

findIndex

fill

keys

entries

includes

 

concat

 

这个方法可以用于数组的拼接,参数是一个或多个数组,返回的结果是拼接数组。MDN array.concat。

 

concat 方法将创建一个新数组,然后将调用它的对象(this 指向的对象,即原数组)中的元素以及所有参数中的数组类型的参数中的元素以及非数组类型的参数本身按照顺序放入这个新数组,并返回该数组。concat 方法并不修改原数组和参数数组,而且对非数组对象同样有效果。

 

返回拼接的新数组;

 

不修改原数组和参数数组;

 

参数可以是非数组。

 

?

var a1 = [1, 2, 3],

  a2 = [4, 5, 6],

  a3 = [7, 8, 9];

var newarr = a1.concat(a2, a3);

newarr //[1, 2, 3, 4, 5, 6, 7, 8, 9]

a1 //[1, 2, 3]

newarr = a1.concat(4, a3);//[1, 2, 3, 4, 7, 8, 9]

newarr = a1.concat(‘hello‘);//[1, 2, 3, "hello"]

?

 

 

every

 

every() 方法测试数组的所有元素是否都通过了指定函数的测试。MDN array.every。

 

arr.every(callback) 会对每一个元素都执行 callback 方法,直到 callback 返回 false。有时候 every 方法会和 forEach 方法相比较,因为 forEach 无法停止,而 every 方法返回 flase 时可以中途停止。

 

若全部通过测试,函数返回值 true,中途退出,返回 false;

 

不对原数组产生影响。

 

?

function isBigEnough(element, index, array) {

  console.log(index);

  return (element >= 10);

}

var passed = [12, 5, 8, 130, 44].every(isBigEnough);

// 0

// 1

// passed is false

passed = [12, 54, 18, 130, 44].every(isBigEnough);

// 0 1 2 3 4

// passed is true

?

 

 

filter

 

filter() 方法使用指定的函数测试所有元素,并创建一个包含所有通过测试的元素的新数组。MDN array.filter。

 

其实这个方法就是一个过滤方法,前面那个 every 方法,只判断不过滤,filter 会过滤掉一些不符合条件的,并返回新数组。

 

返回一个满足过滤条件的新数组;

 

不会改变原数组。

 

?

function isBigEnough(element, index, array) {

  return (element >= 10);

}

var a1 = [19, 22, 6, 2, 44];

var a2 = a1.filter(isBigEnough);

a1 //[19, 22, 6, 2, 44]

a2 //[19, 22, 44]

?

 

 

forEach

 

forEach() 方法对数组的每个元素执行一次提供的函数(回调函数)。MDN array.forEach。

 

函数没有返回值,即 underfined;

 

不对原数组产生影响。

 

?

function logArrayElements(element, index, array) {

    console.log("a[" + index + "] = " + element);

}

 

// 注意索引2被跳过了,因为在数组的这个位置没有项

var result = [2, 5, 9].forEach(logArrayElements);

// a[0] = 2

// a[1] = 5

// a[2] = 9

result //underfined

?

 

 

indexOf

 

indexOf()方法返回给定元素能找在数组中找到的第一个索引值,否则返回-1。MDN array.indexOf。

 

返回值是找到元素的索引值或 -1;

 

不对原数组产生影响。

 

var array = [1, 2, 5];

array.indexOf(5); // 2

array.indexOf(7); // -1

 

 

join

 

join() 方法将数组中的所有元素连接成一个字符串。MDN array.join。

 

其实,对于 join 想到的第一个是字符串的 split 操作,这两个经常搭配用来处理字符串。

 

返回拼接的字符串;

 

不对原数组产生影响。

 

var a1 = [1, 2, 3];

var a2 = a1.join();

a1 //[1, 2, 3]

a2 //"1,2,3"

a2 = a1.join("");//"123"

a2 = a1.join("-");//"1-2-3"

 

 

lastIndexOf

 

lastIndexOf() 方法返回指定元素(也即有效的 JavaScript 值或变量)在数组中的最后一个的索引,如果不存在则返回 -1。从数组的后面向前查找,从 fromIndex 处开始。MDN array.lastIndexOf。

 

其实这个就是 indexOf 的翻版。

 

返回找到的第一个元素的索引;

 

不对原数组产生影响。

 

?

var array = [2, 5, 9, 2];

var index = array.lastIndexOf(2);

// index is 3

index = array.lastIndexOf(7);

// index is -1

index = array.lastIndexOf(2, 3);

// index is 3

index = array.lastIndexOf(2, 2);

?

 

 

map

 

map() 方法返回一个由原数组中的每个元素调用一个指定方法后的返回值组成的新数组。MDN array.map。

 

map reduce 这两个函数在处理数组上一直都是一把手,带来很大的便捷性。

 

返回一个经过回掉函数处理的新数组;

 

不对原数组产生影响。

 

var a1 = [1, 4, 9];

var a2 = a1.map(Math.sqrt);

a1 //[1, 4, 9]

a2 //[1, 2, 3]

 

 

reduce

 

reduce() 方法接收一个函数作为累加器(accumulator),数组中的每个值(从左到右)开始合并,最终为一个值。MDN array.reduce。

 

reduce 是一个合并的过程,从左到右,直到把所有元素合并到一起,并返回最终的结果。它接受两个参数,第一个参数是一个回掉函数,第二个参数是一个初始值,表示处理第一个元素时的前一个值。这个回掉函数接受四个参数,依次是 accumulator(上次处理的结果),currentValue(当前元素的值),index(当前元素索引),array(调用 reduce 的数组)。

 

返回最终合并的结果,即回掉函数的输出,可以为字符串,对象,数组等任意结果;

 

不对原数组产生影响。

 

var getAdd = (pre, cur) => pre + cur;

var a1 = [1, 2, 3];

var a2 = a1.reduce(getAdd, 0);

a1 //[1, 2, 3]

a2 //6

 

 

reduceRight

 

reduceRight() 方法接受一个函数作为累加器(accumulator),让每个值(从右到左,亦即从尾到头)缩减为一个值。(与 reduce() 的执行方向相反)MDN array.reduceRight。

 

var toStr = (pre, cur) => ‘‘ + pre + cur;

var a1 = [1, 2, 3];

var a2 = a1.reduce(toStr, ‘‘);

a2 //"123"

a2 = a1.reduceRight(toStr, ‘‘);

a2 //"321"

 

 

push

 

push() 方法添加一个或多个元素到数组的末尾,并返回数组新的长度(length 属性值)。MDN array.push。

 

如果把数组当作栈,push pop 操作是栈进和出,而往往很多人会忽略函数执行后的返回值。

 

返回 push 操作执行之后数组的长度;

 

肯定改变。

 

var a1 = [1, 2, 3];

var a2 = a1.push(4);

a1 //[1, 2, 3, 4]

a2 //4

 

 

pop

 

pop() 方法删除一个数组中的最后的一个元素,并且返回这个元素。MDN array.pop。

 

返回删除的这个元素;

 

肯定改变。

 

var a1 = [1, 2, 3];

var a2 = a1.pop();

a1 //[1, 2]

a2 //3

 

 

unshift

 

unshift() 方法在数组的开头添加一个或者多个元素,并返回数组新的 length 值。MDN array.unshift。

 

返回 length 值;

 

肯定改变。

 

var a1 = [1, 2, 3];

var a2 = a1.unshift(4);

a1 //[4, 1, 2, 3]

a2 //4

 

 

shift

 

shift() 方法删除数组的 第一个 元素,并返回这个元素。该方法会改变数组的长度。MDN array.shift。

 

shift 方法和 push 方法可以组成一个队列的操作啦。

 

返回删除的这个元素;

 

肯定改变。

 

reverse

 

reverse() 方法颠倒数组中元素的位置。第一个元素会成为最后一个,最后一个会成为第一个。MDN array.reverse。

 

函数返回值是修改了的原数组;

 

原数组会修改。

 

var a1 = [1, 2, 3];

var a2 = a1.reverse();

a1 //[3, 2, 1]

a1 === a2; //true

 

 

slice

 

slice() 方法会浅复制(shallow copy)数组的一部分到一个新的数组,并返回这个新数组。MDN array.slice。

 

slice 的参数包括拷贝的初识位置,结束位置(左闭右开),与 splice 有区别。由于不会改变原数组,这个数组可以用于前拷贝,比如经常看别人使用:arr.slice(0),表示拷贝数组。

 

返回浅拷贝后的新数组;

 

不会改变原数组。

 

var a1 = [1, 2, 3, 4, 5];

var a2 = a1.slice(1, 3);

a1 //[1, 2, 3, 4, 5]

a2 //[2, 3]

 

 

splice

 

splice() 方法用新元素替换旧元素,以此修改数组的内容。MDN array.splice。

 

如其名,分割,会修改原数组的内容,返回一个新数组,而且它的参数也比较多,第一个参数表示初始位置,第二个参数表示分割长度,第三个参数及以后表示分割后在分割处添加新元素。

 

返回分割的元素组成的数组;

 

会对数组进行修改,原数组会减去分割数组。

 

?

var a1 = [1, 2, 3, 4];

var a2 = a1.splice(1, 2);

a1 //[1, 4]

a2 //[2, 3]

a1 = [1, 2, 3, 4];

a2 = a1.splice(1, 2, 5, 6);

a1 //[1, 5, 6, 4]

?

 

 

some

 

some() 方法测试数组中的某些元素是否通过了指定函数的测试。MDN array.some。

 

sort

 

sort() 方法对数组的元素做原地的排序,并返回这个数组。 sort 排序可能是不稳定的。默认按照字符串的Unicode码位点(code point)排序。MDN array.sort。

 

sort 函数用于排序,比较常用,若没有制定排序函数,则按照 unicode 位点进行排序,而且数字会被转成字符串,所以 ‘123’ 要排在 ‘11’ 的后面。

 

我们会用 sort 做一些有意思的排序,比如汉字按照拼音排序。

 

返回排序后的原数组;

 

会对数组进行修改。

 

?

var big = function(a, b){

  return a - b;

}

var a1 = [2, 4, 77, 1];

var a2 = a1.sort(big);

a1 //[1, 2, 4, 77]

a1 === a2; //true

?

 

 

localeCompare 可以对汉字进行排序,当同时出现汉字和字母的时候会有 bug:

 

var sort_py = function(a, b){

  return a.localeCompare(b);

}

var a1 = ["北京", "上海", "南京", "合肥"];

a1.sort(sort_py);

//["北京", "合肥", "南京", "上海"]

 

 

toString

 

toString() 返回一个字符串,表示指定的数组及其元素。MDN array.toString。

 

显然,这个方法和 join 方法比较一下。

 

返回拼接的字符串;

 

不会改变原数组。

 

var a1 = [1, 2, 3];

var a2 = a1.toString();

a2 //"1,2,3"

 

 

ES6 中新添的数组方法

上面的这些方法都是 ES5 的,来看看 ES6 添加了哪些新方法。

 

copyWithin

 

copyWithin() 方法会浅拷贝数组的部分元素到同一数组的不同位置,且不改变数组的大小,返回该数组。MDN array.copyWithin。

 

接受三个参数,分别是要拷贝到的位置 target,拷贝开始位置 start 和结束位置 end。

 

返回修改了的原数组;

 

会对数组进行修改,且是浅拷贝;

 

参数可负,负值时倒推,且 end 为空表示数组长度。

 

var a1 = [1, 2, 3, 4, 5];

var a2 = a1.copyWithin(0, 2, 4);

a1 //[3, 4, 3, 4, 5]

a2 //[3, 4, 3, 4, 5]

a1 === a2; //true

 

 

find

 

如果数组中某个元素满足测试条件,find() 方法就会返回满足条件的第一个元素,如果没有满足条件的元素,则返回 undefined。MDN array.find。

 

返回找到的那个元素,若未找到,返回 underfined

 

不对原数组产生影响。

 

function isBigEnough(element, index, array) {

  return (element >= 10);

}

var a1 = [8, 18, 14];

var num = a1.find(isBigEnough); //18

 

 

findIndex

 

findIndex()方法用来查找数组中某指定元素的索引, 如果找不到指定的元素, 则返回 -1。MDN array.findIndex。

 

这个方法可以参考 find 方法,只是返回值是元素的索引而非元素本身。

 

fill

 

使用 fill() 方法,可以将一个数组中指定区间的所有元素的值, 都替换成或者说填充成为某个固定的值。MDN array.fill。

 

fill 方法接受三个参数,第一个参数 value 表示要填充到值,后面两个 start 和 end 表示开始和结束位置,可选,且左闭右开。

 

函数返回值是修改了的原数组;

 

可对数组产生影响。

 

var a1 = [1, 2, 3, 4, 5];

var a2 = a1.fill(6, 1, 4);

a1 //[1, 6, 6, 6, 5]

a2 //[1, 6, 6, 6, 5]

a1 === a2; //true

 

 

keys

 

数组的 keys() 方法返回一个数组索引的迭代器。MDN array.keys。

 

这个方法会返回一个数组索引的迭代器,迭代器在 ES6 中有特殊的用途。

 

函数返回一个迭代器对象;

 

不会改变原数组。

 

?

var arr = ["a", "b", "c"];

var iterator = arr.keys();

 

console.log(iterator.next()); // { value: 0, done: false }

console.log(iterator.next()); // { value: 1, done: false }

console.log(iterator.next()); // { value: 2, done: false }

console.log(iterator.next()); // { value: undefined, done: true }

?

 

 

entries

 

entries() 方法返回一个 Array Iterator 对象,该对象包含数组中每一个索引的键值对。MDN array.entries。

 

var arr = ["a", "b", "c"];

var eArr = arr.entries();

 

console.log(eArr.next().value); // [0, "a"]

console.log(eArr.next().value); // [1, "b"]

console.log(eArr.next().value); // [2, "c"]

 

 

includes

 

includes() 方法用来判断当前数组是否包含某指定的值,如果是,则返回 true,否则返回 false。MDN array.includes。

 

该函数接受两个参数,第二个参数表示开始查找位置,起始位置为 0。这个方法与 indexOf 方法最大的区别不仅在于返回值一个是索引,一个是布尔值,indexOf 方法使用的是 === 来判断,无法判断 NaN 情况,而 includes 可以判断。

 

返回 true 或 false;

 

不会改变原数组。

 

var a1 = [1, NaN];

a1.indexOf(NaN);//-1

a1.includes(NaN);//true

二、原型

一. 原型与构造函数

 

  Js所有的函数都有一个prototype属性,这个属性引用了一个对象,即原型对象,也简称原型。这个函数包括构造函数和普通函数,我们讲的更多是构造函数的原型,但是也不能否定普通函数也有原型。譬如普通函数:

 

function F(){

  ;

}

alert(F.prototype instanceof Object) //true

 

 

  构造函数,也即构造对象。首先了解下通过构造函数实例化对象的过程。

 

function A(x){

  this.x=x;

}

var obj=new A(1);

实例化obj对象有三步:

 

  1. 创建obj对象:obj=new Object();

 

  2. 将obj的内部__proto__指向构造他的函数A的prototype,同时,obj.constructor===A.prototype.constructor(这个是永远成立的,即使A.prototype不再指向原来的A原型,也就是说:类的实例对象的constructor属性永远指向"构造函数"的prototype.constructor),从而使得obj.constructor.prototype指向A.prototype(obj.constructor.prototype===A.prototype,当A.prototype改变时则不成立,下文有遇到)。obj.constructor.prototype与的内部_proto_是两码事,实例化对象时用的是_proto_,obj是没有prototype属性的,但是有内部的__proto__,通过__proto__来取得原型链上的原型属性和原型方法,FireFox公开了__proto__,可以在FireFox中alert(obj.__proto__);

 

  3. 将obj作为this去调用构造函数A,从而设置成员(即对象属性和对象方法)并初始化。

 

  当这3步完成,这个obj对象就与构造函数A再无联系,这个时候即使构造函数A再加任何成员,都不再影响已经实例化的obj对象了。此时,obj对象具有了x属性,同时具有了构造函数A的原型对象的所有成员,当然,此时该原型对象是没有成员的。

 

  原型对象初始是空的,也就是没有一个成员(即原型属性和原型方法)。可以通过如下方法验证原型对象具有多少成员。

 

 

 

var num=0;

for(o in A.prototype) {

  alert(o);//alert出原型属性名字

  num++;

}

alert("member: " + num);//alert出原型所有成员个数。

 

 

  但是,一旦定义了原型属性或原型方法,则所有通过该构造函数实例化出来的所有对象,都继承了这些原型属性和原型方法,这是通过内部的_proto_链来实现的。

 

  譬如

 

  A.prototype.say=function(){alert("Hi")};

 

  那所有的A的对象都具有了say方法,这个原型对象的say方法是唯一的副本给大家共享的,而不是每一个对象都有关于say方法的一个副本。

 

 

 

二. 原型与继承

 

  首先,看个简单的继承实现。

 

?

?

1 function A(x){

2   this.x=x;

3 }

4  function B(x,y){

5   this.tmpObj=A;

6   this.tmpObj(x);

7   delete this.tmpObj;

8   this.y=y;

9 }

?

?

 

 

  第5、6、7行:创建临时属性tmpObj引用构造函数A,然后在B内部执行,执行完后删除。当在B内部执行了this.x=x后(这里的this是B的对象),B当然就拥有了x属性,当然B的x属性和A的x属性两者是独立,所以并不能算严格的继承。第5、6、7行有更简单的实现,就是通过call(apply)方法:A.call(this,x);

 

这两种方法都有将this传递到A的执行里,this指向的是B的对象,这就是为什么不直接A(x)的原因。这种继承方式即是类继承(js没有类,这里只是指构造函数),虽然继承了A构造对象的所有属性方法,但是不能继承A的原型对象的成员。而要实现这个目的,就是在此基础上再添加原型继承。

 

 

 

  通过下面的例子,就能很深入地了解原型,以及原型参与实现的完美继承。(本文核心在此^_^)

 

?

?

1 function A(x){

2   this.x = x;

3 }

4 A.prototype.a = "a";

5 function B(x,y){

6   this.y = y;

7   A.call(this,x);

8 }

9 B.prototype.b1 = function(){

10   alert("b1");

11 }

12 B.prototype = new A();

13 B.prototype.b2 = function(){

14   alert("b2");

15 }

16 B.prototype.constructor = B;

17 var obj = new B(1,3);

?

?

  这个例子讲的就是B继承A。第7行类继承:A.call(this.x);上面已讲过。实现原型继承的是第12行:B.prototype = new A();

 

  就是说把B的原型指向了A的1个实例对象,这个实例对象具有x属性,为undefined,还具有a属性,值为"a"。所以B原型也具有了这2个属性(或者说,B和A建立了原型链,B是A的下级)。而因为方才的类继承,B的实例对象也具有了x属性,也就是说obj对象有2个同名的x属性,此时原型属性x要让位于实例对象属性x,所以obj.x是1,而非undefined。第13行又定义了原型方法b2,所以B原型也具有了b2。虽然第9~11行设置了原型方法b1,但是你会发现第12行执行后,B原型不再具有b1方法,也就是obj.b1是undefined。因为第12行使得B原型指向改变,原来具有b1的原型对象被抛弃,自然就没有b1了。

 

 

 

  第12行执行完后,B原型(B.prototype)指向了A的实例对象,而A的实例对象的构造器是构造函数A,所以B.prototype.constructor就是构造对象A了(换句话说,A构造了B的原型)。

 

alert(B.prototype.constructor)出来后就是"function A(x){...}" 。同样地,obj.constructor也是A构造对象,alert(obj.constructor)出来后就是"function A(x){...}" ,也就是说B.prototype.constructor===obj.constructor(true),但是B.prototype===obj.constructor.prototype(false),因为前者是B的原型,具有成员:x,a,b2,后者是A的原型,具有成员:a。如何修正这个问题呢,就在第16行,将B原型的构造器重新指向了B构造函数,那么B.prototype===obj.constructor.prototype(true),都具有成员:x,a,b2。

 

 

 

  如果没有第16行,那是不是obj = new B(1,3)会去调用A构造函数实例化呢?答案是否定的,你会发现obj.y=3,所以仍然是调用的B构造函数实例化的。虽然obj.constructor===A(true),但是对于new B()的行为来说,执行了上面所说的通过构造函数创建实例对象的3个步骤,第一步,创建空对象;第二步,obj.__proto__ === B.prototype,B.prototype是具有x,a,b2成员的,obj.constructor指向了B.prototype.constructor,即构造函数A;第三步,调用的构造函数B去设置和初始化成员,具有了属性x,y。虽然不加16行不影响obj的属性,但如上一段说,却影响obj.constructor和obj.constructor.prototype。所以在使用了原型继承后,要进行修正的操作。

 

  关于第12、16行,总言之,第12行使得B原型继承了A的原型对象的所有成员,但是也使得B的实例对象的构造器的原型指向了A原型,所以要通过第16行修正这个缺陷。

6.13