面向对象和数组

1 对象

1.1 初识对象

对象在JavaScript中是对数据类型的笼统的称呼,对象本身也是一种数据类型,在基础课程中讲了单一的值类型,如:

1
2
3
var n = 0; //number类型
var b = true; //boolean类型
var str = 'abc'; //string类型

在Javasript中,一共有五种基本的数据类型(null,undefined,string,boolean,number),由于这几种简单的数据类型是按值传递,故而也可以称之为值类型数据。这类数据类型有一个共同的特点,就是它们都表示的是一个值。就是一个数据只表示了一个值,它对应的变量也是只保存了这一个值。就是说i=3这个表达式中,3就是一个单一的数,那么i也只保存了3这么一个值。

注:null和undefined其实属性标识性的数据,归到值类型或对象类型都不是很准确。

1.2 数据发展

但随着数据的复杂程度提高,单一的值类型已不能满足编程的需要,便产生了用单一的值类型数据(或复合数据)去描述的复合型数据,这个复合数据就是对象类型数据。为了方便工作,方便编程,有了复合型的数据类型,比如:

1
2
3
4
5
6
var obj = {
name:'rose',
age:22,
height:180,
write:function(){ console.log('i can program')}
};

以上定义了一个简单的复合数据类型的变量,这个变量以键值对的方式来存储数据,由于复合数据类型是按引用传递的,也可以称之为引用类型(Object,Array,Date,RegExp,Function)。而ECMA-262使用专业术语词汇将对象定义为“无序属性的集合,其属性可以包含基本值、对象或者函数”,即通过键值对来描述对象的属性和方法。

可以知道,对象就是一种数据结构,用于将多种数据或功能(或叫函数或方法)组织在一起。其实对象类型的数据的意义不仅仅是把多个值组织在一起,更重要的这些集合在一起的值之间还有关联,并且这些值也是对这个对象数据的整体描述,即用数据来描述数据。
当然,也可以把obj看做是一个内存块,在内存块中又划分了若干个小内存块,用来存放obj的值,这种数据的存储方式也是需要注意理解的。

1.3 对象的特点

  • 可以复合存值,存很多值;
  • 键和值对本身是对obj的描述,这些不仅仅是数据了,是描述了obj这个完整的对象的特征;
  • 不同的属性之间也是有关系的,有关联的。如:数组的length属性,数组也是对象类型的,用来描述属性的存值个数。属性之间也是有关联的。

以上是面向对象编程的特点,在编程语言中,我们经常用对象的这种特点,来描述一个复合的完整数据,即通过数据来描述数据,这就是面向对象的基础,例如:

1
2
3
4
5
6
var web_engineer = {
name:'',
age:30,
js:function(){ console.log('javascript')},
css:function(){ console.log('css')}
};

1.4 遍历对象

遍历对象的值,用for…in:

1
2
3
4
5
for(var attr in web_engineer){
alert(attr); //获取属性
alert(web_engineer[attr]); //获取值
alert(web_engineer.attr); //获取值的另一方式,性能好,但不够灵活
}

这样就可以依次按顺序的输出属性及对应的属性值,这种以字符形式的对象遍历输出是没有兼容性问题的。需要注意的是,有一种情况会有兼容性问题—-以数字为属性,这种情况在chrome浏览器中遍历时会将属性为数字的值优先输出。如:

1
2
3
4
5
6
var person1 = {
name:'somebody',
age:22,
0:'number1',
1:'number2'
};

注意,这里的person1[0]不是数组值,它是一种类似于数组的对象结构,称为类数组,在后面会详细的说到。

2 面向对象

加:什么是对象和面向对象,我们用社会生产中商品(物品)和生成商品的方式来解释
注:这里的模式,就是指生产方式,创建对象的方式,上课的时候,一定要理解好这个“模式”的概念。这里说的这些设计模式,叫“创建型设计模式”,是诸多的设计模式中的一种

2.1 创建单一的一个对象

在前端发展早期,公司若需要招聘前端工程师的话,需要对招聘的人才特点进行相应的描述,这时我们可以创建一个obj对象来描述前端工程师的特点,这种描述对象的方式就是对象直接量创建对象。一般地,当数据量比较小的时候,适合用对象直接量创建对象。但麻烦的是,如果需要招聘一万个前端人才,则每招聘一次就需要创建一个对象来描述一次,这样效率就不高了。
更形象地说,在5000年前,如果需要做一件虎皮大衣,那么首先需要先打个老虎取得虎皮作为原材料,再纯手工缝制,才能制作出一件纯正的虎皮大衣成品。

1
2
3
4
5
//对象,物件,产品
//虎皮大衣
var shirt = {
//手工,基于对象的方式
};

小结:显然,这种生产方式是很落后的。相应的,在js中创建对象数据类型也是这样的,纯粹的用对象直接量来单次的描述对象在编程中非常的不方便,需要改进这种创建对象数据类型的方式,工厂模式可以提高这种落后的“生产方式” 。
加:一个对象的设计模式,单例模式的作用

2.2 批量生产对象的方式:工厂模式

我们知道,一个面包工厂生产面包的流程和我们自己在家做面包的流程是一样的,在家手工制作面包的生产方式就是基于对象。不同的是,工厂里面将生产面包的流程进行了改进,可以批量地生产了。而工厂是对生产对象的流程的归纳,或者叫做封装,这是面向对象。所以原来叫基于对象,现在是面向对象。

面向对象和基于对象的区别:生产流程的升级,生产方式的改变。面向对象的就是生产对象(对象是物件或是产品)的方式改进了。同样地,在程序语言设计中,面向对象是一种更高效的生产数据的方式。

回到前面的例子,现在社会上需要很多前端工程师了,需要批量的招聘,需求量很大,但前端工程师人才数量不足,这就需要一个培养前端工程师的流程了,需要培训机构批量的培训出前端工程师:

1
2
3
4
5
6
7
8
9
10
11
12
function FE(){//定义一个前端工程师的规范
var obj = {};//这个注释会引起误解,去掉:生产原料
obj.name = ''; //加工过程
obj.age = '';
obj.writeCss = function(){ console.log('css')};
obj.writeJs = function(){ console.log('js')};
return obj; //生产出来的产品
}
var a = [];
for(var i = 0;i<30;i++){
a.push(FE(i));//按着这个规范,用循环批量创建
}

现在定义了一个培养前端工程师的方法,将方法循环30次,就表示生产出30个前端工程师了,这就是典型的通过工厂模式来创建对象。将这种具体的生产方式抽象出一个生产的流程方法,比如现在有很多羊毛,需要批量生产出很多羊毛衫:

1
2
3
4
5
6
7
8
function factory(material){//定义一个生产的流程(或叫规范也行)
var obj = {};
obj.material = meterial;//原料
obj.attr1 = '';
obj.attr2 = '';
obj.fn = function(){};//比如羊毛衫还有自己的一些功能,则就是函 数型的属性了
return obj;//生产完成,出产品,不要忘了返回
}

这种抽象出的生产对象的方式就是工厂模式,这是一种简单的生产方式,是对原来手工生产羊毛衫的生产方式的改变,原来叫基于对象,现在就是面向对象了。所谓面向对象,就是指生产对象的方式。现在这是其中一种方式。

小结:通过工厂模式,我们就可以大批量的生产物品了,但是有缺点,在商品短缺的年代,批量出来的可以畅销。但是当全世界都在生产,竞争加剧了,产品丰富了之后,就得提高品牌识别,贴牌了,加上商标,来加强竞争力。这就不能用简单的工厂模式了,需要将商品区别开来,提高产品的差异性。产品差异,在编程语言中叫实例识别,其实意思都是一样。产品差异的体现:同样是手机,苹果更贵小米就便宜;同样是汽车,宝马更贵而夏利就便宜。这种产品的差异性可以称之为实例识别。想要解决实例识别的问题则需要使用构造函数模式了。

2.3 有区别的批量生成对象:构造函数

以很有个性的360公司为模板,我们创建一个构造函数:

1
2
3
4
5
6
function FE360(name, age){
this.name = name;
this.age = age;
this.writeCss = function(){};
this.writeJs = function(){};
}

构造函数和工厂模式对比:

  • 更简单了
  • 少了对obj对象的定义,不用new Object
  • 少了return,不需要返回值
  • 用法也不一样了,有区别,现在不是直接运行了,需要new一个实例,用法如下:
1
2
3
4
5
var person2 = new FE360('someone', 24);
person2.age;
person2.name;
person2.writeCss();
//这样就获得了有个性的360工程师的实例

这里的person2不仅仅是一个对象,它还是构造函数FE360的一个实例,是属于类的实例了,FE360也不仅仅是一个普通函数了,而是构造函数了。现在可以继续创建不同的实例:

1
2
3
4
var person3 = new FE360('person3', 26);
var person4 = new FE360('person4', 34);
//这样用构造函数的方法就解决了实例的问题
//Function的特殊性:一个方法是类还是函数,主要在于new的出现

当new一个函数的时候,这个函数就是一个类。new的时候,浏览器的操作:

  1. 首先会主动创建一个对象类型的数据,这个数据是当前函数的实例,或者说,以这个函数名为识别符的实例。可以用instanceof来检验一下:
    alert(person2 instanceof FE360);
  2. 以这个实例为上下文(context,this,当前行为发生的那个主体),再把构造函数当成一个方法来运行,这个方法就是起一个初始化实例的作用
  3. 可以通过实例来使用构造函数的属性和方法了
