我們常用的兩種編程模式
POP--面向過程編程(Process-oriented programming)
面向過程編程是以功能為中心來進行思考和組織的一種編程方法,它強調的是系統的數據被加工和處理的過程,在程序設計中主要以函數或者過程為程序的基本組織 方式,系統功能是由一組相關的過程和函數序列構成。面向過程強調的是功能(加工),數據僅僅作為輸入和輸出存在。這種過程化的思想是一種很朴素和普遍的思 想和方法,人類很多活動都是這種組織模式,比如工廠生產,企業服務等。面向過程以數據的加工處理過程為主線,忽略了過程的所屬、邊界和環境,混淆了服務功 能和自我功能(比如人可以砍樹,這就是一種服務功能,有輸入也有輸出;它可以提供給外部,而行走,則是自我功能,沒有輸入也沒有輸出),外部環境和內部組 織,以及環境數據和原料性數據之間的區別。從思維上來講,面向過程更強調細節,忽視了整體性和邊界性,但這與現實世界有很大的出入,因為現實世界中,這種過程都不是孤立存在的,而是從屬於某個對象,因此,面向過程雖然反映了現實世界的而一個方面(功能),但無法更加形象的模擬或者表示現實世界。比如如下這種寫法:
function A(){ } function B(){ A(); }
感覺互相之間獨立存在的
OOP--面向對象編程(Object Oriented Programming)
世界是由一個個對象組成的,因此面向對象的思維方式更加接近現實世界,面向對象編程的組織方式也更加貼近現實世界。面向對象以對象為中心,將對象的內部組織與外部環境區分開來,將表征對象的內部屬性數據與外部隔離開來,其行為與屬性構成一個整體,而系統功能則表現為一系列對象之間的相互作用的序列,能更加 形象的模擬或表達現實世界。在編程組織中,對象的屬性與方法不再像面向過程那樣分開存放,而是視為一個整體(程序的最終實現其實還是分離的,但這僅僅是物 理實現上的,不影響將對象的這兩個部分視為一個整體),因此具有更好的封裝性和安全性(表征內部的屬性數據需要通過對象的提供的方法來訪問)。面向對象強 調的是整體性,因此面向對象與面向過程在很多方面是可以互補的。同時由於對象繼承和多態技術的引入,使得面向對象具有更強、更簡潔的對現實世界的表達能 力。從而增強了編程的組織性,重用性和靈活性。比如如下這種寫法:
var obj={ default:{}, config:{}, init:function(){ this.A(); this.B(); }, A:function(){ this.config.name=’A’; }, B:function(){ this.config.name=’B’; } }
這種看起來就有點OO的感覺了,把屬性和方法封裝在一個對象里面。面向對象的開發模式是逐漸流行起來,且被開發者們廣泛推廣的模式。
Javascript是一門基於對象的語言,但它不是一種真正的面向對象編程(OOP)語言,對象的屬性都是以鍵值對的形式存在的,就是平時我們所說的數據字典。把對象的屬性和方法封裝在一個對象里面,一般通過四種方式:原始對象,構造函數,原型模式,構造函數和原型混合模式。我個人認為面向對象的寫法讓系統更具有可維護性,可擴展性,可重用性,還有可配置性,功能模塊也讓人感覺一目了然。
下面來講一下關於javascript對應的OOP的封裝,繼承,多態三大特性
- 對象的封裝
(1)對象字面量或者實例化對象模式
var Obj = { Id: '', Width: '', Height: '', init: function() { }, eventsBind: function() { }, renderView: function() { } }
或者
Var obj=new Object();
Obj.id=’’;….
Obj. renderView….
這種封裝方式比較簡單, 最常用的一種模式,簡潔明了,適用於簡單的封裝.這兩種寫法,推薦字面量的方式。
(2) 構造函數模式
function MyPlugin(name, pwd) { this.name = name; this.pwd = pwd; this.Init = function() { }; this.renderView = function() { }; this.bindEvent = function() { } }
這種方式和C#的構造函數方式類似,每次實例化,所有的元素和方法都被重新創建,重新分配內存,互不影響 ,缺點在於實例化時公有的方法指向不同的地址,造成不必要的浪費,性能欠佳 ,方法應該共享才對,下面的混合模式會講到。
(3)原型prototype
var MyPlugin = function(){ } MyPlugin.prototype = { obj: { name: ’aaaaa’ }, Init: function() { }, renderView: function() { }, bindEvent: function() { } }
這種方式的特點是所有的在原型鏈上的方法都是共享的,並且指向同一個地址。這里需要注意,如果原型鏈上面一個屬性對應的是object對象會有一個問題,就是在一個實例對象里面給這個對象的屬性賦值會影響另一個實例對象.
var a=new MyPlugin();
var b= new MyPlugin();
a.obj.name=’bbbbbbbb’
這樣在b中的obj對象的name值會被改變。原因是這里的obj是引用類型,a.obj和b.obj指向的是同一個地址,如果是值類型則不會存在這樣的問題
(4)構造函數和原型混合模式
這種模式的意義在於實例化的時候保持屬性的相互獨立,只共享方法.在做封裝的時候推薦使用這種方式.剛才我們所說的構造函數的缺點和原型模式的缺點在這里得到改善。
var MyPlugin = function(name) { this.name = name; } MyPlugin.prototype = { Show: function(){ Console.log(this.name); } }
(5) 其他寫法
var MyPlugin = function(config) { var fn1 = function() { } var fn2 = function() { } return { test1: fn1, test2: fn2 } }
或者
var MyPlugin = function(config) { var obj = new Object(); obj.fn1 = function() { } obj.fn2 = function() { } return obj; }
剛才我們提到構造函數模式實例化之后對象的方法地址指向不一樣,而原型模式,地址指向一致的說法。
我們來看一下:
var funcDemo=function (name) { } funcDemo.prototype.init=function(){ } var a=new funcDemo('aaa'); var b=new funcDemo('bbb'); console.log(a.init===b.init);
輸出結果是true.
再來看看構造函數:
var funcDemo=function (name) { this. Init=function(){ } } var a=new funcDemo('aaa'); var b=new funcDemo('bbb'); console.log(a.init===b.init);
輸出結果是false.
- 對象的繼承性
1.call ,apply
var funcA = function() { this.show = function() { console.log('funcA'); } } var funcB = function() { funcA.call(this); } var b = new funcB(); b.show();
在這里 funcA.call(this);這句相當於是在funB的內部里面執行了
this.show=function(){
console.log('funcA');
}
而當前的作用域在funcB內部,this指向的是funB的實例化對象,也就是把show方法賦值給了funcB的實例化對象
有一點需要注意,如果是直接執行funcB(),那么當前作用域就是window了,相當於把show方法賦值給了window。等同於
window.show==function(){ console.log('funcA'); }
2.原型繼承
var funcA = function() { this.show = function() { console.log('funcA'); } } var funcB = function() { } funcB.prototype = new funcA(); var b = new funcB(); b.show();
這一句 funcB.prototype=new funcA();
相當於把funB的原型指向了funcA的實例
等同於
funcB. Prototype={ Show:function(){ console.log('funcA'); } }
我們可以寫一個函數來實現繼承
var extend = function(fn, newfn) { var F = function () { }; F.prototype = fn.prototype; newfn.prototype = new F(); newfn.prototype.constructor = newfn; newfn.prototype.superClass =fn.prototype }
3.屬性拷貝
我們先實現屬性拷貝的代碼:
var DeepCopy = function(newobj, obj) { for(var prop in obj) { if(obj.hasOwnProperty(prop)) { var item = obj[prop]; if(Object.prototype.toString.call(item) == '[object Object]') { newobj[prop] = {}; arguments.callee(newobj[prop], item); } else if(Object.prototype.toString.call(item) == '[object Array]') { newobj[prop] = []; arguments.callee(newobj[prop], item); } else newobj[prop] = item; } } return newobj; }
然后將A對象里的屬性賦給B對象 :
var A = { obj: { name: 'AAAA' }, Arr: [ { name: 'AAAA' }, { name: 'BBBB' } ] } var B = { name: ’BBBBBBBBBB’ } DeepCopy(B, A)
- 對象的多態性
和其他語言一樣 ,先定義一個基類,js不存在類的概念,這里我只是類比的說法.
var baseClass=function(){ this.init=function(){ this.test.apply(this,arguments); } }
這里執行init其實是執行了子類的test方法
我們再定義A類,B類
var A=function(){ this.test=function(){ alert('AAAAAAAAAA'); } } var B=function(){ this.test=function(){ alert('BBBBBBBBBBBBB'); } }
然后將A類,B類的原型指向baseClass的實例,就是我們剛才說的原型繼承
A.prototype=new baseClass(); B.prototype=new baseClass();
最后我們實例化
var a=new A(); a.init(); var b=new B(); b.init();
分別輸出'AAAAAAAAAA','BBBBBBBBBBBBB'
我們所實現的js這種多態和強類型語言的多態感覺有所不同,顯得不那么直觀,因為強類型的語言大都是通過抽象類或者接口聲明方法,然后通過子類實現,調用方法的時候其實是實例化抽象類或者接口,指向子類對象的實例。而我們這里的實現實際上是通過父類聲明方法調用子類對父類聲明方法的實現。
Js 面向對象的這部分東西我也是大致的講了一下,講解的不恰當或者不完善的地方,還請詳細指出。如果有興趣交流學習,請看最上角,加入我的qq技術群207058575.
