面向對象編程-基礎(工廠、構造函數、原型、混合、動態等模式)

来源:http://www.cnblogs.com/shouce/archive/2016/03/16/5282019.html
-Advertisement-
Play Games

(1)、工廠模式:封裝一個函數createPerson,這個函數可以創造一個人對象,包含三個屬性和一個方法,然後利用這個函數分別創建了2個對象p1,p2. 工廠模式下解決了創建多個相似對象的問題,但是卻沒有解決對象識別問題(不知道這個對象的類型是數組或函數或正則等)alert(p1 instance


(1)、工廠模式:
封裝一個函數createPerson,這個函數可以創造一個人對象,包含三個屬性和一個方法,然後利用這個函數分別創建了2個對象p1,p2.

複製代碼
function createPerson(name,age,job){
                        var p=new Object();

                        p.name=name;

                        p.age=age;

                        p.job=job;

                        p.showName=function(){

                                alert(this.name);

                        };

                        return p;

                }

                var p1=createPerson('jxj1',24,'student');

                var p2=createPerson('jxj2',25,'teacher');
複製代碼

 

工廠模式下解決了創建多個相似對象的問題,但是卻沒有解決對象識別問題(不知道這個對象的類型是數組或函數或正則等)
alert(p1 instanceof Object); //true
alert(p1 instanceof createPerson); //false
alert(p2 instanceof Object); //true
alert(p2 instanceof createPerson); //false

(2)、構造函數模式
創建一個構造函數,習慣上構造函數的首字母大寫,非構造函數第一個字母小寫,同樣也包含三個屬性和一個方法,然後利用這個函數分別創建了2個實例對象p1,p2.(構造函數也可以當作普通函數來使用,只有使用了new來調用,才作為構造函數使用)
我們先用構造函數從寫上面工廠模式下的函數

複製代碼
function Person(name,age,job){
                        this.name=name;

                        this.age=age;

                        this.job=job;

                        this.showName=fucntion(){

                                alert(this.name);

                        };

                };

                var p1=new Person('jxj1',24,'student');

                var p2=new Person('jxj2',25,'teacher');
複製代碼

 

 


這個構造函數的例子取代了前面的普通函數,二者之間到底有什麼區別呢
相對工廠模式構造函數
1、沒有顯式的創建對象,就是沒有在函數裡面var p=new Object();
2、直接將屬性和方法直接的付給了this對象(理解this的朋友知道,這樣的構造函數在沒有new新對象時,this指向全局對象window);
3、沒有return語句
再次解釋構造函數中的this,構造函數的作用就是為了創建對象,這裡我們有new 了二個新的對象 p1、p2,此時再調用p1和p2時this就指向了自己,而不是window(不懂得去查看this的作用域)
p1和p2是Person的不同實例,這二個對象都有一個屬性constructor(構造函數的屬性),該屬性指向Person這個構造函數
alert(p1.constructor==Person); //true
alert(p2.constructor==Person); //true

好了,之所以介紹構造函數,還沒有說它的優點呢,前面說了工廠方式不能夠解決對象的識別問題,那麼構造函數就可以識別
alert(p1 instanceof Object); //true
alert(p1 instanceof Person); //true
alert(p2 instanceof Object); //true
alert(p2 instanceof Person); //true
其實說了這麼多,構造函數還是不完美的,有缺點,有沒有註意到構造函數中有個showName的方法,該方法有個function函數,問題就是出現在這裡
p1,p2是Person的二個不同的實例,p1和p2中的showName方法是不一樣的(不同實例的同名函數不相等)
alert(p1.showName == p2.showName); //false
換句話說,每次的實例化的構造函數都是下麵這樣的(只是為了理解,不可以這樣寫)

複製代碼
function Person(name,age,job){

                        this.name=name;

                        this.age=age;

                        this.job=job;

                        this.showName=new fucntion(){

                                alert(this.name);

                        };

                };
複製代碼

 

總之,每次實例化時,都產生一個新方法,這就是缺點

好吧,想辦法解決:

複製代碼
function Person(name,age,job){
            this.name=name;
            this.age=age;
            this.job=job;
            this.showName=showName;
        };
        fucntion showName(){
                alert(this.name);
            };
        var p1=new creatPerson('jxj1',24,'student');
        var p2=new creatPerson('jxj2',25,'teacher');
複製代碼

 

在這個例子中,我們把showName()函數的定義轉移到了構造函數外部。而在構造函數內部,我們將sayName 屬性設置成等於全局的sayName 函數。這樣一來,由於sayName 包含的是一個指向函數
的指針,因此p1 和p2 對象就共用了在全局作用域中定義的同一個sayName()函數。這樣做確實解決了兩個函數做同一件事的問題,可是showName就成了全局函數,如果該構造函數有很多方法,呢麽會不會瘋啊,那麼這樣的解決辦法可以解決,但是不好,好吧繼續找方法

(3)、原型
我們創建的每一個函數都有一個prototype(原型)屬性,這個屬性是一個指針,指向一個對象。。。。接下來先不解釋含義了,因為不容易理解,舉個例子:

複製代碼
function Person(name,age,job){
            this.prototype.name='jxj1';
            this.prototype.age=24;
            this.prototype.job='student';
            this.prototype.showName=fucntion(){
                alert(this.name);
            };
        };
        var p1=new Person();
        p1.showName();//jxj1
        var p2=new Person();
        p2.showName();//jxj1
        alert(p1.showName==p2.showName);//true
複製代碼

通過上面的代碼,有沒有覺得,工廠模式和構造函數模式的缺點,都不會在這裡出現啊,這就是原型的強大之處,好吧,接下來我們來理解原型到底是什麼


直接說概念太抽象,對著圖說吧
我們用原型寫的構造函數,將所有屬性和方法都添加到Person的prototype屬性中,此時的構造函數變成了空函數。即使如此,也仍然可以通過調用構造函數來創建新對象,而且新對象還會具有相同的屬
性和方法。但與構造函數模式不同的是,新對象的這些屬性和方法是由(讓)所有實例共用的。換句話說,p1 和p2 訪問的都是同一組屬性和同一個sayName()函數。
看著圖。。。。。
只要創建一個新函數(Person),就會為這個函數創建一個prototype的屬性,這個屬性指向行原型對象(Person protype),而原型對象會獲得一個屬性constructor,該屬性指向原型屬性所在的函數指針Person。當調用構造函數創建新實例以後(person1,person2),該實例內部將包含一個指針[[prototype]],指向構造函數的原型對象.可以看出來,person1和person2與構造函數沒有直接的關係,它們操作的是構造函數的對象原型(Person protype)。請記住:實例中的指針僅指向原型,而不指向構造函數。
看看下麵的二句:
alert(Person.prototype.isPrototypeOf(person1)); //true
alert(Person.prototype.isPrototypeOf(person2)); //true
這2句表明瞭,person1和person2內部都有一個指針[[prototype]]指向原型對象(Person.prototype)。
在ECMAScript5中新增加了一個方法Object.getPrototypeof(),該方法可以返回實例對象(person1,person2)中的指針值
alert(Object.getPrototypeOf(person1) == Person.prototype); //true
alert(Object.getPrototypeOf(person1).name); //"Nicholas"

下麵是是書上對原型中屬性和方法訪問的過程還是很容易理解的,所以我就copy了:
每當代碼讀取某個對象的某個屬性時,都會執行一次搜索,目標是具有給定名字的屬性。搜索首先從對象實例本身開始。如果在實例中找到了具有給定名字的屬性,則返回該屬性的值;如果沒有找到,則繼續搜索指針指向的原型對象,在原型對象中查找具有給定名字的屬性。如果在原型對象中找到了這個屬性,則返回該屬性的值。
也就是說,在我們調用person1.sayName()的時候,會先後執行兩次搜索。首先,解析器會問:“實例person1 有sayName 屬性嗎?”答:“沒有。”然後,它繼續搜索,再問:“person1 的原型有sayName 屬性嗎?”答:“有。”於是,它就讀取那個保存在原型對象中的函數。當我們調用person2.sayName()時,將會重現相同的搜索過程,得到相同的結果。而這正是多個對象實例共用原型所保存的屬性和方法的基本原理。

看了上面的原理,下麵我們舉個例子:

複製代碼
function Person(){
        }
        Person.prototype.name = "Nicholas";
        Person.prototype.age = 29;
        Person.prototype.job = "Software Engineer";
        Person.prototype.sayName = function(){
        alert(this.name);
        };
        var person1 = new Person();
        var person2 = new Person();
        person1.name = "Greg";
        alert(person1.name); //"Greg"——來自實例
        alert(person2.name); //"Nicholas"——來自原型
複製代碼

該例子可以看出,當代碼在讀取對象屬性時,先查找本身自己對象實例person1,當實例中找到了name,就不會再找對象原型中的相應屬性了;若是沒有找到,就繼續搜索原型對象。。。。
換句話說,當實例中存在和原型對象中同名的屬性,那麼會優先選擇實例中的屬性和屬性值。。。。
這裡要想person1.name顯示Nicholas,就需要使用delete.person1.name刪除實例中的同名屬性才可以。。。。。

接下來問題來了,有時候我們需要檢測一個屬性到底是在實例中還是在原型對象中,我們該怎麼辦
hasOwnProperty()是從Object繼承來的,hasOwnProperty()方法可以檢測到一個屬性是存在於原型中還是對象實例中,只有當存在對象對象實例中返回true(其實存在原型中,或是其它非對象實例中都會返回false)
in :當通過對象能夠訪問指定的屬性時就返回true(只要能訪問,不管存在於實例中還是原型中)

可以封裝一個函數來確定,屬性到底是存在於對象中還是原型中:
                function hasPrototypePropery(obj,name){
                        return !obj.hasOwnProperty(name)&&(name in obj);
                };
有些人肯定會疑惑,hasprototypepropery()方法就足夠了啊,幹麼還要in呢?我一開始也是這麼疑惑,逆向思維就知道了。。。
假設現在我要確定一個屬性是存在於原型對象中的,而hasPrototypePropery()只能確定存在對象實例中和非對象實例中,所以只有當在非實例對象中 !obj.hasOwnProperty(name)且能夠通過對象訪問到該屬性時,才能確定一定是在對象原型中,,,,,(不懂得慢慢想)

前面我們用原型的方法解決了工廠和構造函數的缺點,但是,原型寫的有點負責代碼又多,有那麼多重覆的對象.prototype所以我們要改寫一下了

複製代碼
function Person(){    
        };
        Person.prototype(){
            name:'jxj1',
            age:24,
            job:'student',
            showName:function(){
                alert(this.name);
            }
        };
複製代碼


這種通過對象字面量的方式來寫原型對象和原來的寫法產生的效果是一樣的,但是有一個變了,前面說過每創建一個函數,這個函數就會產生一個prototype屬性,指向對象原型,而對象原型中
的constructor屬性會指向創建的那個函數,現在constructor屬性不再指向Person了,改編成指向Object構造函數
如果你需要constructor屬性很重要,可以改寫一下:

  複製代碼
function Person(){    
        };
        Person.prototype(){
            constructor:Person,
            name:'jxj1',
            age:24,
            job:'student',
            showName:function(){
                alert(this.name);
            }
        };
複製代碼

原生模式的重要性並僅僅體現在上面的哪些自定義類型方面,就連原生的引用類都是採用這種模式創建的。原生引用類型(Object、Array、String等)都在其構造函數的原型上定義了方法
例如,在Array.prototype 中可以找到sort()方法,而在String.prototype 中可以找到substring()方法,如下所示。
alert(typeof Array.prototype.sort); //"function"
alert(typeof String.prototype.substring); //"function"
同樣的我們也可以修改原生對象的原型
下麵的代碼就給基本包裝類型

String 添加了一個名為startsWith()的方法。
String.prototype.startsWith = function (text) {
return this.indexOf(text) == 0;
};
var msg = "Hello world!";
alert(msg.startsWith("Hello")); //true

這裡新定義的startsWith()方法會在傳入的文本位於一個字元串開始時返回true。既然方法被
添加給了String.prototype,那麼當前環境中的所有字元串就都可以調用它。由於msg 是字元串,
而且後臺會調用String 基本包裝函數創建這個字元串,因此通過msg 就可以調用startsWith()方法。

當然,非特殊情況下,不推薦在程式中修改原生對象的原型,修改的方法可以會在其它方法中產生命名衝突

原型的缺點:
對比你下原型和構造函數發現了,原型模式省略了初始化參數這一環節,結果導致所有的實例都共用取得相同的屬性值,其實我們並不像看到這樣的結果,這還不是最大的問題。
原生最大的問題就是由共用的本性所導致的,原型中所有屬性被實例共用,這沒有什麼不妥,況且,實例中同名的屬性優先順序更高。然而,對於包含引用類型值的屬性來說,問題就大了。
例如:

複製代碼
function Person(){
        }
        Person.prototype = {
            constructor: Person,
            name : "Nicholas",
            age : 29,
            job : "Software Engineer",
            friends : ["Shelby", "Court"],
            sayName : function () {
            alert(this.name);
        }
        };
        var person1 = new Person();
        var person2 = new Person();
        person1.friends.push("Van");
        alert(person1.friends); //"Shelby,Court,Van"
        alert(person2.friends); //"Shelby,Court,Van"
        alert(person1.friends === person2.friends); //true
複製代碼



當實例person1對象修改的屬性中包含引用類型(比如數組)的值,會反映到person2對象中,我既然創建的是二個實例,怎麼會想她們共用一個數組呢,oh  。。。ON

現在問題又來了,原型也有缺點,繼續想辦法吧:
(4)、構造函數與原型的混合模式
我們需要將二者的優點都結合起來,拋棄她們的缺點,分析一下吧
首先,構造函數中的方法每次實例化都會是不一樣的,但是原型可以改正這個缺點,所以用原型模式來定義方法
其次,當其中一個實例改變原型中引用類型的值,同時另外一個實例在原型中的相應值也會跟著改變,但是構造函數可以改掉這個缺點,所以,用構造函數模式定義引用類型值的屬性
總結,構造函數用於定義實例的屬性(包括基本數據類型和引用數據類型),而原型模式用於定義方法和共用的屬性。
例子:這個例子也就是前面我們舉的例子

複製代碼
function Person(name,age,job){
                        this.name=name;
                        this.age=age;
                        this.job=job;
                        this.friend=['a1','a2'];
                };
            Person.prototype={
                    constructor:Person,
                    showName:function(){
                            alert(this.name);
                    }
            };
            var p1=new Person('jxj1',24,'student');
            var p2=new Person('jxj2',25,'student');
            p1.friend.push('a3');
            alert(p1.friend);//'a1,a2,a3'
            alert(p2.friend);//'a1,a2'
            alert(p1.friend==p2.friend);//false
            alert(p1.showName==p2.showName);//true
複製代碼


從例子中可以看出來,現在引用類型的數據值沒有共用,函數方法變成了共用,所以好像是沒有問題了。。。。。
(5)、動態原型模式
動態原型模式,它把所有信息都封裝在了構造函數中,而通過在構造函數中初始化原型(僅在必要的情況下),又保持了同時使用構造函數和原型的優點。換句話說,可以通過
檢查某個應該存在的方法是否有效,來決定是否需要初始化原型。來看一個例子:

複製代碼
function Person(name, age, job){
            //屬性
            this.name = name;
            this.age = age;
            this.job = job;
            //方法
            if (typeof this.sayName != "function"){
                Person.prototype.sayName = function(){
                alert(this.name);
            };
            }
        }
        var friend = new Person("Nicholas", 29, "Software Engineer");
        friend.sayName();
複製代碼


這裡只在sayName()方法不存在的情況下,才會將它添加到原型中。這段代碼只會在初次調用構造函數時才會執行。此後,原型已經完成初始化,不需要再做什麼修改了。
不過要記住,這裡對原型所做的修改,能夠立即在所有實例中得到反映。因此,這種方法確實可以說非常完美。其中,if 語句檢查的可以是初始化之後應該存在的任何屬性或方法——不必用一大堆
if 語句檢查每個屬性和每個方法;只要檢查其中一個即可。對於採用這種模式創建的對象,還可以使用instanceof 操作符確定它的類型。
使用動態原型模式時,不能使用對象字面量重寫原型。如果在已經創建了實例的情況下重寫原型,那麼就會切斷現有實例與新原型之間的聯繫。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.ServiceStack服務擁有自身的容器—Funq.Container 當我們使用介面註入的方式調用底層方法時,我們需要在AppHost中重寫Configure(Funq.Container container)方法,在方法中添加container.RegisterAutoWiredAs<T,
  • 可以通過http://htmlpreview.github.io/這個網站,直接線上預覽html頁面。 ↓ ↓ 可以發現:這個網站直接將github上的頁面地址當做參數來傳遞。
  • 上海樂司凱信息科技有限公司 是上海邁伊茲咨詢有限公司的子公司 地址是在臨空經濟區,攜程那路,北翟路外環線 稅前10k 20k,說是有牛人30k也能承受,14薪 有意向留言好了 1、精通HTML/HTML5/XHTML,CSS,精通運用CSS+DIV佈局,可使用HTML5、CSS3、JavaScrip
  • 這是一款基於segment.js製作的非常有創意的分段式SVG文字動畫特效。這個文字動畫特效通過動畫SVG的描邊路徑來製作各種文字的動畫效果,效果非常的贊。 這個SVG文字動畫特效的第一個DEMO中的最後幾個例子使用了mo.js插件,一款由Oleg Solomka編寫的用於製作網頁圖形動畫的Java
  • sticky組件,通常應用於導航條或者工具欄,當網頁在某一區域滾動的時候,將導航條或工具欄這類元素固定在頁面頂部或底部,方便用戶快速進行這類元素提供的操作。本文介紹這種組件的實現思路,並提供一個同時支持將sticky元素固定在頂部或底部的具體實現,由於這種組件在網站中非常常見,所以有必要掌握它的實現...
  • 通過第一課的學習,你已經掌握瞭如何通過debug調試器來跟PC上的設計器聯調來實時查看UI設計效果、調試代碼了,接下來通過一系列的demo開發教學你將很快上手學習到如何開發一個真正的App。 要開發App,最重要的就是確定主框架,好的主框架能起到減少工作量、簡化代碼的作用,而頁面通常需要產品人員的U
  • CSS命名規範 一.文件命名規範 全局樣式:global.css;框架佈局:layout.css;字體樣式:font.css;鏈接樣式:link.css;列印樣式:print.css; 二.常用類/ID命名規範 頁 眉:header內 容:content容 器:container頁 腳:footer
  • 第一次打開一個頁面時,讓載入好的圖片先隱藏,然後再執行動畫fadeIn。 這裡的load事件:當所有子元素已經被完全載入完成時,load事件被髮送到這個元素
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...