深刻理解原型對象和原型鏈

frontend/javascript/prototype_object_chain

原型對象和原型鏈在前端的工做中雖然不怎麼顯式的使用到,可是也會隱式的使用了,好比使用的jquery,vue等啦。在進入正題的時候,咱們仍是須要明白什麼是__proto__prototype等知識點,主要講解構造函數,這篇博文大可能是問答形式進行...javascript

原文請戳這裏css

問答環節

Javascript建立對象的方式?

也許你會說出工廠模式、構造函數模式、原型模式、組合使用構造函數和原型模式、動態原型模式、寄生構造函數模式和穩妥構造函數這些,可是咱們能夠對他們進行如下歸類--屬於函數建立對象。html

咱們能夠簡單的將建立對象的方式分爲三種:函數建立對象、字面量建立、Object建立。固然,也能夠只是分紅兩類:函數建立對象和字面量建立對象,由於new Object()中的Object是自己就是一個函數。前端

Object // f Object(){ [native code] }
複製代碼

什麼是prototype?

function(注意是function哦)定義的對象有一個prototype屬性,prototype屬性又指向了一個prototype對象,注意prototype屬性與prototype對象是兩個不一樣的東西,要注意區別。用僞代碼表示以下:vue

var function{
	prototype: prototype{} // function的prototype屬性指向prototype對象
}
複製代碼

注意上面說的是function裏面纔會有prototype屬性,而咱們new出來的對象裏面是沒有的哦。java

# function
function Fun(name){
	this.name = name;
}

Fun.prototype // {constructor:f}

var fun = new Fun('嘉明');
fun.prototype // undefined

# Object
Object.prototype // {constructor:f,__defineGetter__:f,...}

var object = new Object();
object.prototype // undefined

# 字面量,字面量能夠理解沒有prototype啦
var jack = {};
jack.prototype // undefined
複製代碼

__proto__是什麼?

在官方的es5種,定義了一個名叫作[[prototype]]的屬性,每一個對象(除了null)都擁有這樣一個屬性,這個屬性是一個指針,它指向一個名叫作原型對象的內存堆。而原型對象也是一個對象,所以又含有本身的[[prototype]]屬性,又指向下一個原型對象,終點指向咱們的Object.prototype對象。jquery

注意⚠️ 這裏使用的是[[prototype]],而並不是__proto__。但是他們是同一個東西哈:[[prototype]]是官方所定義的屬性,而__proto__是瀏覽器(就是任性)本身對[[prototype]]所作的實現。編程

分三種狀況來講對象內部的__proto__canvas

  1. 使用字面量定義一個普通對象: var foo = {}
  2. 建立一個函數: function Foo(){};包含Object()啦
  3. 建立對象實例: var foo = new Foo();

狀況一:{}瀏覽器

var foo = {};
foo.__proto__; // {}
foo.__proto__ === Object.prototype; // true
foo.hasOwnProperty('prototype'); // false 函數纔有prototype屬性
foo.hasOwnProperty('__proto__'); // false
Object.prototype.hasOwnProperty('__proto__'); // true
複製代碼

代碼的最後一行,一個是返回了false,另外一個是true。⚠️由於它並不存在於foo對象(foo.__proto__)或者Foo.prototype(Foo.prototype.__proto__)或者Foo(Foo.__proto__)中【下面狀況二和三會有代碼驗證】,實際上,它是來自於Object.prototype,與其說是一個屬性,不如說是一個getter/setter。

狀況二:function Foo(){}

1. function Foo(){};
2. Foo.__proto__; // [Function]
3. Foo.__proto__ === Object.prototype; // false
4. Foo.__proto__.__proto__ === Object.prototype; // true
5. Foo.prototype.__proto__ === Object.prototype; // true 函數的原型對象指向
6. Foo.__proto__ == Foo.prototype; //false
7. Foo.hasOwnProperty('__proto__'); // false
8. Foo.hasOwnProperty('prototype'); // true
複製代碼

在函數中,經過上面代碼2,3,4,5能夠知道Foo.__proto__能夠理解爲指向了Foo.prototype(廣義上理解),而實際上兩個又有差異(狹義上,第6點能夠說明,歡迎補充)。而後就是每一個函數都有一個默認的prototype屬性,其指向函數的原型對象。

狀況三:對象實例 new Foo()

function Foo(){};
var foo = new Foo();
foo.__proto__; // Foo {}
foo.__proto__ === Foo.prototype ; true
foo.hasOwnProperty('prototype'); false
foo.hasOwnProperty('__proto__'); false
複製代碼

上面可知,實例中是沒有prototype這個屬性的,對比上面的三種狀況,也說明了只有在函數中才默認建立了prototype屬性,並且指向了相應的函數原型對象。

constructor是什麼?

在javascript語言中,constructor屬性是專門爲function而設計的,它存在於每個function的prototype屬性中,這個constructor保存了指向function的一個引用。

function F(){
	// some code
}

# javascript內部會執行以下的動做
# 1.爲該函數添加一個原型(即prototype)屬性
# 2.爲prototype對象額外添加一個constructor屬性,而且該屬性保存指向函數F的一個引用
複製代碼

對象的實例中也有一個constructor屬性(從prototype那裏獲取的),每個對象實例均可以經過constructor對象訪問它的構造函數,以下:

var f = new F();
f.constructor === F; // true
f.constructor === F.prototype.constructor; // true
複製代碼