1
2
3
4
5
6
7
8
9
function FE360(name, age){
this.name = name;
this.age = age;
this.writeCss =function(){ alert('我叫'+this.name+',我会写css'); };
this.writeJs = function(){ alert('我叫'+this.name+',我会写js'); };
}
person2.writeCss();
person2.writeJs();

小结:对象本来是很笼统的概念,现在person2也是对象,但它是当前FE360的实例,这样就可以细分化了,这是对工厂模式的升级,是生产模式的改进,原来生产的都是一样的,现在可以将商品区分开来了。正如社会的发展,对社会角色的区分也越来越细化。

  • 补充1:对象、类和实例的区分

对象是万事万物,类是对万事万物的细分和区别,实例是类的具体化,再有什么功能或数据类型,再构造函数扩展就行。

  • 补充2:数据不单单是一个值,数据也有功能

比如说,人就是一个抽象的值,不仅仅是一个信息的载体,还可以完成很多功能,比如:会上课会捣乱。数据的范围很广,也是一个数据体。Function类型的数据可以完成很多不同的功能。也就是说函数(function)也是数据。

少一个原型模式prototype,一定要加上
不重复制造轮子的原则:原型模式

2.4 兼顾以上二者的混合模式

注:这里的模式,就是指生产方式,创建对象的方式。这里说的这些设计模式,叫“创建型设计模式”,是诸多的设计模式中的一种

私有属性和共有属性。每一个实例上都会产生一个自己私有的属性和方法,比如说吃饭每个人都会,这是自己的私有属性,但这个功能是上帝就创造好的,不是后天培养创建的。构造函数模式虽然可以解决实例识别的问题,但本着编程的复用原则,构造函数模式却解决不了复用的问题,因为在构造函数内定义的方法是构造函数的私有属性,构造函数的实例之间无法去共享一个相同的方法。
比如:

1
2
alert(person3.writeCss() == person4.writeCss);
//这里alert出现的结果是false,说明这两个方法是不一样的。

那怎么让不同的实例去共享相同的方法呢,Js的原型机制可以实现,这就是js面向对象的核心点。
下面使用创建对象的新模式,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
function FE360(name, age){
this.name = name;
this.age = age;
}
//类的定义就发生了质的变化
FE360.prototype.writeJs = function(){
alert('我叫'+this.name+',我会写js');
};
FE360.prototype.writeCss = function(){
alert('我叫'+this.name+',我会写css');
};
var person3 = new FE360('person3', 26);
var person4 = new FE360('person4', 34);

现在在原型上定义了需要共享的方法,而且只需要定义一次就可以了,此时在测试一下实例person3和person4调用的方法是否一样:

1
alert(person3.writeCss() == person4.writeCss); //弹出结果为true

这说明person3和person4上的writeCss方法指向的是内存中的同一个地址,那么无论定义多少个实例,实例上用的方法都是一样的了。

原型是任何方法天生就有的属性,只有将方法当成一个类的时候,原型才有作用,把方法当成一个普通方法来运行的时候,原型也没什么用。现在将以上的例子来分解,演示原型的实现机制,如图1所示:

图 1 原型的实现机制
创建完对象之后,对象是一个内存地址,没有运行之前,就有一个属性proto(这里是两个下划线,任何一个对象实例上都有这个属性),这个属性是天生的原型机制,会指向原型对象。而在person1中定义的name和age是它的私有属性,如果在私有作用域内找不到这两个属性,就会往原型对象逐级往上找。
把原型链展开来写,一定要把JS是基于原型继承的这一特点写清楚。尽量配上图。要求有图有真相

总结:
加上:什么是模式,可以简单的理解为生成方式,以上解释过了。
1、面对对象的概念不是很深,这个思想不难理解,讲数组的时候可以体会到这个编程思想的用法。
2、工厂模式什么时候去使用,他的使用场合是在哪里,其实在编程过程中会接触到很多这个模式,比如说createElement,这就是一个典型的工厂方法。
3、使用什么样的模式,是按需求来决定。不必生搬硬套的去使用模式,应该自然而然的来使用,完全没有高低优劣之分。比如视频中说的富士康的工厂,他的生产模式就是代工,但是不能说它的生产方式就是低级,因为富士康的定位就是代工生产,生产的效率也很高。模式的使用在于编程的快捷需求,需求决定模式的选择。
4、Js不需要处理大批量的数据量,所以js的面向对象是灵活高效的,相对是比较瘦小的。但是由于js没有这种大数据量的特点,js就在浏览器中执行,标签较少,数据量较小。一切都是为灵活高效而服务的。比如说打一个蚊子,不需要飞机大炮,一个苍蝇拍就够了,这就是需求决定的。编程中使用标签多的时候也不会超过几千个,一个循环就可以了,所以没有必要过多的去强调面向对象。

3 数组基础

3.1 数组定义

创建一个数组最简单的方式是数组直接量的方式:

1
2
var arr = [0,1,2,3,4,5,6,7,8];
console.log(arr1); //5,6,7,8,9

