JavaScript是一种面向对象的脚本语言,但是JavaScript中的对象与其他语言(尤其是像Java、C#这样的静态语言)有很大的不同,JavaScript中的对象是基于原型的。原型是对象的基础,它定义并实现了一个新对象所必须包含的成员列表,并被所有同类对象实例所共享。与其他语言中类的概念相比,原型更像是类的静态成员。本文就JavaScript中对象的创建、继承做初步的讲解,并探讨下对象成员相关的一些概念和特性。(斑头雁原创:http://bantouyan.iteye.com)
一、创建对象
在JavaScript使用new操作符中创建对象,或者用直接量创建对象。与其他语言不同,JavaScript对象在完成创建后仍能增加成员,比如如下的代码:
var obj = new Object();
obj.name = 'alice';
obj.age = 18;
obj.getSalary = function(){return 8000 - this.age;};
这段代码首先创建了一个Object类的对象,然后增加了三个成员。除了用new操作符创建对象外,我们还可以用直接量表示一个对象,如下面的代码:
var obj = {
name: 'alice',
age: 18,
getSalary: function(){return 8000 - this.age;}
};
这段代码创建了一个对象,与前面代码创建的对象一样,但是相比较起来,用直接量方式更加简单明了。(斑头雁原创:http://bantouyan.iteye.com)
二、创建类
除了可以使用JavaScript内建的类外,我们还可以定义自己的类,定义类的方法一般有工厂方法、构造函数、原型方法和混合方法。
1、工厂方法
创建对象的传统方式是首先创建一个Object实例,然后增加成员属性,如果把这些代码用一个函数封装起来并返回所创建的对象,那这个函数就是创建对象的工厂函数。使用工厂函数的示例代码如下:
function pgetSalary() {return 8000 - this.age;}
function Employee(name, age){
var obj = new Object();
obj.name = name;
obj.age = age;
obj.getSalary = pgetSalary;
return obj;
}
var obja = Employee('alice', 18);
var objb = new Employee('cindy', 20, true);
alert(obja instanceof Employee); //output false
采用工厂方式可以方便的创建大批量类似对象,但是工厂函数的名字并不是类的名字,虽然可以用new操作符,但仍无法使用instanceof来判断所创建的对象的类型。另外,如果把成员函数的函数体定义在工厂函数的内部的话,创建对象时会重复的创建成员函数实例(在JavaScript中函数也是一种对象),浪费存储空间。(斑头雁原创:http://bantouyan.iteye.com)
2、构造函数方法
构造函数类似工厂函数,所不同的是构造函数的名字就是类名,可以用new操作符来创建对象。
function pgetSalary() {return 8000 - this.age;}
function Employee(name, age){
this.name = name;
this.age = age;
this.getSalary = pgetSalary;
}
var obja = new Employee('alice', 18);
var objb = new Employee('cindy', 19);
alert(obja instanceof Employee); //output true
同工厂方式一样,如果类有成员函数,最好定义在构造函数意外,否则会因成员函数的重复创建而浪费空间。用构造函数创建对象的好处是所生成的对象可以用instanceof来判断所属的类。(斑头雁原创:http://bantouyan.iteye.com)
3、原型方法
原型是对象的模板,所以我们可以用prototype属性来定义一个类,通常的做法是定义一个空的构造函数,然后在prototype属性上定义类的成员,示例代码如下:
function Employee(){
}
Employee.prototype.name = 'alice';
Employee.prototype.age = 18;
Employee.prototype.getSalary = function() {return 8000 - this.age;};
Employee.prototype.languages = ['Java', 'C#'];
var obja = new Employee();
var objb = new Employee();
alert(obja instanceof Employee); //output true
alert(obja.languages); //output Java,C#
alert(objb.languages); //output Java,C#
objb.languages.push('perl');
var objc = new Employee();
alert(obja.languages); //output Java,C#,perl
alert(objb.languages); //output Java,C#,perl
alert(objc.languages); //output Java,C#,perl
原型方式同构造函数方式一样可以用instanceof判断所创建的对象的类型,但是,原型方式的构造函数不能带任何参数,除此之外,如果某个成员是一个对象实例,那么对这个成员的修改可能会影响到这个类创建的其他对象以及在此之后创建的对象。如示例代码中仅修改了对象objb的languages属性,而在此之前创建的obja与在此之后创建的objc的languages属性也受到了影响。(斑头雁原创:http://bantouyan.iteye.com)
4、混合方式(构造函数+原型)
原型方式的构造函数不能带有参数,这样的类在使用中显得很不方便,因此,把原型方式与构造函数综合起来使用是一种更为实用的方法,示例代码如下:
function Employee(name, age){
this.name = name;
this.age = age;
this.languages = ['Java', 'C#'];
}
Employee.prototype.getSalary = function(){return 8000 - this.age;};
var obja = new Employee('alice', 18);
var objb = new Employee('cindy', 20);
alert(obja.languages); //output Java,C#
alert(objb.languages); //output Java,C#
objb.languages.push('perl');
var objc = new Employee();
alert(obja.languages); //output Java,C#
alert(objb.languages); //output Java,C#,perl
alert(objc.languages); //output Java,C#
混合方式利用构造函数定义非函数成员,用prototype定义函数成员,既避免了因重复创建成员函数造成的空间浪费,又不会造成对象实例间的相互影响(如示例代码中修改objb的languages成员不会影响到obja和objc),是一种比较好的定义类的方式。(斑头雁原创:http://bantouyan.iteye.com)
三、类的继承
JavaScript根本就没有继承类的机制,不过借助JavaScript的一些语言特性,我们仍然可以模拟这种行为,常用的方法有对象冒充、原型链和混合方式。(斑头雁原创:http://bantouyan.iteye.com)
1、对象冒充
JavaScript函数中this会根据函数的调用上下文的不同指向不同的对象,根据这个特性,开发者总结出了对象冒充方式的继承方法,示例代码如下:
function Employee(name, age) {
this.name = name;
this.age = age;
this.languages = ['Java', 'C#'];
}
Employee.prototype.getSalary = function(){return 8000 - this.age;};
function Manager(name, age, level) {
Employee.call(this, name, age);
//Employee.apply(this, [name, age]);
this.level = level;
}
var ma = new Manager('alice', 18, 1);
alert(ma instanceof Employee); //output false
alert(ma instanceof Manager); //output true
alert(ma.name); //output alice
alert('getSalary' in ma); //output false
Fucntion.call()与Fucntion.apply()会执行函数Function本身,只不这两个方法的第一个参数都是用来代替Function内部的this,这两个函数所不同的地方在于call把Function的参数顺序放在第一个参数之后,apply把Function的参数按顺序组成一个数组作为第二个参数调用。
在示例代码中,构造函数Manager调用了Employee的call方法,所以执行嵌套的Employee时内部的this指向的是Manager的this,这样用Manager创建的对象就有了Employee的成员。
对象冒充方式继承有一个好处就是可以实现多继承,像下面的代码就可以实现多继承。
function ClassC(){
ClassA.call(this);
ClassB.call(this);
}
如果ClassA与ClassB有相同名字的成员,那么会因为调用顺序导致ClassB的成员定义会覆盖ClassA的成员定义,这在使用时要加以注意。
对象冒充继承方式的问题在于它无法使用instanceof来判断对象是不是超类的实例,另外也无法继承定义在超类prototype属性上的成员(如示例代码中的成员函数getSalary么有被类Manager继承)。(斑头雁原创:http://bantouyan.iteye.com)
2、原型链
原型链继承与用原型方式创建类一样,方法是构造一个空构造函数,然后用超类的实例代替子类的prototype,示例代码如下:
function Employee(name, age) {
this.name = name;
this.age = age;
this.languages = ['Java', 'C#'];
}
Employee.prototype.getSalary = function(){return 8000 - this.age;};
function Manager(){
}
Manager.prototype = new Employee();
var ma = new Manager();
alert(ma instanceof Employee); //output true
alert(ma instanceof Manager); //output true
alert('name' in ma); //output true
alert('getSalary' in ma); //output true
原型链方式的优点是可以继承超类的所有成员,包括定义在超类prototype上的成员,而且可以用instanceof检测是不是超类的实例,缺点是构造函数不能带参数。(斑头雁原创:http://bantouyan.iteye.com)
3、混合方式(对象冒充+原型链)
如同创建类时可以把构造函数与原型综合在一起一样,继承时也可以把对象冒充方法与原型链综合起来使用,示例代码如下:
function Employee(name, age) {
this.name = name;
this.age = age;
this.languages = ['Java', 'C#'];
}
Employee.prototype.getSalary = function(){return 8000 - this.age;};
function Manager(name, age, level) {
Employee.call(this, name, age);
//Employee.apply(this, [name, age]);
this.level = level;
}
Manager.prototype = new Employee();
var ma = new Manager();
alert(ma instanceof Employee); //output true
alert(ma instanceof Manager); //output true
alert('name' in ma); //output true
alert('getSalary' in ma); //output true
混合方式既可以继承超类所有的成员,又可以使用带参数的构造函数,还可以用instanceof检测对象是不是超类的实例,可以说既综合了对象冒充与原型链的好处,又回避了他们的缺点,是一种比较实用的继承方法。
四、类的原型
在JavaScript中不光可以随时给对象添加成员,而且可以随时修改已有的类的定义,并能影响到该类所有的实例(包括已经创建的和未创建的),这要归功于JavaScript的原型机制。原型规定了类所必须具有的成员列表,而且,原型上的这些成员与成员的值还会被该类所有的实例(包括子类的实例)共享。(斑头雁原创:http://bantouyan.iteye.com)
JavaScript对象的成员分为两种,即实例成员和原型成员。实例成员一般是在构造函数中通过给this变量添加的成员,或给已创建好的对象添加的成员,一个对象的实例成员与其他对象无关,即使是同一个类的实例,也可能有不同的实例成员。原型成员都是通过类的prototype属性定义的,它的名字与值被类的所有实例共享,看下面的代码:
function ClassA(){
this.im = "instance_member";
}
ClassA.prototype.pm = [1, 2, 3];
var obja = new ClassA();
alert(obja.pm); //output 1,2,3
var objb = new ClassA();
alert(objb.pm); //output 1,2,3
ClassA.prototype.foo = function() {alsert(this.im);};
objb.pm.push(4);
var objc = new ClassA();
alert(obja.pm); //output 1,2,3,4
alert(objb.pm); //output 1,2,3,4
alert(objc.pm); //output 1,2,3,4
obja.foo(); //output instance_member
objb.foo(); //output instance_member
objc.foo(); //output instance_member
我们仅仅修改了对象objb的原型成员pm,然而在此之前创建的obja与在此之后创建的objc的成员都受到了影响,不光如此,我们还可以随时修改类,如代码中创建objb后我们给ClassA的原型添加了一个新的成员foo,在此之前创建的obja、objb和再次之后创建的objc就都具有了新成员函数foo。
在获取对象的成员时,JavaScript首先查找对象的实例成员中有没有同名的成员,如果没有则查找原型成员,如果在原型中仍找不到的话就继续查找原型的原型,直到找到或者搜索完整个原型链为止。对成员进行赋值操作时,如果对象已经有一个同名的实例成员,则修改这个实例成员的值,否则将创建一个新的实例成员(即使存在同名的原型成员)。所以如果有一个实例成员的名字与某个原型成员的名字相同,那么这个原型成员将被屏蔽,看下面的代码:
function ClassA(){
this.im = "instance_member";
}
ClassA.prototype.pm = [1, 2, 3];
var obja = new ClassA();
obja.pm = 'alice';
var objb = new ClassA();
alert(obja.pm); //output alice
alert(objb.pm); //output 1,2,3,4
我们创建对象obja后给obja.pm进行赋值操作,尽管ClassA存在原型成员pm,JavaScript仍然给obja创建了一个同名的实例成员,在获取成员pm时,由于首先查找obja的实例成员,所以同名的原型成员被屏蔽,而objb没有这样的实例成员,所以得到的仍是原型成员的值。
我们可以用for-in循环遍历对象所有的成员,如果要检测对象是否具有某个成员,就要依赖JavaScript提供两种方法,一种是in操作符,另外一种是hasOwnProperty()方法。这两种方法的区别是如果hasOwnProperty()返回false,则肯定不是对象的实例成员,如果in操作符返回false,则肯定不是对象的成员。结合前面提到的继承方法,我们看一下对象成员的继承关系,示例代码如下:(斑头雁原创:http://bantouyan.iteye.com)
function ClassA(){
this.am = 'am';
}
ClassA.prototype.ap = 'ap';
function ClassB1(){
ClassA.call(this);
this.bm = 'bm';
}
ClassB1.prototype.bp = 'bp';
function ClassB2(){
this.bm = 'bm';
}
ClassB2.prototype = new ClassA();
ClassB1.prototype.bp = 'bp';
function ClassB3(){
ClassA.call(this);
this.bm = 'bm';
}
ClassB3.prototype = new ClassA();
ClassB1.prototype.bp = 'bp';
var b1 = new ClassB1();
alert(b1.hasOwnProperty('am')); //output true
alert('am' in b1); //output true
alert(b1.hasOwnProperty('ap')); //output false
alert('ap' in b1); //output false
var b2 = new ClassB2();
alert(b2.hasOwnProperty('am')); //output false
alert('am' in b2); //output true
alert(b2.hasOwnProperty('ap')); //output false
alert('ap' in b2); //output true
var b3 = new ClassB3();
alert(b3.hasOwnProperty('am')); //output true
alert('am' in b3); //output true
alert(b3.hasOwnProperty('ap')); //output false
alert('ap' in b3); //output true
从示例代码的运行结果我们可以看到,采用对象冒充方式继承,超类的实例成员会被继承为子类的实例成员,超类的原型成员则不会被继承,因此'ap' in b1将返回false;采用原型链方式继承,超类的实例成员与原型成员都会被继承为子类的原型成员;采用混合方式继承,超类的实例成员会被继承为子类的实例成员,超类的原型成员会被继承为子类的原型成员。
实际上类的prototype也是一个对象,我们用for-in循环来遍历下这些类的prototype属性,代码如下:(斑头雁原创:http://bantouyan.iteye.com)
function ClassA(){
this.am = 'am';
}
ClassA.prototype.ap = 'ap';
function ClassB1(){
ClassA.call(this);
this.bm = 'bm';
}
ClassB1.prototype.bp = 'bp';
function ClassB2(){
this.bm = 'bm';
}
ClassB2.prototype = new ClassA();
ClassB2.prototype.bp = 'bp';
function ClassB3(){
ClassA.call(this);
this.bm = 'bm';
}
ClassB3.prototype = new ClassA();
ClassB3.prototype.bp = 'bp';
var str = "ClassB1\n";
var prot = ClassB1.prototype;
for(var p in prot)
{
str += p + ': ' + prot.hasOwnProperty(p) + ' ' + (p in prot) + "\n";
}
alert(str);
//output ClassB1
//output bp: true true
var str = "ClassB2\n";
var prot = ClassB2.prototype;
for(var p in prot)
{
str += p + ': ' + prot.hasOwnProperty(p) + ' ' + (p in prot) + "\n";
}
alert(str);
//output ClassB2
//output am: true true
//output ap: false true
//output bp: true true
var str = "ClassB3\n";
var prot = ClassB3.prototype;
for(var p in prot)
{
str += p + ': ' + prot.hasOwnProperty(p) + ' ' + (p in prot) + "\n";
}
alert(str);
//output ClassB3
//output am: true true
//output ap: false true
//output bp: true true
从这段代码的运行结果我们可以看出,采用对象冒充方式继承时,子类的原型没有任何从超类继承来的成员;采用原型链方式继承时,超类的实例成员继承为子类原型的实例成员,超类的原型成员继承为子类原型的原型成员;采用混合方式继承时,超类的实例成员继承为子类原型的实例成员,超类的原型成员继承为子类原型的原型成员。综合两段代码我们可以看出,在采用混合方式继承时,对于超类的每一个实例成员,子类都有一个对应的实例成员和一个对应的原型成员,不过原型成员会被实例成员屏蔽。(斑头雁原创:http://bantouyan.iteye.com)
类的原型仍然具有原型成员,说明原型之上还有原型,这些相关联的原型就组成了原型链。前文说过,查找对象的成员时,会从实例成员开始,顺着原型链向上查找,如果一个成员在原型链上的位置越靠上,那么需要查找的操作就越多,花费的代价就越大。不过目前比较新的浏览器都做了这方面的优化,不用再关心这类问题。(斑头雁原创:http://bantouyan.iteye.com)
分享到:
相关推荐
浅析JavaScript实现基于原型对象的“继承” 本文旨在对JavaScript实现基于原型对象的“继承”进行深入分析,并与基于类的继承进行比较。通过对JavaScript的原型继承机制的介绍和实例分析,提出一个改进的“寄生组合...
总的来说,JavaScript中的面向对象编程与传统的基于类的OOP有所不同,它通过原型链和构造函数来实现对象的创建和继承。正确理解并运用这些概念,可以让开发者更有效地利用JavaScript进行面向对象的编程。
- 每个JavaScript对象都有一个内部的`[[Prototype]]`属性,该属性链接到当前对象的原型对象。 - 当尝试访问一个对象的属性或方法时,如果该对象本身没有这个属性或方法,则会沿着`[[Prototype]]`链向上查找,直到...
JavaScript是一种支持两种主要编程范式的语言:函数...总之,JavaScript的面向对象编程依赖于原型机制,通过构造函数和原型链实现了对象的创建、继承和属性/方法的共享。理解这些概念对于深入学习JavaScript至关重要。
每个JavaScript对象都有一个内部的[[Prototype]]属性,通常可以通过`__proto__`或`Object.getPrototypeOf`访问。当试图访问对象的一个属性时,如果该属性不存在于当前对象,JavaScript会向上搜索原型链,直到找到该...
这个文件可能包含了一些示例代码,用于演示上述的JavaScript对象创建、类定义和继承的实践。通过查看和运行这些代码,你可以更好地理解这些概念并应用到实际项目中。 总结来说,JavaScript对象创建涉及字面量、构造...
总的来说,`zInherit`是JavaScript对象继承的一种实现,它利用原型链实现继承关系,使得子类可以继承和扩展父类的属性和方法。理解并熟练掌握这种继承方式,对于深入理解JavaScript的OOP特性以及编写高效的代码至关...
### 探索JavaScript的原型链:原型继承的奥秘 ...总结来说,JavaScript的原型继承机制为开发者提供了强大的工具,用于创建灵活、可扩展的对象模型。掌握原型继承的概念对于高效使用JavaScript至关重要。
这样,当创建新对象时,这些对象可以继承原型对象的功能,从而实现代码的复用和减少冗余。原型为 JavaScript 提供了类似于其他面向对象语言的继承特性。 四、原型链 原型之间的关系形成了一个被称为“原型链”的...
在利用构造函数创建对象时,每个对象都默认与这个原型对象连接,连接后就可以访问到原型对象中的属性和方法 6.6.1 原型 2、作用 利用原型对象可以保存一些公共的属性和方法。当访问某个对象中的一个不存在的属性或...
JavaScript是一种动态类型的脚本语言,最初常常用于网页交互,但随着时间的发展,其应用范围已经扩展到服务器端开发、...在实际开发中,开发者应根据需求选择合适的对象创建和继承方式,以实现高效且易于维护的代码。
本文将深入探讨 JavaScript 中创建对象及对象继承的各种有效策略,特别是如何利用构造函数模式、原型模式以及寄生组合式继承等技术来构建灵活、高效的应用程序。 #### 二、JavaScript 对象与原型 在 JavaScript 中...
它通过在对象创建之后对其进行增强,以实现继承。通常是创建一个仅用于封装继承过程的函数,该函数内部以某种方式来增强对象,最后返回对象。 优点: - 可以在不影响其他对象的情况下,对对象进行功能扩展。 缺点...
在深入探讨JavaScript的原型继承之前,首先要明确的是JavaScript中并没有类似其他编程语言中的类继承的概念。虽然有传言JavaScript 2.0将加入类继承...掌握原型继承对于深入理解JavaScript对象模型和设计模式至关重要。
原型模式是另一种在JavaScript中实现对象创建的方式。每个函数都有一个原型属性(prototype),它指向一个原型对象,所有通过该函数创建的对象都会共享原型对象上的属性和方法。这种方式的优点在于节省内存,因为它...
JavaScript中的原型对象、this的五种用法、原型继承以及Caller和Callee的使用是JavaScript编程中的核心概念。首先,让我们深入理解每个概念。 **原型对象(Prototype)** 在JavaScript中,每当定义一个函数,都会...
JavaScript的原型继承是一种非常灵活的继承机制,它允许对象直接继承另一个对象的属性和方法。 ##### **3.1 原型继承的实现** 实现原型继承的基本思路是让一个对象的`__proto__`属性指向另一个对象。例如: ```...
首先,每个JavaScript对象都有一个内部属性`[[Prototype]]`,通常我们通过`__proto__`或`Object.getPrototypeOf`来访问。这个属性引用了创建当前对象的构造函数的原型对象。原型对象本身也是一个对象,因此它也有...