【JavaScript】理解原型與原型鏈

前言

理解原型和原型鏈,有助於更好的理解JavaScript中的繼承機制。javascript

最近比較有空,因此想寫一篇關於原型和原型鏈的文章,如寫得很差請見諒。java

原型對象

不管何時,只要建立了一個新函數,就會根據一組特定的規則爲該函數建立一個prototype屬性,這個屬性指向函數的原型對象。在默認狀況下,全部的原型對象都會自動得到一個constructor屬性,這個屬性是一個指向prototype屬性所在函數的指針。chrome

function Person(name) {
      this.name = name
    }

Person.prototype.age = 18
Person.prototype.sayName = function() {
  alert(this.name)
}

var person = new Person('張三')
person.sayName() // 張三

上述咱們建立了一個Person構造函數,並建立了一個name實例和原型對象的age屬性和sayName方法,接下來new實例對象都擁有這些屬性方法瀏覽器

1

Person構造函數的prototype屬性指向原型對象,person實例的[[prototype]]指向原型對象(在chrome瀏覽器中是__proto__),咱們能夠經過isPrototypeOf()方法來確認對象之間是否存在原型關係。ES5新增了一個新方法,Object.PrototypeOf(),這個方法返回[[prototype]]的值函數

alert(Peron.prototype.isPrototypeOf(person))  // true