也可以用构造函数的方式来创建:

1
var arr=new Array(元素1,元素2….元素n)

3.2 数组方法

数组有很多内置的强大方法,简单回顾一下:

3.2.1 元素添加和删除

a) push()
该方法可向数组的末尾添加一个或是多个元素,并返回新的数组长度。可以这么来理解,push是对数据的压栈操作,栈不是什么很深的概念,不要被吓倒了,比如说,桌子上堆了厚厚的一摞书,压栈就是把书一本本的往上加,是不是很好理解。使用语法:arr.push(ele1,ele2…eleN);其中,第一个参数是必填的,指要添加到数组的第一个元素,后面的参数可选,在使用过程中也一般只传第一个参数,在做遍历即可。
b) unshift()
该方法可向数组的开头添加一个或多个元素,并返回新的长度。它的作用不要和push方法混淆,一个是从数组的开头进行元素添加,一个是从数组的末尾进行元素的添加。使用语法:arr.unshift(ele1,ele2…eleN);
c) concat()
该方法用于连接两个或是多个数组,注意的是,该方法不会改变现有的数组,只是会返回一个被连接后的数组。使用语法:arr.concat(arr1,arr2…arrN);

1
2
3
var arr = [0,1,2,3,4,5,6,7,8];
var arr2 = arr.concat([1,2,3], [22,33,44]);
console.log(arr2); //0,1,2,3,4,5,6,7,8,1,2,3,22,33,44

d) pop()
该方法和push方法对应,但功能相反,用于删除并返回数组的最后一个元素,同时,数组的长度会被减1。如果数组已经为空,再去执行pop()方法,那么此时会返回undefined值。使用语法:arr.pop(),注意括号内不可传参数。
e) shift()
该方法和unshift方法对应,但功能相反,用于删除并返回数组的第一个元素。使用语法:arr.shift();

3.2.2 slice和splice方法

a) slice()
该方法可以从已有的数组中返回规定的元素,此方法返回的是一个新数组,包含start到end(注意,不包含该元素)的arrObj数组中的元素。

1
2
3
var arr = [0,1,2,3,4,5,6,7,8];
var arr1 = arr.slice(5, 1);
console.log(arr1); //5,6,7,8,9

使用语法:arr.slice(start, end),其中参数start必填,它规定了开始选取元素的位置,但如果是负数的话,就是表示从数组的尾部开始算起的位置(-1为最后一个元素,-2为倒数第二个元素…);参数end是选的,它规定了从何处结束选取,如果不传,则表示从规定的起始位置一直选取到结束,如果是负数,表示从数组的尾部开始算起的元素。在第四部分可以继续探讨slice方法的实现原理。
b) splice()
该方法功能很强大,可以用于对数组执行添加、删除和插入的功能,然后返回被删除的项目,该方法会改变原始的数组。
使用语法:arr.splice(index,count,item1,item2…itemN);
参数说明:index是必填的参数,而且必须是整数,这个参数指定了添加或是删除项目的位置,而且index为负数时,表明可以从数组结尾处规定的位置执行该方法;count也是必填参数,指定要删除的项目数量,如果设置为0,则不删除项目,后面添加参数的话,此时就可实现插入的功能了;item是可选的参数,如果不传,则指向删除的功能,传入的话,可以实现添加和插入的功能
具体范例如下:
1). 插入功能(只插入):splice(start, 0, args);//返回的是处理后的数组
2). 替换功能(删除和插入):splice(start, delCount, args);//返回的是处理后的数组
3). 删除功能(只删除):splice(start, delCount);//返回的是被删除的元素数组,若没有删除任何元素,则返回空数组

在开始了解这几个方法的时候,先普及一个重要的概念,即隐式调用和显式调用。其实这两个概念也很简单,所谓显式调用即是我们在编码的过程中主动的去调用该方法,而隐式调用则是js在处理过程中在进行某些数据类型转换时自动默认的调用该方法。那么为什么在这里提及隐式调用呢,由于toString()和valueOf()等方法属于原型对象都共有的方法,都会在需要的时候隐式的调用,完成相关的数据操作。

3.2.3 toString方法

 该方法用于将一个逻辑值转换成字符串,并返回结果。

1
2
3
4
var age = 123;
var str = age.toString();//str结果为”123”
var b = false;
var bToString = b.toString();//bToString 结果为”false”

 数值、布尔值、对象和字符串都有toString方法,但null和undefined值没有这个方法。
 toString在大多数情况下不需要传递参数,因为默认情况下该方法都是以十进制的格式返回数值的结果。如果传递参数给toString()的话则可以以二进制、八进制或十六进制表示的相应结果。

