1. 程式人生 > 程式設計 >JS原型和原型鏈原理與用法例項詳解

JS原型和原型鏈原理與用法例項詳解

本文例項講述了JS原型和原型鏈原理與用法。分享給大家供大家參考,具體如下:

Javascript語言的繼承機制一直很難被人理解。

它沒有"子類"和"父類"的概念,也沒有"類"(class)和"例項"(instance)的區分,全靠一種很奇特的"原型鏈"(prototype chain)模式,來實現繼承。

Brendan Eich設計javascript之初是為了實現網頁與瀏覽器之間互動的一種簡單的指令碼語言

如果真的是一種簡易的指令碼語言,其實不需要有"繼承"機制。但是,Javascript裡面都是物件,必須有一種機制,將所有物件聯絡起來。所以,Brendan Eich最後還是設計了"繼承"。

背景介紹

1.建構函式

建構函式 ,是一種特殊的方法。主要用來在建立物件時初始化物件。每個建構函式都有prototype(原型)屬性

2.原型模式

每個函式都有prototype(原型)屬性,這個屬性是一個指標,指向一個物件,這個物件的用途是包含特定型別的所有例項共享的屬性和方法,即這個原型物件是用來給例項共享屬性和方法的。

而每個例項內部都有一個指向原型物件的指標。

JS原型和原型鏈原理與用法例項詳解

原型鏈

每個建構函式都有一個原型物件,原型物件都包含一個指向建構函式的指標,而例項都包含指向原型物件內部的指標。我們讓原型物件的例項(1)等於另一個原型物件(2),

此時原型物件(2)將包含一個指向原型物件(1)的指標,

再讓原型物件(2)的例項等於原型物件(3),如此層層遞進就構成了例項和原型的鏈條,這就是原型鏈的概念

建構函式

建構函式 ,是一種特殊的方法。主要用來在建立物件時初始化物件。 即為物件變數賦初始值。每個建構函式的例項都將共享建構函式的初始值。 建構函式的出現是為了解決使用Object建構函式和字面量表示法不方便建立大量重複物件的問題。

傳統建立物件例項的方法

  var person={
    name:'張女士',age:'80',gender:'女'
  };
 console.log(person)

注:這個方法如果用於建立大量相同屬性和方法的物件時,會產生大量重複程式碼

建構函式的方法

//建構函式方法建立物件例項
function Person(name,age,gender) {
this.name=name;
this.age=age;
this.gender=gender;
this.say=function () {
    alert(this.name)
  }
}
var person1=new Person('鍾女士',80,'女');
var person2=new Person('張女士','女');
console.log(person2)
console.log(person1)

原型模式

使用建構函式的問題是,每個方法都要在每個例項上重新建立一遍,即在建構函式的不同例項上的同名函式是不相等的。而我們建立每個建構函式都有一個prototype(原型)屬性,這個屬性是個指標,指向一個物件,而這個物件的用途是包含可以由特定型別的所有例項共享的屬性和方法,我們使用這個原型物件來共享例項的屬性和方法的模式就叫原型模式

//原型模式建立物件
function Person(){
}
Person.prototype.name='鍾女士';
Person.prototype.age=80;
Person.prototype.gender='女';
var person1= new Person();
console.log(person1)
//簡寫原型模式
Person.prototype={
  constructor:Person
  name:'鍾女士',
  age:80,gender:'女'
}

注:每個原型物件都有constructor屬性,由於簡寫模式重寫了預設的prototype物件,所以constructor也會被重新定義,不再指向他的建構函式,所以可以自己寫一個constructor屬性指向他的建構函式

原型鏈

每個建構函式都有原型物件,每個建構函式例項都包含一個指向原型物件的內部指標(proto),如果我們讓第一個建構函式的原型物件等於第二個建構函式的例項,結果第一個建構函式的原型物件將包含一個指向第二個原型物件的指標,再然第三個原型物件等於第一個建構函式的例項,這樣第三個原型物件也將包含指向第一個原型物件的指標,以此類推,就夠成了例項於原型的鏈條,這就是原型鏈的基本概念

function One(){
}
function Two(){
}
function Three(){
}
Two.prototype=new One();
Three.prototype=new Two();
var three=new Three();
console.log(three);
console.log(three.__proto__===Three.prototype) //true
console.log(three.__proto__.__proto__===Two.prototype) //true
console.log(three.__proto__.__proto__.__proto__===One.prototype) //true
console.log(three.__proto__.__proto__.__proto__.__proto__===Object.prototype) //true

