1. 程式人生 > >淺談js中的原型(ECMAScript標準層面)

淺談js中的原型(ECMAScript標準層面)

在網上面搜了一下“什麼是js的原型,以及什麼是_proto_”啥的。。。。,然後出來的全是一堆什麼

alert(xxx.prototype===yyy._proto_)//true//false啥的,有毒吧!!還有原型鏈就更加不用說了,看著就頭疼,反正我是看不懂,全靠死記,就算當時記住了,過一會就忘了,完全起不到作用,所以這篇部落格從另外一個方面(ECMAScript標準層面)談一下js中的原型

js的原型:(prototype),每一個物件都有一個prototype屬性,注意不是物件的例項,你或許聽過js的面向物件是基於原型的,但是你可能沒有仔細琢磨這句話,這句話的意思就是:一個物件的原型的方法和屬性會被其子類繼承,而非原型的方法和屬性不會被子類繼承!!,換句話說:某物件原型的方法和屬性會傳遞給該物件的所有例項。

如果不理解,請看完整篇部落格

前提:

  •     瞭解js中的建構函式
  •     瞭解js中this關鍵字
  •     瞭解js中的call(),apply()函式

這個前提條件有點多哈!下面就簡單的介紹一下先

    1)、js中的建構函式

        這個就不用多說了,js很靈活,一個函式你可以把它當成一個普通只用來呼叫的函式,也可以將它看做用來建立它自己的例項的建構函式,如果把它當成建構函式,就不得不牽扯到this關鍵字了,請接著看

    2)、js中的this關鍵字

        this關鍵字,簡單理解就是例項本身!!這麼說可能有點抽象,請看下面程式碼!

function F1(arg1, arg2) {
        this.name = arg1;
        this.id = arg2;
        this.nicknameArray = new Array("xiaoze", "huahua", "zehua");
    }

        這是一個建構函式,使用了this關鍵字定義了三個屬性,這樣接著看下面程式碼

var ze1 = new F1("ze1", "01038");
    ze1.name;//ze1
    ze1.id;//01038
    ze1.nicknameArray;//xiaoze,huahua,zehua

        這樣我們可以通過new一個F1的例項:ze1,然後就可以通過 . 的方式得到相應的屬性,前面說過,this關鍵字指的是例項本身,那麼,這裡的例項本身就是ze1!!!所以實際上上面兩端程式碼等價於下面這段程式碼:

var ze1 = {};
    function F1(arg1, arg2) {
        ze1.name = arg1;
        ze1.id = arg2;
        ze1.nicknameArray = new Array("xiaoze", "huahua", "zehua");
    }
    F1("ze1", "01038");
    ze1.name;//ze1
    ze1.id;//01038
    ze1.nicknameArray;//xiaoze,huahua,zehua

        說完了this,call(), apply()函式也不得不說了!,因為他們不僅可以加深對於this的理解,還有很多其他的用途,本篇部落格所講的原型就會用到它,所以請繼續往下看!!!

    3)、js中的call(), apply()函式

        網上說他們是加深、中函式呼叫的一種方式,的確,他們函式呼叫的一種方式,當時,本篇部落格談到的他們兩的用途並不是作為函式呼叫,它配合this關鍵字,實現js中的繼承!!這些在本片部落格中都會講到,先來看下這兩個函式的基本用法:

        <1>、cal()函式

function F1(arg1, arg2) {
        this.name = arg1;
        this.id = arg2;
        this.nicknameArray = new Array("xiaoze", "huahua", "zehua");
    }

    var ze1 = {};
    //call函式的第一個引數(某物件的例項)將會取代物件(F1)建構函式中的this關鍵字
    //而其後的引數將是與建構函式的引數相對應
    F1.call(ze1, "ha", "01038");

    ze1.name;//ze1
    ze1.id;//01038
    alert(ze1.nicknameArray);//xiaoze,huahua,zehua*/

            其實可以看到,和前面的this的講解。本質上是一樣的,而後面,我們將會看到call()函式的第一個引數是this關鍵字的用法

        <2>、apply()函式