1
2
3
4
5
var num = 16;
console.log(num.toString(2));//10000 二进制
console.log(num.toString(8));//20 八进制
console.log(num.toString(16));//10 十六进制
console.log(num.toString(5));//31 虽然没有五进制,但是这样传参是可以被toString()方法接受的

 没有重新定义toString方法,看看调用toString方法将Object类型转化成string类型是什么样的结果。

1
2
3
var obj = {name:"Tom", age:18};
console.log(obj.toString());//"[object Object]"
//此时调用的是从Object继承来的原始的toString()方法

 toString方法的妙用—判断数据类型

1
2
3
4
5
Object.prototype.toString.call(null);//”[object Null]”
Object.prototype.toString.call(undefined);//”[object Undefined]”
Object.prototype.toString.call(“abc”);//”[object String]”
Object.prototype.toString.call(123);//”[object Number]”
Object.prototype.toString.call(true);//”[object Boolean]”

1
2
3
4
function fn(){console.log(“test”);}//函数类型
Object.prototype.toString.call(fn);//”[object Function]”
var arr = [1,2,3];//数组类型
Object.prototype.toString.call(arr);//”[object Array]”

3.2.4 排序方法

a) reverse()
该方法是用于颠倒数组中元素的顺序,需要注意的是,该方法直接在原数组上进行操作,会改变原数组,而不会创建一个新的数组,并且,使用时不需要传参,如:arr.reverse()。
b) sort()
该方法用于对数组的元素进行排序,和reverse方法类似,该方法也是直接在原数组上进行排序,会改变原数组。在这里,需要对该方法中的传参好好说明,调用该方法但不传参时,如arr.sort(),此时该方法将按ASCII码的字母顺序对数组中的元素进行排序,这是封装方法时这样设计的,使用时尤其应该注意。

1
2
3
var arr = [2,3,4,6,d,e,t,6];
arr.sort();
console.log(arr);

如果需要按其他的标准进行排序时,可以将函数直接量作为该方法的传参,从而实现排序,如以下写法可以实现升序排序(对DOM元素排序,对对象的排序,用的排序的算法是插入排序的思):

1
2
3
4
5
var aNum = [33,22,67,3,543];
aNum.sort(function(a, b){
console.log(aNum);
return a-b;
});

带单位增序降序的方法:

1
2
3
4
5
var aNum = ['33px','22px','67px','3px','543px'];
aNum.sort(function(a, b){
console.log(aNum);
return parseInt(a) - parseInt(b);
});

3.2.5 数组转换

a) toLocaleString()
toLocaleString()方法用于将数组转换为本地字符串,作用和toString差不多,但此方法是使用地区特定的分隔符来将生成的字符串连接起来。
b) valueOf()
该方法可以返回Array对象的原数值,通常都是在后台隐式的调用该方法,一般不会显式的出现我们的代码中。
c) join()
该方法用于将一个数组的所有元素都按指定的分隔符分隔,转换成字符串。
d) split()
该方法用于将字符串按片段分隔创建数组,和join()的功能正好相反。

3.2.6 位置方法(ECMA5的方法)

位置方法包括indexOf()和lastIndexOf()这两个方法用于搜索整个数组中具有给定值的元素,并且返回找到的第一个元素的索引值,如果没有找到,则返回-1。两者不同的是,indexOf方法是从头至尾的搜索,而lastIndexOf是从尾向前的搜索。
参数说明:indexOf()和lastIndexOf()方法的第一个参数都是必需的,传入的是需要搜索的目标值,而第二个参数是可选的,即指定开始搜索的位置,如果不传的话,indexOf()方法默认从头开始搜索,lastIndexOf方法默认从尾开始搜索。重要的是,第二个参数可以是一个负值,表示相对数组末尾的偏移量,所以这也使得以上两方法的使用没有特别明确的界限。

3.2.7 迭代方法(ECMA5的方法)

在学习理解这五个迭代方法之前,我们来先模拟一下forEach()这个方法的实现,以便理解这些方法中用到的传参(这几个方法的形式参数都是一样的),这对后续学习很有帮忙,我们需要从根处模拟这些方法是用什么样的思路来封装的,好了,请看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Array.prototype.forEach=function(fun,context){
// forEach的实现原理
var len=this.length;
var context=arguments[1];//即使为undefined,call函数也正常运行。
if(typeof fun !=="function"){
throw "输入正确函数!";
}
for(var i=0;i<len;i++){
fun.call(context,this[i],i,this);
//注意这四个参数,很关键,context是上下文,即作用对象,this[i]是各项元素的值,i为各项的索引值,this为执行该方法的主体
}
};
arr.forEach(function(item,index,arr){
console.log(item,index,arr);
//item arr的每一项的内容
//index 各项的索引值
//arr 即是输入的原数组值
});

