[基础]JavaScript中的面向对象(个人学习札记) 三
[基础]JavaScript中的面向对象(个人学习笔记) 三
前面的两篇已经介绍了对象的定义和类的实现方法,现在开始学习面向对象中一个很重要的特性:继承。要用ECMAScript实现继承机制,首先从基类入手。所有开发者定义的类都可以作为基类,但出于安全原因的考虑,本地类和宿主类不作为基类,防止这些代码被恶意攻击。
虽然在ECMAScript中没有java中那样严格定义的抽象类,但有时也可以创建一些不允许访问的类,作为ECMAScript中的抽象类。子类可以像java中那样继承超类的所有属性和方法,包括构造函数及方法实现;子类也可以覆盖超类中的属性和方法,添加超类中没有的属性和方法。
实现继承的方式多种多样,就像定义一个类一样,开发者可以自由的选择,下面还是从实现继承的各种方式说起。
1、对象冒充
构造原始的ECMAScript时,根本没有打算设计对象冒充(object masquerading),它是在开发者理解函数的工作方式,尤其是如何在函数环境中使用this关键字后发展出来的。
其基本原理如下:构造函数使用this关键字给所有属性和方法赋值(即采用类声明的构构造函数方式)。因为构造函数只是一个函数,所以可以使Person的构造函数成为Student的方法,然后调用它。Student就会收到Person的构造函数中定义的属性和方法。例如:
这段代码中,为Person赋予了方法newMethod(指向类Person构造函数的指针)。然后用Student中的构造函数调用该方法,并将参数sName传递给newMethod。其后的一行代码删除了对Person的引用,这样以后就不能调用它。
*所有新的属性和方法都必须在删除了新方法的代码行后定义,否则可能覆盖超类中的相关属性和方法。
利用对象冒充可以做一件比较有趣的事情:多重继承。例如,如果存在两个类ClassX和ClassY,ClassZ要继承这两个类,可以使用以下的代码实现:
回想一下第一篇中说的javascript中没有重载的原因,可以看到这其中存在一个弊端,如果ClassX和ClassY含有相同的属性或者方法,那么后者ClassY具有更高的优先级。
2、call()方法
call()方法是与经典的对象冒充方法最相似的方法。它的第一个参数用作this对象。其他参数直接传递给函数自身。例如:
在这个例子中,函数sayInfo()在对象外定义,即使它不属于任何对象,也可以引用关键字this。根据call()方法的作用,可以知道p1就是this指向的对象,所以p1将name属性传递给了函数,后面的两个参数则分别对应了函数的参数列表,最后可以在页面中显示:My name is yzl,my job is J2EE Developer,sex is male。
根据call()方法的作用,可以将对象冒充中的类Student进行改写,修改代码如下:
代码中想让对象Person中的this关键字指向对象Student,因此call()方法的第一个参数选择了this。
3、apply()方法
apply()方法就像call()方法一样有两个参数,第一个参数依然是this指向的对象,但是第二个参数是要传递给函数的参数的数组,依然采用上面的函数定义的例子:
回想第二篇结尾说的arguments,所以我们在使用apply()方法时还可以将arguments直接当作第二个参数传递给超类,继续修改前面的例子:
使用这种方式时,必须注意超类的参数顺序和子类的参数顺序,最好是它们的参数顺序完全一致,否则必须安排好参数的顺序。
4、原型链
继承这种形式在ECMAScript中原本是用于原型链的。上一篇中介绍了定义类的原型方式,原型链则在它的基础上进行了扩展实现继承。基本的原理是:prototype对象是一个模板,要实例化的对象都以这个模板为基础,prototype对象的任何属性和方法都被传递给那个类的实例。
用原型链方式重新修改前面的代码为:
同时在原型链中instanceof运算符的方式也很特别。对于Student的实例,instanceof类Person或Student都放回true。
*调用超类的构造函数时,没有给它传递参数,这在原型链中是标准做法。要确保构造函数没有任何参数。
*子类的所有属性和方法必须出现在prototype被赋值之后,因为在它之前的所有属性和方法都会被覆盖。
*原型链不支持多重继承。
5、混合方式
在上一篇中已经说过创建类的最好方式是用构造函数方式定义属性,用原型方法定义方法。这种方式同样适用于继承机制,用对象冒充继承构造函数属性,用原型链继承prototype对象的方法。用混合方式改写前面的代码:
这三篇javascript面向对象编程的总结让我从根本上了解了实现javascript中的对象和类;了解了多种类的实现方式;也知道了继承的基本原理和方法,为以后的javascript编程打下了一个不错的基础。写到这里总算完了,以后如果学习到了新的东西我也会放到博客中,希望大家看了能够给一些建议,帮我纠正一些错误,谢谢。
前面的两篇已经介绍了对象的定义和类的实现方法,现在开始学习面向对象中一个很重要的特性:继承。要用ECMAScript实现继承机制,首先从基类入手。所有开发者定义的类都可以作为基类,但出于安全原因的考虑,本地类和宿主类不作为基类,防止这些代码被恶意攻击。
虽然在ECMAScript中没有java中那样严格定义的抽象类,但有时也可以创建一些不允许访问的类,作为ECMAScript中的抽象类。子类可以像java中那样继承超类的所有属性和方法,包括构造函数及方法实现;子类也可以覆盖超类中的属性和方法,添加超类中没有的属性和方法。
实现继承的方式多种多样,就像定义一个类一样,开发者可以自由的选择,下面还是从实现继承的各种方式说起。
1、对象冒充
构造原始的ECMAScript时,根本没有打算设计对象冒充(object masquerading),它是在开发者理解函数的工作方式,尤其是如何在函数环境中使用this关键字后发展出来的。
其基本原理如下:构造函数使用this关键字给所有属性和方法赋值(即采用类声明的构构造函数方式)。因为构造函数只是一个函数,所以可以使Person的构造函数成为Student的方法,然后调用它。Student就会收到Person的构造函数中定义的属性和方法。例如:
function Person(sName){ this.name = sName; this.sayName = function(){ alert("name is " + this.name); }; } function Student(sName,sSubject){ this.newMethod = Person; this.newMethod(sName); delete this.newMethod; this.subject = sSubject; this.saySubject = function(){ alert("Subject is " + this.subject); }; } var s1 = new Student("yzl","computer science"); s1.sayName(); s1.saySubject();
这段代码中,为Person赋予了方法newMethod(指向类Person构造函数的指针)。然后用Student中的构造函数调用该方法,并将参数sName传递给newMethod。其后的一行代码删除了对Person的引用,这样以后就不能调用它。
*所有新的属性和方法都必须在删除了新方法的代码行后定义,否则可能覆盖超类中的相关属性和方法。
利用对象冒充可以做一件比较有趣的事情:多重继承。例如,如果存在两个类ClassX和ClassY,ClassZ要继承这两个类,可以使用以下的代码实现:
function ClassZ(){ this.newMethod = ClassX; this.newMethod(); delete this.newMethod; this.newMethod = ClassY; this.newMethod(); delete this.newMethod; }
回想一下第一篇中说的javascript中没有重载的原因,可以看到这其中存在一个弊端,如果ClassX和ClassY含有相同的属性或者方法,那么后者ClassY具有更高的优先级。
2、call()方法
call()方法是与经典的对象冒充方法最相似的方法。它的第一个参数用作this对象。其他参数直接传递给函数自身。例如:
function sayInfo(sJob,sSex){ alert("My name is " + this.name + ", my job is " + sJob + ", sex is " + sSex); } var p1 = new Object; p1.name = "yzl"; sayInfo.call(p1,"J2EE Developer","male"); // output: My name is yzl,my job is J2EE Developer,sex is male
在这个例子中,函数sayInfo()在对象外定义,即使它不属于任何对象,也可以引用关键字this。根据call()方法的作用,可以知道p1就是this指向的对象,所以p1将name属性传递给了函数,后面的两个参数则分别对应了函数的参数列表,最后可以在页面中显示:My name is yzl,my job is J2EE Developer,sex is male。
根据call()方法的作用,可以将对象冒充中的类Student进行改写,修改代码如下:
function Person(sName){ this.name = sName; this.sayName = function(){ alert("name is " + this.name); }; } function Student(sName,sSubject){ //this.newMethod = Person; //this.newMethod(sName); //delete this.newMethod; [color=red]Person.call(this,sName);[/color] this.subject = sSubject; this.saySubject = function(){ alert("Subject is " + this.subject); }; } var s1 = new Student("yzl","computer science"); s1.sayName(); s1.saySubject();
代码中想让对象Person中的this关键字指向对象Student,因此call()方法的第一个参数选择了this。
3、apply()方法
apply()方法就像call()方法一样有两个参数,第一个参数依然是this指向的对象,但是第二个参数是要传递给函数的参数的数组,依然采用上面的函数定义的例子:
function sayInfo(sJob,sSex){ alert("My name is " + this.name + ", my job is " + sJob + ", sex is " + sSex); } var p1 = new Object; p1.name = "yzl"; sayInfo.apply(p1,new Array("J2EE Developer","male")); // output: My name is yzl,my job is J2EE Developer,sex is male
回想第二篇结尾说的arguments,所以我们在使用apply()方法时还可以将arguments直接当作第二个参数传递给超类,继续修改前面的例子:
function Person(sName){ this.name = sName; this.sayName = function(){ alert("name is " + this.name); }; } function Student(sName,sSubject){ //this.newMethod = Person; //this.newMethod(sName); //delete this.newMethod; //Person.call(this,sName); Person.apply(this.arguments); this.subject = sSubject; this.saySubject = function(){ alert("Subject is " + this.subject); }; } var s1 = new Student("yzl","computer science"); s1.sayName(); s1.saySubject();
使用这种方式时,必须注意超类的参数顺序和子类的参数顺序,最好是它们的参数顺序完全一致,否则必须安排好参数的顺序。
4、原型链
继承这种形式在ECMAScript中原本是用于原型链的。上一篇中介绍了定义类的原型方式,原型链则在它的基础上进行了扩展实现继承。基本的原理是:prototype对象是一个模板,要实例化的对象都以这个模板为基础,prototype对象的任何属性和方法都被传递给那个类的实例。
用原型链方式重新修改前面的代码为:
function Person(){ } Person.prototype.name = "yzl"; Person.prototype.sayName = function(){ alert("name is " + this.name); }; function Student(){ } Student.prototype = new Person(); Student.prototype.subject = "computer science"; Student.prototype.saySubject = function(){ alert("Subject is " + this.subject); } var s1 = new Student(); s1.sayName(); s1.saySubject();
同时在原型链中instanceof运算符的方式也很特别。对于Student的实例,instanceof类Person或Student都放回true。
alert(s1 instanceof Person); alert(s1 instanceof Student);
*调用超类的构造函数时,没有给它传递参数,这在原型链中是标准做法。要确保构造函数没有任何参数。
*子类的所有属性和方法必须出现在prototype被赋值之后,因为在它之前的所有属性和方法都会被覆盖。
*原型链不支持多重继承。
5、混合方式
在上一篇中已经说过创建类的最好方式是用构造函数方式定义属性,用原型方法定义方法。这种方式同样适用于继承机制,用对象冒充继承构造函数属性,用原型链继承prototype对象的方法。用混合方式改写前面的代码:
function Person(sName){ this.name = sName; } Person.prototype.sayName = function(){ alert("name is " + this.name); }; function Student(sName,sSubject){ Person.call(this,sName); this.subject = sSubject; } Student.prototype = new Person(); Student.prototype.saySubject = function(){ alert("Subject is " + this.subject); } var s1 = new Student("yzl","computer science"); s1.sayName(); s1.saySubject();
这三篇javascript面向对象编程的总结让我从根本上了解了实现javascript中的对象和类;了解了多种类的实现方式;也知道了继承的基本原理和方法,为以后的javascript编程打下了一个不错的基础。写到这里总算完了,以后如果学习到了新的东西我也会放到博客中,希望大家看了能够给一些建议,帮我纠正一些错误,谢谢。