JS筆記—— 物件 (原型物件)

hahadelphi發表於2021-09-09

    JavaScript物件是一個很有意思的資料型別,由於js沒有類的概念,js物件就承擔起JavaScript物件導向的重任。
    JavaScript 並沒有類的概念,但是它有物件。
    ECMSscript將物件定義為無序屬性的集合,其屬性可以包含基本值、物件或者函式。
    JavaScript的物件的屬性沒有嚴格的順序,每個屬性和方法都需有一個名字和對應的值,看起來和JSON基本沒有區別(實際上js物件和JSON可相互轉換)。

物件基本概念

物件的建立非常的簡單>>>

顯示申明var obj = new Object()
obj.name = "tom"obj.age = 21obj.sayName = function(){return this.name}

**物件字面量**var obj = {name:"tom",            age:21,            sayName:function(){return this.name}
        }

    ECMA-262第五版定義了內部採用的特性時(attribute), 描述了屬性(property)
注:property是用來描述物件的,attribute是用來描述property的後設資料,此處的attribute區別於HTML的attribute,HTML標籤的attribute是標籤本身所具有的描述資料,和property描述的物件(DOM)不同,也就是這兩個的描述參照並不是同一基準

屬性

    為了支援JavaScript引擎工作,ECMAScript5規定了屬性的型別:資料屬性訪問器屬性

資料屬性

    資料屬性:資料屬性包含一個資料值的位置,在這個位置可以讀取和寫入值。資料屬性有四個描述其行為的特性。

[[Configurable]]:是否能夠對屬性進行其他更改,包括刪除,修改等[[Enumerable]]:是否能透過for-in迴圈返回屬性[[Writable]]:如字面意思,能否寫入值(修改值)[[Value]]:包含屬性的資料值,代表屬性值得儲存位置

四個特性預設值為,true,true,true,undefined

    修改屬性預設特性:Object.defineProperty(obj,propertyName,attrConfig)
    該方法接收三個引數:修改的物件,修改的屬性值名稱,該屬性的特性描述符物件

例如:var Car = {}Object.defineProperty(Car,"price",{    writeable:false,    value:120000})

注意:運用Object.defineProperty()產生的屬性的特性預設為false,false,false,undefined
所以上面的例子中Car的price屬性中,configurable預設為false,enumerable預設為false
直接透過物件建立的屬性並無此限制

訪問器屬性

訪問器屬性就是我們在其他語言中常看到的getter和setter函式,訪問器屬性並不包含值,它也有四個特性

[[Configurable]]:是否能夠對屬性進行其他更改,包括刪除,修改等
[[Enumerable]]:是否能透過for-in迴圈返回屬性
[[Get]]:讀取資料時呼叫的函式,預設為undefined[[Set]]:寫入資料時呼叫的函式,預設為undefined

例如:

var people = {    name:"tom",    _age:17,    adult:false}Object.defineProperty(people,"age",{    get:function(){        return this._age
    },    set:function(val){        this._age = val        if(val > 18){            this.adult = true
        }
    }
})

定義多個屬性
要想定義一個屬性就呼叫一次definProperty方法會非常繁瑣,ES5就存在方法defineProperties支援多屬性定義,只是傳入的引數有些許區別
例如:

Object.defineProperties("people",{    name:{
        writable:false,
        value:"Amy"
    },
    _age:{        get:function(){}        set:function(){}
    }
})

讀取屬性的特性
使用ECMAScript5的Object.getOwnPropertyDescriptor(),可以取得給定屬性的描述符
引數:包含待查屬性的物件,待查屬性的名稱
例如:

var perple = {}Object.defineProperty(perple,"name",{    writable:false,    value:"Jack"})var result = Object.getOwnPropertyDescriptor(people,"name")console.log(result.value)console.log(result.writable)

原型物件

    物件的建立往往是重複的,就好像建立一個學生物件並不能完成所有工作,如果牽扯到班級的概念,可能就要建立班級中所有學生的物件了,物件也有很多的重複性,比如一個班的人所在班級學校都是一樣的,是共有的,為了簡化程式碼,也就有了原型物件的概念。
注:物件建立中,每一個學生代表一個例項,這和其他OO(物件導向)語言的類具有相似的理念。

    我們建立的每一個函式都有一個prototype(原型)屬性,這個屬性是一個指標,指向一個物件,而這個物件的用途。就是可以包含那些由特定型別的例項共享的屬性和方法。透過字面意思,prototype就是透過呼叫建構函式而建立的那個物件的原型物件。而原型物件也有一個指標constructor指向他所代表的建構函式。例如:Person.prototype.constructor == Person

    原型物件就像是一個班級的標籤,例項就是班級的學生,不管這個班級來了多少個新學生,只要這個班級叫一年級,那麼這個班級的學生就都是一年級學生。原型物件包含所有例項公共的屬性為所有例項共享。
    建立了自定義的建構函式之後,其原型物件預設只會取得 constructor 屬性;至於其他方法,則都是從 Object 繼承而來的。當呼叫建構函式建立一個新例項後,該例項的內部將包含一個指標(內部屬性),指向建構函式的原型物件。ECMA-262 第 5 版中管這個指標叫 [[Prototype]] 。雖然在指令碼中沒有標準的方式訪問 [[Prototype]] ,但 Firefox、Safari 和 Chrome 在每個物件上都支援一個屬性__proto__;而在其他實現中,這個屬性對指令碼則是完全不可見的。不過,要明確的真正重要的一點就是,這個連線存在於例項與建構函式的原型物件之間,而不是存在於例項與建構函式之間。

function Person(){}
    Person.prototype.name="Nicholis"
    Person.prototype.age=29
    Person.prototype.job="Software Engineer"
    Person.prototype.sayName=function(){        console.log(this.name)
}var person1 = new Person()
person1 .sayName() // Nicholisvar person2 = new Person()
person2 .sayName() // Nicholisconsole.log(person1 .sayName == person2 .sayName) // true

圖片描述

物件例項關係圖

需要注意的幾點:

  1. 建構函式的prototype指標指向該特定型別物件(Person)的原型物件,同時該物件的constructor指正指向該建構函式。

  2. 例項的[[prototype]]並非建構函式的prototype指標,在主流瀏覽器中的實際實現是_proto_指標,指向建構函式的原型物件。

  3. 例項與建構函式並無直接關係

原型物件屬性和例項屬性

原型物件屬性說不定也有和例項屬性衝突的時候,比如運動會的時候大多數人在開幕式的任務就是走佇列喊口號,但是領頭的人喊的口號和班裡其他人很有可能不同。
領頭:一年三班
同學:團結一心
領頭一年三班
同學:共創佳績

在JS中,發生了衝突該如何選擇,和現實中也是相差無幾的。

function Class13(){}
Class13.prototype.sentence= "永爭第一";
Class13.prototype.say = function(){
    alert(this.sentence);
};var student = new Class13(); // 大多數同學var leader= new Class13();  // 領頭人leader.sentence= "一年三班";
alert(leader.say ()); //"一年三班" —— 來自例項alert(student .say ()); //"永爭第一" ——原型

    以上程式碼不難看出,student例項沒建立例項屬性,便輸出了原型物件屬性,而設定了例項屬性的leader輸出的是例項屬性,領頭人在作為同學的基礎上口號是“永爭第一”,但是新的任務讓他產生了新的屬性,遮蔽了之前的設定。
    當為物件例項新增一個屬性時,這個屬性就會遮蔽原型物件中儲存的同名屬性;換句話說,新增這個屬性只會阻止我們訪問原型中的那個屬性,但不會修改那個屬性。即使將這個屬性設定為 null ,也只會在例項中設定這個屬性,而不會恢復其指向原型的連線。

更簡單的原型語法

每一次都要書寫xxx.prototype著實讓人心煩,還好我們有更簡便的方法——字面量

function Person(){}
Person.prototype = {    name : "Nicholas",    age : 29,    job: "Software Engineer",    sayName : function () {
        alert(this.name);
    }
};

但是用字面量的方法hi重寫整個原型物件,導致的直接後果就是constructor指標不再是預設指向建構函式,而是Object
解決辦法一:在字面量中定義constructor

function Person(){}
Person.prototype = {    constructor : Person,    name : "Nicholas",    age : 29,    job: "Software Engineer",    sayName : function () {
        alert(this.name);
    }
};

問題:constructor預設資料屬性enumerable會設定為true

解決辦法二:透過Object.defineProperty()重新定義constructor

Object.defineProperty(Person.prototype, "constructor", {    enumerable: false,
    value: Person
});

原型物件的問題

  1. 省略了為建構函式傳遞初始化引數這一環節,結果所有例項在預設情況下都將取得相同的屬性值。

  2. 原型物件包含引用型別值的屬性,會使得例項對該屬性的操作產生連鎖反應。

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

以上程式碼反映了引用型別在原型物件中的問題:當例項直接對引用型別進行方法操作時,將不會為例項建立同名例項屬性,而是定址,找到friends陣列的地址,再進行push操作,這就導致了例項改變了原型物件的值,使得其他例項也會取得改變後的friends。


單純的將引用型別進行賦值操作,那就不會有以上問題,因為這個過程中並沒有定址這個環節,而直接進行了例項屬性申明和賦值。如下:

var person1 = new Person();var person2 = new Person();
person1.friends=["hello"];console.log(person1.friends); //"hello"console.log(person2.friends); //"Shelby,Court,Van"console.log(person1.friends === person2.friends); //false

圖片描述

未命名檔案.png

相關方法

isPrototypeOf()
用途:判斷例項與目標是否含有原型物件關係,即判斷物件是否是某例項的原型物件

alert(Person.prototype.isPrototypeOf(person1)); //truealert(Person.prototype.isPrototypeOf(person2)); //true

Object.getPrototypeOf()
用途:獲取例項的原型物件

alert(Object.getPrototypeOf(person1) == Person.prototype); //truealert(Object.getPrototypeOf(person1).name); //"Nicholas"

hasOwnProperty()
用途: 檢測一個屬性是存在於例項中。

function Person(){}
Person.prototype.name = "Tom";var person1 = new Person();var person2 = new Person();
person2.name = "Amy"alert(person1.hasOwnProperty("name")); //falsealert(person2.hasOwnProperty("name")); //truealert(person2.hasOwnProperty("nam")); //false

注:該方法結合運算子 in 可判斷屬性來自例項還是原型
in運算子會判斷給定屬性是否能透過物件訪問,如果能,返回true,不能,返回false
結合hasOwnProperty()就很好判斷屬性來源了

function hasPrototypeProperty(object, name){    return !object.hasOwnProperty(name) && (name in object);
}
alert(hasPrototypeProperty(person1, "name")); //truealert(hasPrototypeProperty(person2, "name")); //false

Object.keys()
用途:要取得物件上所有可列舉的例項屬性。接收一個物件作為引數,返回一個包含所有可列舉屬性的字串陣列。

function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software 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"

Object.getOwnPropertyNames()
用途:得到所有例項屬性,無論它是否可列舉。

ar keys = Object.getOwnPropertyNames(Person.prototype);
alert(keys); //"constructor,name,age,job,sayName"// constructor 不可列舉



作者:FocusOn_
連結:


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2471/viewspace-2817315/,如需轉載,請註明出處,否則將追究法律責任。

相關文章