@
目錄- 原型鏈
- prototype)原型是什么?
- prototype長什么樣子
- 添加屬性
- 訪問原型屬性 初探原理
- 原型鏈的查找
- 理解原型物件
- 如何定義一個可以被繼承的屬性或者方法?
- 原型查找、重寫的就近原則
- 建構式里面的this指向問題
- this是哪里來的
- 一些關于原型的常用操作
- Object.create
- constructor 屬性
- 原型的增刪改查
原型鏈
? JavaScript的物件通過其身上原型這一點特征,來實作與傳統的面向物件編程語言截然不同的繼承機制
prototype)原型是什么?
- 在JS中大多數情況下創建的物件,都擁有一個原型物件,創建的物件,從其原型為模板、來繼承方法和屬性,而原型物件本身都有可能擁有原型,并從中獲取方法和屬性,以此類推,這種關系便被稱為原型鏈,原型鏈的關系解釋了為何一個物件身上會擁有本不屬于他的屬性和方法,
- 所有擁有原型的物件的最頂層便是Object物件的prototype
prototype長什么樣子
? 先復制如下代碼放入到瀏覽器的控制臺回車查看結果:
function test(){}
console.log(test.prototype);
? 放入到控制臺列印的結果為:

這里我把其中的關系做了個圖來方便理解,忽略其他屬性,專注于constructor和__proto__屬性

- __proto__== prototype 這兩個是一樣的,你呼叫test.prototype和呼叫test.___proto___是一樣的
-
通過上面我們發現當呼叫test.prototype時,會出現兩個屬性:constructor、__proto__
- constructor: 該屬性指向了用于構造此實體物件的建構式,在上例中就為function test(){}
-
我們會發現test.prototype.__proto__屬性的參考指向Object的原型物件
-
Object的原型物件里面同樣包含了:constructor、__proto__屬性,只不過Object的__proto__屬性的值為NULL,這跟我們前面說的:所有擁有原型的物件的最頂層便是Object物件的prototype,往后就沒有原型物件了,所以才會出現為NULL的情況
添加屬性
現在讓我們在test的原型上添加一個name屬性
test.prototype.name = 'zhang';
function test(){}
console.log(test.prototype);

我們可以發現在test的原型上多一個名為name的屬性
我們繼續來添加屬性,這次我們要實體化test的物件,需要用到new關鍵字
var obj = new test();
然后我們試著在obj物件里面添加一些屬性,然后再看整體的原型結構是什么樣子的
test.prototype.name = 'zhang';
function test(){}
var obj = new test();
obj.age = 13;
console.log(test.prototype);

這里我把其中的關系做了個圖來方便理解,忽略其他屬性

目前的結構為:obj物件里面有一個age屬性,而test.prototype原型上一個name屬性
訪問原型屬性 初探原理
正常情況下,當我們訪問一個物件里不存在的屬性時,會回傳undefined

下面這一段代碼讓我們來看看結果是什么:
test.prototype.name = 'zhang';
function test(){
this.age = 12;
}
var obj = new test();
console.log(obj.name);

通過控制臺列印我們發現結果為“zhang”
對于這個結果我們應該有疑惑比如:
- 為什么在test物件里面并沒有定義名為name的屬性,可是實體化之后我們卻可以訪問,并獲取其結果,為什么不是undefined
我們可能想到了于上文的原型有關,讓我們再來看段代碼:
console.log(obj.__proto__);
console.log(test.prototype);
讓我們來看看列印結果

可以發現obj.__proto__和test.prototype的列印結果是一致的
到這里我們應該也有些眉頭了
原型鏈的查找
- 當我們訪問obj.name時,瀏覽器首先查找test物件本身是否有這個name屬性,如果有就會直接拿來進行使用,例:

-
如果obj沒有這個name屬性,那么瀏覽器就會從obj的__proto__中查找這個屬性,在這里的obj.__proto__等同于test.prototype
-
如果obj.__proto__上有這個name屬性,那么就會獲取他,就如上例列印所示,在test.prototype上面有一個name屬性值,那么我們列印obj.name 就會取到test.prototype上面的屬性值
-
如果test.prototype物件上也沒有name屬性值,那么我們就會繼續往上一個__proto__上去找具有name屬性值的prototype,比如obj.proto.proto,在本例中,第三層就已經是Object物件了,在實際的例子中可以會有多層
-
如果Object.prototype上面也沒有name屬性,那么最終就會回傳undefined,如下圖所示:

上面的關系用一張圖總結

理解原型物件
有如下代碼,我們來看看
function test(){
this.name = 12;
}
var obj = new test();
console.log(obj.name);
console.log(obj.toString());

這個toString()方法是哪里來的納?結合我們上文理解其實不難想到,應該是原型鏈中某一個環節里面的方法
這這個例子中有如下程序:
- 瀏覽器首先檢查obj物件里面是有可以使用的toString()方法
- 如果沒有可以使用的toString()方法,瀏覽器會查看obj物件的原型物件(即test建構式的prototype屬性),是否有可用的toString()方法
- 如果也沒有,瀏覽器會繼續往上尋找,在本例中就為obj.proto.proto(即Object的prototype屬性)
- 我們在Object的prototype屬性里面找到了我們要使用的toString()方法,于是我們就會看到這個方法被呼叫的結果
注意:原型鏈中的方法和屬性沒有被復制到其他物件——它們被訪問需要通過前面所說的“原型鏈”的方式,
沒有官方的方法用于直接訪問一個物件的原型物件——原型鏈中的“連接”被定義在一個內部屬性中,在 JavaScript 語言標準中用
[[prototype]]表示,然而,大多數現代瀏覽器還是提供了一個名為__proto__(前后各有2個下劃線)的屬性,其包含了物件的原型,你可以嘗試輸入實體化物件.__proto__和實體化物件.__proto__.__proto__,看看代碼中的原型鏈是什么樣的! ---以上引自MDN
如何定義一個可以被繼承的屬性或者方法?
我們來看一下Object物件

我們可以發現他的prototype里面就有我們之前案例的toString方法,但是在他的prototype外面有一些類似于create等方法
我們來段代碼:
function test(){
}
var obj = new test();
console.log(obj.toString());
console.log(obj.create());
結果如下:

系統提示我們obj.create并不是一個函式,但是obj.toString是可以輸出結果的,他們之間在這里的區別只有toString是在Object的原型里面定義的,而create方法是Object物件內部定義的方法,只有他自己是可以訪問的
那么講到這里,我們就可以定義的原型的方法和屬性被繼承
function test(){
}
test.prototype.name = '我要被繼承了';
test.prototype.toInt = function(){
console.log('我是toInt');
}
var obj = new test();
console.log(obj.name);
console.log(obj.toInt());

我們成功的利用test上定義的原型方法和屬性,使obj物件成功的獲取到
原型查找、重寫的就近原則
如果我們的代碼是這樣的,會出現什么結果納?
function test(){
}
Object.prototype.name = '我是Object的name屬性'
test.prototype.name = '我是test的name屬性';
test.prototype.toSring = function(){
console.log('我是test原型上面的toString方法');
}
var obj = new test();
console.log(obj.name);
console.log(obj.toSring());
- 我們先是在Object.prototype的原型物件上定義了名為name的屬性,并賦值
- 我們又在test.prototype的原型物件上也定義了名為name的屬性,并賦值
- 我們在已知Object的原型物件上有一個名為toString的方法,但是我們又缺心眼的在test.prototype的原型的物件上也定義了一個toString方法
- 那么當我們呼叫obj.name、obj.toString() 結果是怎么樣的納

