前言:適合有基礎的同學入門嘗試 / 復習回憶。
對象基礎:
1.創建用戶對象
const user = {// 屬性(鍵值對)name: "小島",age: 20,isAdmin: false,
}
2.方法(函數屬性)
sayHello() {console.log(`你好,我是${this.name}`);
}
3.訪問屬性
console.log(user.name); // "小島"
user.sayHello(); // 調用方法
屬性和方法操作:
1.添加新屬性
user.email = "Island@example.com";
2.刪除屬性?
delete user.isAdmin;
※3.遍歷屬性(重要!)
for (let key in user) {console.log(`${key}: ${user[key]}`);
}
※4.?方法中的this(重點理解!)
const car = {brand: "Toyota",start() {console.log(`${this.brand}啟動了!`);}
};
car.start(); // this指向car對象
原型和繼承
1. 原型鏈:
每個JS對象都有隱藏的[[Prototype]] (原型)
屬性,形成鏈條
同時,?prototype
?屬性也是一個對象(稱為原型對象),JavaScript 會自動為這個原型對象添加?constructor
?屬性,默認指向該函數本身
const animal = {eats: true
};const rabbit = {jumps: true,__proto__: animal // 設置原型(實際開發用Object.create,下面案例會有,先略過)
};console.log(rabbit.eats); // true(來自原型)
console.log(rabbit.jumps); // true(自身屬性)
2. 構造函數(傳統實現方式)
// 1. 創建構造函數(首字母大寫)
function Person(name, age) {this.name = name;this.age = age;
}// 2. 在原型上添加方法
Person.prototype.introduce = function() {console.log(`我是${this.name},今年${this.age}歲`);
};// 3. 創建實例
const p1 = new Person("李四", 30);
p1.introduce(); // 調用原型方法// 原型關系驗證
console.log(p1.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
老式方式的繼承:
了解即可~這種方式是 ES6 類語法出現之前實現繼承的標準方式,理解它有助于深入掌握 JavaScript 面向對象的本質 —— 基于原型的繼承系統。?
// 父類構造函數 - 用于初始化實例屬性
function Animal(name) {this.name = name; // 每個動物都有"name"屬性
}// 重點:在原型上定義方法 - 所有實例共享
Animal.prototype.eat = function() {console.log(`${this.name}在吃東西`);
};// 子類構造函數
function Bird(name) {Animal.call(this, name); // 關鍵:調用父類構造函數,繼承屬性
}// 繼承方法
Bird.prototype = Object.create(Animal.prototype);
Bird.prototype.constructor = Bird;Bird.prototype.fly = function() {console.log(`${this.name}在飛翔`); // 子類特有方法,不會影響父類 Animal
};// 創建Bird實例
const bird = new Bird("小鳥");// 調用繼承自Animal的屬性和方法
console.log(bird.name); // 輸出:"小鳥"
bird.eat(); // 輸出:"小鳥在吃東西"// 調用Bird自身的方法
bird.fly(); // 輸出:"小鳥在飛翔"
?代碼解析:
Animal.call(this, name)
?這行代碼非常重要? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
- 它調用了父類?
Animal
?的構造函數- 通過?
call(this)
?確保父類構造函數中的?this
?指向當前?Bird
?實例- 這樣?
Bird
?實例就能繼承?Animal
?的?name
?屬性Object.create(Animal.prototype)
?創建一個新對象,其原型指向?Animal.prototype
- 把這個新對象賦值給?
Bird.prototype
,使得?Bird
?實例能訪問?Animal
?原型上的方法(如?eat
)- 為什么要修復?
constructor
?
- 因為上一步操作后,
Bird.prototype.constructor
?會指向?Animal
- 手動設置為?
Bird
?才符合邏輯(構造函數的原型的?constructor
?應該指向自身)
原型鏈結構解析
bird實例 →Bird.prototype → Animal.prototype → Object.prototype → null
↑? ? ? ? ? ? ? ? ? ? ? ? ↑? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??↑
fly:? function? ? ? ? eat: function? ? ? ? ? toString等通用方法
↑? ? ? ? ? ? ? ? ? ? ? ?
constructor: Bird? ? ? ? ?
關鍵知識點總結(重點)
- 屬性繼承:通過?
父類.call(this, 參數)
?在子類構造函數中實現 - 方法繼承:通過?
子類.prototype = Object.create(父類.prototype)
?實現 - 構造函數修復:必須手動設置?
子類.prototype.constructor = 子類
- 原型鏈:實例通過?
__proto__
?指向原型對象,形成鏈式查找結構
※3. class語法(ES6現代寫法)
class Animal {// 構造函數:初始化動物名稱constructor(name) {this.name = name;}// 原型方法speak() {console.log(`${this.name} 發出聲音`);}
}// extends繼承
class Dog extends Animal {constructor(name, breed) {super(name); // 調用父類構造函數,初始化名稱this.breed = breed; // 新增 breed 屬性存儲狗的品種}// 方法重寫speak() {console.log(`${this.name} 汪汪叫!`);}// 新增方法run() {console.log(`${this.name} 在奔跑`);}
}// 使用
const myDog = new Dog("旺財", "金毛");
myDog.speak(); // "旺財 汪汪叫!"
myDog.run(); // "旺財 在奔跑"
代碼解析
Animal 基類
- 定義了一個動物類?
Animal
,包含:
- 構造函數?
constructor(name)
:初始化動物名稱- 原型方法?
speak()
:輸出動物發出聲音的通用描述Dog 子類(繼承自 Animal)
- 使用?
extends
?關鍵字實現繼承- 構造函數?
constructor(name, breed)
:
- 通過?
super(name)
?調用父類構造函數,初始化名稱- 新增?
breed
?屬性存儲狗的品種- 方法重寫:重寫了父類的?
speak()
?方法,改為具體的 "汪汪叫"- 新增方法:添加了?
run()
?方法,是 Dog 類特有的行為使用類創建實例
const myDog = new Dog("旺財", "金毛")
?創建了 Dog 類的實例- 調用方法:
myDog.speak()
?和?myDog.run()
?分別調用了重寫的方法和新增方法?
原型鏈深度圖解 :
原型鏈:當訪問一個對象的屬性或方法時,JavaScript 會沿著原型鏈向上查找,直到找到為止
myDog實例 → Dog.prototype → Animal.prototype → Object.prototype → null
↑? ? ? ? ? ? ? ? ? ? ? ? ? ?↑? ? ? ? ? ? ? ? ? ? ? ? ↑? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??↑
breed? ? ? ? ? ? ? ? ? ?run()? ? ? ? ? ? ? speak()基礎版? ? ? ? ? toString等通用方法
↑? ? ? ? ? ? ? ? ? ? ? ? ? ?↑? ? ? ? ? ? ? ? ? ? ? ? ↑
name? ? ? ? ? ? ?constructor? ? ? ? ? ?constructor?
代碼解析:?
第一層:myDog 實例
- 包含實例自身的屬性:
name
(來自父類)和?breed
(自身特有)第二層:Dog.prototype
- 包含 Dog 類定義的方法:
run()
?和重寫的?speak()
- 包含 Dog 類的構造函數
第三層:Animal.prototype
- 包含 Animal 類定義的方法:原始的?
speak()
?方法- 包含 Animal 類的構造函數
第四層:Object.prototype
- 包含 JavaScript 所有對象都具有的通用方法,如?
toString()
、hasOwnProperty()
?等終點:null
- 原型鏈的末端,沒有任何屬性和方法
?幾個小疑問:
一、為什么 Dog 子類必須用?super(name)
?而不能直接?this.name = name
?
原因一:原型鏈初始化順序
子類實例的創建過程是:先初始化父類的屬性和方法,再添加子類自己的特性。
當你在子類構造函數中使用?this
?關鍵字時,JavaScript 要求必須先通過?super()
?完成父類的初始化,否則會報錯。
比如下面的代碼會直接報錯:
class Dog extends Animal {constructor(name, breed) {this.name = name; // 錯誤!必須先調用 super()this.breed = breed;}
}
原因二:代碼復用
父類?Animal
?的構造函數已經實現了?this.name = name
?的邏輯,子類通過?super(name)
?調用父類構造函數,就不需要重復寫?this.name = name
?了,這正是繼承的意義 —— 復用父類代碼。
二、如果創建?Animal
?類的實例,調用?speak()
?會輸出什么?
會輸出父類定義的 “發出聲音”,而不是 “汪汪叫”。
原因是:方法調用遵循 “就近原則”—— 優先使用當前類或實例自身的方法,找不到再沿原型鏈向上找。
舉例說明:
// 創建 Animal 實例
const animal = new Animal("小動物");
animal.speak(); // 輸出:"小動物 發出聲音"
animal
?是?Animal
?的實例,它的原型鏈是:animal → Animal.prototype → Object.prototype → null
。- 調用?
speak()
?時,JavaScript 在?Animal.prototype
?上找到了?speak()
?方法(父類定義的版本),所以直接執行該方法,輸出 “發出聲音”。
?而?Dog
?實例?myDog
?調用?speak()
?時:
- 它的原型鏈是:
myDog → Dog.prototype → Animal.prototype → ...
。 - JavaScript 先在?
Dog.prototype
?上找到了重寫后的?speak()
?方法(“汪汪叫”),就不會再去找父類的版本了。(子類重寫會覆蓋父類方法)
案例實操(在控制臺嘗試)
(1)原型鏈追溯
class A {}
class B extends A {}
const b = new B();console.log(b instanceof B); // true
console.log(b instanceof A); // true
console.log(b instanceof Object); // true// 手動檢查原型鏈
console.log(b.__proto__ === B.prototype,B.prototype.__proto__ === A.prototype,A.prototype.__proto__ === Object.prototype
);
(2)方法覆蓋實驗
class Parent {msg = "父類屬性";show() {console.log(this.msg);}
}class Child extends Parent {msg = "子類屬性";
}const obj = new Child();
obj.show(); // 輸出什么?為什么?
(3)靜態方法
class User {static staticMethod() {console.log("我是靜態方法,通過類名調用");}
}User.staticMethod(); // 正確
const u = new User();
u.staticMethod(); // 報錯
(4)思考題目
1.老式傳統方式:以下代碼輸出什么?為什么?(可以回顧上面的傳統方式)
function Foo() {}
const f1 = new Foo();console.log(f1.constructor === Foo);
console.log(Foo.prototype.constructor === Foo);
首先理解幾個關鍵概念:
Foo
?是一個構造函數f1
?是通過?new Foo()
?創建的實例對象- 每個函數都有一個?
prototype
(原型)屬性- 每個對象都有一個?
constructor
(構造函數)屬性,指向創建它的構造函數第一行輸出?
f1.constructor === Foo
?→?true
- 當通過?
new Foo()
?創建?f1
?時,f1
?本身并沒有?constructor
?屬性- JavaScript 會沿著原型鏈查找,在?
Foo.prototype
?上找到?constructor
?屬性- 這個屬性指向?
Foo
?構造函數,因此?f1.constructor
?最終指向?Foo
第二行輸出?
Foo.prototype.constructor === Foo
?→?true
- 函數的?
prototype
?屬性是一個對象(稱為原型對象)- JavaScript 會自動為這個原型對象添加?
constructor
?屬性,默認指向該函數本身- 因此?
Foo.prototype.constructor
?直接指向?Foo
?構造函數
補充延伸知識:
- 原型對象的?
constructor
?屬性是區分對象類型的重要標識- 如果手動修改了原型對象(如實現繼承時),需要手動修復?
constructor
?指向,否則會出現不符合預期的結果(如前面繼承示例中修復?Bird.prototype.constructor
?的操作)
2.如何實現一個myInstanceof
函數,判斷對象是否是某個類的實例?
3.class語法中,super關鍵字有幾種用法?分別是什么?
- 作為函數:
super(...)
?用于子類構造函數中,調用父類構造函數,初始化繼承的屬性。 - 作為對象:
super.xxx
?用于子類方法中,訪問父類的原型方法(普通方法中)或靜態方法(靜態方法中)。