alert(Object.PrototypeOf(person) == Person.prototype) // true 
alert(Object.PrototypeOf(peroson).name // '張三'

當咱們訪問一個實例對象的某個屬性時,若是在實例中找到具備給定名字的屬性,則返回屬性的值;若是沒有找到,則繼續搜索指針指向的原型對象,在原型對象中查找具備給定名字的屬性,若是在原型對象中找到具備給定名字的屬性,則返回該屬性的值this

看下面這段代碼prototype

function Person() {
}

Person.prototype.name = 'zhangsan';
Person.prototype.age = 18;
Person.prototype.sayName = function() { alert(this.name) };

var person1 = new Person();
var person2 = new Person();

person1.name = 'lisi';
console.log(person1.name); // 'lisi'——來自實例
console.log(person2.name); // 'zhangsan'——來自原型
console.log(person1.hasOwnProperty('name')) // true

delete person1.name; // 'zhangsan'——來自原型
console.log(person1.name);

console.log(person1.hasOwnProperty('name')) // false
console.log(person2.hasOwnProperty('name')) // false

在這個例子中,person1的name被一個新值給屏蔽了。當爲對象實例添加一個屬性時,這個屬性就會屏蔽原型對象中保存的同名屬性。即便將這個屬性設置爲null,也只會在實例中設置這個屬性,而不會恢復其指向原型的鏈接。不過可使用delete操做符刪除實例屬性,從而讓咱們從新訪問原型中的屬性。指針

使用hasOwnProperty()方法,能夠檢測屬性是否來自實例,只在給定屬性存在於對象實例中時,纔會返回truecode

咱們還能夠經過定義函數封裝一個檢測該屬性到底存在於對象中,仍是存在於原型中的方法對象

function hasProtoypeProperty(object, name) {
  return !object.hasProperty(name) && (name in object)
}

function Person() {
}

Person.prototype.name = 'zhangsan';
Person.prototype.age = 18;
Person.prototype.sayName = function() { alert(this.name) };

var person = new Person();
console.log(hasProtoypeProperty(person, 'name')); // true

person.name = 'lisi'
console.log(hasProtoypeProperty(person, 'name')); // false

不少同窗可能注意到前面例子每添加一個屬性和方法就要敲一遍Person.prototype。爲減小沒必要要的輸入,咱們可使用一個包含全部屬性和方法的對象字面量來重寫整個原型對象

function Person() {}

Person.prototype = {
  name: 'zhangsan',
  age: 19,
  sayName: function() {
    console.log(this.name)
  }
}

上述代碼中有一個問題,Person.prototype中的constructor屬性再也不指向Person,此時咱們須要手動將constructor屬性指向Person

function Person() {}

Person.prototype = {
  constructor: Person,
  name: 'zhangsan',
  age: 19,
  sayName: function() {
    console.log(this.name)
  }
}

var person = new Person();
for(var val in person) {
  console.log(val); // 'constructor','name','age','sayName'
}

以這種方式重設constructor屬性會致使它的[[Enumerable]]特性被設置爲true,致使能夠經過for-in遍歷出來,最好的辦法是經過ES5中的Object.defineProperty()方法設置constructor屬性

function Person() {}

Person.prototype = {
  name: 'zhangsan',
  age: 19,
  sayName: function() {
    console.log(this.name)
  }
}

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

原型還有一個特色,是具備動態性,即咱們對原型對象所作的修改都可以當即從實例上反映出來,即便是先建立實例後修改原型對象也是如此

function Person() { }

var person1 = new Person();

Person.prototype = {
  name: 'zhangsan',
  age: 19,
  sayName: function () {
    console.log(this.name)
  }
}

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

person1.sayName(); // Uncaught TypeError: person.sayName is not a function

var person2 = new Person();
person2.sayName(); // zhangsan

前面提過,調用構造函數時會爲實例添加一個指向最初原型的[[prototype]]指針,上述代碼中,重寫原型對象切斷了現有原型與任何以前已經存在的對象實例之間的聯繫。實例中的指針指向原型,而不指向構造函數


原型鏈

在JavaScript中,原型鏈是實現繼承的主要的方法,理解原型鏈有助於咱們理解JavaScript中的繼承機制。

原型鏈,其基本思想是利用原型讓一個引用類型繼承另外一個引用類型的屬性和方法,咱們都知道,每一個構造函數都有一個原型對象,原型對象都包含一個指向構造函數的指針(constructor),實例對象都包含一個指向原型對象的內部指針(__proto__),讓原型對象等於另外一個類型的實例,此時原型對象將包含一個指向另外一個原型的指針,假如另外一個原型又是另一個類型的實例,如此層層遞進,就構成了實例與原型的鏈條,簡稱原型鏈。

在JavaScript中,每一個實例對象都有一個__proto__屬性指向它的構造函數的原型對象prototype,原型對象也有一個本身的原型對象__proto__,層層向上直到一個對象的原型對象爲null幾乎全部JavaScript中的對象都是位於原型鏈頂端的Object的實例。

function Father() {
  this.name = 'zhangsan';
}

Father.prototype.sayName = function() {
  console.log(this.name);
}

function Son() {
  this.age = 19
}

Son.prototype = new Father();

var xiaoming = new Son();
xiaoming.sayName(); // 'zhangsan'

上述代碼中,讓Son構造函數的原型指向Father構造函數的實例,此時就實現了JavaScript中最簡單的繼承原理。讓咱們經過下圖來更好的理解原型鏈。

2

上圖很清晰的描繪了原型鏈中的繼承機制,對象最頂層Object的原型的原型是指向一個null。注意,在爲原型添加方法的代碼必定要放在替換原型的語句以後,也不能經過對象字面量建立原型方法,由於這樣作就會重寫原型鏈。

function Father() {
  this.name = 'zhangsan';
}

Father.prototype.sayName = function() {
  console.log(this.name);
}

function Son() {
  this.age = 19
}

Son.prototype = new Father();
Son.prototype = {
  sayAge: function() {
    console.log(this.age)
  }
}

var xiaoming = new Son();
xiaoming.sayName(); // Uncaught TypeError: xiaoming.sayName is not a function

使用對象字面量的方式到致使FatherSon之間的關係被切斷,所以最後調用sayName()方法就會報錯。

總結

以上就是我對原型原型鏈的理解,原型鏈雖然強大,但它也存在一些問題,最主要的問題就是包含引用類型值的原型屬性會被全部實例共享,本文只是對原型和原型鏈進行分析,關於繼承問題不在本文的討論範圍以內,這是cc的第一篇文章,也算是小白文章,如寫得很差請指出,我會及時更正,謝謝你們~

相關文章
相關標籤/搜索