1. 程式人生 > >JS原型和原型鏈是什麼?

JS原型和原型鏈是什麼?

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

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

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

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

背景介紹

1.建構函式

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

2.原型模式

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

image.png

原型鏈

每個建構函式都有一個原型物件,原型物件都包含一個指向建構函式的指標,而例項都包含指向原型物件內部的指標。我們讓原型物件的例項(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('張女士',80,'女');
    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

相關推薦

關於JS面向對象中原型原型以及他們之間的關系及this的詳解

它的 .proto com js面向對象 text doc 技術分享 回調函數 回調 一:原型和原型對象: 1.函數的原型prototype:函數才有prototype,prototype是一個對象,指向了當前構造函數的引用地址。 2.函數的原型對象__proto__:所有

js原型原型知識整理

question logs typeof 修改 類型 bject 其他 能夠 struct 在清楚了js創建對象和new方法的過程之後,再來看原型的概念就容易理解多了。 原型存在的目的是為了能更加節約內存地繼承。 我認為原型中主要需要搞清楚這4個概念,顯式原型指向什麽,隱式

js原型原型理解 constructor 構造函數

繼承 fun proto get 函數 per font ima CA 一.對象:普通對象 函數對象 二.構造函數特點:1.需要new實例化,內部使用this對象指向即將要生成的實例對象 2.首字母大寫,用於區分普通函數 function Person(name){

js 原型原型

就會 實例 style 查找 創建 his prototype UNC all 原型是JavaScript中一個比較難理解的概念,原型相關的屬性也比較多,對象有”prototype”屬性,函數對象有”prototype”屬性,原型對象有”constructor”屬性。 一、

JS(原型原型

property 擴展 ret alt ron back 技術分享 繼承 style 題目1.如何準確判斷一個變量是數組類型 使用 instanceof 方法 題目2.寫一個原型鏈繼承的例子 實例:封裝 DOM 查詢 定義構造函數 Elem,屬性封裝成 id 打

JS基礎知識(二)原型原型

原型和原型鏈 問題: 如何準確判斷一個變數是陣列型別 寫一個原型鏈繼承的例子 描述new 一個物件的過程 Zepto(或其他框架)原始碼中如何使用原型鏈   知識點:  1.建構函式 function Foo (na

我對js原型原型的理解

我們知道在js中,萬物皆物件,物件可以說是重中之重了。每一個物件都擁有自己的屬性。但是在這個世界中有很多東西都是相似的,可以歸為一類,他們有共同的方法和屬性。不可能讓每一個物件都定義一個屬性吧。那樣太消耗記憶體了。所以,在js中怎麼才能讓多個物件共享一個或多個方法呢?原型的出

JS原型原型

所有的引用型別(物件、陣列、函式),都具有物件特性,即可自由擴充套件屬性(null除外) 所有的引用型別(物件、陣列、函式),都有一個__proto__屬性,屬性值是一個普通的物件。 所有的函式,都有一個prototype屬性,屬性值是一個普通物件。 所有的引用型別(物件、陣列、函式),__proto__屬

JS基礎知識(一)原型原型

原型和原型鏈 問題: 如何準確判斷一個變數是陣列型別 寫一個原型鏈繼承的例子 描述new 一個物件的過程 Zepto(或其他框架)原始碼中如何使用原型鏈   知識點:  1.建構函式 function Foo (name, ag

JS原型原型

1. 前言 JS中原型和原型鏈的概念一直都是混淆不清,確實需要時間,偶爾回頭看看。對於原型和原型鏈的理解,其實一直處於比較淺的概念。有句話說,沒理解透原型和原型鏈,就算還沒有真正入門的前端。希望通過總結這篇文章,我算的上入門前端了,沒入門也能偶

JS原型原型是什麼?

Javascript語言的繼承機制一直很難被人理解。 它沒有"子類"和"父類"的概念,也沒有"類"(class)和"例項"(instance)的區分,全靠一種很奇特的"原型鏈"(prototype chain)模式,來實現繼承。 Brendan Eich設計javascr

JS面試題—原型原型

一、 題目 如何準確判斷一個變數是陣列型別 寫一個原型鏈繼承的例子 描述new一個物件的過程 二、知識點 1.建構函式 function Foo(name, age){ this.name = name

JS三座大山再學習(一、原型原型)

原文地址 ## 前言 西瓜君之前學習了JS的基礎知識與三座大山,但之後工作中沒怎麼用,印象不太深刻,這次打算再重學一下,打牢基礎。衝鴨~~ 原型模式 JS實現繼承的方式是通過原型和原型鏈實現的,JS中沒有類(此處指ES5,ES6中加上了class語法糖) 每個函式都有prototype(原型)屬性,

攻略前端面試官(三):JS原型原型

本文在個人主頁同步更新~ 背就完事了 介紹:一些知識點相關的面試題和答案 使用姿勢:看答案前先嚐試回答,看完後把答案收起來檢驗成果~ 面試官:什麼是建構函式 答:建構函式的本質是一個普通函式,他的特點是需要通過`new`關鍵字來呼叫,用來建立物件的例項。所有的引用型別,如[],{},function等

前端【JS】,深入理解原型原型

對於原型和原型鏈,相信有很多夥伴都說的上來一些,但有具體講不清楚。但面試的時候又經常會碰到面試官的死亡的追問,我們慢慢來梳理這方面的知識! 要理解原型和原型鏈的關係,我們首先需要了解幾個概念;1、什麼是建構函式?2、建構函式與普通函式有什麼區別? 3、原型鏈的頂端是什麼? 4、prototype、__prot

原型原型

一個 不存在 對象 創建 type 繼承 等於 自身 被子 原型鏈的形成真正是靠__proto__ 而非prototype, 當JS引擎執行對象的方法時,先查找對象本身是否存在該方法, 如果不存在,會在原型鏈上查找,但不會查找自身的prototype。一個對象的__pr

原型原型的扯淡

一段 truct type 也有 prot _proto_ con pro 一個 一、首先談談原型 每個對象都有個屬性prototype,名為原型,是一個指針屬性,該屬性指向一個原型對象; 而原型對象也有一個指針屬性constructor,該指針指向原型對象的實例的構造函數

原型原型的理解

pan log 構造 == object proto col on() t對象 1. 什麽是原型?? 我的理解是:函數都有一個prototype屬性,這個屬性是一個指針,指向構造函數的原型對象。一定要理解構造函數的原型對象,是構造函數的原型對象,不是構造函數Per

JavaScript原型原型

str 對象 本質 struct type eat 繼承 bsp 基於 一、對象與函數的關系 1.對象是函數創建,函數也是一種對象。 2.對象的創建方式 2.1.Object構造函數創建對象new Object(); 2.2.字面量創建對象var x = new();其本質

javascript之原型原型

-1 至少 實現 min 方法 有意 在屏幕上 度量單位 更多 眾所周知CSS技術我們雖然很熟悉,在使用的過程卻很容易被困住,這讓我們在新問題出現的時候變得很不利。隨著web繼續不斷地發展,對於新技術新解決方案的要求也會不斷增長。 因此,作為網頁設計師和前端開發人