既然能夠訪問實例的類型f.constructor,那麼咱們就能夠對實例進行特殊的處理啦:

if(f.constructor == F){
	// some code
}
複製代碼

不過別這樣操做,由於constructor是不穩定的(見下文對象中的constructor的做用是什麼呢?),通常不會採起上面的這種操做,而是經過instanceof

if(f instanceof F){
	// some code
}
複製代碼

對象中的constructor的做用是什麼呢?

這裏推薦賀師俊前輩的回答,原文複製以下:

constructor屬性不影響任何javascript的內部屬性。instanceof檢測對象的原型鏈,一般你是沒法修改的(不過某些引擎經過私有的__proto__屬性暴露出來)。

constructor其實沒有什麼用,只是javascript語言設計的歷史遺留物。因爲constructor屬性是能夠變動的,因此未必真的指向對象的構造函數,只是一個提示。不過,從編程習慣上,咱們應該儘可能讓對象的constructor指向其構造函數,以維持這種習慣。

例子解析:

delete Object.prototype.constructor; // true
({}).constructor; // undefined
({}) instanceof Object; // true
複製代碼

原型鏈的最高指向?

《javascript高級程序設計》中有說到全部函數的默認原型都是Object的實例,所以默認原型都會包含一個內部指針,指向Object.prototype。那麼原型的最高指向就是Object了嘛?你能夠理解是Object,但是我認爲是null。

Object.prototype.__proto__; // null
Object.prototype.__proto__===null ; // true
複製代碼

最高指向是Object/null,無傷大雅。

實例和原型的關係?

當讀取實例的屬性時,若是找不到實例的屬性,就會查找與對象關聯的原型的屬性,若是仍是查找不到,就查找原型的原型,一直到頂級爲止。

function Person(){
}
Person.prototype.name = "嘉明";

var person = new Person();

person.name = "jiaming";
console.log(person.name); // "jiaming"

// 刪除實例的屬性後
delete person.name;
console.log(person.name); // "嘉明"

// 追加一個疑問 在__proto__中加屬性會覆蓋原來的嘛
person.__proto__.name = "嘉";
console.log(person.name); // "嘉" 證實成功,不建議這樣修改,畢竟__proto__是瀏覽器廠商實現的,非標準的

// 再追加一個疑問 __proto__添加的屬性或者方法是放在對象的原型上的嘛
var another_person = new Person();
console.log(another_person.name); // "嘉" 證實是放在對象的原型上的
複製代碼

原型的原型呢?

屬性或者方法在本身的原型上沒有找到的話,那就要跑到原型上去找啦。以前有提到過全部函數的默認原型都是Object的實例,所以默認原型都會包含一個內部指針,指向Object.prototype。

那麼一個構造函數function Person(){}就存在這樣的一個關係,實例出來的var person = new Person()person經過__proto__指向構造函數的原型Person.prototype,而後構造函數的原型指向Object的原型,便是Person.prototype.__proto__指向Object.prototype

總結下唄

嗯,仍是針對構造函數來講哈,將上面提到的知識點彙總一下啦。上面都是純文字說明,下面就配上圖片好好理解下。

先上相關代碼:

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

var person = new Person("嘉明");
person.age = 12;
person.sayName(); // "嘉明"
console.log(person.name.toString()); // "嘉明"

var another_person = new Person("jiaming");
another_person.sayName();
複製代碼

上面代碼中,相關的關係以下圖

frontend/javascript/prototype_chain_summerize_img01

實驗環節

小demo是使用canvas畫出小星光,代碼以下:

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>canvas</title>
    <style> body{ margin: 0; padding: 0; position: relative; } #myCanvas{ position: absolute; left: 50%; top: 50%; background: #000; margin-left: -300px; margin-top: -150px; } </style>
</head>
<body>
    <canvas id="myCanvas" width="600" height="300" style="border: 1px solid #000;">

    </canvas>
    <script src="path/to/canvas.js"></script>
</body>
</html>
複製代碼
window.onload = function(){
    var c = document.getElementById('myCanvas');

    var grd = ""; // 漸變的顏色

    // 上下文
    var context = c.getContext("2d");
    
    if(context){
        // x,y,r 座標和半徑
        function Star(x,y,r){
            this.x = x;
            this.y = y;
            this.r = r;
            this.init(this.x,this.y,this.r);
        }
        
        // 繪製星星
        Star.prototype.init = function(x,y,r){
        
        
            context.beginPath();
        
            // 漸變顏色
            grd = context.createRadialGradient(x,y,r-2,x,y,r+2)
            grd.addColorStop(0, 'white');
            grd.addColorStop(1, 'yellow');
            context.fillStyle=grd;
        
            // 畫圓
            context.arc(x,y,r,0,2*Math.PI);
        
            // 填充顏色
            context.fill();
        
            context.closePath();
        
        
        }
        
        // 建立星星
        for(var i = 0; i < 300; i++){
            var x = Math.floor(Math.random()*600);
            var y = Math.floor(Math.random()*300);
            var r = Math.floor(Math.random()*3)+2;
            new Star(x,y,r)
        }
    }else{
        var div = document.createElement("div");
        div.innerHTML = "您的瀏覽器不支持canvas,請升級瀏覽器!";
        document.getElementsByTagName("body")[0].appendChild(div);
    }
}
複製代碼

實現的簡單效果以下圖哈(ps,您可自行驗證哈,改善啥的):

frontend/javascript/prototype_chain_demo

原文請戳這裏

相關文章
相關標籤/搜索