对象:
在传统的面向过程的程序设计中,会造成函数或变量的冗余。而js中对象的目的是将所有的具有相同属性或行为的代码整合到一起,形成一个集合,这样就会方便管理,例如:
var person1={ name:"memphis", age:26, showMessage:function(){ alert("name:"+this.name); } }; person1.showMessage();//输出name:memphis
以上的例子将name、age、showMessage放进person这个对象中,方便管理也减少命名困难。
1)、内存会给每个对象分配一个内存空间;
2)、函数也是一个对象
每一个对象都会占据一定的内存空间。
构造函数:
对象的产生方便了代码的管理,但是又产生了一个问题,如果又定义了一个对象person2,如下:
var person2={ name:"memphis", age:26, showMessage:function(){ alert("name:"+this.name); } };
这样的话person2和person1就产生了代码重复问题(person1和person2有同样的属性和行为);由此就引出了构造函数的概念。
function Person(name,age)={ this.name=name; this.age=age; this.showMessage=function(){ alert("name:"+this.name); }; } var person1=new Person(tan,26); var person2=new Person(song,16);
这样通过构造函数我们就不用反复去重新定义属性和行为,我们就创造了两个对象person1和person2person1和person2也叫作构造函数Person的两个实例。
构造函数:用来在创建对象时初始化对象。特点:构造函数名一般为大写字母开头;与new运算符一起使用来实例化对象。
function Person(){} //Person构造函数 var p=new Person(); //Person构造函数创建对象,也可叫做实例化
原型:
构造函数可以解决代码管理和重复性的问题。但是,正如在 对象 中提到的函数也是对象,也会有一定的内存空间,属性可以每个对象占据一个空间,但是方法也就是行为,他对每个对象来说动作是一样的,只是可能传的参数不一样。因此要想一个方法来解决内存空间被过多的占用的问题。
拟解决方法1:函数的定义转移到构造函数外,例如:
function Person(name,age)={ this.name=name; this.age=age; this.showMessage=showMessage; } function showMessage(){ alert("name:"+name); }
这时构造函数中this.showMessage会指向showMessage这个全局变量,先在构造函数的内部去找showMessage这个变量,然后去外部找。
这个解决方法看似可以,但一个对象若有多个方法时,代码的封装性无法体现,而且全局的函数只是为一个对象服务,全局性体现的不明显。
拟解决方法2:原型
每个函数在创建的时候都会默认的分配一个prototype属性,构造函数也不例外。
那么prototype属性是什么?
=> prototype是一个指针,它指向一个对象。指针就像是:
想要把一本书存在一间房子里(行为),要完成这个行为,就要先建房子然后把书放进房子,下次要存一本新书,就还要先建房子再放另一本书,这就很麻烦了。
更好的解决办法:
整个行为中,房子是一个确定的动作,不需要每次都重建,建一次房子把地址记下来,以后每一次有放书这个动作时只需要通过地址找到对应的房子就行了。
如上面的例子:
showMessage就是建房子的动作,name是书,prototype属性是地址。构造函数就是每放一本书就要建一个房子,而原型就是通过地址找房子。
//建房子 function Person(){ } //房子里有什么并确定了指针的指向 Person.prototype={ name:"memphis", age:"22", showMessage:function(){ alert("name:"+this.name); } }; var person1 = new Person();
person1这样就有了指针,就能访问指针指定的内存空间。
1)、person1中也可以有自己属性,而且对于person1和原型Person中都有的属性,优先考虑的是person1中的属性。
2)、不能通过person1改变原型属性值,实例person1只是获取了一个指向原型的指针,并没有改变原型的权利。(注意:对于包含引用类型的属性——例如数组。有一些操作是允许通过地址去访问内存的,例如push,这些操作就有可能改变原型属性,这个改变会造成灾难性的后果,因为所有引用这个原型的对象都会随之改变。)
原型链:
没有原型的对象为数不多,Object.prototype算一个,它不继承任何属性。其他原型对象都是普通对象,普通对象都具有原型。Object.prototype是祖宗,其他的构造函数都是它儿子或者孙子、重孙子。它的孙子的原型是他儿子,同样也是它,因为他儿子是继承它的,这样一来,每个对象都继承至少一个原型对象,这一系列链接的原型对象就是所谓的“原型链”。
每一个对象都有自己的原型对象,原型对象本身也是对象,原型对象也有自己的原型对象,这样就形成了一个链式结构,叫做原型链。
上面说的都是同类型下的对象,person1和person2都是Person的实例。如果有Person和Man两个引用类型。Person中的属性和方法是有Man需要的,想直接从Person中所有的属性和方法拿过来,这就是继承。
作为一个继承的引用类型,能获取谁的属性和方法,name在这个问题上就会有原型链的概念。当对象尝试获某个属性时,该对象没有,会尝试从原型对象中去找,原型对象没有,在从原型对象的原型去找,最后到达Object.prototype。
例如: function Person(name,age){ this.pname=name; this.page=age; } Person.prototype={ showPMessage:function(){ alert("name:"+this.pname); } }; function Man(name,age){ this.mname=name; this.mage=age; } // 这一句话表示原型的指针指向了Person Man.prototype = new Person("memphis",22,"man"); // 给原型添加方法一定要放在替换原型的语句之后 Man.prototype.test="wo shi"; Man.prototype.showMMessage=function(){ alert("name:"+this.mname); }; var song=new Man("song",1); song.showMMessage();//显示song song.showPMessage();//显示memphis
继承
1. for-in继承
function Person(){ //父类 this.name="水煮鱼"; this.age=18; } function Son(){ //子类 } var p=new Person(); var s=new Son(); for(var k in p){ s[k]=p[k]; } console.log(s.name); //水煮鱼 console.log(s.age); //18
2. 原型继承
function Human(){ this.name="香辣虾"; this.age=21; } function Man(){ } Man.prototype=new Human(); var m=new Man(); console.log(m.name); //香辣虾 console.log(m.age); //21
3. 经典继承
var animal={ name:"阿咪", type:"猫科" }; var a=Object.create(animal) //ES5属性 console.log(a.name); //阿咪 console.log(a.type); //猫科
Object.create()是让一个对象的原型继承另外一个对象;所以虽然a.name和a.age是可以访问成功的,但实际上a本身并没有这些属性,而是a的原型上有这些属性。
如下图所示: