js高級程序設計-讀書記錄 ch6

6.1.1 屬性類型
1ECMA有兩種屬性:數據屬性和訪問器屬性
1.數據屬性
這都是處理對象的嗎
要修改屬性默認的特性,必須使用object.defineProperty()方法。這個方法接收三個參數:屬性所在的對象,屬性的名字和一個描述符對象。描述符對象的屬性必須是:configurable. enumerable.writable和value。設置其中的一或多個值,可以修改對應的特性值如
var person={};
Object.definePropetyName(person,"name",{
    writable:false;
    value:"nicola";//只讀的 賦值操作會被忽略,在嚴格模式下,賦值操作將會導致拋出錯誤。
}};
alert(person.name);//nicola 
他媽的這例子真的有反應它的作用嘛????
把configurable設置爲false,表示不能從對象中刪除屬性。如果對這個屬性調用delete,則在非嚴格模式下什麼也不會發生,嚴格模式拋錯。
默認值都是false
2.訪問器屬性
不包含數據值,包含一對兒函數:getter和setter。
var book={
    _year:2004,
    edition:1
};
Object.defineProperty(book, "year",{
    get:function(){
        return this._year;
    },
    
    set:function(newValue){
        if(newValue>2004){
            this._year=newValue;
            this.edition+=newValue-2004;
        }
    }
});

book.year=2005;
alert(book.edition); //2
_year中的下劃線是一種常用的記號,用於表示只能通過對象方法訪問的屬性。而訪問器屬性則包含一個getter函數和一個setter函數。這是使用訪問器屬性的常見方法,即設置一個屬性的值會導致其它屬性發生變化。
不一定要同時制定getter和setter,只指定getter意味着只讀。
6.1.2 定義多個屬性
Object.defineProperties()方法:由於爲對象定義多個屬性的可能性很大,使用該方法可以通過描述符一次定義多個屬性。接收兩個對象參數:第一個對象是要添加和修改其屬性的對象,第二個對象的屬性與第一個對象中藥添加或修改的屬性一一相應。如:

var book={};
Object.defineProperties(book,{
    _year:{
        value:2004
    },
    edition:{
        value:1
    },
    year:{
        get:function(){
            return this._year;
        },
   
        set:function(newValue){//自動獲取newValue嗎
            if(newValue>2004){
                this._year=newValue;
                this.edition+=newValue-2004;
            }
        }
    }
});
以上代碼對外上定義了兩個數據屬性(_year和edition)和一個訪問器屬性(year)
6.1.3 讀取屬性的特性
Object.getOwnPropertyDescriptor()方法,可以取得給定屬性的描述符。接收兩個參數:屬性所在的對象和要讀取器描述符的屬性名稱。返回值是一個對象,如果是訪問器屬性,這個對象的屬性有configurable, enumerable, writable和value。例如:
var book={};
Object.defineProperties(book,{
    _year:{
        value:2004
    },
    
    edition:{
        value:1
    },
    
    year:{
        get:function(){
            return this._year;
        },
        
        set:function(newValue){
            if(newValue>2004){
                this._year=newValue;
                this.edition+=newValue-2004;
            }
        }
    }
});

var descriptor = Object.getOwnPropertyDescriptor(book,"_year");
alert(descriptor.value); //2004
alert(descriptor.configurable);//false(能否delete)
alert(typeof descriptor.get);//undefined
var descriptor = Object.getOwnPropertyDescriptor(book, "year");
alert(descriptor.value);//undefined pourquoi?
alert(descriptor.enumerable);//false
alert(typeof descriptor.get);//funtion
//對於數據屬性_year,value等於最初的值,configurable是false,而get等於undefined。對於訪問器屬性year,value等於undefined,enumerable是false,而get是一個指向getter函數的指針。
6.2 創建對象

6.2.1 工廠模式
用函數來封裝以特定接口創建對象的細節,如
function createPerson(name,age,job){
    var o=new Object();
    o.name=name;
    o.age=age;
    o.job=job;
    o.sayName=function(){
        alert(this.name);
    };
    return o;
}
var person1=createPerson("nicolas",29,"software engineer");
var person2=createPerson("greg",27,"doctor");

6.2.2 構造函數模式
構造函數可用來創建特定類型的對象。
像Object和array這樣的原生構造函數,在運行時會自動出現在執行環境中。此外,也可以創建自定義的構造函數,從而定義自定義對象類型的屬性和方法。例如

function Person(name,age,job){
    this.name=name;
    this.age=age;
    this.job=job;
    this.sayName=function(){
        alert(this.name);
    };
}

var person1=new Person("nico",29,"software engineer");
var person2=new Person("greg",27,"doctor");
//沒有顯式地創建對象
//直接將屬性和方法賦給了this對象
//沒有return語句
//按照慣例,構造函數始終都應該以一個大寫字母開頭,而非構造函數應該以一個小寫字母開頭。

要創建Person的實例,必須使用new操作符。以這種方式調用構造函數實際上會經歷以下4個步驟:
(1)創建一個新對象;
(2)將構造函數的作用域賦給新對象(因此this就指向了這個新對象)
(3)執行構造函數中的代碼(爲這個新對象添加屬性)
(4)添加新對象

對象的constructor屬性最初是用來標識對象類型的,但是,檢測對象類型,還是instanceof 操作符更可靠一些。
1.將構造函數當做函數
構造函數與其它函數的唯一區別就在於它們的調用方式。構造函數不存在定義的特殊語法。任何函數,只用通過new操作符來調用,那麼它就可以作爲構造函數。
看例子
//當作構造函數使用
var person=new Person("nico",29,"software engineer");
person.sayName();//nico

//作爲普通函數
Person("greg",27,"doctor");//添加到window
window.sayName(); //greg

//在另一個對象的作用域中調用
var o=new Object();
Person.call(o,"kris",25,"nurse");
o.sayName();//kris
2.構造函數的問題
就是構造函數中的每個方法都要在每個實例上重新創建一遍。(函數也是對象,因此每定義一個函數,就是實例化了一個對象)不同實例上的同名函數是不相等的。
不如把函數定義轉移到構造函數外部,如
function Person(name,age,job){
    this.name=name;
    this.age=age;
    this.job=job;
    this.sayName=sayName;//把它變成一個指針
}

function sayName(){//全局函數
    alert(this.name);//但是此處的this?
}
//但是此種方法沒有封裝性了
var person1=new Person("nico",29,"engineer");
var person2=new Person("greg",27,"doctor");
6.2.3 原型模式
創建的每個函數都有一個prototype屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含可以由特定類型的所有實例共享的屬性和方法。prototype就是通過調用構造函數而創建的那個 對象實例的原型對象(您說了跟沒說似的)。換句話說,不必在構造函數中定義對象實例的信息,而是可以將這些信息直接添加到原型對象中。如
function Person(){
    //構造函數變成空函數
}

Person.prototype.name="nico";
Person.prototype.age=29;
Person.prototype.job="engineer";
Person.prototype.sayName=function(){
    alert(this.name);
};

var person1=new Person();
person1.sayName();//"nico"

var person2=new Person();
person2.sayName();//"nico"

alert(person1.sayName==person2.sayName);//true 兩個函數的指針
1.理解原型對象
無論什麼時候,只要創建了一個新函數,就會根據一組特定的規則爲該函數創建一個prototype屬性,這個屬性指向函數的原型對象。在默認情況下,所有原型對象都會獲得一個構造函數屬性,這個屬性包含一個指向prototype屬性所在函數的指針。就拿前面的例子來說,person.prototype.constructor指向person。
創建了自定義的構造函數之後,其原型對象默認只會獲得constructor屬性,其它方法都是從object繼承而來的。當調用構造函數創建一個新實例時,該實例的內部將包含一個指針(內部屬性),指向構造函數的原型對象。
 
Person.prototype指向了原型對象,而Person.prototype.constructor又指回了Person。Person的每個實例——person1和person2都包含一個內部屬性,屬性僅僅指向了Person.prototype,它們與構造函數沒有直接的關係。
alert(Person.prototype.isPrototypeOf(person1)); //true
使用Object.getPrototypeOf()可以方便取得一個對象的原型,而這在利用原型實現繼承的情況下是非常重要的。

每當代碼讀取某個對象的某個屬性時,都會執行一次搜索,目標是具有給定名字的屬性。搜索首先從對象實例本身開始。如果在實例中沒找到就繼續搜索指針指向的原型對象。

原型最初只包含constructor屬性,而該屬性也是共享的,因此可以通過對象實例訪問。

原型中的值只能被覆蓋(屏蔽),不能被重寫。
function Person(){
    
}

Person.prototype.name="nico";
Person.prototype.age=29;
Person.prototype.job="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); //nico

delete person1.name;//delete的用法
alert(person1.name); //nico

使用hasOwnProperty()方法可以檢測一個屬性是存在於實例中,還是在原型中。如
function Person(){
    
}

Person.prototype.name="nico";
Person.prototype.age=29;
Person.prototype.job="engineer";
Person.prototype.sayName=function(){
    alert(this.name);
};

var person1=new Person();
var person2=new Person();
alert(person1.hasOwnProperty("name")); //false


person1.name="greg";
alert(person1.name);
alert(person1.hasOwnProperty(name)); //true
  1. 原型與in操作符
有兩種方式使用in操作符:單獨使用和在for-in循環中使用。在單獨使用時,in操作符會在通過對象能夠訪問給定屬性時返回true,無論該屬性存在於實例中還是原型中。同時使用hasOwnProperty()方法和in操作符,就可以確定該屬性到底是存在於對象中,還是在原型中。
function hasPrototypeProperty(object, name){
    return !object.hasOwnProperty(name)&&(name in object);
}
要取得對象上所有可枚舉的實例屬性,可以使用ECMA的Object.keys()方法。這個方法接收一個對象作爲參數,返回一個包含所有可枚舉屬性的字符串數組。看例子
function Person(){
    
}

Person.prototype.name="nico";
Person.prototype.age=29;
Person.prototype.job="engineer";
Person.prototype.sayName=function(){
    alert(this.name);
}//所以相當於這個函數返回的是指向該函數的指針吧

var keys=Object.keys(Person.prototype);
alert(keys); //name age job sayName

var p1=new Person();
p1.name="rob";
p1.age=31;
var p1keys=Object.keys(p1);
alert(p1keys); //name age
3.更簡單的原型語法
function Person(){
    
}
//對象字面量
Person.prototype={
    //如果constructor的值真的很重要可以寫成
    //constructor:Person,
    name:"nico";
    age:29;
    job:"engineer";
    say : function(){
    alert(this.name);
}
}
但是constructor屬性不再指向Person了。此處的語法本質上完全重寫了默認的prototype對象,因此constructor屬性也就變成了新對象的constructor屬性。(指向object構造函數),不再指向Person函數。
注意(以下都看不懂):以這種方式(添加了constructor屬性)重設constructor屬性會導致它的enumerable特性被設置爲true。默認情況下,原生的constructor屬性是不可枚舉的。
4.原型的動態性
實例中的指針僅指向原型,而不指向構造函數。看例子:
function Person(){
    
}
var friend =new Person();
Person.prototype={
    constructor:Person,
    name:"nico",
    age:29,
    job:"doc",
    sayName:function(){
        alert(this.name);
    }
};
friend.sayName(); //error
因爲是先創建了對象,而後又重寫了原型。而把原型修改爲另外一個對象就等於切斷了構造函數與最初原型之間的聯繫。
(重寫原型對象切斷了現有原型與任何之前已經存在的對象實例之間的聯繫;它們引用的仍然是最初的原型)
5.原生對象的原型
原型模式的重要性不僅體現在創建自定義類型方面,就連所有原生的引用類型,都是採用這種模式創建的。
所有原生引用類型(object,array,string)都在其構造函數的原型上定義了方法。
通過原生對象的原型,不僅可以取得所有默認方法的引用,而且也可以定義新方法。可以像修改自定義對象的原型一樣修改原生對象的原型,因此可以隨時添加方法。下面的代碼就給基本包裝類型String添加了一個名爲startsWith()的方法。
String.prototype.startsWith=function(text){
    return this.indexOf(text)==0;
};

var msg="hello world";
alert(msg.starsWith("hello")); //true
//在傳入的文本位於一個字符串開始時返回true
原型對象的問題:1.省略了爲構造函數傳遞初始化參數這一環節。結果所有實例在默認情況下都將取得相同的屬性值。2.最大問題是由其共享的本性所導致的。看例子
function Person(){
    
}
Person.prototype={
    constructor:Person,
    name:"nico",
    age:29,
    job:"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
所以很少有人單獨使用原型模式。
6.2.4 組合使用構造函數模式和原型模式
構造函數模式用於定義實例屬性,而原型模式用於定義方法和共享的屬性。結果,每個實例都會有自己的一份實例屬性的副本,但同時又共享着對方法的引用個,最大限度地節省了內存,而且這種方式還支持向構造函數傳遞參數。如:
function Person(name, age, job){
    this.name=name;
    this.age=age;
    this.job=job;
    this.friends=["shelby","court"];
}
Person.prototype={
    constructor:Person,
    sayName:function(){
        alert(this.name);
    }
};
//其實這也相當於把屬性和方法分開了吧
var person1=new Person("nico",29,"engineer");
var person2=new Person("greg",27,"doctor");

person1.friends.push("van");//此時這個數組還是指向原型中的,所以共享了?!
alert(person1.friends); //shelby court van
alert(person2.friends); //shelby court
alert(person1.friends===person2.friends); //false
alert(person1.sayName===person2.sayName); //true
6.2.5 動態原型模式
動態原型模式把所有信息封裝在了構造函數中,而通過在構造函數中初始化原型(僅在必要的情況下),又保持了同時使用構造函數和原型的優點。換句話說(作者口頭禪),可以通過檢查某個應該存在的方法是否有效,來決定是否需要初始化原型。看例子
function Person(name,age,job){
    //屬性
    this.name=name;
    this.age=age;
    this.job=job;
    
    //方法
    //只有sayName()方法不存在的情況下,纔會把它添加到原型中。
    //這段代碼只有在初次調用構造函數時纔會執行。
    //這裏對原型所做的修改,能夠立即在所有實例中得到反映。
    if(typeof this.sayName!="function"){
        Person.prototype.sayName=function(){
            alert(this.name);
        };
    }
}

var friend = new Person("nico",29,"engineer");
friend.sayName();

在使用動態模型時,不能使用對象字面量重寫原型。如果在已經創建了實例的情況下重寫原型,那麼就會切斷現有實例與新原型之間的聯繫。

6.2.6 寄生構造函數模式
通常,在前述的幾種模式都不適用的情況下,可以使用寄生parasite構造函數模式。基本思想:創建一個函數,該函數的作用僅僅是封裝創建對象的代碼,然後再返回新創建的對象;但從表面上看,這個函數又很像是典型的構造函數。看例子
function Person(){
    var o=new Object();
    o.name=name;
    o.age=age;
    o.job=job;
    o.sayName=function(){
        alert(this.name);
    };
    return o;
}
var friend =new Person("nico",29,"engineer");
friend.sayName();//nico
//這個方法很眼熟了

再看個例子
function SpecialArray(){
    var values=new Array();//
    
    //添加值
    values.push.apply(values,arguments);
    
    //添加方法
    values.toPipedString=function(){
        return this.join("|");
    };
    
    //返回數組
    return values;
}
var colors=new SpecialArray("red","blues","green");
alert(color.toPipedString());
關於寄生構造函數模式的說明:返回的對象與構造函數或者與構造函數的原型屬性之間沒有關係;也就是說,構造函數返回的對象與在構造函數外部創建的對象沒有什麼不同。(看不懂)

6.2.7 穩妥構造函數模式
穩妥對象:沒有公共屬性,而且其方法也不引用this的對象。最適合在一些安全的環境中,或者在防止數據被其它應用程序改動時使用。直接看例子吧
function Person(name,age){
    var o=new Object();
    //可以在此處定義私有變量和函數
    
    //添加方法
    o.sayName=function(){
        alert(name);
    };
    
    return o;
}

6.3 繼承
ECMA只支持實現繼承。因爲函數沒有簽名,故無法實現接口繼承。(簽名是啥,我也不懂)
6.3.1 原型鏈
利用原型讓每一個引用類型繼承另一個引用類型的屬性和方法。
“假如讓原型對象等於另一個類型的實例,此時的原型對象將包含一個指向另一個原型的指針。相應地,另一個原型中也包含着指向另一個構造函數的指針”如此層層遞進,就構成了實例與原型的鏈條。
一種基本模式,如:
function SuperType(){
    this.property=true;
}

//原型對象中定義的方法
SuperType.prototype.getSuperValue=function(){
    return this.property;
};

function SubType(){
    this.subproperty=false;
}

//繼承了SuperType
SubType.prototype=new SuperType();
//↑原型對象指向另一個實例(父對象)? 而這個實例又指向的是其對應的原型對象 這樣相當於把兩個原型對象聯繫起來了吧

//它其中還是有getSuperValue的吧...
SubType.prototype.getSubValue=function(){
    return this.subproperty;
};
var instance=new SubType();
alert(instance.getSuperValue()); //true

沒有使用SubType默認提供的原型,而是給它換了一個新原型。
調用instance.getSuperValue()會經歷三個搜索步驟:1)搜索實例,2)搜索SubType.prototype。3)搜索SuperType.prototype(找到)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章