js對象–自定義構造函數、原型鏈、繼承、拷貝
js是一門基於對象的語言,對象是一個很重要的知識點,本文是我個人對於js對象的理解。
類(class)—js中構造函數就可以模擬class
類其實是人類對事物進行分類而得出來的類別,比如:老虎,人類,學生,這些都是類,那麼 ,在代碼中,我們把擁有共同特徵的東西,並把這些特徵抽取出來設爲一個類,在js中,並沒有類的概念,因爲它不是面嚮對象語言,但他可以模擬類;
對象(object)
對象其實就是具體的事物,比如:你家的貓,某某某人。
他有自己的特徵(屬性)和行爲(方法);
創建對象
js創建對象有下面三種方式:
- 通過系統構造函數創建:
var obj = new Object();
- 通過字面量的方式創建:
var obj1 = {};
- 通過自定義構造函數創建(Person就是自定義的構造函數):
var obj2 = new Person();
對象使用方法
- 通過.的方式賦值、取值:
var obj.name="小羊";
var name = obj.name;//name="小羊"
- 通過[]的方式賦值、取值:
var obj["name"]="小羊";
var name = obj["name"];//name="小羊"
- 對象在內存中的存儲方式:
對象是引用數據類型的具體數據儲存在堆裏,地址儲存在棧裏,我們獲取的是地址,然後通過地址來取值、賦值,具體如下圖:
自定義構造函數
構造函數名一般首字母爲大寫,便於區分,函數內部通過this來給屬性和方法賦值;
通過new來創建實例對象
var Person =function(name,age){
this.name = name;
this.age = age;
this.eat = function(){
console.log(this.name+"在吃飯");
}
};
var per = new Person("小羊",20);
console.log(per.name);//小羊
console.log(per.age);//20
per.eat();//小羊在吃飯
也可以通過.或[]方法給實例對象添加額外的屬性和方法:
per.sex = "女";
per.sleep = function(){
console.log(this.name+"在睡覺");
};
console.log(per.sex);//女
per.sleep();//小羊在睡覺
創建實例對象圖解
原型和原型鏈
構造函數中有一個原型(prototype)屬性,原型是一個對象,
由於實例對象是根據構造函數創建的,所以實例對象也有一個原型(proto)屬性,prototype和proto指向同一個原型對象(內存空間);
目的
爲什麼需要原型屬性呢?—爲了節省內存空間
根據構造函數創建多個實例對象時,每個實例對象都會把根據構造函數生成相應的屬性和方法放在自己的內存空間中,每個實例對象屬性的值是不一樣的,但他們的方法都是一樣的,比如:我們每個人都吃飯,吃飯這個行爲是一樣的;所以我們沒必要爲每個實例對象都複製一份方法,所以就需要原型(prototype)這個屬性了,原型是個對象,是引用類型,所以他是prototype裏面儲存的是地址,這個地址指向存儲原型具體數據的空間,那麼,我們把相同的屬性和方法存儲在原型(prototype)中,創建實例對象時就不用爲這些屬性和方法開闢空間了,這樣就節省內存空間實現數據共享;
原型和實例對象、構造函數的關係如圖
constructor–構造器指向相應的構造函數
作用:節省內存空間,數據共享
使用:
- 構造函數.prototype.名字=值或方法;
Person.prototype.eat = function(){
console.log(this.name+"在吃飯");
}
- 構造函數.prototype=對象;
Person.prototype={
constructor:Person,
eat:function(){
console.log(this.name+"在吃飯");
}
}
手動添加構造器---constructor;
Student.prototype = new Person();
改變prototype的指向
當把Person的實例對象複製個Student的原型(prototype)會怎樣呢?—會使Student的原型(prototype)指向Person的原型(prototype)
代碼如下:
var Person=function (name,age) {
this.name=name,
this.age=age;
};
var Studen=function (id, score) {
this.id=id;
this. score= score;
};
Person.prototype.eat=function () {
console.log(this.name+"在吃飯");
};
Studen.prototype.scoreShow=function () {
console.log(this.score);
}
Studen.prototype=new Person("小羊",20);
var stu = new Studen(1,80);
console.log(stu.id);//1
console.log(stu.score);//80
console.log(stu.name);//小羊
console.log(stu.age);//20
stu.eat();//小羊在吃飯
stu.scoreShow();//報錯:stu.scoreShow is not a function
關係圖:
原型鏈
因爲每個對象都有一個都一個proto屬性,原型(prototype)對象也有proto屬性指向Objcet的prototype,所以通過proto 屬性可以一直追尋下去,直到Objcet.prototype,Objcet.prototype沒有proto,Objcet.prototype===null;
改變prototype的指向可使這條原型鏈無限延長。
注意
- 實例對象中有一個屬性叫proto,也是對象,叫原型,不是標準的屬性,瀏覽器使用的(ie8沒有改屬性)
- 構造函數中有一個屬性叫prototype,也是對象,叫原型,是標準的屬性,程序員使用 原型中的方法和屬性,是可以互相調用的;
- 實例對象先在自身尋找,如果不存在則去原型對象裏找;
- 實例對象可以直接訪問原型對象
- 構造函數中的this是實例對象===原型對象中的this是實例對象;
繼承—子類與父類的關係
現在有下面三個個類:
Person:屬性—>name,age;方法—>eat;
Student:屬性—>name,age,id,score;方法—>eat,scoreShow;
Personnel:屬性—>name,age,jobNumber,score;方法—>eat,job;
Student與Personnel都有Person的屬性與方法,除此之外,它們還有自己獨特的屬性和方法,這種情況下使用繼承,就能節省內存空間、解決代碼的冗餘、數據共享。
作用—節省內存空間、解決代碼的冗餘、數據共享
實現
因爲js不是面嚮對象語言沒有繼承,需要模擬繼承;
1.原型繼承
通過改變原型的指向實現繼承。
代碼:
var Person=function (name,age) {
this.name=name,
this.age=age;
};
var Studen=function (id, score) {
this.id=id;
this. score= score;
};
Person.prototype.eat=function () {
console.log(this.name+"在吃飯");
};
Studen.prototype=new Person("小羊",20);
//改變原型指向後再進行添加子類共有的方法和屬性
Studen.prototype.scoreShow=function () {
console.log(this.score);
};
//實例化對象
var stu = new Studen(1,80);
var stu1 = new Studen(2,90);
//stu=====================
console.log(stu.id);//1
console.log(stu.score);//80
console.log(stu.name);//小羊
console.log(stu.age);//20
stu.eat();//小羊在吃飯
stu.scoreShow();//80
//stu1========================
console.log(stu1.id);//2
console.log(stu1.score);//90
console.log(stu1.name);//小羊
console.log(stu1.age);//20
stu1.eat();//小羊在吃飯
stu1.scoreShow();//90
缺陷:兩個不同的Student實例對象的name,age是一樣的,需要重新賦值;
借用構造函數
代碼:
var Person=function (name,age) {
this.name=name,
this.age=age;
};
var Studen=function (name,age,id, score) {
Person.call(this,name,age)
this.id=id;
this. score= score;
};
Person.prototype.eat=function () {
console.log(this.name+"在吃飯");
};
Studen.prototype.scoreShow=function () {
console.log(this.score);
};
//實例化對象
var stu = new Studen("小羊",20,1,80);
var stu1 = new Studen("小和",21,2,90);
//stu=====================
console.log(stu.id);//1
console.log(stu.score);//80
console.log(stu.name);//小羊
console.log(stu.age);//20
stu.scoreShow();//80
stu.eat();//報錯:stu.eat is not a function
//stu1========================
console.log(stu1.id);//2
console.log(stu1.score);//90
console.log(stu1.name);//小和
console.log(stu1.age);//21
stu1.scoreShow();//90
stu1.eat();//報錯:stu.eat is not a function
缺陷:無法原型中的公用屬性和方法
組合繼承
將原型方法和借用函數方法組合起來
var Person=function (name,age) {
this.name=name,
this.age=age;
};
var Studen=function (name,age,id, score) {
Person.call(this,name,age);
this.id=id;
this. score= score;
};
Person.prototype.eat=function () {
console.log(this.name+"在吃飯");
};
Studen.prototype=new Person();
//改變原型指向後再進行添加子類共有的方法和屬性
Studen.prototype.scoreShow=function () {
console.log(this.score);
};
//實例化對象
var stu = new Studen("小羊",20,1,80);
var stu1 = new Studen("小和",21,2,90);
//stu=====================
console.log(stu.id);//1
console.log(stu.score);//80
console.log(stu.name);//小羊
console.log(stu.age);//20
stu.scoreShow();//80
stu.eat();//小羊在吃飯
//stu1========================
console.log(stu1.id);//2
console.log(stu1.score);//90
console.log(stu1.name);//小和
console.log(stu1.age);//21
stu1.scoreShow();//90
stu1.eat();//小和在吃飯