JavaScript 原型與繼承
阿新 • • 發佈:2020-03-21
# JavaScript 原型與繼承
> JavaScript 中函式原型是實現繼承的基礎。prototype、construct、原型鏈以及基於原型鏈的繼承是面向物件的重要內容
## prototype
1. 原型即 prototype,是函式的一個屬性,是一個物件
```javascript
function Car() {}
console.log(typeof Car.prototype);
console.log(Car.prototype);
// object
// {...}
```
2. 所有被建構函式構造出的物件都能訪問 prototype 上定義的屬性和方法
```javascript
function Car() {
this.brand = "BMW";
this.color = "red";
}
Car.prototype.price = "100 萬";
var car1 = new Car();
var car2 = new Car();
console.log(car1.price);
console.log(car2.price);
// 100 萬
// 100 萬
```
3. 建構函式內部和 prototype 定義了同名屬性,例項物件會優先呼叫建構函式中的屬性
```javascript
function Car() {
this.price = "10 萬";
}
Car.prototype.price = "100 萬";
var car1 = new Car();
console.log(car1.price);
// 10 萬
```
4. 通過例項物件不能更改 prototype 上的屬性
```javascript
function Car() {}
Car.prototype.price = "100 萬";
var car1 = new Car();
car1.price = "10 萬";
console.log(Car.prototype.price);
// 100 萬
```
> 一般將不變化的內容或方法放在 prototype 下,需要動態變化的放在構造方法內,通過引數配置
## constructor
1. constructor 指向建構函式本身
例項物件的 constructor 屬性指向建構函式
```javascript
function Car() {}
var car = new Car();
console.log(car.constructor);
console.log(Car)
// Car(){}
// Car(){}
```
2. constructor 可以被更改
constructor 可以被修改,但是並不會影響例項化物件
```javascript
function Bike() {
this.name = "bike";
}
Bike.prototype.name = "Bike";
function Car() {}
Car.prototype = {
constructor: Bike
}
var car = new Car();
console.log(Car.prototype);
console.log(car.name);
// {constructor: Bike(){}, ...}
// undefined
```
## `__proto__`
1. 建構函式在例項化時,將其 prototype 掛載到函式內 this 的 `__proto__` 下
```javascript
function Car() {}
Car.prototype.name = "Jett";
var car = new Car();
console.log(Car.prototype);
console.log(car.__proto__);
// Car.prototype ->
// {
// name: "Jett",
// construct: Car(){}
// _proto_: {...}
// }
// car._proto_ ->
// {
// name: "Jett",
// construct: Car(){}
// _proto_: {...}
// }
//
```
可以看到,打印出的 Car.prototype 和 car.`__proto__` 內容一致。因為在例項化物件時,Car.prototype 被掛載到函式內的 this.`__proto__` 上,即例項物件的 `__proto__` 屬性上
prototype 是建構函式的屬性,`__proto__` 屬於每個例項物件的,是一個內部屬性,它們指向相同的內容
2. 可以通過例項物件訪問 `__proto__` 屬性,並對其進行修改
```javascript
function Car() {}
Car.prototype.name = 'BWM';
var car = new Car();
console.log(car.name);
car.__proto__= {
name:"Benz"
}
console.log(car.name);
// BWM
// Benz
```
也可以更改 prototype 的屬性到達效果
```javascript
function Car() {}
Car.prototype.name = 'BWM';
var car = new Car();
console.log(car.name);
Car.prototype.name = 'Benz';
console.log(car.name);
// BWM
// Benz
```
但是,將 prototype 重新賦值並不能對之前例項化的物件造成影響
```javascript
function Car() {}
Car.prototype.name = 'BWM';
var car = new Car();
console.log(car.name);
Car.prototype = {
name: "Benz"
}
console.log(car.name);
// BWM
// BWM
```
這是因為重新賦值相當於建立新物件,使 prototype 指向的新的物件,而例項物件的 `__proto__` 屬性依然指向原來的內容,相當於一個物件的兩個引用,其中一個不在指向該物件,而且指向了新物件
這不能對已經例項化出的物件造成影響,但是後面再例項化物件則可以造成影響,因為例項化過程中將修改後的 prototype 掛載到了例項物件的 `__proto__` 屬性下,二者指向同一物件
## 原型鏈
1. prototype 中的 `__proto__` 屬性
```javascript
function Car() {}
var car = new Car();
console.log(Car.prototype);
```
當我們列印建構函式的 prototype 屬性時,可以看到
```javascript
{
constructor: Car(),
__proto__: {...}
}
```
prototype 中也有 `__proto__` 屬性,例項化過程 protorype 被掛載到例項物件的 `__proto__` 下,這就意味著例項物件的 `__proto__` 中也有一個 `__proto__` 屬性
因為這裡的 prototype 是一個非空物件,是由 new Object() 或者其他自定義構造方法例項化出的,自然也有 `__proto__` 屬性
2. 鏈式的 `__proto__`
原型鏈是由 `__proto__` 組成的連結,原型鏈的頂端是 Object.prototype
```javascript
JuniorCoder.prototype.basicSkill = "html/css";
function JuniorCoder() {
this.lowerSkill = "javascript"
}
var junior = new JuniorCoder();
SeniorCoder.prototype = junior;
function SeniorCoder() {
this.advancedSkill = "vue";
}
var senior = new SeniorCoder();
console.log(senior);
```
這裡將 JuniorCoder() 的例項物件賦值給 SeniorCoder.prototype,打印出
```javascript
SeniorCoder {
advcedSkill: "vue",
__proto__: { // senior.__proto__ ,即 SeniorCoder.protoype
lowerSkill: "javascript",
__proto__: { // junior.__proto__ ,即 JuniorCoder.prototype
basicSkill: "html/css",
__proto__: { // Object.prototype
constructor: Object(),
toString: toString()
// ...
}
}
}
}
```
可以看出,senior 的 `__proto__` 屬性指向 JuniorCoder() 例項 junior,這是因為之前 將 junior 賦值給了 SeniorCoder.prototype
此外,junior 的 `__proto__` 也指向了一個物件,這個物件就是 JuniorCoder.porotype,相當於 new Object() 得出的,所以 junior 的 `__proto__` 下的 `__proto__` 就是 Object.prototype,這就是原型鏈的頂端,在裡面我們還可以看到 toString 方法等等
3. 訪問原型鏈上屬性
```javascript
JuniorCoder.prototype.basicSkill = "html/css";
JuniorCoder.prototype.sex = "man";
function JuniorCoder() {
this.lowerSkill = "javascript"
this.age = 22;
}
var junior = new JuniorCoder();
SeniorCoder.prototype = junior;
function SeniorCoder() {
this.advancedSkill = "vue";
}
var senior = new SeniorCoder();
console.log(senior.age);
console.log(senior.sex);
// 22
// man
```
senior 可以訪問 junior 本身的屬性,也可以訪問 JuniorCoder.prototype 上的屬性,因為 junior 被掛載到了 SeniorCoder.prototype 上
```javascript
JuniorCoder.prototype.basicSkill = "html/css";
function JuniorCoder() {
this.lowerSkill = "javascript";
this.years = 3;
}
var junior = new JuniorCoder();
SeniorCoder.prototype = junior;
function SeniorCoder() {
this.advancedSkill = "vue";
}
var senior = new SeniorCoder();
senior.years++;
// 等同於 senior.years = senior.years + 1;
console.log(senior.years);
console.log(junior.years);
// 4
// 3
```
可以看到,通過 senior 試圖改變 years 屬性並不能真正影響 junior 的 years 屬性,實際上只是在 senior 下建立了新的 years 屬性,並將 junior.years 加一的結果賦值給它
## Object.creat()
1. Object 的 creat 方法用於建立物件,引數指定 prototype,可以為物件或 null
```javascript
var test = {
name: "obj"
}
var obj = Object.create(test);
console.log(obj.name);
console.log(obj.__proto__ == test);
// obj
// true
```
2. Object.creat(null)
```javascript
var obj = Object.create(null);
console.log(obj);
document.write(obj);
// {}
// 報錯
```
控制檯顯示 obj 是一個空物件,沒有任何屬性,包括 `__proto__`,如果使用 document.write(obj) 則會報錯,因為 document.write 方法會把引數轉成字串再列印在頁面,預設呼叫 toString() 方法,toString 方法需要從原型鏈上繼承而來,而 obj 是一個完全的空物件,沒有原型鏈,也沒有 toString 方法,所以會報錯
## 基於原型的繼承
1. 利用原型鏈實現繼承
```javascript
JuniorCoder.prototype.basicSkill = "html/css";
function JuniorCoder() {
this.lowerSkill = "javascript"
this.age = 22;
}
var junior = new JuniorCoder();
SeniorCoder.prototype = junior;
function SeniorCoder() {
this.advancedSkill = "vue";
}
var senior = new SeniorCoder();
```
senior 繼承了 junior 的自身屬性及原型鏈
2. call/apply 實現繼承
```javascript
function JuniorCoder(lowerSkill) {
this.lowerSkill = lowerSkill;
}
function SeniorCoder(lowerSkill, advancedSkill) {
JuniorCoder.apply(this, [lowerSkill]);
this.advancedSkill = advancedSkill;
}
var senior = new SeniorCoder("javascript", "vue");
```
繼承了 JuniorCoder 例項的自身屬性,不能繼承原型鏈
3. 公共原型繼承
```javascript
JuniorCoder.prototype.basicSkill = "html/css";
function JuniorCoder() {
this.lowerSkill = "javascript"
}
SeniorCoder.prototype = JuniorCoder.prototype;
function SeniorCoder() {
this.advancedSkill = "vue";
}
var senior = new SeniorCoder();
```
senior 繼承 JuniorCoder 例項的原型鏈,不繼承自身屬性,但是改動 SeniorCoder.prototype 會影響 JuniorCoder.prototype
4. 中間物件繼承(聖盃模式)
```javascript
JuniorCoder.prototype.basicSkill = "html/css";
function JuniorCoder() {
this.lowerSkill = "javascript"
}
Buffer.prototype = JuniorCoder.prototype;
function Buffer() {}
SeniorCoder.prototype = new Buffer();
function SeniorCoder() {
this.advancedSkill = "vue";
}
SeniorCoder.prototype.basicSkill = "markdown";
console.log(SeniorCoder.prototype.basicSkill);
console.log(JuniorCoder.prototype.basicSkill);
// markdown
// html/css
```
繼承原型鏈,不繼承自身屬性,prototype 不相互影響,這種繼承方式更為實用
進行封裝以後,更適應企業級開發
```javascript
JuniorCoder.prototype.basicSkill = "html/css";
function JuniorCoder() {
this.lowerSkill = "javascript"
}
function SeniorCoder() {
this.advancedSkill = "vue";
}
inherit(SeniorCoder, JuniorCoder);
SeniorCoder.prototype.basicSkill = "markdown";
console.log(new SeniorCoder());
console.log(new JuniorCoder());
function inherit(Target, Origin) {
Target.prototype = Object.create(Origin.prototype);
Target.prototype.constructor = Target;
Target.prototype.superClass = Origin;
}
```
使用 Object 的 creat 方法直接建立中間物件,將 construtor、superClass 屬性設定好,便於分析和維護
## hasOwnProperty()
判斷屬性是否是例項物件本身的,如果是則返回 true
```javascript
Car.prototype.brand = "BMW";
function Car() {
this.color = "red";
}
var car = new Car();
console.log(car.hasOwnProperty("brand"));
console.log(car.hasOwnProperty("color"));
// false
// true
```
## instanceOf
判斷例項物件的原型鏈上是否有某個構造方法
```javascript
JuniorCoder.prototype.basicSkill = "html/css";
function JuniorCoder() {
this.lowerSkill = "javascript"
}
function SeniorCoder() {
this.advancedSkill = "vue";
}
inherit(SeniorCoder, JuniorCoder);
function inherit(Target, Origin) {
Target.prototype = Object.create(Origin.prototype);
Target.prototype.constructor = Target;
Target.prototype.superClass = Origin;
}
var senior = new SeniorCoder();
console.log(senior instanceof SeniorCoder);
console.log(senior instanceof JuniorCoder);
console.log(senior instanceof Object);
// true
// true
//