我理解的繼承,是爲了解決代碼重複浪費空間與編寫精力的問題,若有兩個對象,面試
// person1
var person1={
name:'tom',
say(){
console.log(this.name);
}
}
// person2
var person2={
name:'jerry',
say(){
console.log(this.name);
}
}
複製代碼
這兩個對象都有相同的name
屬性和say()
方法,只是name
屬性值不一樣,形成了代碼的重複浪費,所以提出了節省代碼的方式:數組
function person(name){
var obj=new Object();
obj.name=name;
obj.say=function(){
console.log(obj.name);
};
return obj;
}
var person1=person("tom");
var person2=person("jerry");
複製代碼
function Person(name){
this.name=name;
this.say=function(){
console.log(this.name);
};
}
var person1=new Person("tom");
var person2=new Person("jerry");
複製代碼
其實兩種方式大同小異,由於在使用new操做符來建立一個實例對象時,產生了如下4個步驟:bash
var obj=new Object();
this
指向該對象,對該對象進行賦值obj.name='name
__proto__
指向構造函數的原型obj.__proto__=Person.prototype
這兩種產生對象的方法,雖然節省了代碼的書寫量,但在內存上仍然消耗相同的空間,每建立一個新的實例對象仍然要建立新的屬性和方法。因此就有了原型。函數
(1)首先,js裏全部的函數都有一個prototype
屬性,該屬性是一個對象;同時js裏面全部的對象(除去基本類型number,string,boolean,null和undefined
以外的全部)都有一個__proto__
屬性,因此一個函數有prototype
和__proto__
兩個屬性,能夠經過console.dir(fn)
查看。ui
function Person(name){
this.name=name;
}
console.dir(Person);
複製代碼
(2)
prototype
裏有個構造器
constructor
,指向的就是該構造函數,全部的對象都是由構造函數實例化獲得的,如今咱們來看一下剛纔講new操做符時的第3個步驟:
__proto__
指向構造函數的原型obj.__proto__=Person.prototype
用圖來表示就是:__proto__
屬性都指向構造函數的
prototype
對象,因此能夠把共享的方法寫在
prototype
裏,這樣只須要建立一個方法就能夠了。
function Person(name){
this.name=name;
}
Person.prototype.say=function(){
console.log(this.name);
}
var p1=new Person("tom");
p1.say(); // tom
var p1=new Person("jerry");
p1.say(); // jerry
複製代碼
有了原型的概念,先給出原型鏈的概念:實例對象在使用屬性或者調用方法時,若是本身沒有,則會往上一級級查找prototype
對象,直到找到爲止,若是最終也找不到則報錯,就拿上面講的,p1本身沒有say方法,可是原型對象裏面有該方法,因此能夠調用。this
有了原型鏈的概念,咱們就能夠實現繼承了,即讓子類構造函數的prototype
指向父類的一個實例對象。這樣經過原型鏈的查找就能夠繼承到父類的方法,咱們一般須要繼承的都是方法。spa
function Person(name){
this.name=name;
}
Person.prototype.say=function(){
console.log(this.name);
}
// 子類構造函數
function Student(name){
this.name=name;
}
// 將子類添加到原型鏈中
Student.prototype=new Person("tom");
// 子類本身的原型方法必須在改變原型指向後添加
Student.prototype.play=function(){
console.log(this.name+" play");
}
var s1=new Student("jerry");
s1.say(); // 原型鏈上的方法 jerry
s1.play(); // 本身原型上的方法 jerry play
// this一直指向都是s1,跟實例對象tom沒有關係
複製代碼
function Fn(){
// 實例方法,只能經過實例對象.的形式調用
this.work=function(){
console.log("work");
}
// 內部方法 只能內部調用
function learn(){}
};
// 靜態方法,只能經過函數名.的形式調用
Fn.say=function(){
console.log("say")
}
// 原型方法,只能實例.的形式調用
Fn.prototype.play=function(){
console.log("play")
}
Fn.say(); // say
Fn.play(); // 報錯
Fn.work(); // 報錯
var f1=new Fn();
f1.say(); // 報錯
f1.play(); // play
f1.work(); // work
複製代碼
咱們可使用console.dir(Person)
查看一下:prototype
[1,2,3].isArray()
Array.splice.call(obj)
複製代碼
完美搞錯,真感謝那個面試小哥哥還耐心地給我講解(捂臉羞愧)。
其實打印如下構造函數console,dir(Array)
就能夠看到3d
isArray
是靜態方法,
splice
是原型方法,因此正確的應該是:
Array.isArray([1,2,3]);
[].splice.call(obj); // []是Array的一個實例化對象
複製代碼
l instanceof R 就是判斷l的原型鏈上是否有R.prototypecode
s1 instanceof Student // true
s1 instanceof Person // true
複製代碼
父類原型上的引用屬性會被子類們共享,一個子類更改了,其他的也會被更改;
子類實例沒法向父類構造函數傳參
構造函數能夠解決向父類構造函數傳參的問題,但沒有辦法繼承父類原型上的方法。
function Person(name){
this.name=name;
}
Person.prototype.say=function(){
console.log("say");
}
function Student(name,age){
Person.call(this,name);
this.age=age;
}
var s1=new Student("xixi",12);
s1.name; // xixi
s1.age; // 12
s1.say(); // 報錯
複製代碼
即便用構造函數來繼承屬性,使用原型來繼承原型方法
function Person(name){
this.name=name;
}
Person.prototype.say=function(){
console.log("say");
}
function Student(name,age){
// 繼承屬性
Person.call(this,name);
this.age=age;
}
// 繼承方法
Student.prototype=new Person();
var s1=new Student("xixi",12);
s1.name; // xixi
s1.age; // 12
s1.say(); // say
複製代碼