本文內容:
一、構造函數、原型對象與實例對象之間的關係;
二、isPrototypeOf()和Object.getPrototypeOf();
三、實例對象上與原型對象上同名的屬性處理;
四、hasOwnProperty()方法和in操做符判斷屬性來自實例對象自己仍是它的原型對象;
五、for-in、Object.keys()和Object.getOwnPropertyNames()方法獲取實例對象或者原型對象上的屬性;
六、需注意的特殊問題javascript
function Person () {} Person.prototype.name = 'zhangsan' Person.prototype.age = 23 Person.prototype.sayName = function () { console.log(thi.name) } var p1 = new Person() p1.sayName() // 'zhangsan' var p2 = new Person() p2.sayName() // 'zhangsan'
首先每個函數都有一個prototype屬性,這個屬性指向一個對象,這個對象就是原型對象;
默認狀況下,全部原型對象都會自動得到一個constructor屬性,這個屬性指向prototype屬性所在的函數,也就是構造函數
**當調用構造函數建立一個實例對象後,該實例對象有一個內部屬性:[[prototype]](在部分瀏覽器中可經過__proto__訪問到),這個屬性指向構造函數的原型對象**
如在上面的例子中:構造函數Person有一個prototype屬性,它指向原型對象,而原型對象默認有且只有一個constructor屬性,該屬性指向構造函數Person,即Person.prototype.constructor指向Person,實例對象p1和p2也都包含一個內部屬性([[prototype]]),該屬性指向Person.prototype。
它們之間的關係如圖:
另外還須要注意的一個問題:
雖然p1和p2兩個實例中並無sayName(),可是在例子中咱們也能訪問到,成功打印出‘zhangsan’,這是經過查找對象屬性來實現的,其過程以下:
每當代碼讀取到某個屬性時,都會執行一次搜索,首先從實例對象自己開始,若是實例對象上找到了該屬性,則返回該屬性值,若沒有找到,則繼續到實例的[[prototype]]指向的原型對象上去找,也就是去建立此實例的構造函數的prototype指向的原型對象上找,找到了就返回該屬性值。
調用p1.sayName(),首先p1上沒有定義該屬性,那麼要到Person.prototype中去找,Person.prototype中有sayName()的定義,則取該函數。java
由於[[prototype]]爲內部屬性,咱們沒法直接訪問到它,可是咱們能夠經過isPrptotypeOf()方法來肯定對象與它的構造函數的原型對象之間的關聯;數組
console.log(Person.prototype.isPrototypeOf(p1)) // true console.log(Person.prototype.isPrototypeOf(p2)) // true
ECMAScript5中新增了Object.getPrototypeOf()方法用來獲取一個對象的原型:瀏覽器
console.log(Object.getPrototypeOf(p1) == Person.prototype) // true console.log(Object.getPrototypeOf(p1).name) // 'zhangsan'
在實例對象上定義了與原型對象上同名的屬性後,會屏蔽掉原型對象的該屬性,訪問的是實例對象上的屬性。函數
使用hasOwnProperty()方法可以判斷某個屬性是不是實例對象自己的屬性。優化
function Person () {} Person.prototype.name = 'zhangsan' var p1 = new Person() console.log(p1.hasOwnProperty('name')) // false p1.job = 'coder' console.log(p1.hasOwnProperty('job')) // true
in操做符,能夠判斷屬性是否能夠經過對象訪問到,不管是原型上的屬性仍是實例上的。this
console.log('name' in p1) // true console.log('job' in p1) // true
結合in操做符和hasOwnProperty()方法就可以準確的判斷出,一個屬性是實例上對象自己的,仍是原型對象上的。prototype
function isPrototypeProperty (obj, name) { return !obj.hasOwnProperty(name) && name in obj }
for-in循環返回的事全部可以經過對象訪問的、可枚舉的(enumerable)屬性,既包含存在於實例中的屬性,又包含原型中的屬性。code
ECMAScript5中新增的方法,返回的是全部可枚舉的實例屬性的字符串數組對象
function Person () {} Person.prototype.name = 'zhangsan' Person.prototype.sayName = function () { console.log(this.name) } console.log(Object.keys(Person.prototype)) // ['name', 'sayName'] var p1 = new Person() p1.name = 'lisi' p1.age = 23 console.log(Object.keys(p1)) // ["name", "age"]
返回全部的實例屬性,不管它是否可枚舉。
function Person () {} Person.prototype.name = 'zhangsan' Person.prototype.sayName = function () { console.log(this.name) } console.log(Object.keys(Person.prototype)) // ['constructor','name', 'sayName']
以前的代碼中,每添加一個屬性就要寫一遍Person.prototype,爲了簡化代碼,能夠作優化以下:
function Person() {} Person.prototype = { name : 'zhangsan', age : 23, sayName : function () { console.log(this.name) } } var p1 = new Person()
要注意這樣作會對對原型對象的constructor有影響,以前提到,每個函數都有一個prototype屬性,這個屬性自動獲取了一個constructor屬性,它指向構造函數,可是上面這種寫法至關於給Person的prototype屬性從新賦予了一個新的對象,而這個對象不存在constructor屬性了。
另一個問題:重寫構造函數的原型和調用構造函數建立實例的順序很重要!
當咱們像下面這樣作時會致使報錯:
function Person () {} var p1 = new Person() Person.prototype = { name : 'zhangsan', sayName : function () { console.log(this.name) } } console.log(p1.name) // 報錯
如上代碼,因爲先建立了實例對象,這時p1的[[prototype]]屬性指向了Person.prototype,即構造函數Person的原型對象,接着,重寫了Person.prototype,再訪問p1.name的時候,因爲p1實例上找不到name屬性,因而要到它指向的原型對象上面找,而它的原型對象依然是最初構造函數默認指向的那個原型對象,並非後面賦值的字面兩對象,因此是找不到的,所以報錯。