function F1(arg1, arg2) {
        this.name = arg1;
        this.id = arg2;
        this.nicknameArray = new Array("xiaoze", "huahua", "zehua");
    }

    var ze1 = {};
    //apply函式的第一個引數(某物件的例項)將會取代物件(F1)建構函式中的this關鍵字
    //而其後的引數是一個數組,是與建構函式的引數相對應
    F1.apply(ze1, ["ha", "01038"]);

    ze1.name;//ze1
    ze1.id;//01038
    alert(ze1.nicknameArray);//xiaoze,huahua,zehua*/

            可以發現,apply和call函式極其的相似,第一個入參的含義相同,後面的引數在本質上面還是一樣的

正式:

1、ECMAScript標準

    既然涉及到了ECMAScript這個詞,就不得不說一下它是個什麼東西了:我想,能搜到這篇部落格的讀者都是聽說過javascript的,那麼,javascript和ECMAScript是什麼關係呢?簡單的說就說:ECMAScript是javascript這類指令碼語言的標準,而javascript是這個標準的實現和擴充套件(是先有javascript,後又ECMAScript的,想詳細瞭解的可以其ECMAScript官網檢視官方文件),標準的魅力這是讓人愉悅,馬士兵(編者的java的啟蒙老師,必須贊一波)說過,一流公司買標準,二流公司買服務,三流公司賣產品(題外話)。

2、原型

    一、以物件中屬性和方法的定義為例需要特別注意的是,這並不是繼承,這和繼承一點關係都沒有,建立物件的例項並不是繼承

        在不使用原型(prototype)定義物件的屬性和方法:也就是隻使用建構函式的方式來定義屬性和方法

function F1(arg1, arg2) {
        this.name = arg1;
        this.id = arg2;
        this.nicknameArray = new Array("xiaoze", "huahua", "zehua");
        this.func = function () {
            alert(this.nicknameArray);
        }
    }
var ze1 = new F1("ze1", "01038");
    var ze2 = new F1("ze2", "01038");
    ze1.func();//xiaoze,huahua,zehua
    ze2.func();//xiaoze,huahua,zehua

            可以發現,我們new了兩個F1的例項,但是,這樣就會帶來一個問題:func()這個函式會因為我們new了兩個例項而被建立兩次!這樣不合理,而js的原型可以解決這個問題!下面看只利用js的原型來定義物件的方法和屬性

        只利用js的原型來定義物件的方法和屬性

function F1(arg1, arg2) {

    }
    F1.prototype.name = "ze1";
    F1.prototype.id = "01038";
    F1.prototype.nicknameArray = new Array("xiaoze", "huahua", "zehua");
    F1.prototype.func = function () {
        alert(this.nicknameArray);
    };

    var ze1 = new F1("ze1", "01038");
    var ze2 = new F1("ze2", "01038");
    ze1.nicknameArray[0] = "zeze";
    ze1.func();//zeze,huahua,zehua
    ze2.func();//zeze,huahua,zehua

            可以發現兩個問題:

            1)、現在不能通過建構函式來傳遞引數,可以發現,這裡的屬性是寫死的!

            2)、使用prototype的方式傳遞給例項的是指標,所以正如上面的例子,某個例項的屬性改變了,其他的該物件的例項的屬性都會發生相應的變化

            所以,我們通常使用建構函式/原型的混合模式來定義物件的屬性和方法,也就是使用建構函式定義物件的非函式屬性,而使用原型定義物件的函式屬性,看到這裡,有木有對js中的原型有更好的理解呢?如果嫌不夠,那就繼續往下看——基於原型的js繼承機制

        使用建構函式定義物件的非函式屬性,而使用原型定義物件的函式屬性

