JavaScript面向对象
在JavaScript中,是没有接口的概念的,但是JS也是面向对象(Object-Oriented,OO)编程的。
在JS中,对象是无序属性的集合,其属性可以包含基本值、对象或者函数,每个属性或方法都有一个名字,而每个名字都映射到一个值。
属性及其特性
在ECMAScript中,为了描述属性(property)的各个特征,定义了只有内部才能使用的特性(attribute)。
ECMAScript中的属性分为两类:数据属性和访问器属性。
数据属性:包含一个数据值的位置,可以写入和读取值。
数据属性有4个特性来描述其行为:
(1)[[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特
性,或者能否把属性修改为访问器属性。 默认值为true。
(2)[[Enumerable]]:表示能否通过for-in循环返回属性。默认值为true。
(3)[[Writable]]:表示能否修改属性的值。默认值为true。
(4)[[Value]]:包含这个属性的数据值。默认值为undefined。
修改属性默认的特性:Object.defineProperty(属性所在的对象,属性的名字,描述符对象)
描述符对象的属性必须是:configurable、enumerable、writable、value。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>修改属性的特性</title>
<script>
var person= {};
Object.defineProperty(person,"name",{
writable : false,
value :"original"
});
person.name = "new";
console.log(person.name);//original
</script>
</head>
</html>
在调用Object.defineProperty方法时,如果不指定,configurable、enumerable和writable特性的默认值都是false。访问器属性
访问器属性不包含数据值,包含一对getter函数和setter函数(这两个函数并不是必需的)。
访问器属性有4个特性:
(1)[[Configurable]]:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为数据属性。默认为true
(2)[[Enumerable]]:表示能否通过 for-in循环返回属性。 默认为true
(3)[[Get]]:在读取属性时调用的函数,默认为undefined
(4)[[Set]]:在写入属性时调用的函数,默认为undefined
访问器属性不能直接定义,必须使用Object.defineProperty()来定义。
var book ={
_year:2004, // _year前的_是一种常用的记号,用于表示只能通过对象方法访问的属性
edition : 1
};
Object.defineProperty(book,"year",{
get : function(){
return this._year;
},
set : function(yearValue){
if(yearValue > 2004){
this._year = yearValue;
this.edition += yearValue - 2004;
}
}
});
book.year = 2005;
console.log(book.edition);
在上面的代码中,year就是一个访问器属性。为了实现为对象定义多个属性,ECMAScript5定义了一个Object.defineProperties()方法。需要两个对象参数:第一个对象是要添加和修改其属性的对象,第二个对象的属性与第一个对象中要添加或修改的属性一一对应。
var book = {};
Object.defineProperties(book,{
_year : {
value : 2004,
writable : true//在调动defineProperties()方法时,如果不设置writable,Enurable和configurable,那么默认为false.
},
edition : {
value : 1,
writable : true
},
year : {
get : function(){
return this._year;
},
set : function(newYear){
if(newYear > 24)
{
this._year = newYear;
this.edition = newYear - 2004;
}
}
}
});
book.year = 2007;
console.log(book.year);//2007
console.log(book.edition);//3
读取属性的特性
Object.getOwnPropertyDescriptor()方法,可以取得给定属性的描述符。其中接收两个参数:属性所在的对象和要读取其描述符的属性名称。返回值是一个对象,如果是访问器属性,这个对象的属性有configurable、enumerable、get和set。如果是数据属性,这个对象的属性有configurable、enumerable、value和writable。
创建自定义对象
方式一:创建一个Object实例,然后再为它添加属性和方法。
var person = new Object();
person.name = "haha";
person.age = 23;
person.getName = function(){
console.log(this.name);
}
person.getName();
方式二:对象字面量方式
var people = {
name : "haha2",
age :45,
getName : function(){
console.log("people");
}
};
people.getName();
方式三:使用模式创建对象
在前两种创建对象的方式中,存在一个缺点:使用同一个接口创建很多对象,会产生大量的重复代码。所以使用模式来创建自定义对象。
模式一:工厂模式
工厂模式中,抽象了创建具体对象的过程。提供一个函数,在这个函数中封装了创建对象的细节,并把这个函数作为创建对象的接口,可以无数次地调用这个函数,返回所需的对象。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>构造函数模式创建对象</title>
<script>
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
console.log(this.name);
};
}
var person1 = new Person("haha",23,"engineer");
var person2 = new Person("xixi",34,"teacher");
</script>
</head>
</html>
缺点:工厂模式虽然解决了创建多个类似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)
模式二:构造函数模型
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
console.log(this.name);
}
}
var person1 = new Person("anna",23,"doc");
var person2 = new Person("bob",34,"teacher");
按照惯例,构造函数始终都应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头。对于以上代码,要创建Person的新实例,必须使用new操作符。以这种方式调用构造函数实际上会经历以下4个步骤:
(1)创建一个新对象;
(2)将构造函数的作用域赋给新对象(因此this就指向了这个新对象);
(3)执行构造函数中的代码(为这个新对象添加属性);
(4)返回新对象。
构造函数模式优于工厂模式的地方是:创建自定义的构造函数意味着可以将它的实例标识为一种特定的类型。
构造函数本质上也是函数,所以构造函数也可以像普通函数那样使用。任何函数,只要通过new操作符来调用,那它就可以作为构造函数;而任何函数,如果不通过new操作符来调用,那它跟普通函数一样。
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
console.log(this.name);
}
}
var person1 = new Person("anna",23,"doc");//创建了自定义类型Person的实例person1
person1.sayName();//anna
Person("bob",34,"it");//构造函数作为函数被调用,此时的执行环境为window
window.sayName();
var o = new Object();
Person.call(o,"op",56,"tea");//在o的作用域中,创建了一个实例
o.sayName();
构造函数模式的缺点:每个方法都要在每个实例上重新创建一遍。这是因为在ECMAScript中,每定义一个函数,就会实例化一个对象。从逻辑上来看,上面的构造函数中,下面的代码
this.sayName = function(){
console.log(this.name);
}
等价于:this.sayName = new function(){
console.log(this.name);
}
所以,不同实例上的同名函数是不相等的。在实际使用中,如果创建多个对象,那么就会创建多个完成相同任务的Function实例,这样是不必要的。模式三:原型模式创建对象
在理解原型模式之前,先理解一下原型对象
无论什么时候,只要创建一个新函数(这个函数可以为普通函数,不一定是构造函数),就会根据一组特定的规则为该函数创建一个prototype属性,这个属性是一个指针,指向函数的原型对象。原型对象的用途是包含可以由特定类型的所有实例共享的属性和方法。在默认情况下,所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所在函数的指针。
创建了自定义的构造函数之后,其原型对象默认只会取得constructor属性,至于其他方法,则都是从Object继承而来的。由于原型对象中的属性和方法是可以供该自定义类型的其他实例共享的,所以当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性[[Prototype]],在实现中无法访问),这个指针指向了构造函数的原型对象。
使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。也就是说,不必在构造函数中定义对象实例的消息,而是可以将这些信息直接添加到原型对象中。
function Person(){
}
Person.prototype.name = "anna";
Person.prototype.age = 21;
Person.prototype.sayName = function(){
alert(this.name);
}
var person1 = new Person();
person1.sayName();//anna
var person2 = new Person();
person2.sayName();//anna
alert(person1.sayName == person2.sayName);//true
几个函数:
(1)isPrototypeOf()
我们发现,实例与构造函数是没有直接关系,而是实例和原型对象之间存在着联系,可以使用isPrototypeOf()方法,来确定这种关系。Person.prototype.isPrototypeOf(person1);
(2)Object.getPrototypeOf()
取得一个对象的原型,即[[Prototype]]的值。
Object.getPrototypeOf(person1)
(3)hasOwnProperty()
检测一个属性是存在于实例中,还是存在于原型中。这个方法只有在给定属性存在于对象实例中时,才会返回true。当被检测的属性存在于原型中,而不存于与实例中时,返回的结果为false。
每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性。搜索首先从对象实例本身开始。如果在实例中找到了具体给定名字的属性,则返回该属性的值;如果没有找到,则继续搜索指针指向的原型对象,在原型对象职工查找具有给定名字的属性。如果在原型对象中找到了这个属性,则返回该属性的值。
person1.hasOwnProperty("name")
(4)ECMAScript5中的Object.getOwnPropertyDescriptor()方法只能用于实例属性,要取得原型属性的描述符,必须直接在原型对象上调用Object.getOwnPropertyDescriptor()方法。
知识点:
(1)虽然可以通过对象访问保存在原型中的值,但却不能通过对象实例重写原型中的值(其实这样是很合理的,因为原型对象是用来共享的,如果其中一个实例能够改变原型中的属性值,那么将会影响到其他实例的使用)。如果我们在实例中添加了一个属性,而该属性与实例原型中的一个属性同名,那么我们就在实例中穿件该属性,该属性将会屏蔽原型中的那个属性。
(2)使用delete操作符可以完全删除实例属性,能够重新访问原型中的属性。
(3)原型中in操作符
in操作符的使用有两种:单独使用和for-in循环中使用。
单独使用:in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中。
for-in循环:返回的是所有能够通过对象访问的、可枚举的属性,其中既包括存在于实例中的属性,也包括存在于原型中的属性。屏蔽了原型中不可枚举属性(将[[Enumerable]]标记为false的属性)的实例属性也会在for- in循环中返回。
(4)Object.keys()
获得对象上所有可枚举的实例属性。该方法接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组。
(5)Object.getOwnPropertyNames()
获得所有的实例属性,无论属性是否可枚举
(6)简化原型语法
在使用原型对象实例化对象时,每一次添加一个属性和方法,都需要输入Person.prototype。现在我们可以这样简化:
function Person(){
}
Person.prototype = {
name : "haha",
age : 34,
sayName :function(){
alert(this.name);
}
};
在这里,使用了对象字面量形式创建了新对象Person.prototype。在这个过程中,产生的差别是:以之前的方式创建的原型对象的constructor的属性指向了Person构造函数。但是以对象字面量形式获得的原型对象,相当于重写了默认的prototype对象,因此constructor属性也就变成了新对象的constructor属性,指向的是Object构造函数。如果在使用中,constructor的值很重要,那么可以为其特意设定值。
function Person(){
}
Person.prototype = {
constructor:Person,
name : "haha",
age : 34,
sayName :function(){
alert(this.name);
}
};
(7)原型的动态性
由于在原型中查找值的过程是一次搜索,因此对原型对象所做的任何修改都能够立即从实例上反映出来,即使是先创建了实例后修改原型也照样如此。
function Person(){
}
Person.prototype.name = "anna";
Person.prototype.age = 23;
Person.prototype.sayName = function(){
alert(this.name);
};
var friend = new Person();
Person.prototype.sayHi = function(){
alert("hi");
}
friend.sayHi();
在上面的代码中,虽然先创建了一个Person的实例friend,在之后再向Person的原型对象中添加了方法,但是由于实例和原型对象之间是松散关系,只是使用一个指针将两者联系起来,实例不是原型的一个副本,所以在friend.sayHi()这一行,会在friend实例中搜索sayHi方法,发现没有该方法,然后就会在原型中搜索,发现该方法存在,所以不会出错。
虽然可以随时为原型添加属性和方法,并且修改能够立即在所有对象实例中反映出来,但是如果是重写整个原型对象,那么情况就不一样了。
function Person(){
}
var friend = new Person();
Person.prototype= {
constructor : Person,
name : "anna",
age : 23,
sayName : function(){
alert(this.name);
}
};
friend.sayName();
如下图,解释上面的过程:
重写原型对象切断了现有原型与任何之间已经存在的对象实例之间的联系,已经存在的对象实例会引用最初的原型。
(8)原生对象的原型
原型模式的重要性不仅体现在创建自定义类型方面,就连所有原生的引用类型,都是采用这种模式创建的。
可以通过原生引用类型.prototype获得原生对象的原型
原型对象的问题
原型模式的缺点:
(1)省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。
(2)原型对象是可以被共享的,对于包含引用类型值的属性来说,会出现问题。
function Person(){
}
Person.prototype = {
name :"anna",
age : 11,
friends : ["bob","pappe"]
}
var p1 = new Person();
var p2 = new Person();
p1.friends.push("haha");
document.write(p1.friends+"<br>");//bob,pappe,haha
document.write(p2.friends);//bob,pappe,haha
虽然在之前相似的实例中,我们修改了实例中基本类型的值,遇到实例属性名和原型属性名相同的情况,实例属性值会覆盖原型属性值,但是如果属性是一个引用类型,那么就麻烦了,因为实例共享原型,而引用类型的属性是通过引用访问的,不同的实例使用属性名,访问的是同一个引用类型的实例,当其中一个实例操作引用类型的值时,就会导致原型中的属性发生改变,其他实例再次访问的将会是改变后的值。所以最好不要单独使用原型模式生成对象。模式四:组合使用构造函数模式和原型模式
创建自定义类型的最常见方式:组合使用构造函数模式与原型模式。构造函数用于定义实例属性,而原型模式用于定义方法和共享的属性。这样将会保证每个实例有自己的一份实例属性的副本,但同时又共享着对方法的引用,节约内存,同时能够实现向构造函数传递参数。
function Person(name,age){
this.name = name;
this.age = age;
this.friend = ["anna","alice"];
}
Person.prototype = {
constructor : Person,
sayName : function(){
alert(this.name);
}
};
var p1 = new Person("bob",12);
var p2 = new Person("petter",23);
p1.friend.push("lili");
alert(p1.friend);//anna,alice,lili
alert(p2.friend);//anna,alice
动态原型模式
在将构造函数模型和原型模型结合起来使用时,构造函数和原型模型是分开使用的,在代码上的体现就是会在不同的地方出现这两个部分。动态原型模型可以将这两种模型糅合在一起。
function Person(name,age){
this.name = name;
this.age = age;
if(typeof this.sayName != "function")
{
Person.prototype.sayName = function(){
alert(this.name);
};
}
}
if语句中,只有当sayName方法不存在的时候,才会将它添加到原型中,在初次调用这个构造函数的时候,原型已经完成初始化。if语句检查的可以是初始化后应该存在的人格属性或方法,所以不需要用一大堆if语句检查每个属性和方法,只要检查其中一个即可。模式五:寄生构造函数模式
基本思想:创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象;从表面看,这个函数又很像是典型的构造函数。
function Person(name,age){
var o = new Object();
o.name = name;
o.age = age;
o.sayName = function(){
alert(this.name);
};
return o;
}
var friend = new Person("anna",12);
friend.sayName();
构造函数在不返回值的情况下,默认会返回新对象实例。如果在构造函数的末尾添加了一个return语句,就可以重写调用构造函数时返回的值。
function SpecialArray(){
var values = new Array();
values.push.apply(values,arguments);//注意在这行代码中,values.push是对push函数对象的引用,然后掉员工了该对象的apply()函数,在数组上添加值
values.toPipedString = function(){
return this.join("|");
};
return values;
}
var colors = new SpecialArray("r","b","g");
alert(colors.toPipedString());//r|b|g
在上述过程中,new一个SpecialArray的实例的过程 ,实际是先创建一个Array实例,然后使用push方法进行初始化,最后返回的是Array实例。在寄生构造函数模式中,返回的对象与构造函数或者与构造函数的原型属性之间没有关系,也可以理解为,构造函数反悔的对象与在构造函数外部创建的对象没什么不同。不能通过instanceof操作符来确定对象类型。所以不建议使用这种模式来生成对象
模式六:稳妥构造函数模式
稳妥对象:指的是没有公共属性,而且其方法也不引用this的对象。
稳妥构造函数遵循与寄生构造函数类似的模式,但有两点不同:一是新创建对象的实例方法不引用this;二是不使用new操作符调用构造函数。
function Person(name,age){
var o = new Object();
//可以在这里定义私有变量和函数
//添加方法
o.sayName = function(){
alert(name);
};
return o;
}
var friend = Person("anna",12);
friend.sayName();
在上面的代码中,生成的friend对象保存了一个稳妥对象。要想访问其数据成员,必须调用sayName()。【可以这么理解:在friend实例生成时,只是调用了Person()方法,那么name 和age,就相当于两个局部变量了,其作用域为Person函数内,在Person()函数访问name和age属性的唯一方法是,调用sayName()】。
继承
OO语言中,有两种继承方式:接口继承和实现继承。接口继承只继承方法签名,实现继承会继承实际的方法。
在ECMAScript中是没有方法签名的,所以只存在实现继承的方式,并且继承的实现是依靠原型链来实现的。
原型链
ECMAScript中继承的实现原理是:让一个引用类型继承另一个引用类型的属性和方法。
那么如何建立原型链呢?可以将(子引用类型)的原型对象等于另一个类型(父引用类型)的实例,这样,此时的原型对象将包含一个指向另一原型的指针,相应地,另一个原型中也包含一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条,这就构成了原型链。
实现原型链的一种基本模式,是:
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());
实现原型链,本质上扩展了原型搜索机制:当以读取模式访问一个实例属性时,首先会在实例中搜索该属性。如果没有找到该属性,则会继续搜索实例的原型。在通过原型量实现继承的情况下,搜索过程就得以沿着原型链继续向上。
在上面的例子中,我们认为是有两个类型,但是实际上,所有的引用类型都是默认继承Object的,而这个继承也是通过原型链实现的。记住:所有函数的默认原型都是Object的实例,在默认原型中都会包含一个内部指针,指向Object.prototype。所以在上面的原型链中,还需要再加入另外一个继承层次。
如何确定原型和实例的关系
方法一:instanceof操作符
只要用instanceof操作符来测试实例与原型链中出现过的构造函数,结果就会返回true。
alert(instance instanceof Object);//true
alert(instance instanceof SubType);//true
alert(instance instanceof SuperType);//true
方式二:isPrototypeOf()
只要是原型链中出现过的原型,都可以说是该原型所派生的实例的原型,都会返回true。
alert(Object.prototype.isPrototypeOf(instance));//true
alert(SuperType.prototype.isPrototypeOf(instance));//true
alert(SubType.prototype.isPrototypeOf(instance));//true
子类型中重写父类型中的方法或者是子类型中添加父类型中不存在的方法:给原型添加方法的代码一定要放在替换原型的语句之后。
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;
}
//重写父类型中的方法
SubType.prototype.getSuperValue = function(){
return "haha";
}
var instance = new SubType();
alert(instance.getSuperValue());//haha
在通过原型链实现继承时,不能使用对象字面量创建原型方法,这样将会重写原型链。
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
}
function SubType(){
this.subproperty = false;
}
SubType.prototype = new SuperType();
//使用字面量添加新方法,会导致上一行代码无效,现在的原型将包含的是一个Object实例,原来的原型链将会被切断。
SubType.prototype = {
getSubValue :function(){
return this.subproperty;
}
}
问题一
在包含引用类型值的原型属性会被所有实例共享 。而在继承中,原型实际上会变成另一个类型的实例。于是,原先的实例属性也就变成了现在的原型属性了。
function SuperType(){
this.colors =["b","r","g"];
}
function SubType(){
}
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("p");
alert(instance1.colors);
var instance2 = new SubType();
instance2.colors.push("p");//b,r,g,p
alert(instance2.colors);//b,r,g,p
问题二
在创建子类型的实例时,不能向超类型的构造函数中传递参数。实际上,应该说是没有办法不在影响所有对象实例的情况下,给超类型的构造函数传递参数。所以,在实际使用中,很少会单独使用原型链。
借用构造函数继承
基本思想:在子类型构造函数的内部调用超类型构造函数。需要使用apply()或call()方法。
这种方法,也被称为时伪造对象继承或经典继承。
function SuperType(){
this.colors = ["b","g","r"];
}
function SubType(){
SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("p");
alert(instance1.colors);//b,g,r,p
var instance2 = new SubType();
alert(instance2.colors);//b,g,r
借用构造函数技术的优势:可以在子类型构造函数中向超类型构造函数传递参数。<script>
function SuperType(name){
this.name = name;
}
function SubType(){
SuperType.call(this,"parent");//继承了父类,借用父类的构造函数,并传入了初始化的参数。
this.age = 29;
}
var ins = new SubType();
document.write(ins.name);//parent
document.write("<br>");
document.write(ins.age);//29
</script>
为了防止超类型中的属性覆盖子类型中的属性,所以,可以先调用超类型的构造函数,然后再设置子类型的属性。借用构造函数的问题:
使用构造函数,本身存在的一个问题是:构造函数中的方法是不共享的,所以难以实现实现函数的复用。
组合继承
组合继承,也称为伪经典继承,组合了原型链技术和借用构造函数技术。组合继承是最常用的继承方式
技术原理:使用原型链技术实现对原型属性和方法的继承,通过借用构造函数技术实现对实例属性(这些实例属性就是由子类各个实例自有的属性,是不能共享的)的继承。
<script>
//超类型构造函数,初始化基本类型属性和引用类型属性
function SuperType(name){
this.name = name;
this.colors = ["r","b","g"];
}
//在超类型的原型对象中定义方法
SuperType.prototype.sayName = function(){
console.log(this.name);
};
//子类的构造函数
function SubType(name,age){
//继承超类型的属性
SuperType.call(this,name);
//子类自己的属性
this.age = age;
}
//继承超类型的方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
//子类型自己的方法
SubType.prototype.sayAge = function(){
console.log(this.age);
}
var ins1 = new SubType("child1",23);
ins1.colors.push("add");
console.log(ins1.colors);
ins1.sayAge();
ins1.sayName();
var ins2 = new SubType("child2",34);
console.log(ins2.colors);
ins2.sayAge();
ins2.sayName();
</script>
原型式继承
function object(o){
function F(){}
F.prototype = o;
return new Fun();
}
把已有的对象o作为F的原型对象,而F只是一个临时性的构造函数,最后返回这个临时类型的一个新实例。相当于对传入的对象进行了一次复制。function object(o){
function F(){}
F.prototype = o;
return new F();
}
var person = {
name : "person1",
friends : ["f1","f2","f3"]
}
var nPerson1 = object(person);
nPerson1.name = "nperson1";
nPerson1.friends.push("f4");
console.log(nPerson1.friends);//["f1", "f2", "f3", "f4"]
var nPerson2 = object(person);
nPerson2.name = "nPerson2";
nPerson2.friends.push("f5");
console.log(nPerson2.friends);["f1", "f2", "f3", "f4", "f5"]
var person = {
name : "person1",
friends : ["f1","f2","f3"]
}
var np1 = Object.create(person);
console.log(np1.name);
console.log(np1.friends);
np1.friends.push("f4");
console.log(np1.friends);
var np2 = Object.create(person,{
name :{value : "person2"}
});
console.log(np2.name);
console.log(np2.friends);
寄生式继承
function createAnother(original){
var clone = object(original);
clone.sayHi = function(){
alert("hi");
};
return clone;
}
寄生组合式继承
function inheritPrototype(subType,superType){
var prototype = object(superType.prototype);//创建对象
prototype.constructor = subType;//增强对象
subType.prototype = prototype;//指定对象
}