JavaScript 重點知識
了解構造函數原型對象的文法特征,掌握 JavaScript 中面向對象程式設計的實作方式,基于面向對象程式設計思想實作 DOM 操作的封裝。
- 了解面向對象程式設計的一般特征
- 掌握基于構造函數原型對象的邏輯封裝
- 掌握基于原型對象實作的繼承
- 了解什麼原型鍊及其作用
- 能夠處理程式異常提升程式執行的健壯性
一、面向對象
學習 JavaScript 中基于原型的面向對象程式設計式的文法實作,了解面向對象程式設計的特征。
面向對象程式設計是一種程式設計思想,它具有 3 個顯著的特征:封裝、繼承、多态。
1.1 封裝
封裝的本質是将具有關聯的代碼組合在一起,其優勢是能夠保證代碼複用且易于維護,函數是最典型也是最基礎的代碼封裝形式,面向對象思想中的封裝仍以函數為基礎,但提供了更進階的封裝形式。
命名空間
先來回顧一下以往代碼封裝的形式:
<script>
// 普通對象(命名空間)形式的封裝
let beats = {
name: '狼',
setName: function (name) {
this.name = this.name;
},
getName() {
console.log(this.name);
}
}
beats.setName('熊');
beats.getName();
</script>
複制代碼
以往以普通對象(命名空間)形式封裝的代碼隻是單純把一系列的變量或函數組合到一起,所有的資料變量都被用來共享(使用 this 通路)。
構造函數
對比以下通過面向對象的構造函數實作的封裝:
<script>
function Person() {
this.name = '佚名';
// 設定名字
this.setName = function (name) {
this.name = name;
}
// 讀取名字
this.getName = () => {
console.log(this.name);
}
}
// 執行個體對像,獲得了構造函數中封裝的所有邏輯
let p1 = new Person();
p1.setName('小明');
console.log(p1.name);
// 執行個體對象
let p2 = new Person();
console.log(p2.name);
</script>
複制代碼
同樣的将變量和函數組合到了一起并能通過 this 實作資料的共享,所不同的是借助構造函數建立出來的執行個體對象之間是彼此不影響的。
總結:
- 構造函數展現了面向對象的封裝特性
- 構造函數執行個體建立的對象彼此獨立、互不影響
- 命名空間式的封裝無法保證資料的獨立性
注:可以舉一些例子,如女娲造人等例子,加深對構造函數的了解。
原型對象
實際上每一個構造函數都有一個名為
prototype
的屬性,譯成中文是原型的意思,
prototype
的是對象類據類型,稱為構造函數的原型對象,每個原型對象都具有
constructor
屬性代表了該原型對象對應的構造函數。
<script>
function Person() {
}
// 每個函數都有 prototype 屬性
console.log(Person.prototype);
</script>
複制代碼
了解了 JavaScript 中構造函數與原型對象的關系後,再來看原型對象具體的作用,如下代碼所示:
<script>
function Person() {
// 此處未定義任何方法
}
// 為構造函數的原型對象添加方法
Person.prototype.sayHi = function () {
console.log('Hi~');
}
// 執行個體化
let p1 = new Person();
p1.sayHi(); // 輸出結果為 Hi~
</script>
複制代碼
構造函數
Person
中未定義任何方法,這時執行個體對象調用了原型對象中的方法
sayHi
,接下來改動一下代碼:
<script>
function Person() {
// 此處定義同名方法 sayHi
this.sayHi = function () {
console.log('嗨!');
}
}
// 為構造函數的原型對象添加方法
Person.prototype.sayHi = function () {
console.log('Hi~');
}
let p1 = new Person();
p1.sayHi(); // 輸出結果為 嗨!
</script>
複制代碼
構造函數
Person
中定義與原型對象中相同名稱的方法,這時執行個體對象調用則是構造函中的方法
sayHi
。
通過以上兩個簡單示例不難發現 JavaScript 中對象的工作機制:當通路對象的屬性或方法時,先在目前執行個體對象是查找,然後再去原型對象查找,并且原型對象被所有執行個體共享。
<script>
function Person() {
// 此處定義同名方法 sayHi
this.sayHi = function () {
console.log('嗨!' + this.name);
}
}
// 為構造函數的原型對象添加方法
Person.prototype.sayHi = function () {
console.log('Hi~' + this.name);
}
// 在構造函數的原型對象上添加屬性
Person.prototype.name = '小明';
let p1 = new Person();
p1.sayHi(); // 輸出結果為 嗨!
let p2 = new Person();
p2.sayHi();
</script>
複制代碼
總結:結合構造函數原型的特征,實際開發重往往會将封裝的功能函數添加到原型對象中。
1.2 繼承
繼承是面向對象程式設計的另一個特征,通過繼承進一步提升代碼封裝的程度,JavaScript 中大多是借助原型對象實作繼承的特性。
龍生龍、鳳生鳳、老鼠的兒子會打洞描述的正是繼承的含義,分别封裝中國人和日本人的行為特征來了解程式設計中繼承的含義,代碼如下:
<script>
// 封裝中國人的行為特征
function Chinese() {
// 中國人的特征
this.arms = 2;
this.legs = 2;
this.eyes = 2;
this.skin = 'yellow';
this.language = '中文';
// 中國人的行為
this.walk = function () {}
this.sing = function () {}
this.sleep = function () {}
}
// 封裝日本人的行為特征
function Japanese() {
// 日本人的特征
this.arms = 2;
this.legs = 2;
this.eyes = 2;
this.skin = 'yellow';
this.language = '日文';
// 日本人的行為
this.walk = function () {}
this.sing = function () {}
this.sleep = function () {}
}
</script>
複制代碼
其實我們都知道無論是中國人、日本人還是其它民族,人們的大部分特征是一緻的,然而展現在代碼中時人的相同的行為特征被重複編寫了多次,代碼顯得十分備援,我們可以将重複的代碼抽離出來:
原型繼承
基于構造函數原型對象實作面向對象的繼承特性。
<script>
// 所有人
function Person() {
// 人的特征
this.arms = 2;
this.legs = 2;
this.eyes = 2;
// 人的行為
this.walk = function () {}
this.sing = function () {}
this.sleep = function () {}
}
// 中國人
function Chinese() {
this.skin = 'yellow';
this.language = '中文';
}
// 日本人
function Japanese() {
this.skin = 'yellow';
this.language = '日文';
}
</script>
複制代碼
上述代碼可以了解成将
Chinese
和
Japanese
共有的屬性和方法提取出來了,也就是說
Chinese
和
Japanese
需要【共享】一些屬性和方法,而原型對象的屬性和方法恰好是可以被用來共享的,是以我們看如下代碼:
<script>
// 中國人
function Chinese() {
this.skin = 'yellow';
this.language = '中文';
}
// 日本人
function Japanese() {
this.skin = 'yellow';
this.language = '日文';
}
// 人們【共有】的行為特征
let people = {
// 人的特征
arms: 2,
legs: 2,
eyes:2,
// 人的行為
walk: function () {},
sleep: function () {},
sing: function () {}
}
// 為 prototype 重新指派
Chinese.prototype = people;
Chinese.prototype.constructor = Chinese;
</script>
複制代碼
如下圖所示:
建立對象
people
将公共的的屬性和方法獨立出來,然後指派給構造函數的
prototype
這樣無論有多少個民族都可以共享公共的屬性和方法了:
<script>
// 人們【共有】的行為特征
let people = {
// 人的特征
arms: 2,
legs: 2,
eyes:2,
// 人的行為
walk: function () {},
sleep: function () {},
sing: function () {}
}
// 中國人
function Chinese() {
this.skin = 'yellow';
this.language = '中文';
}
// 日本人
function Japanese() {
this.skin = 'yellow';
this.language = '日文';
}
function Englist() {
this.skin = 'white';
this.language= '英文';
}
// 中國人
Chinese.prototype = people;
Chinese.prototype.constructor = Chinese;
let c1 = new Chinese();
// 日本人
Japanese.prototype = people;
Janpanese.prototype.constructor = Japanese;
// 英國人
English.prototype = people;
English.prototype.constructor = English;
// ...
</script>
複制代碼
繼承是一種可以“不勞而獲”的手段!!!上述代碼中
Chinese
、
Japanese
、
English
都輕松的獲得了
people
的公共的方法和屬性,我們說
Chinese
、
Japanese
、
English
繼承了
people
。
上述代碼中是以命名空間的形式實作的繼承,事實上 JavaScript 中繼承更常見的是借助構造函數來實作:
<script>
// 所有人
function Person() {
// 人的特征
this.arms = 2;
this.legs = 2;
this.eyes = 2;
// 人的行為
this.walk = function () {}
this.sing = function () {}
this.sleep = function () {}
}
// 封裝中國人的行為特征
function Chinese() {
// 中國人的特征
this.skin = 'yellow';
this.language = '中文';
}
// 封裝日本人的行為特征
function Japanese() {
// 日本人的特征
this.skin = 'yellow';
this.language = '日文';
}
// human 是構造函數 Person 的執行個體
let human = new Person();
// 中國人
Chinese.prototype = human;
Chinese.prototype.constructor = Chinese;
// 日本人
Japanese.prototype = human;
Japanese.prototype.constructor = Japanese;
</script>
複制代碼
如下圖所示:
原型鍊
基于原型對象的繼承使得不同構造函數的原型對象關聯在一起,并且這種關聯的關系是一種鍊狀的結構,我們将原型對象的鍊狀結構關系稱為原型鍊,
<script>
// Person 構造函數
function Person() {
this.arms = 2;
this.walk = function () {}
}
// Person 原型對象
Person.prototype.legs = 2;
Person.prototype.eyes = 2;
Person.prototype.sing = function () {}
Person.prototype.sleep = function () {}
// Chinese 構造函數
function Chinese() {
this.skin = 'yellow';
this.language = '中文';
}
// Chinese 原型對象
Chinese.prototype = new Person();
Chinese.prototype.constructor = Chinese;
// 執行個體化
let c1 = new Chinese();
console.log(c1);
</script>
複制代碼