function F1(arg1, arg2) {
        this.name = arg1;
        this.id = arg2;
        this.nicknameArray = new Array("xiaoze", "huahua", "zehua");
    }
    F1.prototype.func = function () {
        alert(this.nicknameArray);
    };

    var ze1 = new F1("ze1", "01038");
    var ze2 = new F1("ze2", "01038");
    ze1.nicknameArray[0] = "zeze";
    ze1.func();//zeze,huahua,zehua
    ze2.func();//xiaoze,huahua,zehua

            可以發現,通過這種方式,完美的解決了上面的所有問題

            1、使用原型定義物件的函式屬性,該函式只會被建立一遍!

            2、當改變某例項的屬性值的時候,並不會影響到該物件的其他例項

            3、可以通過建構函式來傳遞引數

    二、基於原型的js繼承機制

        最基本是:將某函式物件(父類)直接賦予給某物件的屬性(子類)

function F1(arg1, arg2) {
        this.name = arg1;
        this.id = arg2;
        this.nicknameArray = new Array("xiaoze", "huahua", "zehua");
        this.alertName = function () {
            alert(this.name);
        };
    }

    function F2(arg1,arg2, arg3) {
        //注意,這裡的F1是被當做普通的函式進行傳遞,而不是建構函式
        this.f1 = F1;
        this.f1(arg1, arg2);
        this.school = arg3;
        this.alertSchool = function () {
            alert(this.school);
        };
    }

    var f1 = new F1("ze1", "01038");
    var f2 = new F2("ze2", "01038", "SDU");
    f1.alertName();//ze1
    f2.alertName();//ze2
    f2.alertSchool();//SDU

            可以看到,這裡大量用到this關鍵字,注意上面的那行註釋!!所有,這行程式碼:this.f1 = F1等價於下面的程式碼:

this.f1 = function (arg1, arg2) {
            this.name = arg1;
            this.id = arg2;
            this.nicknameArray = new Array("xiaoze", "huahua", "zehua");
            this.alertName = function () {
                alert(this.name);
            };
        };

            可以發現,這個就是F1的原始碼!!而當new出F2時,這些this將全部被f2這個例項給替換,成為f2的屬性和方法!,

        基於call()函式的繼承

function F1(arg1, arg2) {
        this.name = arg1;
        this.id = arg2;
        this.nicknameArray = new Array("xiaoze", "huahua", "zehua");
        this.alertName = function () {
            alert(this.name);
        };
    }

    function F2(arg1,arg2, arg3) {
        //注意,這裡傳入了this作為call函式的第一個引數
        F1.call(this, arg1, arg2);
        this.school = arg3;
        this.alertSchool = function () {
            alert(this.school);
        };
    }

    var f1 = new F1("ze1", "01038");
    var f2 = new F2("ze2", "01038", "SDU");
    f1.alertName();//ze1
    f2.alertName();//ze2
    f2.alertSchool();//SDU

            注意到上面的那行註釋,以this關鍵字作為call函式的第一個引數,想必,現在讀應該知道這個this在F2例項化的時候會被替換成什麼物件了吧!沒錯,在這個例子中,this被f2替換,這樣,是不是更好的理解了call函式呢!

        基於apply()函式的繼承

            因為這個函式和call函式極其的相似,在這裡就請讀者參照上面的例子自行完成吧!

        基於原型的繼承

            終於進入正題了,看下面例子

function F1(arg1, arg2) {
    this.name = arg1;
    this.id = arg2;
    this.nicknameArray = new Array("xiaoze", "huahua", "zehua");
    }
    F1.prototype.alertName = function () {
        alert(this.name);
    };

    function F2(arg1,arg2, arg3) {
        //利用這種方式(有些地方成為“物件冒充”),繼承非函式屬性
        F1.call(this, arg1, arg2);
        this.school = arg3;
        this.alertSchool = function () {
            alert(this.school);
        };
    }
    //注意,這裡講F1的例項傳遞給F2的原型物件
    //注意,這裡的F1是不用帶引數的
    //我們使用這種方式(原型),繼承函式屬性
    F2.prototype = new F1();

    var f1 = new F1("ze1", "01038");
    var f2 = new F2("ze2", "01038", "SDU");
    f1.alertName();//ze1
    f2.alertName();//ze2
    f2.alertSchool();//SDU

            注意到上面的那些註釋!,以及前面內容的鋪墊,這些程式碼是不是很容易理解!通過這行程式碼可以看出(F2.prototype = new F1();),F2的原型物件(也就是F2.prototype)是F1的例項,這樣,F1的原型物件的所有的方法和屬性都將傳遞給F2.prototype,這樣,上面的那句程式碼就等價於下面這句程式碼:

//在這個例子中F2.prototype = new F1();等價於下面程式碼
//因為F1中只有一個alertName是原型方法(這裡只算自己定義的,其實還有很多是繼承而來的)

F2.prototype.alertName = function () {
        alert(this.name);
    };

            這樣寫,是不是就好理解了,就理解了為什麼F2的例項f2可以使用alertName方法了

這裡有一個小坑,就是:因為我們是將F2的原型物件重新賦予為F1的例項,這樣在F2.prototype = new F1()這句程式碼之前的所有為F2.prototype賦值的程式碼(例如F2.prototype.xxx=yyy)都將失效,所以一個解決方法是:將這些新的賦值程式碼寫在F2.prototype = new F1()這句程式碼之後就行了。

說了這麼多,是不是對js中的原型在應用層面有了較好的瞭解了呢!

注意:這篇部落格中的例項是基於ECMAScript規範來的,並不是javascript

相關推薦

js原型ECMAScript標準層面

在網上面搜了一下“什麼是js的原型,以及什麼是_proto_”啥的。。。。,然後出來的全是一堆什麼 alert(xxx.prototype===yyy._proto_)//true//false啥的,有毒吧!!還有原型鏈就更加不用說了,看著就頭疼,反正我是看不懂,全靠死記,

JS原型對象和原型

並且 nbsp 繼承 div prototype strong 存在 除了 函數 我們知道原型是一個對象,其他對象可以用它實現屬性繼承,除了prototype,又有__proto__ 1. prototype和__proto__的區別 prototype是函數才有的屬性

JS括號[]用法

中括號運算子總是能代替點運算子。但點運算子卻不一定能全部代替中括號運算子。 中括號運算子可以用字串變數的內容作為屬性名。點運算子不能。 中括號運算子可以用純數字為屬性名。點運算子不能。 中括號運算子可以用js的關鍵字和保留字作為屬性名。點運算子不能。     例一:    

js的MVC

模擬 ner end i++ 反饋 mov 構架 als 觀察 MVC是什麽? MVC是一種架構模式,它將應用抽象為3個部分:模型(數據)、視圖、控制器(分發器) 本文將用一個經典的例子todoList來展開 一個事件發生的過程(通信單向流動): 1、用戶在視圖V上與應用

JS的閉包

不能 程序 含義 函數 刪除 今天 func 空間 而且   今天 大年初一,祝各位小夥伴們狗年旺旺啊,閑來也沒事,只能鉆研一下自己的分內之事,也就是作為一個前端碼農的身份,得時刻保持學習的態度,溫故而知新,每天都給自己一個小目標去完成,日積月累,所想達到的狀態,都會有所見

JS的!=、== 、!==、===的用法和區別 JSNull與Undefined的區別 讀取XML文件 獲取路徑的方式 C#Cookie,Session,Application的用法與區別? c#反射 抽象工廠

main 收集 data- 時間設置 oba ase pdo 簡單工廠模式 1.0 var num = 1; var str = ‘1‘; var test = 1; test == num //true 相同類型 相同值 te

js的正則表示式

很多時候多會被正則表示式搞的暈頭轉向,最近抽出時間對正則表示式進行了系統的學習,整理如下: 正則表示式的建立 兩種方法,一種是直接寫,由包含在斜槓之間的模式組成;另一種是呼叫 RegExp 物件的建構函式。 兩種方法的建立程式碼如下: // 直接建立 const regex1 = /ab+c/; co

JS的'+'連結符

console.log('----1----')  console.log('12' + '34')//'1234'  console.log('12' + 34 )//'1234'  console.log(12 + '34')//'1234'  conso

js的for迴圈和while迴圈:

-for迴圈: 它的語法如下: for (語句 1; 語句 2; 語句 3) { 被執行的程式碼塊 } 語句 1 在迴圈(程式碼塊)開始前執行 語句 2 定義執行迴圈(程式碼塊)的條件 語句 3 在迴圈(程式碼塊)已被執行之後執行