在物件例項中,訪問物件原型的方法

  • 1、使用proto屬性

此屬性是瀏覽器支援的一個屬性,並不是ECMAScript裡的屬性

  • 2.Object.getPrototypeOf
  • 3.使用constructor.prototype的方法

對於不支援proto的瀏覽器,可以使用constructor,訪問到物件的建構函式,在用prototype訪問到原型

使用原型鏈解釋ANUGLAR作用域

在開發過程中,我們可能會出現控制器的巢狀,看下面這段程式碼:

  <div ng-controller="OuterCtrl">
    <span>{{a}}</span>
     <div ng-controller="InnerCtrl">
      <span>{{a}}</span>
     </div>
   </div>
  <script>
  function OuterCtrl($scope) {
  $scope.a = 1;
  }
  function InnerCtrl($scope) {
  }
  </script>

我們可以看到介面顯示了兩個1,而我們只在OuterCtrl的作用域裡定義了a變數,但介面給我們的結果是,兩個a都有值,現在自控制器裡的a是從父控制器裡繼承過來的

我們可以父子級的作用域看成兩個原型物件,其中一個原型物件繼承另一個原型物件的例項

function Outer() {
  this.a = 1;
}
function Inner() {
}
var outer = new Outer();
Inner.prototype=new Outer();
var inner = new Inner();
console.log(outer.a)
console.log(inner.a)

Angular的實現機制其實也就是把這兩個控制器中的$scope作了關聯,外層的作用域例項成為了內層作用域的原型。

既然作用域是通過原型來繼承的,自然也就可以推論出一些特徵來。比如說這段程式碼,點選按鈕的結果是什麼?

<div ng-controller="OuterCtrl">
  <span>{{a}}</span>
  <div ng-controller="InnerCtrl">
    <span>{{a}}</span>
    <button ng-click="a=a+1">a++</button>
  </div>
</div>
<script>
function OuterCtrl($scope) {
  $scope.a = 1;
}
 
function InnerCtrl($scope) {
}
</script>

點了按鈕之後,兩個a不一致了,裡面的變了,外面的沒變,這是為什麼?

function Outer() {
  this.a = 1;
}
function Inner() {
}
var outer = new Outer();
Inner.prototype=new Outer();
var inner = new Inner();
inner.a = inner.a + 1;
console.log(outer.a)
console.log(inner.a)

因為在原型鏈中,訪問一個例項屬性時,會在例項本身查詢,如果找不到,則搜尋例項的原型,如果再搜尋不到,則繼續沿著原型鏈往上查詢。找到之後則會賦給該例項,所以inner上面就被賦值了一個新的a,outer裡面的仍然保持原樣,這也就導致了剛才看到的結果。

上下級共享變數

比如說,我們就是想上下級共享變數,不建立新的,該怎麼辦呢?

function Outer() {
  this.data = {
    a: 1
  };
}
function Inner() {
}
var outer = new Outer();
Inner.prototype = outer;
var inner = new Inner();
console.log(outer.data.a);
console.log(inner.data.a);
inner.data.a += 1;
console.log(outer.data.a);
console.log(inner.data.a);

我們可以把a寫在一個物件裡,當inner找到物件data並賦值到自己身上時,其實是複製了物件的指標(參考高程第4章複製引用型別和基本型別的區別),我們對物件裡的屬性的改動都會反映到所有引用該物件的元素上。
反映到AngularJs,我們可以這麼寫

<div ng-controller="OuterCtrl">
  <span>{{data.a}}</span>
  <div ng-controller="InnerCtrl">
    <span>{{data.a}}</span>
    <button ng-click="data.a=data.a+1">increase a</button>
  </div>
</div>
<script>
function OuterCtrl($scope) {
  $scope.data = {
    a: 1
  };
}
function InnerCtrl($scope) {
}
</script>

這樣點選按鈕兩個控制器的a都會+1

感興趣的朋友可以使用線上HTML/CSS/JavaScript程式碼執行工具:http://tools.jb51.net/code/HtmlJsRun測試上述程式碼執行效果。

更多關於JavaScript相關內容可檢視本站專題:《JavaScript常用函式技巧彙總》、《javascript面向物件入門教程》、《JavaScript查詢演算法技巧總結》、《JavaScript錯誤與除錯技巧總結》、《JavaScript資料結構與演算法技巧總結》及《JavaScript數學運算用法總結》

希望本文所述對大家JavaScript程式設計有所幫助。