我們都知道面嚮物件語言如 Java, C++ 等都基本實現了 封裝, 繼承, 多型
等特性, 用面嚮物件語言來程式設計的基本套路就是抽象出類, 然後例項化, 用例項呼叫方法來模擬進行程式間的通訊.
但 JS 不是物件導向的語言, 或者我們稱它的指令碼語言, 它的一等公民就是 物件/函式
. 本篇這裡主要來說 JS 如何能進行模擬 OO 語言實現 繼承
特性的, 即它巧妙的原型設計.
什麼是 prototype
以前我也不懂這些東西, 後來用多了就漸漸懂了, 或者說先記住,會用等積累多了自然就明白了.
- 任何函式都有
prototype
屬性, 翻譯成中文就是 "原型" 的意思, 它是一個指標, 指向原型物件 - prototype 屬性值是個
物件
, 它預設擁有用constructor
屬性指向是函式自身
function sum(a, b) {
return a + b
}
console.log('函式的原型是一個物件: ', sum.prototype, typeof sum.prototype)
console.log('次物件自有的 constructor 屬性值是函式自身', sum.prototype.constructor);
console.log(sum.prototype.constructor == sum);
console.log('1 + 1 的值是: ', sum.prototype.constructor(1, 1));
函式的原型是一個物件: {} object
次物件自有的 constructor 屬性值是函式自身 [Function: sum]
true
1 + 1 的值是: 2
這例子說明, 函式物件天生就有一個 prototype 屬性, 它是一個物件. 且此物件的也天生具有一個 constructor 屬性指向函式自身.
那這個感覺有點內迴圈的邏輯設計有什麼用呢?
- 普通函式的
prototype
屬性確實沒啥大作用, 但對於建構函式
用大作用 - 建構函式的
prototype
屬性是它例項的原型
建構函式的 prototype
屬性是它例項的原型
這個結論非常非常重要!!!
假設有一個建構函式: People // function
則它會自帶一個原型屬性: People.prototyep // object
現在它有一個例項: const youge = new People()
劃重點!
youge.__ proto __ == People.prototype
這裡的 __ proto __ 和 prototype 功能一樣, 只是前者是瀏覽器新增的方法做相容而已
只有函式物件才有 prototye, 其他物件不一定有. 但所有物件都有 __ proto __
function People(name, age, sex) {
this.name = name
this.age = age
this.sex = sex
}
// 例項化
var youge = new People('油哥', 18, '男')
// 測試三角關係是否存在, 即例項的原型 是否等於 建構函式的 prototype
console.log(youge.__proto__ === People.prototype); // 返回 true
例項的原型, 是其建構函式的 prototype 屬性, 這個結論真的是太重要啦!
可以想象成建構函式 People 是母親, People.prototype 是父親. 建構函式 (母親) People 生出來的兒子 (例項) youge 的原型 __proto __
就指向了他的父親, 父親的 constructor
類似配偶, 又指向了 People 母親
這個說法似乎有點問題, 但意思差不多吧, 那這個東東到底有什麼用呢 ?
答案就是: 原型鏈查詢!
原型鏈查詢
- JS 規定, 例項可以打點訪問它的原型上屬性和方法, 這又被成為
原型鏈查詢
function People(name, age, sex) {
this.name = name
this.age = age
this.sex = sex
}
// 在原型上新增屬性和方法, 則例項都可以進行訪問
People.prototype.want = '躺平'
People.prototype.eat = function (name) {
return name
}
// 例項化
var youge = new People('油哥', 18, '男')
// 例項可訪問它原型上的屬性或方法
console.log('想: ', youge.want);
console.log('吃: ', youge.eat('肉肉'));
console.log(youge)
// 給物件繫結了同原型上同名方法時, 會遮蔽掉原型的
youge.want = '上班'
console.log('想: ', youge.want);
想: 躺平
吃: 肉肉
People { name: '油哥', age: 18, sex: '男' }
想: 上班
物件的 hasOwnProperty 方法
hasOwnProperty()
可以檢查物件是否真正自己擁有
某個屬性或者方法in
運算子只能檢查某個屬性或方法是否可以被物件訪問, 不能檢查是否是自己
function People(name, age, sex) {
this.name = name
this.age = age
this.sex = sex
}
// 在原型上新增屬性和方法, 則例項都可以進行訪問
People.prototype.want = '躺平'
People.prototype.eat = function (name) {
return name
}
// 例項化
var youge = new People('油哥', 18, '男')
console.log(youge.hasOwnProperty('name'));
console.log(youge.hasOwnProperty('sex'));
console.log(youge.hasOwnProperty('sex'));
// 這個 want 是原型上的, 就不是自己的屬性/方法啦
console.log(youge.hasOwnProperty('want'));
// in 的話原型上的屬性也會算給物件
console.log('want' in youge);
true
true
true
false
true
所以, 這個 in
還是沒有像 Python 裡面的 in 強大, 看來還是得用 hasOwnProperty()
穩一點.
可以看到這個原型還是真和麵向物件裡面的 類 有點像的, 但是我覺得它更巧妙一點呢. 同時, 如果我們要進行封裝啥的, 那直接可以將方法和屬性寫到原型上, 這樣就相等於物件導向中的 類方法
, 讓所有的例項物件都能使用, 同時更節約記憶體.