044_定义类或对象
1. 純構造函數方式
1.1. 使用構造函數創建對象, 第一步選擇類名, 即構造函數的名字。根據慣例, 這個名字的首字母大寫, 以使它與首字母通常是小寫的函數名分開。請考慮下面的例子:
function Car(color, doors, mpg) {this.color = color;this.doors = doors;this.mpg = mpg;this.showColor = function() {alert(this.color);}; }var car1 = new Car("red", 4, 23); var car2 = new Car("blue", 3, 25);1.2. 上例使用純構造函數方式創建對象的問題: 構造函數會重復生成函數, 為每個對象都創建獨立的函數版本。
2. 純原型方式
2.1. 該方式利用了對象的prototype屬性, 可以把它看成創建新對象所依賴的原型。
2.2. 這里, 首先用空構造函數來設置類名。然后所有的屬性和方法都被直接賦予prototype屬性。我們重寫了前面的例子, 代碼如下:
function Car() {}Car.prototype.color = "blue"; Car.prototype.doors = 4; Car.prototype.mpg = 25; Car.prototype.showColor = function() {alert(this.color); };var car1 = new Car(); var car2 = new Car();2.3. 原型方式的問題
2.3.1. 首先, 這個構造函數沒有參數。使用原型方式, 不能通過給構造函數傳遞參數來初始化屬性的值, 因此car1和car2的color屬性都等于"blue", doors屬性都等于4, mpg屬性都等于 25。這意味著必須在對象創建后才能改變屬性的默認值, 這點很令人討厭, 但還沒完。真正的問題出現在屬性指向的是對象, 而不是函數時。函數共享不會造成問題, 但對象卻很少被多個實例共享。請思考下面的例子:
function Car() {}Car.prototype.color = "blue"; Car.prototype.doors = 4; Car.prototype.mpg = 25; Car.prototype.drivers = new Array("Mike", "John"); Car.prototype.showColor = function() {alert(this.color); };var car1 = new Car(); var car2 = new Car();car1.drivers.push("Bill");alert(car1.drivers); // 輸出"Mike, John, Bill" alert(car2.drivers); // 輸出"Mike, John, Bill"2.3.2. 上面的代碼中, 屬性drivers是指向Array對象的指針, 該數組中包含兩個名字"Mike"和"John"。由于drivers是引用值, Car的兩個實例都指向同一個數組。這意味著給car1.drivers添加值"Bill", 在car2.drivers 中也能看到。輸出這兩個指針中的任何一個, 結果都是顯示字符串"Mike, John, Bill"。
3. 混合的構造函數和原型方式
3.1. 聯合使用構造函數和原型方式, 就可像用其他程序設計語言一樣創建對象。這種概念非常簡單, 即用構造函數定義對象的所有非函數屬性, 用原型方式定義對象的函數屬性(方法)。結果是, 所有函數都只創建一次, 而每個對象都具有自己的對象屬性實例。
3.2. 我們重寫了前面的例子, 代碼如下:
function Car(color, doors, mpg) {this.color = color;this.doors = doors;this.mpg = mpg;this.drivers = new Array("Mike", "John"); }Car.prototype.showColor = function() {alert(this.color); };var car1 = new Car("red", 4, 23); var car2 = new Car("blue", 3, 25);car1.drivers.push("Bill");alert(car1.drivers); // 輸出"Mike, John, Bill" alert(car2.drivers); // 輸出"Mike, John"3.3. 這種方式是JavaScript采用的主要方式, 它具有其他方式的特性, 卻沒有他們的副作用。
4. 動態原型方法
4.1. 動態原型方法的基本想法與混合的構造函數和原型方式相同, 即在構造函數內定義非函數屬性, 而函數屬性則利用原型屬性定義。唯一的區別是賦予對象方法的位置。下面是用動態原型方法重寫的Car類:
function Car(color, doors, mpg) {this.color = color;this.doors = doors;this.mpg = mpg;this.drivers = new Array("Mike", "John");if (typeof Car._initialized == "undefined") {Car.prototype.showColor = function() {alert(this.color);};Car._initialized = true;} }4.2. 直到檢查typeof Car._initialized是否等于"undefined"之前, 這個構造函數都未發生變化。這行代碼是動態原型方法中最重要的部分。如果這個值未定義, 構造函數將用原型方式繼續定義對象的方法, 然后把 Car._initialized設置為true。如果這個值定義了(它的值為true時, typeof的值為 Boolean), 那么就不再創建該方法。簡而言之, 該方法使用標志(_initialized)來判斷是否已給原型賦予了任何方法。該方法只創建并賦值一次, 傳統的OOP開發者會高興地發現, 這段代碼看起來更像其他語言中的類定義了。
5. 字符串連接
5.1. 對象令人感興趣的一點是用它們解決問題的方式。JavaScript中最常見的一個問題是字符串連接的性能。與其他語言類似, JavaScript的字符串是不可變的, 即它們的值不能改變。請考慮下面的代碼:
var str = "hello "; str += "world";5.2. 實際上, 這段代碼在幕后執行的步驟如下:
5.2.1. 創建存儲"hello "的字符串。
5.2.2. 創建存儲"world"的字符串。
5.2.3. 創建存儲連接結果的字符串。
5.2.4. 把str的當前內容復制到結果中。
5.2.5. 把"world"復制到結果中。
5.2.6. 更新str, 使它指向結果。
5.3. 每次完成字符串連接都會執行步驟5.2.2到5.2.6, 使得這種操作非常消耗資源。如果重復這一過程幾百次, 甚至幾千次, 就會造成性能問題。解決方法是用Array對象存儲字符串, 然后用join() 方法(參數是空字符串)創建最后的字符串。想象用下面的代碼代替前面的代碼:
var arr = new Array(); arr[0] = "hello "; arr[1] = "world"; var str = arr.join("");5.4. 這樣, 無論數組中引入多少字符串都不成問題, 因為只在調用join()方法時才會發生連接操作。此時, 執行的步驟如下:
5.4.1. 創建存儲結果的字符串。
5.4.2. 把每個字符串復制到結果中的合適位置。
5.5. 雖然這種解決方案很好, 但還有更好的方法。問題是, 這段代碼不能確切反映出它的意圖。要使它更容易理解, 可以用StringBuffer類打包該功能:
function StringBuffer () {this._strings_ = new Array(); }StringBuffer.prototype.append = function(str) {this._strings_.push(str); };StringBuffer.prototype.toString = function() {return this._strings_.join(""); };5.6. 這段代碼首先要注意的是strings屬性, 本意是私有屬性。它只有兩個方法, 即append()和toString()方法。append()方法有一個參數, 它把該參數附加到字符串數組中, toString()方法調用數組的join 方法, 返回真正連接成的字符串。要用StringBuffer對象連接一組字符串, 可以用下面的代碼:
var buffer = new StringBuffer(); buffer.append("hello "); buffer.append("world"); var result = buffer.toString();5.7. 例子
5.7.1. 代碼
<!DOCTYPE html> <html lang="zh-CN"> <head><meta charset="utf-8" /><title>字符串連接</title> </head> <body><script type="text/javascript">function StringBuffer () {this._strings_ = new Array();}StringBuffer.prototype.append = function(str) {this._strings_.push(str);};StringBuffer.prototype.toString = function() {return this._strings_.join("");};var buffer = new StringBuffer();buffer.append('<h1>華為Mate40 RS 5G保時捷限量版手機</h1>')buffer.append('<h3>商品介紹</h3>');buffer.append('品牌: 華為(HUAWEI)');buffer.append('<br />');buffer.append('商品編號: 10023728787945');buffer.append(' ');buffer.append('商品毛重: 300.00g');buffer.append(' ');buffer.append('運行內存: 12GB');buffer.append(' ');buffer.append('機身存儲: 512GB');buffer.append('<br />');buffer.append('存儲卡: NM存儲卡');buffer.append(' ');buffer.append('攝像頭數量: 后置五攝');buffer.append(' ');buffer.append('后攝主攝像素: 5000萬像素');buffer.append(' ');buffer.append('前攝主攝像素:1300萬像素');buffer.append('<br />');buffer.append('主屏幕尺寸(英寸): 6.67英寸');buffer.append(' ');buffer.append('分辨率: 全高清FHD+');buffer.append(' ');buffer.append('充電器: 11V/6A;10V/4A');buffer.append(' ');buffer.append('熱點: 5G');buffer.append('<br />');buffer.append('操作系統: Android(安卓)');buffer.append(' ');buffer.append('價格: ¥ 19288.00');var result = buffer.toString();document.write(result);</script> </body> </html>5.7.2. 效果圖
總結
以上是生活随笔為你收集整理的044_定义类或对象的全部內容,希望文章能夠幫你解決所遇到的問題。