js面向對象與原型

来源:http://www.cnblogs.com/moqiutao/archive/2016/03/10/5263433.html
-Advertisement-
Play Games

創建對象 var box = new Object();//創建對象 box.name = 'Lee'; //添加屬性 box.age = 100; box.run = function(){ return this.name + this.age + "運行中"; //this 表示當前作用域下對


創建對象

var box = new Object();//創建對象
box.name = 'Lee';      //添加屬性
box.age = 100;
box.run = function(){
    return this.name + this.age + "運行中";  //this 表示當前作用域下對象
}

// this 表示new Object()實例出來的那個對象
alert(box.run());

 

這就是創建對象最基本的方法,但是有個缺點,想創建一個類似的對象,就會產生大量的代碼。

工廠模式

為瞭解決多個類似對象聲明的問題,我們可以使用一種叫做工廠模式的方法,這種方法就是為瞭解決實例化對象產生大量重覆的問題。

function createObject(name,age){
    var obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.run = function(){
        return this.name+this.age+"歲年齡";
    }
    return obj;
}

var box1 = createObject('Lee',20);
var box2 = createObject('Jack',30);
console.log(box1.run());
console.log(box2.run());

 

工廠模式解決了重覆實例化的問題,但還有一個問題,那就是識別問題,因為根本無法搞清他們到底是哪個對象的實例。

alert(typeof box1); //Object
alert(box1 instanceof Object);//true

 

構造函數

ECAMScript中採用構造函數(構造方法)可用來創建特定的對象。類似於Object對象。

//構造函數
function Box(name,age){
    this.name = name;
    this.age = age;
    this.run = function(){
        return this.name + this.age +"運行中...";
    };
};

var box1 = new Box('Lee',100);
var box2 = new Box('Jack',200);

console.log(box1.run());
console.log(box2.run());

 

使用構造函數的方法,即解決了重覆實例化的問題,又解決了對象識別的問題,但問題是,這裡並沒有new Object(),為什麼可以實例化Box(),這個是哪裡來的呢?

使用了構造函數的方法,和使用工廠模式的方法他們不同之處如下:
1.構造函數方法沒有顯示的創建對象(new Objectt()),但它在後臺自動var obj = new Object();
2.直接將屬性和方法賦值給this對象,this就相當於obj;
3.沒有return語句,不需要返回對象引用,它是在後臺自動返回的。

//構造函數
function Box(name,age){
    this.name = name;
    this.age = age;
    this.run = function(){
        return this.name + this.age +"運行中...";
    };
};

function Dack(name,age){
    this.name = name;
    this.age = age;
    this.run = function(){
        return this.name + this.age +"運行中...";
    };
};

var box1 = new Box('Lee',100);
var box2 = new Box('Jack',200);
var box3 = new Dack('MrLee',300);

console.log(box1.run());
console.log(box2.run());
console.log(box3.run());

//解決了對象識別問題
console.log(box1 instanceof Box); //true
console.log(box2 instanceof Box); //true
console.log(box3 instanceof Box); //false
console.log(box3 instanceof Dack);//true

 

對象冒充:使用call()方法

var o= new Object();
Box.call(o,'Lee',100);
console.log(o.run());

 

看下一個問題:

 var box1 = new Box('Lee',100); //實例化後地址為1
 var box2 = new Box('Lee',100); //實例化後地址為2

 console.log(box1.name == box2.name); //true
 console.log(box1.age == box2.age);      //true
 console.log(box1.run() == box2.run());//true //構造函數體內的方法的值是相當的
 console.log(box1.run == box2.run); //false //因為他們比較的是引用地址

 

上面的代碼運行說明引用地址不一樣,那麼構造函數內的方法也可以這樣寫:

this.run = new Function("return this.name + this.age +'運行'") 

 

如何讓他們的引用地址一樣,下麵代碼:

function Box(name,age){
    this.name = name;
    this.age = age;
    this.run = run;
};

function run(){
    return this.name +this.age+"運行中...";
}
 var box1 = new Box('Lee',100); //實例化後地址為1
 var box2 = new Box('Lee',100); //實例化後地址為2
 console.log(box1.run == box2.run); //true //因為他們比較的是引用地址  

 

把構造函數內部的方法通過全局來實現引用地址一致。
雖然使用了全局函數run()來解決了保證引用地址一致的問題,但是這種方式又帶來了一個新的問題,全局中的this在對象調用的時候是Box本身,而當普通函數調用的時候,this又代表window。

