JavaScript 中函數原型是實現繼承的基礎。prototype、construct、原型鏈以及基於原型鏈的繼承是面向對象的重要內容 ...
JavaScript 原型與繼承
JavaScript 中函數原型是實現繼承的基礎。prototype、construct、原型鏈以及基於原型鏈的繼承是面向對象的重要內容
prototype
-
原型即 prototype,是函數的一個屬性,是一個對象
function Car() {} console.log(typeof Car.prototype); console.log(Car.prototype); // object // {...}
-
所有被構造函數構造出的對象都能訪問 prototype 上定義的屬性和方法
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 萬
-
構造函數內部和 prototype 定義了同名屬性,實例對象會優先調用構造函數中的屬性
function Car() { this.price = "10 萬"; } Car.prototype.price = "100 萬"; var car1 = new Car(); console.log(car1.price); // 10 萬
-
通過實例對象不能更改 prototype 上的屬性
function Car() {} Car.prototype.price = "100 萬"; var car1 = new Car(); car1.price = "10 萬"; console.log(Car.prototype.price); // 100 萬
一般將不變化的內容或方法放在 prototype 下,需要動態變化的放在構造方法內,通過參數配置
constructor
-
constructor 指向構造函數本身
實例對象的 constructor 屬性指向構造函數
function Car() {} var car = new Car(); console.log(car.constructor); console.log(Car) // Car(){} // Car(){}
-
constructor 可以被更改
constructor 可以被修改,但是並不會影響實例化對象
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__
-
構造函數在實例化時,將其 prototype 掛載到函數內 this 的
__proto__
下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__
屬於每個實例對象的,是一個內部屬性,它們指向相同的內容 -
可以通過實例對象訪問
__proto__
屬性,並對其進行修改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 的屬性到達效果
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 重新賦值並不能對之前實例化的對象造成影響
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__
屬性下,二者指向同一對象
原型鏈
-
prototype 中的
__proto__
屬性function Car() {} var car = new Car(); console.log(Car.prototype);
當我們列印構造函數的 prototype 屬性時,可以看到
{ constructor: Car(), __proto__: {...} }
prototype 中也有
__proto__
屬性,實例化過程 protorype 被掛載到實例對象的__proto__
下,這就意味著實例對象的__proto__
中也有一個__proto__
屬性因為這裡的 prototype 是一個非空對象,是由 new Object() 或者其他自定義構造方法實例化出的,自然也有
__proto__
屬性 -
鏈式的
__proto__
原型鏈是由
__proto__
組成的鏈接,原型鏈的頂端是 Object.prototypeJuniorCoder.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,列印出
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 方法等等 -
訪問原型鏈上屬性
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 上
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()
-
Object 的 creat 方法用於創建對象,參數指定 prototype,可以為對象或 null
var test = { name: "obj" } var obj = Object.create(test); console.log(obj.name); console.log(obj.__proto__ == test); // obj // true
-
Object.creat(null)
var obj = Object.create(null); console.log(obj); document.write(obj); // {} // 報錯
控制台顯示 obj 是一個空對象,沒有任何屬性,包括
__proto__
,如果使用 document.write(obj) 則會報錯,因為 document.write 方法會把參數轉成字元串再列印在頁面,預設調用 toString() 方法,toString 方法需要從原型鏈上繼承而來,而 obj 是一個完全的空對象,沒有原型鏈,也沒有 toString 方法,所以會報錯
基於原型的繼承
-
利用原型鏈實現繼承
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 的自身屬性及原型鏈
-
call/apply 實現繼承
function JuniorCoder(lowerSkill) { this.lowerSkill = lowerSkill; } function SeniorCoder(lowerSkill, advancedSkill) { JuniorCoder.apply(this, [lowerSkill]); this.advancedSkill = advancedSkill; } var senior = new SeniorCoder("javascript", "vue");
繼承了 JuniorCoder 實例的自身屬性,不能繼承原型鏈
-
公共原型繼承
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
-
中間對象繼承(聖杯模式)
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 不相互影響,這種繼承方式更為實用
進行封裝以後,更適應企業級開發
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
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
判斷實例對象的原型鏈上是否有某個構造方法
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
// true