這篇文章主要為大家詳細介紹JS物件導向程式設計詳細說明,幫助大家更詳細的對JS物件導向進行學習,有興趣的朋友可以參考一下
序言
在JavaScript的大世界裡討論物件導向,都要提到兩點:1.JavaScript是一門基於原型的物件導向語言2.模擬類別語言的物件導向方式。對於為什麼要模擬類語言的面向對象,我個人認為:某些情況下,原型模式能夠提供一定的便利,但在複雜的應用中,基於原型的面向對象系統在抽象性與繼承性方面差強人意。由於JavaScript是唯一一個由各大瀏覽器支援的腳本語言,所以各路高手不得不使用各種方法來提高語言的便利性,優化的結果就是其編寫的程式碼越來越像類語言中的面向對象方式,從而也掩蓋了JavaScript原型系統的本質。
基於原型的物件導向語言
原型模式如類別模式一樣,都是一種程式設計泛型,也就是程式設計的方法論。另外最近大紅大紫的函數程式設計也是一種程式設計泛型。 JavaScript之父Brendan Eich在設計JavaScript時,從一開始就沒打算為其加入類別的概念,而是藉鑒了另外兩門基於原型的語言:Self和Smalltalk。
既然同為物件導向語言,那就得有創建物件的方法。在類別語言中,物件是基於模板來創建,首先定義一個類別作為對現實世界的抽象,然後由類別來實例化物件;而在原型語言中,物件以克隆另一個物件的方式創建,被複製的母體稱為原型物件。
克隆的關鍵在於語言本身是否為我們提供了原生的克隆方法。在ECMAScript5中,Object.create可以用來複製物件。
var person = { name: "tree", age: 25, say: function(){ console.log("I'm tree.") } }; var cloneTree = Object.create(person); console.log(cloneTree);
原型模式的目的並不在於得到一個一模一樣的對象,而提供了一種便捷的方式去創建對象(出自《JavaScript設計模式與開發實務》)。但由於語言設計的問題,JavaScript的原型存在著許多矛盾,它的某些複雜的語法看起來就那些基於類別的語言,這些語法問題掩蓋了它的原型機制(出自《JavaScript語言精粹》)。如:
function Person(name, age){ this.name = name; this.age = age; } var p = new Person('tree', 25)
實際上,當一個函數物件嗔建立時,Function建構器產生的函式物件會執行類似這樣的一些程式碼:
this.prototype = {constructor: this}
新的函數物件被賦予一個prototype屬性,它的值是一個包含constructor屬性且屬性值為該新函數的物件。當對一個函數使用new運算子時,函數的prototype的屬性的值會被當作原型物件來克隆出新物件。如果new運算子是一個方法,它的執行過程如下:
Function.prorotype.new = function() { //以prototype属性值作为原型对象来克隆出一个新对象 var that = Object.create(this.prorotype); //改变函数中this关键指向这个新克隆的对象 var other = this.apply(that, arguments); //如果返回值不是一个对象,则返回这个新克隆对象 return (other && typeof other === 'object') ? other : that; }
從上面可以看出,雖然使用new運算子呼叫函數看起來像是使用模板實例化的方式來創建對象,但本質還是以原型對象來克隆出新對象。
由於新複製的物件能否存取原型物件的一切方法和屬性,加上new運算子的特性,這便成了利用原型模擬類別語言的基石。
利用原型模擬類別語言
抽象
# 用原型模式來模擬類,首先是抽象方式。根據JavaScript語言的特點,通常一個類別(實際上是偽類別)通常是將欄位放置在
建構函數(實際上是new 運算子呼叫的函數,JavaScript本身並沒有構造函數的概念)中,而將方法放置在函數的prototype屬性裡。
function Person(name, age) {
this.name = name;
this.age = age;
};
Person.prototype.say = function(){
console.log("Hello, I'm " + this.name);
};
继承是OO语言中的一个最为人津津乐道的概念。许多OO语言都支持两种继承方式:接口继承和实现继承。接口继承之继承方法签名,而实现继承则继承实际的方法。但是ECMAScript中无法实现接口继承,只支持实现继承,而且其实现继承主要是依靠原型链来实现的。(出自《JavaScript高级程序设计》 6.3节——继承)在高三中作者探索了各种关于继承的模拟,如:组合继承、原型继承、寄生继承、寄生组合继承,最终寄生组合式成为所有模拟类式继承的基础。
function Person(name, age) { this.name = name; this.age = age; }; Person.prototype.say = function(){ console.log("Hello, I'm " + this.name); }; function Employee(name, age, major) { Person.apply(this, arguments); this.major = major; }; Employee.prototype = Object.create(Person.prototype); Employee.prorotype.constructor = Employee; Employee.prorotype.sayMajor = function(){ console.log(this.major); }
高三中只给出了单继承的解决方案,关于多继承的模拟我们还得自己想办法。由于多继承有其本身的困难:面向对象语言如果支持了多继承的话,都会遇到著名的菱形问题(Diamond Problem)。假设存在一个如左图所示的继承关系,O中有一个方法foo,被A类和B类覆写,但是没有被C类覆写。那么C在调用foo方法的时候,究竟是调用A中的foo,还是调用B中的foo?
所以大多数语言并不支持多继承,如Java支持单继承+接口的形式。JavaScript并不支持接口,要在一个不支持接口的语言上去模拟接口怎么办?答案是著名的鸭式辨型。放到实际代码中就是混入(mixin)。原理很简单:
function mixin(t, s) { for (var p in s) { t[p] = s[p]; } }
值得一提的是dojo利用MRO(方法解析顺序(Method Resolution Order),即查找被调用的方法所在类时的搜索顺序)方式解决了多继承的问题。
到此,我们已经清楚了模拟类语言的基本原理。作为一个爱折腾的程序员,我希望拥有自己的方式来简化类的创建:
最终,在借鉴各位大牛的知识总结,我编写了自己的类创建工具O.js:
(function(global) { var define = global.define; if (define && define.amd) { define([], function(){ return O; }); } else { global.O = O; } function O(){}; O.derive = function(sub) { debugger; var parent = this; sub = sub ? sub : {}; var o = create(parent); var ctor = sub.constructor || function(){};//如何调用父类的构造函数? var statics = sub.statics || {}; var ms = sub.mixins || []; var attrs = sub.attributes || {}; delete sub.constructor; delete sub.mixins; delete sub.statics; delete sub.attributes; //处理继承关系 ctor.prototype = o; ctor.prototype.constructor = ctor; ctor.superClass = parent; //利用DefineProperties方法处理Attributes //for (var p in attrs) { Object.defineProperties(ctor.prototype, attrs); //} //静态属性 mixin(ctor, statics); //混入其他属性和方法,注意这里的属性是所有实例对象都能够访问并且修改的 mixin(ctor.prototype, sub); //以mixin的方式模拟多继承 for (var i = 0, len = ms.length; i < len; i++) { mixin(ctor.prototype, ms[i] || {}); } ctor.derive = parent.derive; //_super函数 ctor.prototype._super = function(f) { debugger; return parent.prototype[f].apply(this, Array.prototype.slice.call(arguments, 1)); } return ctor; } function create(clazz) { var F = function(){}; F.prototype = clazz.prototype; //F.prototype.constructor = F; //不需要 return new F(); }; function mixin(t, s) { for (var p in s) { t[p] = s[p]; } } })(window);
类创建方式如下:
var Person = O.derive({ constructor: function(name) {//构造函数 this.setInfo(name); }, statics: {//静态变量 declaredClass: "Person" }, attributes: {//模拟C#中的属性 Name: { set: function(n) { this.name = n; console.log(this.name); }, get: function() { return this.name + "Attribute"; } } }, share: "asdsaf",//变量位于原型对象上,对所有对象共享 setInfo: function(name) {//方法 this.name = name; } }); var p = new Person('lzz'); console.log(p.Name);//lzzAttribute console.log(Person);
继承:
var Employee = Person.derive({//子类有父类派生 constructor: function(name, age) { this.setInfo(name, age); }, statics: { declaredClass: "Employee" }, setInfo: function(name, age) { this._super('setInfo', name);//调用父类同名方法 this.age = age; } }); var e = new Employee('lll', 25); console.log(e.Name);//lllAttribute console.log(Employee);
以上就是本文的全部内容,希望对大家的学习有所帮助。
以上是JS物件導向程式設計詳細說明的詳細內容。更多資訊請關注PHP中文網其他相關文章!