原型

function Box(){
       //構造函數函數體內什麼都沒有,這裡如有過,叫做實例屬性,實例方法
} 

Box.prototype.name="Lee"; //原型屬性
Box.prototype.age=100;
Box.prototype.run=function(){ //原型方法
    return this.name+this.age+"運行中...";
}

var box1=new Box();
var box2=new Box();
console.log(box1.run == box2.run); //true
console.log(box1.prototype);//這個屬性是一個對象,訪問不到
console.log(box1.__proto__);//這個屬性是一個指針指向prototype原型對象。 

 

如果是實例方法,不同的實例化,他們的方法地址是不一樣的,是唯一的。
如果是原型方法,那麼他們的地址是共用的,大家都一樣。

console.log(box1.constructor); //構造屬性,可以獲取構造函數 

 

PS:IE瀏覽器在腳本訪問__proto__會不能識別,火狐和谷歌及其他某些瀏覽器能識別。雖然可以輸出,但是無法獲取內部信息。

判斷一個對象是否指向該構造函數的原型對象,可以使用isPrototypeOf()方法來測試。

console.log(Box.prototype.isPrototypeOf(box1)); //true //只要實例化對象,即都會指向

 

原型模式的執行流程:
1.先查找構造函數實例里的屬性或方法,如果有,立刻返回;
2.如果構造函數實例里沒有,則去它的原型對象里找,如果有,就返回。

如何判斷屬性時構造函數的實例里,還是原型里?可以使用hasOwnProperty()函數來驗證:

console.log(box1.hasOwnProperty('name'));//如果實例里有返回true,否則返回false  

 

如何判斷屬性是原型里的?

function Box(){
       
} 

Box.prototype.name="Lee"; //原型屬性
Box.prototype.age=100;
Box.prototype.run=function(){ //原型方法
    return this.name+this.age+"運行中...";
}

function isProperty(object,property){
    return !object.hasOwnProperty(property) && (property in object);
}
var box1=new Box();
console.log(isProperty(box1,'name'));

 

為了讓屬性和方法更好的體現封裝的效果,並且減少不必要的輸入,原型的創建可以使用字面量的方式

使用字面量的方式創建原型對象,這裡的{}就是對象,是object,new Object就相當於{}

function Box(){} 

Box.prototype={
    name:'Lee',
    age:100,
    run:function(){
        return this.name+this.age+"運行中...";
    }
}

var box = new Box();
console.log(box.constructor == Box); //false 

 

字面量創建的方式使用constructor屬性不會指向實例,而會指向Object,構造函數創建的方式則相反。
這裡的Box.prototype={}就相當於創建了一個新的對象,所以 box.constructor是Object。
如何讓box.constructor指向Box呢?

function Box(){} 

Box.prototype={
    constructor:Box,//直接強制指向即可
    name:'Lee',
    age:100,
    run:function(){
        return this.name+this.age+"運行中...";
    }
}

var box = new Box();
console.log(box.constructor == Box); //true

 

重寫原型,不會保留之前原型的任何信息,把原來的原型對象和構造函數對象的實例切斷了。

function Box(){} 

Box.prototype={
    constructor:Box,
    name:'Lee',
    age:100,
    run:function(){
        return this.name+this.age+"運行中...";
    }
}

//重寫原型
Box.prototype={
    age:200
}
var box = new Box();
console.log(box.name); //undefined

 

查看sort是否是Array原型對象里的方法

alert(Array.prototype.sort);

 

在如下 判斷String原型對象里是否有substring方法

alert(String.prototype.substring);

 

給String 添加addstring方法:

String.prototype.addstring=function(){
    return this+',被添加了!';
}
var box="Lee";
console.log(box.addstring());

 

註:原型模式創建對象也有自己的缺點,它省略了構造函數傳參初始化這一過程,帶來的缺點就是初始化的值都是一致的。而原型最大的缺點就是它最大的優點,那就是共用。

原型中所有屬性是被很多實例共用的,共用對於函數非常合適,對於包含基本值的屬性也還可以。但如果屬性包含引用類型,就存在一定的問題:

function Box(){}

Box.prototype={
    constructor:Box,
    name:'Lee',
    age:100,
    family:['哥哥','姐姐','妹妹'],
    run:function(){
        return this.name+this.age+"運行中...";
    }
};

