我们先来看对象是如何进行定义的
"无序属性的集合,其属性可以包括基本值、对象或者函数",对象是一组没有特定顺序的的值。对象的没个属性或方法都有一个名字,每个名字都映射到一个值。
简单来理解对象就是由属性和方法来组成的
对于一些功能相同或者相似的代码,我们可以放到一个函数中去,多次用到此功能时,我们只需要调用即可,无需多次重写。
在这里我们可以理解为创造对象的几种模式:单例模式,工厂模式,构造函数模式,原型模式等。
1、单例模式 小王在一个小公司,就自己一个前端,所以他写js都是这样的
1 var a = 1;
2 function getNum(){
3 return 1;
4 }
后来公司又招了个前端小明,于是变成他们2个一起写同一个js了。一天小王发现自己写的getNum方法出问题了,原来是小华写的js中也有个getNum的函数,代码合并后把他的覆盖掉了,于是便找小华理论去,经过一番妥协后,两人都把自己的代码改了改
1 var xiaoming = {
2 num:1,
3 getNum:function(){
4 return 1;
5 }
6 }
7
8 var xiaohua = {
9 num:2,
10 getNum: function(){
11 return 2;
12 }
13 }
这就是我们所谓的单例模式(命名空间)
我们把描述同一个事物的方法或者属性放到同一个对象里,不同事物之间的方法或者属性名相同相互也不会发生冲突。
单例模式的优劣
1 var utils = {
2 getCss:function(){
3 //code
4 },
5 getByClass:function(){
6 //code
7 },
8 setCss:function(){
9 //code
10 }
11 }
1 var person1 = {
2 name:'小明',
3 age:24,
4 showName:function(){
5 console.log('我的名字是:'+this.name)
6 }
7 };
8 var person1 = {
9 name:'小华',
10 age:25,
11 showName:function(){
12 console.log('我的名字是:'+this.name)
13 }
14 };
2、工厂模式
1 function CreatePerson(name,age){
2 var obj={};//1.创建一个空对象
3 //2.加工对象
4 obj.name=name;
5 obj.age=age;
6 obj.showName=function(){
7 console.log('我的名字是:'+this.name)
8 };
9 return obj;//3.输出对象;
10 }
11 var person1 = CreatePerson('小明',23)
12 var person2 = CreatePerson('小华',23)
13 person1.showName(); //我的名字是:小明
14 person2.showName(); //我的名字是:小华
3、构造函数模式
1 //构造函数:首字母大写(约定俗成);
2 function CreatePerson(name,age){ //创建一个自定义的类
3 //构造函数中的this,都是new出来的实例
4 //构造函数中存放的都是私有的属性和方法;
5 this.name=name;
6 this.age=age;
7 this.showName=function(){
8 console.log('我的名字是:'+this.name)
9 }
10 }
11 //实例1
12 var person1 = new CreatePerson('小明',25)
13 //实例2
14 var person2 = new CreatePerson('小华',24)
这里说一下工厂模式和构造函数模式的区别:
1. 在调用的时候不同: 工厂模式:调用的时候,只是普通函数的调用createPerson(); 构造函数模式:new CreatePerson();
2. 在函数体内不同: 工厂模式有三步:
1)创建对象
2)加工对象
3)返回对象;
构造函数模式只有1步: 只有加工对象; 因为系统默认会为其创建对象和返回对象;
3. 构造函数默认给我们返回了一个对象,如果我们非要自己手动返回的话:
(1)手动返回的是字符串类型:对以前实例上的属性和方法没有影响;
(2)手动返回的是引用数据类型:以前实例身上的属性和方法就被覆盖了;实例无法调用属性和方法;
构造函数的方法都是私有方法,每个实例调用的都是自己私有的方法,同样也会有许多重复的代码。
我们可以使用原型模式来解决每个实例中都有相同方法的函数的问题
3、原型模式
1 function CreatePerson(name,age){
2 this.name=name;
3 this.age=age;
4 }
5 // 我们把公有的方法放到函数的原型链上
6 CreatePerson.prototype.showName = function(){
7 console.log('我的名字是:'+this.name)
8 }
9 var person1 = new CreatePerson('小明',25)
10 var person2 = new CreatePerson('小华',24)
11 person1.showName() //小明
原型模式的关键:
1)每个函数数据类型(普通函数,类)上,都有一个属性,叫prototype。
2)prototype这个对象上,天生自带一个属性,叫constructor:指向当前这个类;
3)每个对象数据类型(普通对象,prototype,实例)上都有一个属性, 叫做__proto__:指向当前实例所属类的原型;
这3句话理解了,下边的东西就可以不用看了 //手动滑稽
通过例子我们来看这几句话是什么意思
1 function CreatePerson(name,age){
2 this.name=name;
3 this.age=age
4 }
5 CreatePerson.prototype.showName=function(){
6 console.log('我的名字是:'+this.name)
7 }
8 var person1 = new CreatePerson('小明',25);
9 console.dir(person1)
在chrome浏览器控制台中显示
1 从图中可以看出,person1这个对象上有name和age两个属性,
2 person1的__proto__指向了它的构造函数(CreatePerson)的prototype上,
3 而且还有一个showName的方法。
4 并且它们中有一条链关联着: person1.__proto__ === CreatePerson.prototype
接着来看
1 function Foo(){
2 this.a=1;
3 }
4 Foo.prototype.a=2;
5 Foo.prototype.b=3;
6 var f1 = new Foo; //没有参数的话括号可以省略
7 console.log(f1.a) //1
8 console.log(f1.b) // 3
9
10 以这个为例,
11 当我们查找f1.a时,因为f1中有这个属性,所以我们得出 f1.a=1;
12 当我们查找f1.b时,f1中没有这个属性,我们便顺着f1.__proto__这条链去
13 它的构造器的prototype上找,所以我们得出了 f1.b = 3;
接着来说,Foo.prototype是个对象,那么它的__proto__指向哪里呢 还记的刚刚说的那句 每个对象数据类型(普通对象,prototype,实例)上都有一个属性,叫做__proto__:指向当前实例所属类的原型 此外,我们应该知道 每一个对象都是function Object这个构造函数的实例
所以我们可以接着还原这个原型图
等等,图上貌似多了个个Object.prototype.__proto__ 指向了null,这是什么鬼?
我们这么来理解,Object.prototype是个对象, 那么它的__proto__指向了它的构造函数的prototype上, 最后发现了还是指向它自身,这样转了个圈貌似是无意义的,于是便指向了null
还没完,我们发现对象都是函数(构造器)创造出来的,那么函数是谁创造的呢?石头里蹦出来的么? 在js中,function都是由function Function这个构造器创造的,每一个函数都是Function的实例
现在基本上我们就能得出了完整的原型图了
是不是有点乱?根据我们刚刚讲的是能把这个图理顺的, 这里需要注意下,Function.__proto__是指向它的prototype的
多说一点,判断数据类型的方法时,我们知道有个instanceof的方法 比如
A instanceof B
instanceof判断的规则就是:
沿着A的__proto__这条线查找的同时沿着B的prototype这条线来找,如果两条线能找到同一个引用(对象),那么就返回true。如果找到终点还未重合,则返回false。
再来看我们之前的那个例子
1 function Foo(){
2 this.a=1;
3 }
4 Foo.prototype.a=2;
5 Foo.prototype.b=3;
6 var f1 = new Foo; //没有参数的话括号可以省略
7 console.log(f1.a) //1
8 console.log(f1.b) // 3
9
10 当我们查找f1.a时,因为f1中有这个属性,所以我们得出 f1.a=1;
11 当我们查找f1.b时,f1中没有这个属性,我们便顺着f1.__proto__这条链去它的构造器的prototype上找,所以我们得出了 f1.b = 3;
当我们查找一个对象的属性时,先在这个对象的私有空间内查找,如果没找到,就顺着对象的__proto__这条链去它的构造器的ptototype上查找,如果还没找到,接着沿__proto__向上查找,直到找到Object.prototype还没有的话,这个值就为undefined,这就是所谓的原型链
列举下网页中的一些相关的原型链
有兴趣的同学可自行通过浏览器控制台看看我们常用的方法都是在哪个类上定义的,比如getElementsByTagName,addEventListener等等
在这里就主要说一下组合继承(call + 原型链)
1 function Father(){
2 this.xxx= 80;
3 this.yyy= 100;
4 this.drink = function(){}
5 }
6 Father.prototype.zzz= function(){}
7 var father = new Father;
8 function Son(){
9 this.aaa = 120;
10 this.singing = function(){}
11 Father.call(this);
12 }
13 Son.prototype = new Father;
14 Son.prototype.constructor = Son;
15 var son = new Son
16 console.dir(son)
这么写有个不好的地方就是:子类私有的属性中有父类私有的属性,子类公有的属性中也有父类私有的属性; 根据我们前边的知识,我们可以这么来改写
1 function Father(){
2 this.xxx= 80;
3 this.yyy= 100;
4 this.drink = function(){}
5 }
6 Father.prototype.zzz= function(){}
7 var father = new Father;
8 function Son(){
9 this.aaa = 120;
10 this.singing = function(){}
11 Father.call(this); //利用call继承了父类的私有属性
12 }
13 Son.prototype.__proto__ = Father.prototype
14 var son = new Son
15 console.dir(son)
最后来一张思维导图