JavaScript 變量、函數與原型鏈

 定義 || 賦值java

    1-函數的定義
      函數定義的兩種方式:
        「定義式」函數:function fn(){ alert("喲,喲!"); }
        「賦值式」函數:var fn = function(){ alert("切可鬧!"); }
        @頁面加載時,瀏覽器會對JavaScript代碼進行掃描,並將 定義式函數進行預處理(相似C等的編譯)。【函數聲明提高】
         處理完再由上至下執行,遇到賦值式函數 則只是將函數賦值給一個變量,不進行預處理,待調用時才進行處理。
        @在定義前面調用函數時,定義式函數正常執行,賦值式函數會報錯 (提示:oFn is not a function)。數組

    2-變量與函數的定義
      變量:①var a; 定義變量a。
         ②var a = 123;  定義變量a,再給變量a賦值。
      函數:①function fn(...){...}  聲明函數fn。
         ②var oFn = function(...){...}  先定義變量oFn和一個匿名函數,再將匿名函數賦值給變量oFn。
        @定義變量和定義函數都會先預處理,變量賦值則是在執行中完成。
        @定義變量的做用:只是指明變量做用域。
         有定義沒賦值的變量 和 使用沒定義的變量 值都爲undefined。
        @定義函數的做用:除了指明函數做用域,同時定義函數體結構——包括函數體內部的變量定義和函數定,此過程遞歸。瀏覽器

複製代碼
            alert(a);    //function a(){}
            alert(b);    //function b(){}
            alert(c);    //undefined
            var a = "a";
            function a() {}
            function b() {}
            var b = "b";
            var c = "c";
            var c = function() {}
            alert(a);    //a
            alert(b);    //b
            alert(c);    //function(){}

①雖然第一個 alert(a) 在最前面,可是你會發現它輸出的值居然是 function a() {},這說明,函數定義確實在整個程序執行以前就已經完成了。
②再來看 b,函數 b 定義在變量 b 以前,可是第一個 alert(b) 輸出的仍然是 function b() {},這說明,變量定義確實不對變量作什麼,僅僅是聲明它的做用域而已,它不會覆蓋函數定義。
③最後看 c,第一個 alert(c) 輸出的是 undefined,這說明 var c = function() {} 不是對函數 c 定義,僅僅是定義一個變量 c 和一個匿名函數。
④再來看第二個 alert(a),你會發現輸出的居然是 a,這說明賦值語句確實是在執行過程當中完成的,所以,它覆蓋了函數 a 的定義。
⑤第二個 alert(b) 固然也同樣,輸出的是 b,這說明無論賦值語句寫在函數定義以前仍是函數定義以後,對一個跟函數同名的變量賦值總會覆蓋函數定義。
⑥第二個 alert(c) 輸出的是 function() {},這說明,賦值語句是順序執行的,後面的賦值覆蓋了前面的賦值,無論賦的值是函數仍是其它對象。 
複製代碼

    3-變量賦值
      對於弱類型的JavaScript,聲明變量不須要聲明其類型。
      隨之的問題,在使用 直接量和引用量 卻混亂一片:
        ①var x = "111";  var y = x;  x = "222";  alert(y);
          在JavaScript中,此時y值爲111,即字符串的賦值是直接量操做,直接把數據賦值給y的存儲空間。
          在java等語言中,y的值爲222,即x在存儲器中將地址(指針)賦給變量y。
        ②var x = ["111"];  var y = x;  x[0] = "222";  alert(y[0]);
          在JavaScript中,此時卻與①不一樣,y[0]值爲222,引用量操做,即x把在存儲器中的地址(指針)賦給了y。
        ③var x = ["111"];  var y = x;  x = ["222","333"];  alert(y[0]);
          在Javascript中,此時y的值又是111,即此賦值又是直接量操做。
      JavaScript解析器 對不一樣類型的差別:
        var x = "xxxx";  var y = ["11","22"];
        ①在字符串中,解析器直接把字符串賦給變量x(直接量)。
        ②在數組中,解析器把數組的指針賦給變量y(引用量)。
        上述問題②中,x[o] = "222"因爲沒有給x新定義值,沒有新開闢存儲空間,只修改了它存儲空間裏的數據,故仍是引用量。
        上述問題③中,建立var x = ["111"]時,解析器在內存中爲數組建立存儲空間,x則得到該空間的地址(指針),
               再執行x = ["2","3"]給數組新定義值時,解析器會開闢新存儲空間放這個數組,x則爲新存儲空間的指針。ide

      由上述可知,JavaScript的變量能存儲直接量 也能存儲引用量。
        在大字符串鏈接 和 循環裏賦值等地方,需留意此變量特性對執行效率的影響。函數

複製代碼
var x="";
var big = "這裏是一大坨字符串...";
for (i=0; i<100; i++){
     a += big;
}
x = a; 

//由於是字符串操做,使用直接量,每次循環都要操做大字符串,很是笨重,效率低下。若是改用引用量操做,即經過數組,效率甚至會提升十幾倍: 

