最近重溫了《JavaScript 高級程序設計(第3版)》和學習了《Understanding ECMAScript 6》,趁此機會記錄一下,加深一下自己的理解。
ECMAScript 5
不具備傳統的面向對象所支持的類和接口等基本結構,使用引用類型(一種數據結構),描述一類對象所具有的屬性和方法。
ECMAScript 6
具備傳統的面向對象所支持的類和接口等基本結構。
下面具體講解ECMAScript 5 的引用類型和 ECMAScript 6的類。
ECMAScript 5:
1. 對象:ECMA-262把對象定義爲:無序屬性的集合,其屬性可以包含基本值、對象或者函數。等同於:對象是一組沒有特定順序的值,對象每個屬性或方法都有一個名字,每個名字都映射到一個值。
2. 創建對象:《JavaScript 高級程序設計(第3版)》書中提到三種模式,分別是工廠模式、構造函數模式、原型模式。
① 工廠模式:雖然解決了創建多個相似對象的問題,但卻沒有解決對象識別的問題(即怎樣知道一個對象的類型)。
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("Eve", 29, "software engineer");
var person2 = createPerson("Joy", 27, "Doctor");
② 構造函數模式:使用構造函數的主要問題,就是每個方法都要在每個實例上重新創建一遍。
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("Eve", 29, "software engineer");
var person2 = new Person("Joy", 27, "Doctor");
console.log(person1.constructor == Person); //true,表示是Person這一類人
console.log(person2.constructor == Person); //true,表示是Person這一類人
console.log(person1 instanceof Object); //true,表示是Object的實例
console.log(person1 instanceof Person); //true,表示是Person的實例
console.log(person2 instanceof Object); //true,表示是Object的實例
console.log(person2 instanceof Person); //true,表示是Person的實例
console.log(person1.sayName == person2.sayName); //false,表示不是同一個方法
需要注意的是:構造函數與其他函數的唯一區別,調用方式不同。任何函數只要通過new操作符來調用,那它就可以作爲構造函數;而任何函數,如果不通過new操作符來調用,那它跟普通函數沒有什麼兩樣。
//當作構造函數使用
var person1 = new Person("Eve", 29, "software engineer");
var person2 = new Person("Joy", 27, "Doctor");
person1.sayName();
person2.sayName();
//作爲普通函數調用
Person("Kim", 27, "Doctor"); //添加到window
window.sayName();
//在另一個對象的作用域中調用
var o = new Object();
Person.call(o, "happy", 25, "Teacher");
o.sayName();
③ 原型模式:無論什麼時候,只要創建了一個新函數,就會根據一組特定的規則爲該函數創建一個prototype屬性,這個屬性指向函數的原型對象。這個原型對象的用途是包含可以由特定類型的所有實例共享的屬性和方法。這就意味着它同時解決了工廠模式的對象識別的問題和構造函數模式的方法共享問題。
function Person(){}
Person.prototype.name = "Eve";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() {
console.log(this.name);
}
var person1 = new Person();
person1.sayName(); //Eve
var person2 = new Person();
person2.sayName(); //Eve
console.log(person1.sayName == person2.sayName); //true,表示同一個方法
構造函數、原型和實例的關係:每一個構造函數都有一個原型對象,原型對象都包含一個指向構造函數的指針,而實例都包含一個指向原型對象的內部指針。
Person的每一個實例——person1和person2都包含一個內部屬性,該屬性僅僅指向了Person.prototype; 即實例與構造函數沒有直接的關係。讀取對象屬性時,搜索:對象實例本身(若沒有則)->指針指向的原型對象。
需注意:重寫原型對象需謹慎。
function Person(){}
var friend = new Person(); //先聲明瞭實例
Person.prototype = { //再重寫原型對象
constructor: Person,
name: "Eve",
age: 29,
job: "Software Engineer",
sayName: function() {
console.log(this.name);
}
}
friend.sayName(); //Uncaught TypeError: friend.sayName is not a function
下圖表示重寫原型對象前後對比:
④ 組合使用構造函數模式和原型模式:較優。構造函數模式:定義實例屬性;原型模式:定義方法和共享的屬性。若只使用原型模式,當包含引用類型的屬性時,如數組,實例會共享同一個數組,實例A操作數組B,會導致實例C中的數組B同時發生變化,在大數情況下,這是我們不願意看到的情況。因此,結合構造函數模式,在構造函數中定義實例屬性,每個實例都會有自己的一份實例屬性的副本,但同時又共享着對方法的引用,最大限度地節省了內存。
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["Shelby","Court"];
}
Person.prototype = {
constructor: Person,
sayName: function(){
console.log(this.name);
}
}
var person1 = new Person("Eve", 29, "Software Engineer");
var person2 = new Person("Joy", 27, "Teacher");
person1.friends.push("Van");
console.log(person1.friends); //["Shelby", "Court", "Van"]
console.log(person2.friends); //["Shelby", "Court"]
console.log(person1.friends == person2.friends); //false
console.log(person1.sayName == person2.sayName); //true
持續更新中...歡迎交流~
參考資料:
1. JavaScript高級程序設計(第三版)--[美] Nicholas C.Zakas 著 李鬆峯 曹力 譯
2. Understanding ECMAScript 6 (https://www.gitbook.com/book/oshotokill/understandinges6-simplified-chinese/details)