js中有一個話弯菊,叫萬物皆對象纵势,因為js是基于原型的
原型
當(dāng)我們創(chuàng)建一個對象時 let obj = { age: 25 },我們可以發(fā)現(xiàn)能使用很多種函數(shù),但是我們明明沒有定義過它們钦铁,對于這種情況你是否有過疑惑软舌?
當(dāng)我們在瀏覽器中打印 obj 時你會發(fā)現(xiàn),在 obj 上居然還有一個 proto 屬性牛曹,那么看來之前的疑問就和這個屬性有關(guān)系了佛点。
其實每個 JS 對象都有 proto 屬性,這個屬性指向了原型躏仇。這個屬性在現(xiàn)在來說已經(jīng)不推薦直接去使用它了恋脚,這只是瀏覽器在早期為了讓我們訪問到內(nèi)部屬性 [[prototype]] 來實現(xiàn)的一個東西。
講到這里好像還是沒有弄明白什么是原型焰手,接下來讓我們再看看 proto 里面有什么吧糟描。
看到這里你應(yīng)該明白了,原型也是一個對象书妻,并且這個對象中包含了很多函數(shù)船响,所以我們可以得出一個結(jié)論:對于 obj 來說,可以通過 proto 找到一個原型對象躲履,在該對象中定義了很多函數(shù)讓我們來使用见间。
在上面的圖中我們還可以發(fā)現(xiàn)一個 constructor 屬性,也就是構(gòu)造函數(shù)
打開 constructor 屬性我們又可以發(fā)現(xiàn)其中還有一個 prototype 屬性工猜,并且這個屬性對應(yīng)的值和先前我們在 proto 中看到的一模一樣米诉。所以我們又可以得出一個結(jié)論:原型的 constructor 屬性指向構(gòu)造函數(shù),構(gòu)造函數(shù)又通過 prototype 屬性指回原型篷帅,但是并不是所有函數(shù)都具有這個屬性史侣,F(xiàn)unction.prototype.bind() 就沒有這個屬性。
所以每一個構(gòu)造函數(shù)都有一個原型對象(也就是Person.prototype)魏身,每一個原型對象都有一個指針constructor指向構(gòu)造函數(shù)惊橱,每一個實例都有一個內(nèi)部指針(proto),指向原型對象箭昵,原型對象上的屬性和方法能被實例所訪問
繼承
類與類之間的關(guān)系 基類 父類 子類
//call&apply方法實現(xiàn)繼承
function Person(name,age){
this.name = name;
this.age = age;
this.sayHello = function (){
console.log(this.name);
}
}
function Male(name,age){
//繼承父類Person call&apply 調(diào)用的是父類的構(gòu)造函數(shù)
// Person.call(this,name,age);
Person.apply(this,[name,age]);
}
var male = new Male("ly",20);
male.sayHello();
//這里不能用call&apply的原因是父類的構(gòu)造函數(shù)里邊什么也沒有 現(xiàn)在要調(diào)用的是父類的原型對象
function Person(){
}
//Person.prototype里的屬性和方法可以被Person的實例訪問到
Person.prototype.name = "jhon";
Person.prototype.age = 20;
Person.prototype.sayHello = function (){
console.log(this.name);
}
function Male(){
this.sayHi=function (){
console.log("aa")
}
}
//Male.prototype.__proto__ 指向Person.prototype
Male.prototype = new Person();
//Male.prototype = Person.prototype;
var male = new Male();
male.sayHello();
console.log(male.__proto__);//正常情況下應(yīng)該指向Male.prototype 但是現(xiàn)在指向Person.prototype
//male.__proto__ -> Male.prototype Male.prototype.__proto__ -> Person.prototype
//當(dāng)male調(diào)用sayhello方法時會找到Male.prototype税朴,如果Male.prototype沒有的話就會繼續(xù)向它的父類找,直到找到Object.prototype停止家制,Object.prototype.__proto__為null
//原型鏈 原型鏈上的屬性和方法都能被實例所訪問到
console.log(male.__proto__ == Male.prototype);//true
console.log(Male.prototype.__proto__ == Person.prototype);//true
male.sayHi();
var obj = {};
//將其他類型轉(zhuǎn)換成字符串時正林,默認(rèn)會調(diào)用toString方法,這個方法是頂層原型對象上的方法颤殴,可以改寫,改寫之后觅廓,轉(zhuǎn)換的結(jié)果以改寫結(jié)果為準(zhǔn)
obj.toString = function(){
return 111111;
}
document.write(obj);//111111
### 組合繼承
function Person(name,age){
this.name = name;
this.age = age;
}
Person.prototype.sayHello = function (){
console.log(this.name);
}
function Male(name,age){
Person.call(this,name,age);
}
//Male.prototype = new Person();
//弊端在于Person.call的時候運行了一次構(gòu)造函數(shù)Person,當(dāng)Male.prototype = new Person()的時候又運行了一次
// Male.prototype = Person.prototype;
// //這時候Person.call指向的是Person里的name和age诅病,而Male.prototype = Person.prototype指的是sayhello 弊端是這時候相當(dāng)于傳址哪亿,父類person的實例也能夠訪問到子類Male里的原型對象的方法,而這是不合道理的
// Male.prototype.sayHi = function(){
// console.log("aa");
// }
// var person = new Person();
// person.sayHi();
//遍歷person.prototype call繼承實例屬性 這種方式繼承原型方法
for(var i in Person.prototype){
Male.prototype[i] = Person.prototype[i];
}
var male = new Male("ly",20);
male.sayHello();
寄生式組合繼承 Object.create()
var obj1 = {a:1};
var obj2 = {b:2};
var a = Object.create(obj1);
console.log(a.__proto__);//結(jié)果是a:1 這時候的obj1是作為創(chuàng)建出來的實例a的原型對象存在 a.__proto__指obj1
function Person(name,age){
this.name = name;
this.age = age;
}
Person.prototype.sayHello = function (){
console.log(this.name);
}
function Male(name,age){
Person.call(this,name,age);
}
Male.prototype = Object.create(Person.prototype);
Male.prototype.constructor = Male;
var male = new Male("ly",20);
male.sayHello();
console.log(male.__proto__.constructor);//本來應(yīng)該指向Male 但是現(xiàn)在指向了Person 需要加上Male.prototype.constructor = Male 讓它的原型對象指向自己
ES6繼承
class Person{
constructor(name,age){
this.name = name;
this.age = age;
}
sayHello(){
console.log(this.name);
}
//static 是一個靜態(tài)方法 也就是說foo可以認(rèn)為是這個構(gòu)造函數(shù)自帶的一個方法
static foo(){
console.log("aa");
}
}
console.log(Person.prototype)
//用到exends關(guān)鍵字和super方法
class Male extends Person{
constructor(name,age){
//相當(dāng)于拿到了Person的this.name和this.age 同時改變了this指向
super(name,age)
}
sayHi(){
super.sayHello();
}
}
var male = new Male("ly",20);
male.sayHello();
male.sayHi();//結(jié)果一樣