var box1 = new Box();
console.log(box1.family); //'哥哥','姐姐','妹妹'
box1.family.push("弟弟");
console.log(box1.family);//'哥哥','姐姐','妹妹','弟弟'

var box2 = new Box();
console.log(box2.family);//'哥哥','姐姐','妹妹','弟弟'

 

從上面代碼可以看出,在第一個實例修改後引用類型,保持了共用。box2.family共用了box1添加後的引用類型的原型。

為瞭解決構造傳參和共用問題,可以組合構造函數+原型模式:

function Box(name,age){  //保持獨立的用構造函數
    this.name=name;
    this.age=age;
    this.family=['哥哥','姐姐','妹妹'];
}

Box.prototype={ //保持共用的用原型
    constructor:Box,
    run:function(){
        return this.name+this.age+"運行中...";
    }
}

var box1 = new Box('Lee',100);
console.log(box1.family); //'哥哥','姐姐','妹妹'
box1.family.push("弟弟");
console.log(box1.family);//'哥哥','姐姐','妹妹','弟弟'



var box2 = new Box('Jack',200);
console.log(box2.family); //'哥哥','姐姐','妹妹' //引用類型沒有使用原型,所以沒有共用

 

動態原型模式

//把原型封裝到構造函數里
function Box(name,age){
    this.name=name;
    this.age=age;
    this.family=['哥哥','姐姐','妹妹'];

    console.log('原型初始化開始');  //執行了兩次
    Box.prototype.run=function(){
        return this.name+this.age+"運行中...";
    }
    console.log('原型初始化結束'); //執行了兩次
}

//原型的初始化,只要第一次初始化就可以了,沒必要每次構造函數實例化的時候都初始化
var box1 = new Box('Lee',100);
var box2 = new Box('Jack',200);

 

為了只讓第一次初始化,那麼就判斷

function Box(name,age){
    this.name=name;
    this.age=age;
    this.family=['哥哥','姐姐','妹妹'];

    if(typeof this.run!='function'){
        console.log('原型初始化開始'); //執行了一次次
        Box.prototype.run=function(){
            return this.name+this.age+"運行中...";
        };
        console.log('原型初始化結束'); //執行了一次
    }
}

//原型的初始化,只要第一次初始化就可以了,沒必要每次構造函數實例化的時候都初始化
var box1 = new Box('Lee',100);
var box2 = new Box('Jack',200);

 

寄生構造函數
如果以上都不能滿足需要,可以使用一下寄生構造函數。
寄生構造函數=工廠模式+構造函數

function Box(name,age){
    var obj = new Object();
    obj.name=name;
    obj.age=age;
    obj.run=function(){
        return this.name+this.age+"運行中...";
    }
    return obj;
}

var box1 = new Box('Lee',100);
var box2 = new Box('Jack',200);

 

穩妥構造函數

在一些安全的環境中,比如禁止使用this和new,這裡的this是構造函數里不使用的this,這裡的new是在外部實例化構造函數時不使用new。這種創建方式叫做穩妥構造函數。

function Box(name,age){
    var obj = new Object();
    obj.name=name;
    obj.age=age;
    obj.run=function(){
        return this.name+this.age+"運行中...";
    }
    return obj;
}

var box1 = Box('Lee',100);
var box2 = Box('Jack',200);

 

繼承

繼承是面向對象中一個比較核心的概念。其它正統面向對象語言都會用兩種方式實現繼承:一個是介面實現,一個是繼承。而ECMAScript只支持繼承,不支持介面實現,而實現繼承的方式依靠原型鏈完成。

function Box(){
    this.name="Lee";
}

function Jack(){
    this.age=100;
}

Jack.prototype = new Box();

var jack = new Jack();
console.log(jack.name); //Lee

 

為瞭解決引用共用和超類型無法傳參的問題,我們採用一種叫借用構造函數的技術,或者成為對象冒充(偽造對象、經典繼承)的技術解決這兩個問題。

function Box(name){
    this.name=name;
}

Box.prototype.age=200;

function Jack(name){
    Box.call(this,name);
}

var jack = new Jack('Lee');

console.log(jack.name);//Lee
console.log(jack.age);//undefined   

 

但是上面的代碼可以看出,對象冒充沒有繼承原型鏈上的age屬性。所以要繼承Box的原型,就出現下麵的組合繼承。
組合繼承即是原型鏈+借用構造函數的模式