代码解析:审查以上代码,这是用原型的方法来模拟forEach的实现,forEach()方法中有两个传参,第一个fun(传入一个函数),第二个是context(上下文,可以理解为执行该方法的对象)。一般在使用过程中传入fun即可,在fun中自定义自己想要实现的功能。在fun中有三个传参,在代码中写的很清楚,请理解之。
a) every()
该方法对数组中的每一项都运行给定的函数直接量,如果该函数对每一项都返回true,则该方法返回true,注意是每一项都满足条件该方法才会返回true。

1
2
3
4
5
6
var arr=[1,2,3,4,5,6,7];
var result=arr.every(function (item, index, array){ return item>5; });
alert(result); //此时会返回的结果是 false;
var arr1=[3,4,5];
var result1=arr1.every(function (item, index, array){ return item>2; });
alert(result1); //此时会返回的结果是 true;

b) filter()
该方法对数组中的每一项都运行给定函数,返回该函数返回true的项组成的数组,从单词字面意思理解,该方法是对数据执行一个过滤的作用,满足条件的返回,不满足条件的丢弃,仅此而已,用个例子说明:

1
2
3
var arr=[1,2,3,4,5,6,7];
var result=arr.filter(function (item, index, array){ return item>5; });
alert(result); //此时会返回的结果是 [6,7];

c) forEach
对数组中的每一项运行给定函数,需要注意的是,这个方法没有返回值。在编码的过程中使用这个方法会带来很多便利,因为该方法就有一个遍历数组元素的作用,不用每次的写一个for循环来获取数组的每一项进行操作了。
d) map()
对数组中的每一项运行给定的函数,返回每次函数调用的结果组成的数组,注意哦,这里返回的可是数组,和some()等方法返回布尔值不一样,使用过程中需要区别。
e) some()
对数组中的每一项运行给定的函数,如果该函数对任一项都返回true,则该方法返回true,和every()方法有点相似,但是要区别啊亲,简单区别的话可以从单词下手,every()是传入的函数需要对每一项都需要满足条件,该方法次啊会返回true;而some()方法是只要传入的函数对数组中的某一项返回true,该方法就会返回true。

3.2.8 缩小方法

a) reduce()
该方法从数组的第一项开始逐个遍历至尾,使用指定的函数来将数组的元素进行整合,只生成单个的值,这就是缩小方法,很好理解吧。另外,需要对该方法的参数进行说明一下,reduce()需要两个参数,第一个参数是执行化简操作的函数,这个参数必需;第二个参数是一个传递给函数的初试值,这里需要理解一下,所谓初始值就是传给第一个函数参数执行操作的第一个值,在接下来的操作中,这个值就是上一次函数的返回值了,而当第二个传参不使用时,化简函数就使用数组的第一个元素和第二个元素作为其第一个和第二个参数进行计算。请结合以下代码理解:

1
2
3
4
var arr=[1,2,3,4,4,5,6,6];
var sum=arr.reduce(function (x, y){ return x+y; }, 0); //数组元素求和
var multi=arr.reduce(function (x, y){ return x*y}, 1); //求出数组中各元素的积
var max=arr.reduce(function (x, y){ return (x>y)?x:y}); //求出最大值为6

b) reduceRight()
该方法的使用和reduce()是一样的,这里可以联想到indexOf()方法和lastIndexOf()方法的关系,即reduceRight()方法是按照数组索引从高到低的处理数组。

4 数组和面向对象

4.1数组和对象的关系

数组也是对象:使用typeof对一个数组运算后会返回的是字符串“object”,这便足以说明数组就是对象。

数组是有序存储数据的集合:数组是一段线性分配的内存,它可以通过整数去计算偏移并访问其中的元素,由此可以看出数组是有序对象存储的集合。

数组是一种访问速度很快的数据结构:因为数组的属性都是以数字形式存放在栈中,从栈中获取数据是非常快捷的。

类数组对象:而由于JavaScript中没有数组这样的数据结构,但却提供了一种拥有一些类数组特性的对象,将数组的下标转变成字符串,将其作为属性,这种我们称之为类数组对象。虽然类数组对象的访问速度比真正的数组慢,但它使用起来却更加的灵活方便,且其属性的检索和更新方式和对象都是一模一样的。

4.2数组的前世今生

Js中的Object是原始的类,负责构造对象,如:

1
Var obj= {name: ‘’,age:22, height:199 };

但由于方法不足,提供了一套封装数据的基本模型,由于功能有限需要更改和改进,有了以下的关联数组:

1
2
Obj[‘name’]; //php中叫做关联数组,可以以字符串为索引,作为查找值的依据
Obj[‘age’];

Js是很小巧的语言,数组和对象都是轻量级的。把对象里面的一部分功能做了改进,把数字也可以作为属性,索引,这样改进的好处:数字可以进行简单的数学运算,这样就派生出了数组类—-Array类。
数组只能以数字为索引属性的对象,创建这个类的时候,在它的原型上定义了一些属性和方法:

1
2
3
4
var a = [‘1’, ‘2’, ‘3’];
length属性;//Array.prototype.length
splice方法;
sort方法等,我们也可以定义出这些方法

