JS 原型鏈查詢 (上)

致于数据科学家的小陈發表於2024-04-12

我們都知道面嚮物件語言如 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() 穩一點.

可以看到這個原型還是真和麵向物件裡面的 類 有點像的, 但是我覺得它更巧妙一點呢. 同時, 如果我們要進行封裝啥的, 那直接可以將方法和屬性寫到原型上, 這樣就相等於物件導向中的 類方法, 讓所有的例項物件都能使用, 同時更節約記憶體.

相關文章