function Box(name){
    this.name=name;
}

Box.prototype.age=200;

function Jack(name){
    Box.call(this,name);
}

Jack.prototype = new Box();

var jack = new Jack('Lee');

console.log(jack.name);//Lee
console.log(jack.age);//200

 

原型式繼承

//臨時中轉函數
function obj(o){
    function F(){};
    F.prototype = o;
    return new F();
}

//這是字面量的聲明方式,相當於var box = new Box();
var box={
    name:'Lee',
    age:100,
    family:['哥哥','姐姐','妹妹']
};

var box1 = obj(box);
console.log(box1.family);//'哥哥','姐姐','妹妹'
box1.family.push('弟弟');
console.log(box1.family);//'哥哥','姐姐','妹妹','弟弟'

var box2 = obj(box);
console.log(box2.family);//'哥哥','姐姐','妹妹','弟弟'

 

存在的問題就是引用類型共用了。

寄生式繼承
把原型式與工廠模式結合起來。

//臨時中轉函數
function obj(o){
    function F(){};
    F.prototype = o;
    return new F();
}


//寄生函數
function create(o){
    var f=obj(o);
    f.run=function(){
        return this.name+"方法";
    }
    return f;
}


//這是字面量的聲明方式,相當於var box = new Box();
var box={
    name:'Lee',
    age:100,
    family:['哥哥','姐姐','妹妹']
};

var box1 = create(box);
console.log(box1.run());

 

寄生組合繼承

//臨時中轉函數
function obj(o){
    function F(){};
    F.prototype = o;
    return new F();
}


//寄生函數
function create(box,desk){
    var f=obj(box.prototype);
    f.constructor=desk; //調整原型構造指針
    desk.prototype=f;
}


function Box(name,age){
    this.name=name;
    this.age=age;
}

Box.prototype.run=function(){
    return this.name+this.age+"運行中...";
}

function Desk(name,age){
    Box.call(this,name,age); //對象冒充
}

//通過寄生組合繼承來實現繼承
create(Box,Desk); //這句話用來替代Desk.prototype = new Box();

var desk = new Desk('Lee',100);
console.log(desk.run());

 


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

-Advertisement-
Play Games
更多相關文章
  • SSRS 報表 如何加參數 連接上以後出現一個問題 就是給報表加上參數以後報表不斷刷新,跟上次那個報表刷新是同樣的問題。那麼下麵我們來解決一下。 1. 這是給報表添加預設參數進入頁面後就不斷的刷新刷新。 ReportParameter para = new ReportParameter("Repo
  • 背水一戰 Windows 10 之 UI: UI 設計概述, 啟動屏幕(閃屏), 屏幕方向
  • 出處:http://www.cnblogs.com/Interkey/ 偶然看到一個可以自刪除的程式,於是瞭解下如何實現。然後整理如下: 思路: 在.NET程式中,因為運行中的程式是受系統保護的,不能自己刪除自身的,所以自刪除的思路: 在關閉本程式之前啟動新的進程打開另一個程式,調用這個程式來刪除原
  • Insus.NET近段時間應朋友的要求,寫一個GridView多層嵌套和摺疊與展開。這個功能的GridView多層嵌套沒有問題,因為已經做了無限次數,但是摺疊與展開的功能,卻花上不少時間(網上找資料),雖找到資料可參考,還是瞭解明它,並修改適合自己程式使用。效果如下: 站點中多個頁面使用,因此Ins
  • 高德地圖api解釋的實在是太爛了,一度想用系統獲取location,添加button實現定位功能,奈何高德地圖右上角又有定位按鈕,於是乎高德地圖定位研究如下: package com.example.basicmap; import java.util.List; import android.ap
  • BeanFactory是Spring IOC實現的基礎,這邊定義了一系列的介面,我們通過這些介面的學習,可以大致瞭解BeanFactory體系各介面如何分工合作.為閱讀具體實現打下基礎.
  • JSP中文亂碼解決方案,給出了較為詳細的步驟。
  • 數組(Array),字面上講,就是一組相同的數據,一種簡單的線性結構,對應到記憶體上,就是一塊連續的固定大小的記憶體塊的組合。一旦用到數組,說明我們對數據的規模是心中有數的,因此數組的大小是需要提前預定的。 效率,電腦永遠不會停止追求效率。為了追求效率,數組表現出它最明顯的兩個特點,其一,數組大小需要
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...