javascript中的原型和原型鏈(三)

1. 圖解原型鏈


 

1.1 「鐵三角關係」(重點)

function Person() {};
var p = new Person();

  

這個圖描述了構造函數,實例對象和原型三者之間的關係,是原型鏈的基礎:
(1)實例對象由構造函數new產生;
(2)構造函數的原型屬性實例對象的原型對象均指向原型
(3)原型對象中有一個屬性constructor指向對應的構造函數javascript

原型鏈:p --> Person.prototype
描述:實例對象可以訪問到 Person.prototype 中不一樣名的屬性和方法
驗證:java

p instanceof Person; // true
  
p.__proto__ === Person.prototype; // true
  
Person.prototype.constructor === Person; // true

1.2 以原型爲構造函數

原型鏈:p --> Person.prototype --> Object.prototype --> null
描述:
(1)因爲構造函數的原型也是對象,所以:它也有原型對象,指向Object.__proto__
(2)因爲構造函數的原型的原型也是對象,所以:它也有原型對象,指向null(特例)
驗證:函數

p instanceof Person; // true
p instanceof Object; // true
 
Person.prototype instanceof Object; // true

1.3 深刻研究,引出Function構造函數

 

一、原型鏈1:見1.2中的原型性能

二、原型鏈2:Person --> Function.prototype --> Object.prototype --> null
描述:
(1)構造函數Person做爲實例對象時,Person = new Function()隱式調用,所以Person --> Function.prototype
(2)因爲Function.prototype也是對象,Function.prototype = new Object()隱式調用,所以Function.prototype --> Object.prototype
驗證:this

Person instanceof Function; // true
Person instanceof Object; // true
Function.prototype instanceof Object; // true

三、原型鏈3:Function --> Function.prototype --> Object.prototype --> null
描述:
構造函數Function做爲實例對象時,Function = new Function()隱式調用,所以Function --> Function.prototypespa

Function 這條原型鏈是最爲特殊的「鐵三角關係」,理解Function = new Function()就很是好理解了
驗證:prototype

Function.__proto__ === Function.prototype; // true
Function instanceof Function; // true
Function instanceof Object; // true

1.4 完整的原型鏈

圖中新增了Object = new Function()的邏輯3d

驗證:指針

Object instanceof Function;// true

幾個結論:
(1)對象都有原型對象,對象默認繼承自其原型對象
(2)全部的函數都是 Function 的實例
(3)全部的原型鏈尾端都會指向Object.prototypecode

下面提幾個問題:
(1)上圖有幾條原型鏈?分別列出來(上面已給出)
(2)如何在代碼層面驗證原型鏈上的繼承關係?(見第四節)
(3)圖中有幾個「鐵三角」關係?分別列出來

2. 原型鏈改寫(重點)


 

當實例對象被建立時,其原型鏈就已經肯定了,當其對應的原型屬性指向改變時,也沒法改變原型鏈
function Person({name="小A", age=21}={}) {
  this.name = name;
  this.age = age;
};

// 狀況1:在修改原型屬性前實例化對象
var p1 = new Person();

// 添加原型屬性(方法)
Person.prototype.sayName = function() {
  console.log(this.name);
}
// Person.prototype.SayHi = function() {}

// 狀況2:在修改原型屬性後實例化對象
var p2 = new Person();

p1.sayName(); // "小A"
p2.sayName(); // "小A"

實例對象p1和實例對象p2的原型鏈相同,爲 p1(p2) --> Person.prototype --> Object.prototype
=> 因爲是在原有原型對象上添加的方法,至關於對象的擴展,故兩個實例對象均能執行該方法

 

function Person({name="小A", age=21}={}) {
  this.name = name;
  this.age = age;
};

// 狀況1:在修改原型屬性前實例化對象
var p1 = new Person();

// 重寫原型對象
Person.prototype = {
  sayName: function() {
    console.log(this.name);
  }
}

// 狀況2:在修改原型屬性後實例化對象
var p2 = new Person();

p2.sayName(); // "小A"
p1.sayName(); // p1.sayName is not a function

重寫原型對象的方式,會改變實例對象的原型鏈,以下圖所示:

可是,爲何p1的原型鏈沒有變,而p2的原型鏈變了呢?

當實例對象被建立時,其原型鏈就已經肯定了,當其對應的原型屬性指向改變時,也沒法改變原型鏈
原型鏈是以實例對象爲核心的,不能被原型對象的改變而誤導

重寫原型對象的方式會在原型鏈繼承中常用到!!!

3. 對象與函數(重點)


 

看到這裏,咱們可能已經分不清函數與對象了,思考30秒,函數與對象是什麼關係?

官方定義: 在Javascript中,每個函數實際上都是一個函數對象
function fn() {};
var obj = {};

fn instanceof Object; // true
fn instanceof Function; // true

obj instanceof Object; // true
obj instanceof Function; // false

原型鏈解釋:
fn對應的原型鏈:fn --> Function.prototype --> Object.prototype
obj對應的原型鏈:obj --> Object.prototype

從函數的定義來講: 在javascript中一切函數實際都是函數對象,但對象不必定是函數

Function instanceof Object; // true
Object instanceof Function; // true

Function instanceof Function; // true

