JavaScript 面向对象 原型(prototype) 继承
1.对象的概念:无需属性的集合,属性可以为数值,对象或函数,ECMAscript中没有类的概念,这点是javascript与其他面向对象(OO)语言不同的地方。
//创建一个自定义对象 var person=new Object(); person.name="Tom"; person.age=23; person.job="web前端工程师"; person.sayName=function(){ alert(person.name); } person.sayName(); //用对象字面量语法来创建自定义对象 var person={ name:"Tom", age:23, job:"web前端工程师", sayName:function(){ alert(this.name); } } person.sayName();
2.属性类型
(1)为了描述对象属性(property)的各种特征,ECMAscript引入特性(attribute)的概念,同时为了表示特性是内部值,所以将特性放在[[]]中。
(2)ECMAscript有俩中属性:数据属性和访问器属性
数据属性的特性:[[Configurable]]:表示能否通过delete删除属性从而重新定义属性,默认为true。
[[Enumerable]]:表示能否通过for-in循环来返回属性。默认为true。
[[Writable]]:表示能否修改属性的值。默认为true。
[[Value]]:包含这个属性的值。默认为undefined。
如果想修改数据属性的特性可以调用defineProperty(属性所在对象,"属性名",{描述符对象})。
var person={}; Object.defineProperty(person,"name",{ writable:false, value:"Tom" }); alert(person.name); delete person.name; alert(person.name);
注意:调用defineProperty()时,没有指定Configurable,writable,enumerable特性时,默认为false。
访问器属性:[[Configurable]]同上。
[[Enumerable]]同上。
[[Get]]:在读取属性是调用,默认为undefined
[[Set]]:在写入属性时调用,默认为undefined
Object.defineProperties()可以通过描述符一次定义多个属性
Object.getOwnPropertyDescriptor(book,"_year");读取属性的特性,返回的是对象。
-----------------------------------------------------------------分割线---------------------------------------------------------------------------------------------------
创建对象的方式:
1.工厂模式
function createPerson(name,age,job) { var o=new Object(); o.name=name; o.age=age; o.job=job; o.sayName=function(){ alert(this.name);//this指的是o } return o; } var person1=createPerson("Tom",23,"web前端工程师"); person1.sayName();
工厂模式虽然解决多次创建相似对象的重复性问题,但是并没有解决对象识别问题,也就是typeof之后他都显示object,具体的对象是什么并没有显示(ps传送门对象都有哪些http://blog.sina.com.cn/s/blog_70a3539f0101eww3.html),所以构造函数模式出现了。
2.构造函数模式
function Person(name,age,job) { this.name=name; this.age=age; this.job=job; this.sayName=function(){ alert(this.name);//this是Person } } var person1=new Person("Tom",23,"web前端工程师"); person1.sayName();
构造函数模式和工厂模式的区别
1.没有显式的创建对象。
2.将属性和方法赋给了this对象。
3.没有return语句。
4.函数名第一个字母大写。
构造函数模式优于工厂模式的原因就是,构造函数模式中的对象实例(person1)通过constructor属性或instanceof操作符可以验证person1既是Object的实例,也是Person的实例,同时也证明所有对象均来自于Object。
但是构造函数也有缺点,对象是引用类型,对象实例化不是指针的改变,而是简单的复制,复制对象的方法和属性,假设一个对象有上千个实例,它就会复制上千个功能相同的方法,这显然是不可取的,我们看下面这个例子:
function Person(name,age,job) { this.name=name; this.age=age; this.job=job; this.sayName=sayName; } function sayName(){ alert(this.name) } var person1=new Person("Tom",23,"web前端工程师"); person1.sayName();
我们也可以把sayName()函数的定义战役到构造函数的外部,这样我们就将sayName属性设置成等于全局的sayName函数,这样实例化对象就共享全局作用域中的同一个sayName(),解决了构造函数对象方法的多次创建问题。但是全局作用域定义的sayName()函数只能被某个对象调用谈什么全局作用域,而且如果构造函数对象的方法有很多,就需要定义很多全局函数,封装性又从何谈起,于是原型模式应运而生。
3.原型模式
我们创建的每一个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,这个对象中包含着所有对象实例的属性和方法,这个对象就是原型对象。通俗的说,原型对象中的方法和属性可以被所有对象的实例所共享,对象实例就不用多次创建相同的方法和属性,我们看下面这个例子:
function Person(){ }; Person.prototype={ name:"Tom", age:23, job:"web前端工程师", sayName:function(){ alert(this.name); } } var person1=new Person(); person1.sayName();
虽然可以通过对象实例访问保存在原型对象中的值,但却不能通过对象实例重写原型的值。其实对象实例获取某一属性的值是从本身开始寻找,然后是原型对象,最后是构造函数对象,所以重写对象实例的属性值(这个值可以通过delete操作符删除)仅仅是阻断了获取原型属性值的途径,但是没有改变其中的值,我们看下面这个例子:
function Person(){
};
Person.prototype.name="Tom";
Person.prototype.age=23;
Person.prototype.job="web前端工程师";
Person.prototype.sayName=function(){
alert(this.name);
}
var person1=new Person();
var person2=new Person();
person1.name="Mike";
alert(person1.name);
alert(person2.name);
alert(person1.name);
alert(person2.name);
用对象字面量语法表示的时候,原型对象的constructor属性不在指向Person,因为每创建一个函数,同时会创建它的prototype对象,用对象字面量语法本质上相当于重写了prototype对象,constructor属性也会变成新对象的constructor属性(这里指向Object),我们看下面这个例子:
function Person(){ }; Person.prototype={ constructor:Person, name:"Tom", age:23, job:"web前端工程师", sayName:function(){ alert(this.name); } } var person1=new Person(); var person2=new Person(); person1.name="Mike"; alert(person1.name); alert(person2.name);
原型模式的缺点:因为所以对象实例共享原型对象的方法和属性,但是往往实例都有他自己私有的属性,这时候原型模式就不适用了,所以我们可以组合使用构造函数模式和原型模式。
4.组合使用构造函数模式和原型模式
组合使用构造函数模式和原型模式结合了构造函数和原型模式的优点,构造函数定义实例的私有属性,原型模式定义共享属性和方法,下面我们看一个例子:
function Person(name,age,job){ this.name=name; this.age=age; this.job=job; }; Person.prototype={ constructor:Person, sayName:function(){ alert(this.name); } } var person1=new Person("Tom"); var person2=new Person("Mike"); alert(person1.name); alert(person2.name);
---------------------------------------------------------------分割线------------------------------------------------------------------------------------------------------
继承
许多OO语言支持俩中继承方式:接口继承(继承方法的签名)和实现继承(继承实际的方法),因为函数没有签名,所以ECMAscript只有实现继承,而实现继承主要由原型链来实现。
1.原型链
简单地说,原型链就是让子对象的原型等于父对象的实例,层层递进,实现实例和原型的链条。下面看一个例子:
function SuperType(){ this.property=true; } SuperType.prototype.getSuperValue=function(){ return this.property; } function SubType(){ this.subproperty=false; } SubType.prototype=new SuperType(); SubType.prototype.getSubValue=function(){ return this.subproperty; } var instance=new SubType(); alert(instance.getSuperValue());//true
这里需要注意SubType.prototype对象的constructor属性已经不指向SubType,而是指向SuperType.
原型链的问题:
1.原型链也会遇到原型模式的问题,这里就不多说了。
2.创建子对象的实例时,没有办法在不影响所有对象实例的情况下,给父对象的构造函数传递参数。
2.借用构造函数
function SuperType(){ this.colors=["red","blue","green"]; } function SubType(){ SuperType.call(this)//或者apply() } var instance1=new SubType(); instance1.colors.push("black"); alert(instance1.colors); var instance2=new SubType(); alert(instance2.colors);
借用构造函数也可以通过子对象向父对象传递参数
function SuperType(name){ this.name=name } function SubType(){ SuperType.call(this,"Tom")//或者apply() } var instance1=new SubType(); alert(instance1.name);
虽然借用构造函数解决了实例共享问题和无法传递参数的问题,但是他做的并不完美,因为所有方法都在构造函数中定义,函数的复用性从何谈起,而且父原型对象中定义的方法,对子对象是不可见的,结果所有类型都只能使用构造函数模式,于是组合继承出现了。
3.组合继承
function SuperType(name,colors) { this.name=name; this.colors=colors; }; SuperType.prototype.sayName=function() { alert(this.name); }; function SubType(age,name,colors) { SuperType.call(this,name,colors); this.age=age; }; SubType.prototype=new SuperType(); SubType.prototype.sayAge=function() { alert(this.age); }; var person1=new SubType(23,"Tom",["blue","red","green"]); document.writeln(person1.colors);//来自父对象构造函数 document.writeln(person1.name);//来自父对象构造函数 person1.sayName();//来自父原型对象 document.writeln(person1.age);//来自子对象构造函数 person1.sayAge();//来自子原型对象
这里再强调一下,构造函数创建的属性是实例私有的,原型创建的属性和方法是实例共享的。
4.原型式继承
这种方法并没有使用严格意义的构造函数,而是借助原型可以基于已有的对象创建新对象,同时还不用创建自定义类型来达到这个目的。
以下代码是封装在object函数当中的,在使用的时候直接使用object()
function object (o)//这个o相当于父对象实例 { function F(){}//这个F相当子对象 F.prototype=o;//继承 return new F();//实例化传出 }
但是ECMAscript5定义了Object.create()方法,可以直接使用,他有俩个参数,下面来一个例子:
var person={ name:"Tom", age:23, job:"web前端工程师" }; var anotherPerson=Object.create(person,{ name:{ value:"Mike" } }); alert(anotherPerson.name);
在上面这个例子中,create的第二参数是一个对象,其中的属性如果和person重名,则会覆盖person的属性。
5.寄生式继承
function object (o)//这个o相当于父对象实例 { function F(){}//这个F相当子对象 F.prototype=o;//继承 return new F();//实例化传出 } function createAnother(o) { var clone=object(o); clone.sayName=function() { alert(this.name) }; return clone; } var person={ name:"Tom", age:23 } var anotherPerson=createAnother(person); alert(anotherPerson.name);
寄生式继承的缺点:不能做到函数复用而降低效率。
6.寄生组合式继承
组合继承虽然是javascript最常用的继承模式,但是他也不是完美的,这种继承的问题是不管怎么样我么都会调用俩次父对象构造函数,大家可以看之前的例子,下面这个是寄生组合式继承的例子,是最完美的继承:
function object (o)//这个o相当于父对象实例 { function F(){}//这个F相当子对象 F.prototype=o;//继承 return new F();//实例化传出 } function inheritPrototype(subType,superType) { var prototype=object(superType.prototype);//创建对象 prototype.construct=subType;//增强对象 subType.prototype=prototype;//指定对象 } function SuperType(name) { this.name=name; } SuperType.prototype.sayName=function() { alert(this.name); } function SubType(name,age) { SuperType.call(this,name); this.age=age; } inheritPrototype(SubType,SuperType); SubType.prototype.sayAge=function(){ alert(this.age); } var person1=new SubType("Tom",23);
person1.sayName();
----------------------------------------------------------分割线---------------------------------------------------------------------------------------------------
以上就是javascript对象,原型和继承总结的所有知识,如果有任何错误请大家指正。