JavaScript中的對象javascript
對象的概念java
JavaScript對象的描述: An object is a collection of properties and has a single prototype object. The prototype may be either an object or the null value.數組
從上面一段話中咱們能夠看出,在JavaScript中,對象是一個屬性的集合,而且有一個原型對象。 而這個原型自己或者爲null,或者也是一個對象。瀏覽器
JavaScript中內置了11種對象,咱們這裏關注比較特殊的兩個對象Function和Object。ide
Function: 在JavaScript中函數也是對象,用來構造對象的函數叫作構造函數,如 function Animal (){}, Animal就是一個構造函數,全部的函數均可以用來構造對象,用new就是構造函數,但一般咱們採用首字母大寫來與普通函數區別。 全部的構造函數都是由Function這個函數對象構造出來的,它構造了系統中全部的函數對象,包括用戶自定義的函數對象,系統內置的函數對象,也包括它本身。能夠用如下代碼證實:函數
function Animal() {}; 佈局
console.log(Animal instanceof Function); // 用戶自定義函數對象 this
console.log(Object instanceof Function); //系統內置Object函數對象idea
console.log(Function instanceof Function); //Function自己spa
console.log(Date instanceof Function); //系統內置的Date對象
運行結果
Object:Object是JavaScript中另一個比較重要的內置對象,全部的對象都將繼承Object原型。它自己也是一個構造函數,且由Function構造。
理解構造關係在JavaScript中十分重要,它決定了JavaScript中的原型鏈。對象有一個重要的屬性[[prototype]], 這個[[prototype]]是一個引用,其指向構造對象自己的構造函數的prototype。 爲了便於表達,咱們用Mozilla定義的__proto__來表示[[prototype]](非標準,其它JavaScript引擎不必定叫__proto__)。注意不要把__proto__和prototype弄混, 全部的對象都有一個額外的屬性__proto__來指向一個原型,構造函數也不例外,這個__proto__就指向其構造函數的prototype。在本文的描述中__proto__只是指向一個prototype,而prototype自己是個對象,通常用來存放函數。
在ECMAScript中這樣描述prototype:
Each constructor is a function that has a property named 「prototype」 that is used to implement prototype-based inheritance and shared properties.
經過上面描述咱們可知,prototype只是構造函數的一個屬性,其用於實現基於原型的繼承和共享屬性。
下面咱們經過一段代碼的內存佈局圖來理解對象的構造關係以及其__proto__的指向關係。
function Animal() {};
var dog = new Animal();
這兩句簡單的代碼在內存中佈局以下:
__proto__和prototype總結
prototype: prototype是在構造函數生成的時候,會默認由Object函數給其構造一個prototype,因此其prototype的__proto__指向Object的prototype,因此全部的構造函數都有prototype, 可是實例對象沒有,就是你手工賦值一個prototype,那prototype也是一個普通屬性,和原型沒有關係。
__proto__: 全部對象(包括構造函數, 由於函數也是對象)都有__proto__屬性,這個屬性指向其構造函數的prototype。 每一個
由於實例的__proto__指向其構造函數的prototype, 構造函數的prototype的__proto__又指向Object的prototype,因此實例能夠根據這種關聯找到全部prototype上的屬性和方法,這種指向關係即咱們常說的原型鏈。
對象的建立
因爲JavaScript語法的靈活性,其對象的建立有不少方式,在閱讀別人源碼的時候,常常會看到各類不一樣的建立方法。下面咱們看看幾種常見的建立對象方式的優缺點。
var person = {
name:"Archer",
age:29,
job:"software engineer",
sayName:function(){
console.log(this.name);
}
};
person.sayName();
這種方式建立對象簡單易用,可是不方便代碼重用。
function createPerson(name,age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
console.log(this.name);
};
return o;
}
var person1 = createPerson("Archer", 29,"software engineer");
var person2 = createPerson("idda", 24,"software engineer");
這種方式建立也很簡單,能夠重用代碼。
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
console.log(this.name);
};
}
var person1 = new Person("Archer", 29,"software engineer");
var person2 = new Person("idda", 24,"software engineer");
構造函數和工廠模式比:
構造函數模式時,建立對象實際爲以下4步:
構造函數和其它普通函數的區別:
//當構造函數使用
var person = new Person("Archer", 29,"software engineer");
person.sayName(); //Archer
//普通函數使用
Person("idda",20,"Doctor"); //在瀏覽器中,是添加到window
window.sayName(); //idda
console.log(window.age); //20
構造函數的缺點:每一個方法都要在每一個實例中建立一遍:
console.log(person1.sayName === person2.sayName); //false
內存佈局爲:
function Person(){
}
Person.prototype.name = "Archer";
Person.prototype.age = 28;
Person.prototype.job = "software engineer";
Person.prototype.sayName = function(){
console.log(this.name);
};
var person1 = new Person();
var person2 = new Person();
咱們建立的每一個函數都有一個prototype(原型)屬性, 這個屬性是一個指針,指向一個prototype對象。 這個prototype對象包含全部此函數的實 例共享的屬性和方法。
原型模式的優勢對比構造函數來講就是讓全部實例共享它包含的屬性和方法。
console.log(person1.sayName === person2.sayName); //true
執行:Console.log(person1.name);
跟蹤person1的狀態可見,person1沒有name屬性,會根據原型鏈去起__proto__重查找name,其值爲Archer.
執行person1.name = "idda";
後跟蹤其狀態,可見person1增長了一個同名屬性name。
因此更改person1的屬性,不會修改person2的屬性,代碼及結果以下:
執行結果
內存佈局以下:
原型的簡化語法
function Person(){
}
Person.prototype ={
name:"archer",
age:29,
job:"software engineer",
sayHello:function(){
console.log("hi");
}
};
注意簡化寫法constructor再也不指向Person,而是指向Object,若是須要指向Person,可手動修改:
function Person(){
}
Person.prototype ={
constructor:Person,
name:"archer",
age:29,
job:"software engineer",
sayHello:function(){
console.log("hi");
}
};
另一個微小的區別,其constructor的[[Enumerable]]的值由false變成了true,能夠代碼重置
Object.defineProperty(Person.prototype,」constructor」,{
enumerable:false,
value:Person
}
);
對原型的修改,都會反映到其指向它的實例。若是重寫了對象原型後,對象和其新原型沒有任何聯繫,得不到新原型的任何屬性
function Person(){
}
var friend = new Person();
Person.prototype ={
name:」archer」,
age:29,
job:」software engineer:,
sayHello:function(){
console.log(「hi」);
}
};
firend不能訪問name,sayHello等屬性,如圖
原型模型的問題是全部實例都共享原型,對於原型中的值類型,能夠訪問原型中的值,可是若是修改原型中的值,則會給對象自身添加一個同名屬性,當下次訪問此屬性的時候,則返回此屬性,再也不查找做用域鏈。對於引用類型,若是不修改引用,而修改其堆上的值,就會致使全部實例的值都發生更改。
,如:
function Person(){
}
Person.prototype.name = "Archer";
Person.prototype.age = 28;
Person.prototype.job = "software engineer";
Person.prototype.firends = ["idda","gang"];
Person.prototype.sayName = function(){
console.log(this.name);
};
var person1 = new Person();
var person2 = new Person();
person1.firends.push("maxi");
下圖能夠看見,咱們修改的是person1,可是person2也跟着改了:
因此咱們不多直接用原型模式,通常來講咱們都會採用組合模式,組合模式是JavaScript中經常使用的模式
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["mike","xiaoyu","maxi"];
}
Person.prototype = {
constructor: Person,
sayHello:function(){
console.log(this.name);
}
}
var person1 = new Person("archer",20,"software engineer");
var person2 = new Person("idea",29,"peasant-worker");
構造函數中的屬性name,age,job,friends都是自身屬性,將存儲在實例自己裏(不在原型中),修改不會影響其它實例,而函數sayHello是共享的,存儲在原型中。
內存佈局以下:
對象的繼承
JavaScript中對象的繼承也比較靈活,咱們列舉幾種常見的模式,並討論其優缺點
function SuperType(){
this.name = "parent";
}
SuperType.prototype.getSuperValue = function(){
return this.name;
}
function SubType(){
this.sub_name = "child";
}
//注意:此處繼承了SuperType,其實是將SubType的原型改成SuperType的實例,從而擁有其原型鏈
SubType.prototype = new SuperType();
//注意,此方法必定要放在上面賦值語句後面,也不能採用字面量方式聲明(字面量方式聲明其實是建立一個Object對象),不然修改了原型的指針,沒法繼承其原型鏈。
SubType.prototype.getSubValue = function(){
return this.sub_name;
}
var sub = new SubType();
console.log(sub.getSuperValue());
內存佈局入下:
原型繼承的缺點:
function SuperType(){
this.colors = new ["blue」,」green」,」red"];
}
function SubType(){
//借用構造函數繼承
SuperType.call(this);
this.sayHello = function(){
console.log("hi");
}
}
借用構造的問題是,方法都在構造函數內定義,沒法重用代碼。
function SuperType(name){
this.name = name;
this.colors = ["white","red"]
}
SuperType.prototype.sayHello = function(){
console.log(this.name);
}
function SubType(name,age){
SuperType.call(this,name); //第二次調用
this.age = age;
}
SubType.prototype = new SuperType(); //第一次調用
SubType.prototype.constuctor = SubType;
SubType.prototype.sayAge = function(){
console.log(this.age);
}
var person = new SubType("Archer",29);
person.sayAge();
內存佈局爲:
組合繼承的缺點在於調用了兩次SuperType的構造函數,並且new SuperType調用了構造函數,形成內存浪費。SuperType的構造函數生產的變量會被SubType調用的call(this,name)給覆蓋。可用下列方法改進:
將 SubType.prototype = new SuperType(); 替換爲:
var F = function(){
};
F.prototype = SuperType.prototype;
SubType.prototype = new F();
改進前:
改進後能夠看見其__proto__指向F,因此沒有給colors和name分配內存。