原型鏈解釋:
Function對應的原型鏈(Function做爲實例對象):Function --> Function.prototype --> Object.prototype
Object對應的原型鏈(Object做爲實例對象):Object --> Function.prototype --> Object.prototype

因爲Function和Object都是構造函數,在內置對象中,均會調用new Function()的方法

結論:
(1)函數必定是對象,可是對象不必定是函數
(2)對象都是由函數來建立的

針對第一點,這兩個原型鏈可驗證:
fn --> Function.prototype --> Object.prototype
obj --> Object.prototype

針對第二點,可這樣驗證:

var obj = { a: 1, b: 2}
var arr = [2, 'foo', false]

// 實際過程
var obj = new Object()
obj.a = 1
obj.b = 2

var arr = new Array()
arr[0] = 2
arr[1] = 'foo'
arr[2] = false

//typeof Object === 'function'  
//typeof Array === 'function 

4. 幾個定義


 

4.1 原型的定義和做用

function Person() {};
var p = new Person();
構造函數的prototype屬性的值(Person.prototype),
也能夠說成經過調用構造函數而建立出來的那個實例對象的原型對象(p.__proto__)
  1. 只要是函數就有 prototype 屬性,即函數的原型屬性(因爲對象是由函數建立的,所以對象也有prototype屬性)
  2. 函數的原型屬性也是對象(所以,這個對象也有對應的prototype屬性)
  3. 由構造函數建立出來的對象會默認連接到其構造函數的這個屬性上(constructor)
  4. 構造函數的 prototype 屬性的做用是:實現數據共享(繼承)

4.2 幾個術語

實例對象中有一個屬性叫 __proto__ ,它是非標準屬性,指向構造函數的原型屬性

Person.prototype 構造函數的原型屬性
p.__proto__ 實例對象的原型對象

構造函數的原型屬性實例對象的原型對象是一個東西,只是從不一樣的角度訪問原型

5. 屬性搜索原則和屬性來源判斷


 

5.1 屬性搜索原則(重點)

當代碼讀取某個對象的某個屬性時,都會執行一次搜索,目標是具備給定名字的屬性。搜索先從對象實例自己開始,若是在實例中找到了具備給定名字的屬性,則返回該屬性的值;若是沒有找到,則繼續搜索指針指向的原型對象,在原型對象中查找具備給定名字的屬性。若是在原型對象中找到了這個屬性,則返回這個屬性,若是沒有找到,則繼續在這個原型對象的原型對象中查找,直到找到這個屬性,不然返回undefined

簡言之,沿着對象的原型鏈查找屬性,返回最近的屬性,這就是屬性搜索原則

function Person(){}

Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
  alert(this.name);
};

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

person1.name = "Greg";
alert(person1.name); //"Greg" 來自實例
alert(person2.name); //"Nicholas" 來自原型 

一樣的,這也是屬性屏蔽的原則

// 接着上面的例子
delete person1.namel;
alert(person1.name); // "Nicholas" 來自原型

5.2 hasOwnProperty()方法與in操做符

使用hasOwnProperty()方法能夠檢測一個屬性是存在於實例中,仍是在原型中,這個方法只在給定屬性存在於對象實例中時,纔會返回true
function Person(){}

Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
  alert(this.name);
};

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

alert(person1.hasOwnProperty("name"));  //false

person1.name = "Greg";
alert(person1.name); //"Greg" 來自實例 
alert(person1.hasOwnProperty("name")); //true

alert(person2.name); //"Nicholas" 來自原型
alert(person2.hasOwnProperty("name")); //false

delete person1.name;
alert(person1.name); //"Nicholas" 來自原型
alert(person1.hasOwnProperty("name")); //false
有兩種方式使用in操做符:單獨使用和在for-in循環中使用。在單獨使用時,in操做符會在經過對象可以訪問給定屬性時返回true, 不管該屬性存在於實例中仍是原型中

所以,同時使用hasOwnProperty()和in操做符,就能夠肯定某個屬性究竟是存在於對象中仍是存在於原型中

function hasPrototypeProperty(object, name){
  return !object.hasOwnProperty(name) && (name in object);
}

順便一提,因爲in操做符會在整個原型鏈上查找屬性,處於性能考慮,在使用for-in循環時,建議多加一層判別

function Person(){}

Person.prototype.name = "Nicholas";
Person.prototype.age = 29;

var p = new Person();
p.sex = "fale";

for(key in p) {
  console.log(key); // sex name age 
}

// 實際上,咱們通常只是在查找實例中的屬性
for(key in p) {
  if(p.hasOwnProperty(key)) {
    console.log(key); // sex 屏蔽了原型中的屬性
  }
}

5.3 instanceof操做符

instanceof 用來判斷一個構造函數的prototype屬性所指向的對象是否存在另一個要檢測對象的原型鏈上

更形象來講,對於 A instanceof B來講,它的判斷規則是:沿着A的__proto__這條線來找,同時沿着B的prototype這條線來找,若是兩條線能找到同一個引用,即同一個對象,那麼就返回true。若是找到終點還未重合,則返回false。不理解不要緊,下面會結合圖例分析

function Person() {}
var p = new Person();
console.log(p instanceof Object);//true
console.log(p instanceof Person);//true
相關文章
相關標籤/搜索