js的eval()函式

1.定義 eval()是一個函式,有且只有一個引數string,為字串型別 eval(string) 特點:若string為js程式碼時,會直接解析執行,若是普通字串,則返回原字串。 2.例項 2.1引數string為js程式碼: eval("va

AndroidCallback回撥的使用

今天專案的Bug基本修改完成了,於是就對自己還未了解的回撥函式進行了學習。回撥其實就是在一定的時間裡做“一件事”,至於“這件事”具體做的是什麼不會管,只管做“這件事“,比如Boss叫員工去吃飯,但每個員工可能吃不同的食物。只不過,回撥是對介面而言。簡單來說就是,A物件呼叫

js如何動態新增表頭/表列/表格內容

我想很多童鞋用js動態向表格中新增資料很熟悉,而且也覺得非常簡單!是的,對於寫頁面的童鞋來說,最喜歡寫查詢的頁面了,動態向表格繫結資料。用for迴圈就可以輕鬆搞定。 如果我們的業務需求有所變化,可能我們要的資料就不是這樣一條一條的中規中矩的。如果你還是新手,又

JS的高階函式

在JavaScript中,函式的功能十分強大。它們是第一類物件,也可以作為另一個物件的方法,還可以作為引數傳入另一個函式,不僅如此,還能被一個函式返回!可以說,在JS中,函式無處不在,無所不能,堪比孫猴子呀!當你運用好函式時,它能助你取西經,讓程式碼變得優雅簡潔,運用不好

js“三元表示式” 三元運算子

前言 各位大神,大家好,相約週三。我們又見面了。 眾所周知,三元表示式在程式碼量上比if…else語句更簡潔一些。但是博主在可讀性上更加偏向於if…else語句。三元表示式不僅在js中使用,在很多後臺程式語言,比如java、php中都有使用,不過在js中對於

深入學習js作用域之eval()和with

在深入學習js之淺談作用域(一)中 將作用域定義為一套規則,用來管理引擎如何在當前作用域以及巢狀的子作用域中根據識別符號名稱進行變數查詢。 作用域分為兩種主要的工作模式:1.詞法作用域(大多數程式語言包括js) 2.動態作用域(Bash指令碼、Perl中的一些模式) 1.

js事件preventDefault()和addEventListener()

js中有許多預設事件方法,當我們觸發時就會自動執行,比如點選連結跳轉,右鍵彈出屬性選單等等。於是為了滿足我們自定義的行為,需要阻止事件預設行為,即preventDefault()方法。 preventDefault() preventDefault()是

JavaScript的事件事件處理程序

時差 s參數 dom mouse 點擊事件 dom節點 不同 進一步 腳本   事件就是用戶或者瀏覽器自身執行的某種動作。諸如click、load和mouseover,都是事件的名字。而響應某個事件的函數就叫事件處理程序。事件處理程序的名字以“on”開頭,比如click事件

JavaScript的事件事件類型

元素 滾動 鍵盤 合成 html 另一個 date mov 焦點事件   Web瀏覽器能夠發生的事件有很多種類型,不同的事件類型有不同的事件信息。DOM3級的事件類型主要包括:UI事件,用戶與頁面上的元素交互時觸發;焦點事件,元素獲得或失去焦點觸發;鼠標事件,用戶通過鼠標在

JavaScript的事件事件委托

沒有 str 方法 比較 獲取 ack 使用 點擊 通過   事件處理程序為Web程序提供了系統交互,但是如果頁面中的事件處理程序太多,則會影響頁面的性能。每個函數都是對象,都會占用內存,內存中對象越多,性能越差。需要事先為DOM對象指定事件處理程序,導致訪問DOM的次數增

JAVA位元組流讀寫檔案

InputStream  此抽象類是表示位元組輸入流的所有類的超類。需要定義 InputStream 的子類的應用程式必須始終提供返回下一個輸入位元組的方法。  int available()  返回此輸入流方法的下一個呼叫方可以不受阻塞地從此輸入流讀取(或跳過)的位