結果是我們的name和toString全部執行的test原型物件里面的定義的方法和屬性,這里就引出了原型鏈查找時的就近原則
Object是在test原型鏈的上層,所以當我們呼叫name時會先使用test原型物件的屬性,如果沒有再往上進行查找
同時這里也出現了我們定義的方法和Object原型物件里面的定義的方法,名字相同的情況下,這種情況下,我們在原型鏈底層定義的與頂層同名的函式時,底層的方法會覆寫頂層同名的函式(也稱為重寫),所以當呼叫同名函式時,按照原型鏈的就近規則,我們會取離得最近的原型物件里面的同名函式(這里的頂層可以理解為樹的根節點,也就是Object)
在這里我們如果直接呼叫Object.toString()方法

是不出現test定義的toString,因為我們是直接越過了底層原型鏈進行的頂層原型鏈的呼叫
建構式里面的this指向問題
現在讓我們回歸到test建構式里面的來看看this指向的問題
函式和建構式他們之間其實沒有什么區別,但是一個可以實體化一個不可以是為什么?
其實主要是看new這個關鍵字,讓我們來看如下的代碼:
function test() {
console.log(this);
this.name = 'name';
}
test.prototype.age = 123;
var obj = new test();
讓我們來看看結果:

這里的列印的this是一個物件,物件里面的prototype屬性有我們在test原型上定義的age屬性和constructor,這里我畫個圖來方便大家理解

- this首先是一個物件,當你在test()建構式內部用this.屬性定義屬性會放在圖中藍色區域,物件結果的name的同一層
- 而__proto__物件里面放的就是test原型鏈里面定義的屬性比如:age
- 這里在原型鏈尋找中,當我們沒有在test里面找到age屬性時,就會隱式的呼叫__proto__屬性里面的age屬性

- 我們列印obj會發現其結構是與this相同的,其實在我們new 物件時,就已經把這個this的值,傳給了new 物件時要賦值的變數
this是哪里來的
這個this是哪里來的納?
其實當我們new 物件時,在建構式內部會有隱式的幾部操作
function test() {
// var this = {
// __proto__: test.prototype
// }
console.log(this);
this.name = 'name';
// return this;
}
test.prototype.age = 123;
var obj = new test();
當我們看到這幾部操作時,我想就都明白了為什么我們在建構式內部定義屬性需要this來進行賦值
一些關于原型的常用操作
Object.create
- Object.create(引數) 可以指定的引數(原型物件)創建一個新的物件
示例如下:
function test() {
}
test.prototype.age = 123;
var obj = new test();
var obj2 = Object.create(test);
console.log(obj);
console.log(obj2);

我們可以發現Obj2的__proto__指向的原型物件為obj,而obj的原型物件為test,create相當于對指定的原型物件下面再添加一層鏈的關系
這里Object.create(null)的引數是可以為null的
我們可以發現obj2是沒有原型的,所以這也是文章開始時說的大部分物件都是有原型的
constructor 屬性
- 每個實體物件都從原型中繼承了一個constructor屬性,該屬性指向了用于構造此實體物件的建構式,
案例如下:
function test() {
}
test.prototype.age = 123;
var obj = new test();
console.log(obj.constructor);

這里我們看到obj.constructor回傳的是test建構式

- 第一個輸出是因為我們創建了物件,列印出了param引數,但是因為沒有引數傳遞,所以為undefined
- 第二次是我們的列印的constructor
- 這里回傳的constructor因為是一個物件,所以我們可以在后面加上()來實體化這個物件,這里我們實體化了他,并傳遞了引數12132,所以他第三次列印出了引數,因為已經成功到創建了物件
原型的增刪改查
-
查
- 這里我們可以在原型底層實體化物件中,直接.屬性或者方法,讓瀏覽器隱式的幫我們查找,也可以直接顯示的查找他們的__proto__屬性
-
改
-
建構式.prototype.屬性/方法 = 值
-
-
增
-
建構式.prototype.屬性/方法 = 值 如果嫌一個個賦值太麻煩,那么我們可以直接賦值一個物件 建構式.prototype = { key:vlaue, name:'1123' }
-
-
刪
-
delete 建構式.prototype.屬性/方法
-
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/327837.html
標籤:JavaScript
上一篇:2.準備—本地工具

