上篇的最後咱們提到了hasOwnProperty
是用來檢測某個屬性是否爲當前實例的私有屬性的,咱們還本身編寫了hasPubProperty
用來檢測某個屬性是否爲當前實例的公有方法的;私有方法上文中已經介紹,就是實例自己私有的方法,存在當前實例中;那什麼是公有方法,他們又在哪裏呢?這就是咱們今天要講的原型和原型鏈。javascript
咱們以數組爲例:java
每個數組都是Array這個內置數組類的實例;數組
let arr1 = [10, 20];
let arr2 = [30, 40];
console.log(arr1 instanceof Array); //=>true
console.log(arr1.hasOwnProperty('push')); //=>false
console.log(arr1.push === arr2.push); //=>true
arr1.push(100); //=>對象.屬性 說明PUSH是ARR1的一個屬性,並且是公有屬性(其它數組中經常使用的方法也都是數組實例的公有屬性)
複製代碼
那push
等一系列數組的方法在哪裏呢?瀏覽器
咱們先記住三句話:很重要、很重要、很重要,重要的事說三遍bash
一、每個函數都天生具有一個屬性:
prototype
(原型),prototype
的屬性值是一個對象(瀏覽器默認會給其開闢一個堆內存)函數
- =>「原型對象上所存儲的屬性和方法,就是供當前類實例所調用的公有的屬性和方法」
二、在類的
prototype
原型對象中,默認存在一個內置的屬性:constructor
(構造函數),屬性值就是當前類(函數)自己,因此咱們也把類稱爲構造函數ui三、每個對象都天生具有一個屬性:
__proto__
(原型鏈),屬性值是當前實例(對象)所屬類的prototype
原型this
仍是以Array
數組內置類爲例spa
類(自定義類仍是JS內置類)都是函數數據類型prototype
咱們能夠打開控制檯輸出:
由於Array是內置類,因此存儲的爲原生代碼,瀏覽器爲了保護原生代碼,不讓咱們查看具體內容,因此輸出爲[native code]
,若是是咱們本身建立的自定義類是能夠看見的;
這裏咱們記住一句話
全部的實例都是對象數據類型值(除基本數據類型值的實例外)
let arr1 = [10, 20];
let arr2 = [30, 40];
複製代碼
數組arr1
中的0:10
,1:20
,length:2
都是arr1
實例的私有屬性; arr1
和arr2
裏的內容互不衝突(緣由在單例模式中講過);
每個實例對象,本身堆內存中儲存的屬性都是私有屬性
不管是arr1
仍是arr2
均可以調用數組的push
...等方法,可是這些方法私有裏又都沒有,那這些方法怎麼出來的呢?
這裏咱們就用到了上面的三句話:
prototype
屬性;屬性值是一個對象每個函數(包括👇)都天生具有一個屬性:
prototype
(原型),prototype
的屬性值是一個對象(瀏覽器默認會給其開闢一個堆內存)
- 普通函數
- 類也是函數類型的值
原型對象上所存儲的屬性和方法,就是供當前類實例所調用的公有的屬性和方法
prototype
原型對象中,默認存在一個constructor
屬性,屬性值就是當前函數自己在類的
prototype
原型對象中,默認存在一個內置的屬性:constructor
(構造函數),屬性值就是當前類(函數)自己,因此咱們也把類稱爲構造函數
dir(Array)
找到
prototype
找到
constructor
結果是
Array
,這樣一直找會無限循環...
__proto__
屬性,屬性值是當前實例所屬類的prototype
原型每個對象都天生具有一個屬性:
__proto__
(原型鏈),屬性值是當前實例(對象)所屬類的prototype
原型 這裏的對象爲泛指:包括
- 對象數據類型值
- 普通對象
- 數組對象
- 正則對象
- ...
- 實例也是對象類型值(除基本值外)
- 類的
prototype
原型屬性值也是對象- 函數也具有對象的特徵(它有一重身份就是對象類型)
- ...
如今咱們輸出:
arr1.length
或者 arr1[0]
;
arr1.push()
;
__proto__
原型鏈屬性,找所屬類prototype
原型上的公共屬性和方法arr1.push() === arr2.push()
;
arr1.push
在找到 arr2.push
true
;因此咱們說原型上存放的是實例的公共屬性和方法;
arr1.proto.push
push
方法,相似於 Arrary.prototype.push
這樣找arr1.__proto__.push === arr2.push === Array.prototype.push
咱們知道arr1
arr2
實例對象,他們所屬的類是 Array
,因此 arr1.__proto__===Array.prototype
確定沒有問題,
那咱們
Array.prototype
這個對象是誰的實例呢?
- 一、確定不是
Array
的實例(他是Array
的原型),數組纔是Array
的實例- 二、他自己是一個對象;
全部的對象數據類型值,都是內置類
Object
的一個實例
那Object
的原型也是一個對象,每一個對象都有一個__proto__
屬性指向當前所屬類的原型,而全部對象數據類型,都是Object
的一個實例;
咱們發現他最後指向了他本身;指向本身就失去了原型鏈查找的意義,因此咱們規定Object.prototype.__proto__ === null
Object
是全部對象的基類,在他的原型上的__proto__
屬性,若是存在也是指向本身的原型,這樣沒有意義,因此他的__proto__
屬性值爲null
ARR1(數組的實例對象)的整個原型鏈:
__proto__
找到所屬類的原型Array.prototype
;若是尚未Array.prototype
的__proto__
找到Object.prototype
; 若是尚未這一系列就是咱們的原型鏈查找;
原型鏈查找機制:基於實例的__proto__找所屬類的prototype
- => 實例的私有屬性方法
- => 實例的公共屬性和方法
基於這種查找機制,幫助咱們實現了實例既有私有的屬性和方法,也有公有的屬性和方法了
這也是整個面向對象的核心。
咱們能夠打開控制檯,輸出dir[10,20])
:
arr1.hasOwnProperty
時
arr1.__proto__.__proto__ => Object.prototype
arr1.hasOwnProperty("push") => false
Array.prototype.hasOwnProperty("push") => true
push
是arr1
實例的公有方法;可是是Array.prototype
的私有屬性
hasOwnProperty
是arr1
實例的「公有屬性方法」
__proto__
查找就有的__proto__
找prototype
上的JS中的全部值,最後基於
__proto__
原型鏈,都能找到Object.prototype
原型,也就是都是對象類的實例,也就是都是對象,這就是「萬物接對象」
老規矩,咱們在來一道題
function Fn() {
this.x = 100;
this.y = 200;
this.getX = function () {
console.log(this.x);
}
}
Fn.prototype.getX = function () {
console.log(this.x);
};
Fn.prototype.getY = function () {
console.log(this.y);
};
let f1 = new Fn;
let f2 = new Fn;
console.log(f1.getX === f2.getX);
console.log(f1.getY === f2.getY);
console.log(f1.__proto__.getY === Fn.prototype.getY);
console.log(f1.__proto__.getX === f2.getX);
console.log(f1.getX === Fn.prototype.getX);
console.log(f1.constructor);
console.log(Fn.prototype.__proto__.constructor);
f1.getX();
f1.__proto__.getX();
f2.getY();
Fn.prototype.getY();
複製代碼
作原型類的題目,沒有比畫圖更好的方式了
console.log(f1.getX === f2.getX); //=> false
console.log(f1.getY === f2.getY); //=> true
console.log(f1.__proto__.getY === Fn.prototype.getY); //=> true
console.log(f1.__proto__.getX === f2.getX); //=> false
console.log(f1.getX === Fn.prototype.getX); //=> false
console.log(f1.constructor); //=> Fn 實例的構造函數通常指的就是它所屬的類
console.log(Fn.prototype.__proto__.constructor); //=> Object
f1.getX() ;
執行的是私有的getX => function () {console.log(this.x);}
方法中的this => f1
代碼執行
console.log(this.x); => f1.x => 100
f1.__proto__.getX() ;
執行的是原型上公有的getX => function () {console.log(this.x);};
方法中的this => f1.__proto__
代碼執行
console.log(this.x); => f1.__proto__.x => undefined
f2.getY() ;
執行的是原型上公有的getY => function () {console.log(this.y);};
方法中的this => f2
代碼執行
console.log(this.y) => f2.y =>200
Fn.prototype.getY() ;
執行的是原型上的getY => function () {console.log(this.y);};
方法中的this => Fn.prototype
代碼執行
console.log(this.y) => Fn.prototype.y => undefined
複製代碼
(供其實例調取使用的公有屬性和方法)
Fn.prototype.xxx = xxx
(經常使用)Fn.prototype
設置別名)constructor
這個屬性let prop = Fn.prototype;
prop.A = 100;
prop.B = 200;
prop.C = 300;
複製代碼
Object.prototype.xxx = xxx
(不經常使用)f1.__proto__.xxx = xxx
(基本不用)__proto__
找到的就是所屬類的原型,也至關於給原型上擴展屬性方法IE
瀏覽器中,爲了防止原型鏈的惡意篡改,是禁止咱們本身操做__proto__
屬性的(IE中不讓用__proto__
)Fn.prototype = {...}
(經常使用)Fn.prototype
constructor
這個屬性的;因此真實項目中,爲了保護結構的嚴謹性,咱們須要本身手動設置constructor
Object.assign(原來對象,新對象)
function Fn(num) {
this.x = this.y = num;
}
Fn.prototype = {
x: 20,
sum: function () {
console.log(this.x + this.y);
}
};
let f = new Fn(10);
console.log(f.sum === Fn.prototype.sum); //true
f.sum();//20
Fn.prototype.sum();//NaN
console.log(f.constructor);//Object
複製代碼
JS
中有不少內置類,並且在內置類的原型上有不少內置的屬性和方法,雖然內置類的原型上有不少的方法,可是不必定徹底夠項目開發所用,因此真實項目中,須要咱們本身向內置類原型擴展方法,來實現更多的功能操做
Array.prototype.xxx = xxx
(以數組爲例)因此通常咱們本身在內置類原型上擴展的方法,設置的屬性名作好加上前綴
瀏覽器爲了保護內置類原型上的方法,不容許咱們從新定向內置類原型的指向(嚴格模式下會報錯)
需求1:模擬內置的
PUSH
方法
- 在類的原型上編寫的方法,讓方法執行,咱們通常都這樣操做:
實例.方法()
,因此方法中的THIS
通常都是咱們要操做的這個實例,咱們基於THIS
操做就是操做這個實例
/* * JS中有不少內置類,並且在內置類的原型上有不少內置的屬性和方法 * Array.prototype:數組做爲Array的實例,就能夠調取原型上的公共屬性方法,完成數組的相關操做 => arr.push():arr基於__proto__原型鏈的查找機制,找到Array.prototype上的push方法,而後把push方法執行,push方法執行 * + 方法中的THIS是要操做的arr這個數組實例 * + 做用是向arr(也就是this)的末尾追加新的值 * + 返回結果是新增後數組的長度 * * 向內置類原型擴展方法: * Array.prototype.xxx = xxx * =>這種方法存在風險:咱們本身設置的屬性名可能會把內置的屬性給覆蓋掉 * =>通常咱們本身在內置類原型上擴展的方法,設置的屬性名最好加上前綴 * * Array.prototype={...} * =>瀏覽器爲了保護內置類原型上的方法,不容許咱們從新定向內置類原型的指向(嚴格模式下會報錯) */
Array.prototype.myPush = function () {
console.log('本身的PUSH');
};
let arr = [10, 20];
arr.myPush(100);
複製代碼
Array.prototype.myPush = function myPush(value) {
// this:要操做的數組arr實例
this[this.length] = value;
return this.length;
};
let arr = [10, 20];
console.log(arr.myPush(100), arr);
let arr2 = [];
console.log(arr2.myPush('小芝麻'), arr2);
複製代碼
需求2:數組的原型上有
SORT
實現數組排序的方法,可是沒有實現數組去重的方法,咱們接下來向內置類原型擴展方法:myUnique
,之後arr.myUnique
執行能夠把數組去重
Array.prototype.myUnique = function myUnique() {
// this:當前要操做的數組實例
let obj = {};
for (let i = 0; i < this.length; i++) {
let item = this[i];
if (typeof obj[item] !== "undefined") {
this[i] = this[this.length - 1];
this.length--;
i--;
continue;
}
obj[item] = item;
}
obj = null;
//爲了實現鏈式寫法
return this;
};
let arr = [12, 23, 13, 23, 12, 12, 2, 3, 1, 2, 3, 2, 1, 2, 3];
arr.myUnique().sort((a, b) => a - b);
console.log(arr);
複製代碼
arr
之因此能調用myUnique
或者sort
等數組原型上的方法,是由於arr
是Array
的實例,arr.myUnique().sort((a, b) => a - b).map(item => {
return '@@' + item;
}).push('小芝麻').shift();
//Uncaught TypeError: arr.myUnique(...).sort(...).map(...).push(...).shift is not a function
//=> 由於push返回的是新增後數組的長度,是個數字,再也不是數組了,就不能繼續調用數組的方法了
複製代碼
基於
new
執行,構造函數-函數體中的this
是當前類的一個實例給實例擴展的私有或者公有方法,這些方法中的
this
徹底看前面是否有「點」來決定
仍是上面的例題