把这些方法定义在原型上,把0,1,2这些属性自定义在类上,这样就不用写属性,可以直接写值了。对象是非常简便苗条的描述数据类型,数组就是这样演变过来的,而且在对象的基础了加了一些功能而产生的,Array继承又丰富了Object类。

4.3 对象和数组的同源性

我们可以在一对方括号中包围零个或多个用逗号分隔的值来方便的创建新数组

1
2
3
4
var empty=[];
var num=[0, 1, 21.2, true, false, ‘string’, null, undefined, [‘another’, ‘array’], {object: true}, NaN, Infinity ];
empty.length == 0;//true,数组empty的长度为0;
num.length == 12; //true,数组num的长度为12,且数组中允许包好任意混合类型的值。

在数组num中,数组的第一个值将自动获得属性名‘0’, 第二个值将获得属性名‘1’,依次类推,可以很直观的看到数组有序存储数据的特性。同样的,我们来看使用对象直接量来创建一个保存一样数据的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var num_obj={
‘0’:0,
‘1’:1,
‘2’:21.2,
‘3’:true,
‘4’:false,
‘5’:’string’,
‘6’:null,
‘7’:undefined,
‘8’:[‘another’, ‘array’],
‘9’:{object:true},
‘10’:NaN,
‘11’:Infinity
};

这样我们就用对象直接量的方式保存了和数组num一样的数据,不同的是,我们需要指定每一项数据的相应属性名,才可以实现相同的目的。这是因为num继承自Array.prototype,而num_obj继承自Object.prototype,所以num可以有序存储数据而且还具有length(当然后面会接触到function也有length属性,而function也是对象,所以不能说只有数组有length而对象就没有length,这是不对的)。

4.4检测数组类型

由于以上所讲述到的数组和对象的同源性,所以在JavaScript中对对象和数组的区别经常是混乱的,在对数组和对象的使用上也经常不明确,所以有必要明确两个误区:

  • 不要在必须使用数组时使用对象,或者在必须使用对象时使用了数组
    选用数组或对象的规则:当属性名是小而连续的整数时,应该使用数组,或是当对属性的位置和排列顺序有特殊要求时,应该使用数组;否则,应该使用对象。
  • 正确区分对象和数组
    不能单纯的运用typeof来检测数组,这会出现‘object’,通过数组去重来正确理解:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    //数组内单一数据类型去重复
    var a=[88,88,44,33,27,44,88,20,27,67,22,27,27];
    //var a=[2,2,2,2,2,2,2,2,2,2,2,2,2]
    Array.prototype.distinct=function (){//在Array原型上拓展去重方法
    var a=this;
    for(var j=0;j<a.length-1;j++){//使用简单的双循环逐个比较
    var item=a[j];//将待比较的数提出来
    for(var i=j+1;i<a.length;){
    if(item===a[i]){//待比较数与数组中其他数做比较
    a.splice(i,1); //两数相等,则删除相应项
    }else{
    i++;
    }
    }
    }
    }
    alert(a.distinct());
    alert(a);

这是简单的数组去重复,数组中没有包括对象类型数,都是简单的数值去重,在if逻辑表达式中会隐式的调用typeof运算符来判断数据类型。但是在数组中包含对象类型的值时,此时去重便不能实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var a=[88,88,{},{},{},33,27,44,88,20,27,67,22,27,27];
Array.prototype.distinct=function (){
var a=this;
var obj={};//把数组里的每个值,转变为这个对象属性和值。如果当前的这个值,没有出现重复
for(var i=0;i<a.length;){
if(obj[a[i]]!=a[i]){
//第一次是obj[88] !=88 obj[88]是undefined
//如果不等则是true,则给obj构建一个属性和值相同的键值对。说明它原来没有出现过,现在出现了则将其转化为对象的属性
//第二 obj[88]已经存在了
obj[a[i]]=a[i];//说明没有重复项
i++;
}else{
a.splice(i,1);
}
}
}
alert(a.distinct());
alert(a);

想知道去重后alert(a)的结果吗?

小结:原来使用的typeof运算符对数组的检测是不严谨的,所以在数组中含有对象类型值时进行去重不能实现。但是,我们可以自定义一个方法来实现对数组和对象的判断。既然前面咱们已经学习了原型等知识,就可以自定义一个更加简洁的方法来检测数组类型了。

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

补充:call和apply的应用和区别
A) call 和apply方法具有相同的功能,都可以直接改变被执行函数的作用域指向传参指定的新对象,即更改被执行函数的内部指针this的指向。这在面向对象的js编程过程中有时是很有用的。

1
2
3
4
5
function fn(){
alert(this.x + this.y);
}
var obj = {x: 2, y: 1};
fn.call(obj); //弹出的结果为3

B) 两者的区别:

1
2
Fn.call(context, arg1, arg2, arg3.....);//call方法中第二个以后的传参不限个数
Fn.apply(context, arg[i]); //apply方法第二个参数必须为数组,数组中为需要传递的参数

4.3数组方法模拟与扩展(深入理解prototype)

此讲主要是讲数组的原理性的东西
面向对象就是用来封装数据的,数组类就是典型的面向对象的类。一个是对数据的封装,归纳分类;另外一个是逻辑关系的分类,就是函数方法。以下分解几个方法,进一步探究Array上方法的由来:

1
2
3
4
5
6
7
8
9
10
11
//复制数组里的第s到第e项之前,不影响原来的实例
Array.prototype.slice = function(s, e){
var arr = [];
e = (typeof e == 'number')?e:this.length;
for(var i=s;i<e;i++){
arr.push(this[i]);
}
return arr;
};
var arr = [0,1,2,3,4,5,6,7,8];
arr.slice();

4.3.1 数组去重复的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
/*------------------------
*函数名称:Array.prototype.distinct
*功能描述:牺牲空间换时间,利用自定义属性的方法去重
*思 路:
*-----------------------*/
Array.prototype.distinct=function (){
var obj={};//定义一个对象,用于存放数组中不重复的元素
for(var i=0;i<this.length;){//遍历数组中的元素
if(obj[this[i]]!=this[i]){//若obj对象中没有该属性值,则向obj中添加一个属性并设置对应属性值,属性和属性值均为该数组元素
obj[this[i]]=this[i];
i++;
}else{
this.splice(i,1);//如果obj中已经有了该数组元素,则把比较到的这个重复元素删除
}
}
};
/*------------------------
*函数名称:
*功能描述:forEach方法去重
*思 路:
*-----------------------*/
Array.prototype.distinct=function (){
var a=[],obj={},temp=this;
temp.forEach(function (value, index, temp){
if(!obj[typeof (value)+value]) {
a.push(value);
obj[typeof (value)+value]=true;
}
});
return a;
};
/*------------------------
*函数名称:
*功能描述:解决对象去重问题
*思 路:
*-----------------------*/
function multiTypeDistinct (arr){
//判断对象类型的方法
function isEqual(obj1,obj2){
//判断两个对象的地址是否一样,地址一样则必相等,这里只为了优化性能
if(obj1===obj2){
return true;
}
if(typeof(obj1)=="object"&&typeof(obj2)=="object"){
//判断两个对象类型一致且为object类型
var count=0;
for(var attr in obj1){
count++;
if(!isEqual(obj1[attr],obj2[attr])){
return false;
}
}
for(var attr in obj2){
count--;
}
return count==0;
}else if(typeof(obj1)=="function"&&typeof(obj2)=="function"){
//判断两个对象类型一致且为function类型
if(obj1.toString()!==obj2.toString()){
return false;
}
}else { //判断两个对象类型不一致,再判断值是否相等
if(obj1!=obj2){
return false;
}
}
return true;
};
//temp作为传入数组arr的备份,在不改变原数组的基础上进行去重操作
var temp=arr.slice(0);
for(var i=0;i<temp.length;i++){
for(j=i+1;j<temp.length;j++){
if(isEqual(temp[j],temp[i])){
temp.splice(j,1);//删除该元素
j--;
}
}
}
return temp;
}
/*------------------------
*函数名称:listToArray()
*功能描述:将nodeList类型转化为数组
*思 路:
*-----------------------*/
function listToArray(eles){
try{
return Array.prototype.slice.call(eles,0)
}catch(e){//如果try里的代码出现了异常,则执行catch里的代码
var a=[];
for(var i=0;i<eles.length;i++){
a.push(eles[i]);
}
return a;
}
}
/*------------------------
*函数名称:stringDist
*功能描述:字符串转换成数组去重
*思 路:
*-----------------------*/
var str='777748908883859999';
var arr=[].slice.call(str,0);//将字符串转化为数组
function stringDist(arr){
var obj={},a=[];
for(var i=0,len=arr.length;i<len;i++){
var temp=arr[i];
if(!obj.hasOwnProperty(temp)){ //判断obj对象是否具有temp属性
obj[temp]=1;
a.push(temp);
}else{
obj[temp]++;
}
}
return a;
//return obj; //返回每一项及其重复的个数
}
console.log(stringDist(arr));

4.3.2 数组克隆的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Array.prototype.clone = function(){
return this.slice(0); //利用js内置的方法,执行效率特别高
};
Array.prototype.clone = function(){
var a = [];
for(var i=0;i<this,length;i++){
a.push(this[i]);
}
return a;
};
//测试
var a = [3,44,5,6,66];
var a1 = a.clone();
alert(a1 == a);
semifi wechat
欢迎您扫一扫上面的微信号,添加好友!
觉得不错就赏一个吧`(*∩_∩*)′

热评文章