var s = "這裏又是一大坨字符串...";
var a = [];
for (i=0; i<100; i++){
    a[i] = s;
}
s = a.join(""); 
複製代碼

      4-原型的定義和賦值
      原型:若是構造器有個原型對象A,由構造器建立的對象實例(Object Instance)都複製於原型對象A。
        ①每一個對象都有一個原型鏈,由自身向上包含一個或多個對象,自己爲起始對象。
        ②在JavaScript中,一個對象 或 一個對象實例沒有原型,不存在「持有某個原型」的說法,只存在「構造自某個原型」的說法。
                構造器纔有原型,<構造器>.prototype屬性指向原型。spa

複製代碼
function fn() {
    var name = "大蝦";
    var age = 23;
    function code() {
        alert("切克鬧!");
    };
}
var obj = new fn();
複製代碼

        上述代碼中:
          ①obj爲對象實例,fn爲一個構造器。
          ②obj.prototype;  //undefined,對象實例沒有原型。
           fn.prototype;  //[object Object],原型是一個對象。
          ③obj.constructor;  //輸出fn()的函數代碼。
           fn.construtor;  //function Function(){[native code]},native code表示JavaScript引擎的內置函數。
           obj.construtor == fn;  //true,obj構造自fn。
          ④fn.prototype.construtor == fn;  //true,函數原型的構造器 默認爲函數自己。prototype

      對象實例 複製構造器的原型對象時,採用的是讀遍歷機制複製的。
        讀遍歷機制:指僅當寫某個實例的成員時,將成員信息複製到實例映像中。
              即構造的新對象裏面的屬性 指向原型中的屬性。讀取對象實例的屬性時,獲取的是原型對象的屬性值。指針

複製代碼
    Object.prototype.value = "abc";
    var obj1 = new Object();
    var obj2 = new Object();
    obj2.value = 10;
    
    alert(obj1.value);//輸出abc,讀取的是原型Object中的value
    alert(obj2.value);//輸出10,讀取的是obj2成員列表中的value
    delete obj2.value;//刪除obj2中的value,即在obj2的成員列表中將value刪除掉
    alert(obj2.value);//輸出abc,讀取的是原型Object中的value
複製代碼

               

      上述說明了讀遍歷機制 如何管理實例對象成員列表 和 原型中的對象成員。
        ①只有第一次對屬性進行寫操做時,纔會在對象的成員列表中 添加該屬性的記錄。
        ②當obj1和obj2經過new構造出來,只是一個指向原型的引用,這樣的讀遍歷 避免了建立對象實例可能的大量內存分配。
        ③obj2.value屬性被賦值10時,obj2的成員表中添加了一個value成員並賦值10。
          此成員表記錄了對象發送了修改的成員名、值與類型。遵循2個原則:
          a、保證在讀取是首先訪問。
          b、對象中午指定屬性時,遍歷對象的整條原型鏈,直到原型爲空或找到該屬性。
        ④delete obj2.value刪除的是成員表的屬性。code

      原型的構造器
        函數的原型 是內置的Object()構造器的一個實例。但該對象實例建立後,constructor屬性總會先被賦值爲當前函數。
        究其根源在於構造器(構造函數)的原型(prototype)的constructor屬性指向構造器自己。對象

複製代碼
function MyObject() {
}

alert(MyObject.prototype.constructor == MyObject);  //顯示true,代表原型的構造器老是指向函數自身的

delete MyObject.prototype.constructor;  //刪除該成員(指向自身的原型構造器)

alert(MyObject.prototype.constructor == Object);
alert(MyObject.prototype.constructor == new Object().constructor);
//刪除操做使該成員指向了父代類原型中的值
//均顯示爲true
複製代碼

         上例中,myObject.protptype與new Object()沒有實質區別,只是在建立時將myObject的constructor值賦值爲自身。
        函數與構造器並無明顯的界限:
          當指定一個函數的prototype時,該函數就會成爲構造器。
          此時用new建立實例時,引擎會構造一個新對象,把這個新對象的原型鏈 鏈接向該函數prototype屬性就能夠了。

       原型繼承中的「原型複製」
        經過設置 不一樣構造器建立出來的實例的constructor屬性,能指向同個構造器。

複製代碼
function MyObject() {
}
function MyObjectEx() {
}
MyObjectEx.prototype = new MyObject();
var obj1 = new MyObject();
var obj2 = new MyObjectEx();
alert(obj2.constructor == MyObject);    //true
alert(MyObjectEx.prototype.constructor == MyObject);    //true
複製代碼

        obj1和obj2是由不一樣的兩個構造器(MyObject和MyObjectEx)產生的實例。然而,兩個alert都會輸出true,即由兩個不相同的構造器產生的實例,它們的constructor屬性卻指向了相同的構造器。這體現了原型繼承中的「原型複製」。MyObjectEx的原型是由MyObject構造出來的對象實例,即obj1和obj2都是從MyObject原型中複製出來的對象,所以它們的constructor指向的都是MyObject!

相關文章
相關標籤/搜索