javascript中java语言不一样,它没有类这个说法,更没有子类父类一说,所以它所谓的继承机制,也是和其他语言完全不同的。
创建对象三种方式
1.最简单的方式,创建一个object对象,然后为它添加属性和方法
1
2
3
4
|
var person= new object();
person.name= "zhangsan" ;
person.age=20; person.sayName= function (){alert( this .name);};
|
2.工厂方式
本质上就是对上述最简单的方式的包装。
1
2
3
4
5
6
7
8
|
function createPerson(name,age)
{ var person= new object();
person.name=name; person.age=age; person.sayName= function (){alert( this .name);};
return person;
} |
缺点是不知道对象的类型。
3 构造函数模式
即定义一个函数,该函数作为类似类中的构造函数,用于生成对象
1
2
3
4
5
6
|
function Person(name,age)
{ this .name=name;
this .age=age;
this .sayName= function (){alert( this .name);};
} |
通过new方式创建对象,var person1=new Person("zhangsan",20);
该方式经历4个步骤
1,创建一个新对象
2,函数的作用域付给力新对象,this就表示了新的对象
3,执行函数中的代码,为新对象设置属性
4,返回新对象。
构造函数也是普通函数,任何函数只要用new操作符调用,都可以成为构造函数。
Person作为普通函数调用的话,this就是window对象。
Person("zhangsan",20);
window.sayName();
缺点是太浪费资源,每个实例中的属性都是不同的,特别是方法不能复用:
var person1=new Person("zhangsan",20);
var person2=new Person("zhangsan",20);
person1.sayName==person2.sayName// false
4,原型模式
每个函数都有一个prototype属性,定义在其中的属性,被各个实例对象所共享。也就是说,实例对象的属性和方法,分成两种,一种是自己定义的,另一种是prototype*享的。 以Person为例子,按原型模式申明类:
1
2
3
4
5
6
|
function Person(){}
Person.prototype.name= "zhangsan" ;
Person.prototype.age=20; Person.prototype.sayName= function (){alert( this .name);};
var person1= new Person();
var person2= new Person();
|
此时person1这的属性和person2中的属性是指向同一个引用。
person1.sayName==person2.sayName// true
理解原型
constructor属性,函数的prototype属性有一个constructor属性,该属性的值默认情况下指向该prototype属性所在的函数。比如:
Person.prototype.constructor
为
function Person(){}
其中__proto__是内部属性,javascript脚本中是没有的,只能通过chrome调试的时候才能看到。所有实例对象的__proto__都指向其构造器的prototype。
person1和person2对象的__proto__内部属性都指向了Person的原型,和Person自身没有任何关系。
当person1的sayName方法调用的时候,实际会执行2次查找,现在person1实例上找sayName,找不到,会去person1指向的原型上去找。
上述可知道,如果在person1上也找到sayName方法,那么原型上的方法就无法被调用到了。
通过方法 hasOwnProperty可以判断一个方法是在实例中还是在原型中。
再次强调,实例中的指针仅指向原型,而不是指向构造函数。
通过快捷写法
function Person(){}
Person.prototype.name="zhangsan";
Person.prototype.age=20;
Person.prototype.sayName=function(){alert(this.name);};
这种写法比较罗嗦
可以用json方式来写:
function Person(){}
Person.prototype={
name:"zhangsan",
age:20,
sayName:function(){alert(this.name);}
}
这种写法比较简洁,但是有一个问题,就是constructor属性的只不再指向Person了,这种语法等于完全重写了默认的prototype,因此constructor属性也被指到Object构造函数了。如果有必要的话,需要把constructor值手动赋值:
1
2
3
4
5
6
|
function Person(){}
Person.prototype={ name: "zhangsan" ,
age:20, sayName: function (){alert( this .name);}
} |
原型模式缺点,就是由于其共享性。它的所有属性是共享的,对于函数来说,求之不得,对于字段属性,这种共享可能会引起大问题。因此,单独使用原型模式很少见。
取长补短
同时使用构造函数模式和原型模式,非常简单,把需要共享的放到原型中,不需要共享的放到构造函数中。
1
2
3
4
5
6
7
8
|
function Person(name,age)
{ this .name=name;
this .age=age;
} Person.prototype={ sayName: function (){alert( this .name);}
} |
这种方式是目前最流行的方式。
还有动态原型模式,寄生构造函数模式,稳妥构造函数某事,不作介绍。
继承(主要两种方法,原型链和call/apply)
原型链,利用原型让一个引用类型继承另一个引用类型的属性和方法。
如果原型对象指向了另一个类型的实例,那么原型对象指向实例-->指向另一个原型对象-->constructor指向所在的函数。
这个就是所谓的原型链。如下图中SuperType和SubType展现的那样,SubType继承了SuperType。
上图中可以看到,原理存在于SuperType的实例中的所有属性和方法,都能在SubType中访问到了。
用代码表示如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
function SuperType() {
this .property = true ;
} SuperType.prototype.getSuperValue = function () {
returnthis.property;
} function SubType() {
this .subproperty = false ;
} //发生继承行为 SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function () {
returnthis.subproperty;
} var instance = new SubType();
alert(instance.getSuperValue()); //true
alert(instance instanceofObject); //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true
alert(Object.prototype.isPrototypeOf(instance)); //true
alert(SuperType.prototype.isPrototypeOf(instance)); //true
alert(SubType.prototype.isPrototypeOf(instance)); //true
|
代码中,重写了SubType的原型,而不是用原来默认的。由于property是一个实例属性,getSuperValue是一个原型方法。所以property会出现在SubType Prototype(SuperType的实例)中,而getSuperValue不会出现。instance的constructor现在只想的SuperType。
还有一个要提及的是,Javascript中,所有的函数默认的原型都是Object的实例,SuperType函数的prototype也是指向Object Prototype。 因此通过这样一个链条访问属性的时候,还是会通过搜索机制顺藤摸瓜的找到对应的属性。
原型链的缺点很明显,所有的子类的属性是共享的。这个缺点是致命的,因此实践中很少单独使用原型链。
call/apply方法这种方法称为constructor stealing(借用构造函数)。
call方法的作用,官方解释 call方法:
语法:call([thisObj[,arg1[, arg2[, [,.argN]]]]]) 定义:调用一个对象的一个方法,以另一个对象替换当前对象。
说明: call 方法可以用来代替另一个对象调用一个方法。call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。 如果没有提供 thisObj 参数,那么 Global 对象被用作 thisObj。
我的理解,就是把原有对象中的代码,放到thisObj中运行,代码中如果出现this,代表的就是thisObj中。
举个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
function Animal(){
this .name = "Animal" ;
this .showName = function (){
alert( this .name);
}
} /**定义一个Cat类*/ function Cat(){
this .name = "Cat" ;
} /**创建两个类对象*/ var animal = new Animal();
var cat = new Cat();
//通过call或apply方法,将原本属于Animal对象的showName()方法交给当前对象cat来使用了。 //输入结果为"Cat" animal.showName.call(cat, "," );
//animal.showName.apply(cat,[]); |
关于call和apply方法的概念,可以自行网上查阅,其中call和apply的差别在于调用时候的参数。 call调用时,参数为用逗号分隔的一组值,而apply调用的时候,参数为是一个数组,一个记忆方式为,C对应comma,A对应Array。 call的主要运用场景就是在面向对象值的模拟继承关系。
例子:
1
2
3
4
5
6
7
8
9
10
11
12
|
function SuperType(){
this .colors = [ "red" , "blue" , "green" ];
} function SubType(){
//继承了SuperType SuperType.call( this );
} var instance1 = new SubType();
instance1.colors.push( "black" );
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green"
|
代码中,通过call方法,在SubType中,借用了SuperType中的函数的代码,以此来为完善自己的属性。这就导致SubType中的实例都有了自己的colors属性。 这种方式可以在子类的构造方法调用中输入参数。
1
2
3
4
5
6
7
8
9
10
11
|
function SuperType(name)
{ this .name= name;
} function SubType(){ //继承了SuperType,并且传递了参数
SuperType.call( this , "zhangsan" );
this .age=20;
} var instance= new SubType();
alert(instance.name) //zhangsan
alert(instance.age) //20
|
当然,这种方式也存在缺点,方法都在构造函数中定义,并没有真正复用。
1
2
3
4
5
6
7
8
9
10
|
function SuperType(){
this .colors = [ "red" , "blue" , "green" ];
this .sayHi= function (){console.log( "Hi" )};
} function SubType(){
//继承了SuperTypeSuperType.call(this); } var instance1 = new SubType();
var instance2 = new SubType();
alert(instance1.sayHi==instance2.sayHi); //false
|
组合继承原型链和call方式都有缺点,但是把两者取长补短,形成组合继承。其思想非常简单,用原型链方式,对需要共享的原型属性和方法实现继承。再通过call方式借用构造函数来实现无需共享的属性的继承。这样即有了共享的属性,也有了不共享的属性。
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
|
function SuperType(name) {
this .name = name;
this .colors = [ "red" , "blue" , "green" ];
} //sayName能够被共享 SuperType.prototype.sayName = function () {
alert( this .name);
}; function SubType(name, age) {
//继承属性
call方式继承 ,name属性不会被共享
SuperType.call( this , name);
this .age = age;
} //继承方法 原型链方式继承 SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function () {
alert( this .age);
}; var instance1 = new SubType( "Nicholas" , 29);
instance1.colors.push( "black" );
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType( "Greg" , 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27
|
可以看到,对于需要共享的属性、方法,采用原型链的方式继承,对于不需要的共享的,比如属性,则用call方法实现继承。这种方式是javascript中最常用的继承方式。 原型式继承 这个方式是json格式的发明人Douglas Crockford所创造。 提出了一个object()函数,可以做到这一点。
1
2
3
4
5
|
function object(o) {
function F() {}
F.prototype = o; return new F();
} |
这个object()函数,借用了一个临时函数,把传入的对象作为父对象,作为临时函数的prototype属性,并返回这个临时函数的一个实例作为子对象,从而使得子对象与父对象连在一起。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
var person = {
name: "Nicholas" ,
friends: [ "Shelby" , "Court" , "Van" ]
}; var anotherPerson = object(person);
anotherPerson.name = "Greg" ;
anotherPerson.friends.push( "Rob" );
var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda" ;
yetAnotherPerson.friends.push( "Barbie" );
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
|
这个例子中,person对象作为父对象,把它传入到object函数中,会返回一个新的对象,该对象的原型就是person,所以它的原型中就包含一个基本类型值属性和一个引用类型值属性。这意味着person.friends 不仅属于person 所有,而且也会被anotherPerson以及yetAnotherPerson 共享。实际上,这就相当于又创建了person 对象的两个副本。
参考资料 《javascript高级程序设计》 http://www.jb51.net/article/63977.htm http://www.iteye.com/topic/599108 http://www.cnblogs.com/sharpxiajun/p/4148932.html http://*.com/questions/9001830/the-reason-to-use-js-call-method
本文转自cnn23711151CTO博客,原文链接:http://blog.51cto.com/cnn237111/1691531 ,如需转载请自行联系原作者