8種javascript創建物件(類別)的方法,依依介紹給大家,希望大家喜歡。
1. 使用Object建構函式來建立一個物件
下面程式碼建立了一個person對象,並用兩種方式列印出了Name的屬性值。
var person = new Object(); person.name="kevin"; person.age=31; alert(person.name); alert(person["name"])
上述寫法的另外一種表現形式是使用對象字面量創建一個對象,不要奇怪person[“5”],這裡是合法的;另外使用這種加括號的方式欄位之間是可以有空格的如person[“my age”].
var person = { name:"Kevin", age:31, 5:"Test" }; alert(person.name); alert(person["5"]);
雖然Object 建構子或物件字面量都可以用來建立單一對象,但這些方式有個明顯的缺點:使用同一個介面建立許多對象,會產生大量的重複程式碼。為解決這個問題,人們開始使用工廠模式的變體。
2、工廠模式
工廠模式是軟體工程領域一種廣為人知的設計模式,這種模式抽象化了創建具體對象的過程,考慮到在ECMAScript中無法創建類別,開發人員發明了一種函數,用函數來封裝以特定介面創建物件的細節,如下面的例子所示。
function createPerson(name, age, job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ alert(this.name); }; return o; } var person1 = createPerson("Nicholas", 29, "Software Engineer"); var person2 = createPerson("Greg", 27, "Doctor");
工廠模式雖然解決了創建多個相似物件的問題,但卻沒有解決物件辨識的問題(即如何知道一個物件的類型)。隨著JavaScript
的發展,又一個新模式出現了。
3. 建構子模式
像Object 和Array 這樣建構函數,在運行時會自動出現在執行環境中。此外,也可以建立自訂的建構函數,從而定義自訂物件類型的屬性和方法。例如,可以使用建構函式模式將前面的範例重寫如下。
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.sayName = function(){ alert(this.name); }; } var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor");
在這個範例中,Person()函數取代了createPerson()函數。我們注意到,Person()中的程式碼除了與createPerson()中相同的部分外,還存在以下不同之處:
沒有明確地建立物件;
直接將屬性和方法賦給了this 物件;
沒有return 語句。
要建立Person 的新實例,必須使用new 運算子。以這種方式呼叫建構函式實際上會經歷以下4個步驟:
(1) 建立一個新物件;
(2) 將建構函數的作用域賦給新物件(因此this 就指向了這個新物件);
(3) 執行建構函式中的程式碼(為這個新物件新增屬性);
(4) 傳回新物件。
在前面例子的最後,person1 和person2 分別保存著Person 的一個不同的實例。這兩個物件都有一個constructor(建構函式)屬性,該屬性指向Person,如下所示。
alert(person1.constructor == Person); //true alert(person2.constructor == Person); //true
物件的constructor 屬性最初是用來識別物件類型的。但是,提到檢測物件類型,還是instanceof操作符要更可靠一些。我們在這個例子中所建立的所有物件既是Object 的實例,同時也是Person的實例,這一點透過instanceof 操作符可以得到驗證。
alert(person1 instanceof Object); //true alert(person1 instanceof Person); //true alert(person2 instanceof Object); //true alert(person2 instanceof Person); //true
建立自訂的建構函式意味著將來可以將它的實例標識為一種特定的型別;而這正是建構函式模式勝過工廠模式的地方。在這個例子中,person1 和person2 之所以同時是Object 的實例,是因為所有物件都繼承自Object。
建構子的問題
建構函式模式雖然好用,但並非沒有缺點。使用建構函式的主要問題,就是每個方法都要在每個實例上重新建立一遍。
ECMAScript 中的函數是對象,因此每定義一個函數,也就是實例化了一個對象。從邏輯角度講,此時的建構函數也可以這樣定義。
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.sayName = new Function("alert(this.name)"); // 与声明函数在逻辑上是等价的 }
從這個角度來看建構函數,更容易明白每個Person 實例都包含一個不同的Function 實例(以顯示name 屬性)的本質。說明白些,以這種方式創建函數,會導致不同的作用域鍊和標識符解析,但創建Function 新實例的機制仍然是相同的。因此,不同實例上的同名函數是不相等的,以下程式碼可以證明這一點。
alert(person1.sayName == person2.sayName); //false
然而,建立兩個完成同樣任務的Function 實例的確沒有必要;況且有this 物件在,根本不用在執行程式碼前就把函數綁定到特定物件上面。因此,大可像下面這樣,透過把函數定義轉移到建構函數外部來解決這個問題。
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.sayName = sayName; } function sayName(){ alert(this.name); } var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor");
如果物件需要定義很多方法,那麼就要定義很多全域函數,於是我們這個自訂的引用型別就絲毫沒有封裝性可言了。好在,這些問題可以透過使用原型模式來解決。
4、原型模式
function Person(){ } Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function(){ alert(this.name); }; var person1 = new Person(); person1.sayName(); //"Nicholas" var person2 = new Person(); person2.sayName(); //"Nicholas" alert(person1.sayName == person2.sayName); //true
要理解原型对象,可见我的另一篇博客:JavaScript prototype详解
前面例子中每添加一个属性和方法就要敲一遍Person.prototype。为减少不必要的输入,也为了从视觉上更好地封装原型的功能,更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象,如下面的例子所示。
function Person(){ } Person.prototype = { name : "Nicholas", age : 29, job: "Software Engineer", sayName : function () { alert(this.name); } };
在上面的代码中,我们将Person.prototype 设置为等于一个以对象字面量形式创建的新对象。最终结果相同,但有一个例外:constructor 属性不再指向Person 了。前面曾经介绍过,每创建一个函数,就会同时创建它的prototype 对象,这个对象也会自动获得constructor 属性。而我们在这里使用的语法,本质上完全重写了默认的prototype 对象,因此constructor 属性也就变成了新对象的constructor 属性(指向Object 构造函数),不再指向Person 函数。此时,尽管instanceof操作符还能返回正确的结果,但通过constructor 已经无法确定对象的类型了,如下所示。
var friend = new Person(); alert(friend instanceof Object); //true alert(friend instanceof Person); //true alert(friend.constructor == Person); //false alert(friend.constructor == Object); //true
在此,用instanceof 操作符测试Object 和Person 仍然返回true,但constructor 属性则等于Object 而不等于Person 了。如果constructor 的值真的很重要,可以像下面这样特意将它设置回适当的值。
function Person(){ } Person.prototype = { constructor : Person, name : "Nicholas", age : 29, job: "Software Engineer", sayName : function () { alert(this.name); } };
需要注意一点就是:实例中的指针仅指向原型,而不指向构造函数。
原型对象的问题:原型模式也不是没有缺点。首先,它省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。虽然这会在某种程度上带来一些不方便,但还不是原型的最大问题。原型模式的最大问题是由其共享的本性所导致的。
function Person(){ } Person.prototype = { constructor: Person, name : "Nicholas", age : 29, job : "Software Engineer", friends : ["Shelby", "Court"], sayName : function () { alert(this.name); } }; var person1 = new Person(); var person2 = new Person(); person1.friends.push("Van"); alert(person1.friends); //"Shelby,Court,Van" alert(person2.friends); //"Shelby,Court,Van" alert(person1.friends === person2.friends); //true
5、组合使用构造函数模式和原型模式(最常用)
创建自定义类型的最常见方式,就是组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。结果,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存。另外,这种混成模式还支持向构造函数传递参数;可谓是集两种模式之长。
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.friends = ["Shelby", "Court"]; } Person.prototype = { constructor : Person, sayName : function(){ alert(this.name); } } var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor"); person1.friends.push("Van"); alert(person1.friends); //"Shelby,Count,Van" alert(person2.friends); //"Shelby,Count" alert(person1.friends === person2.friends); //false alert(person1.sayName === person2.sayName); //true
6、动态原型模式
有其他OO 语言经验的开发人员在看到独立的构造函数和原型时,很可能会感到非常困惑。动态原型模式正是致力于解决这个问题的一个方案,它把所有信息都封装在了构造函数中,而通过在构造函数中初始化原型(仅在必要的情况下),又保持了同时使用构造函数和原型的优点。换句话说,可以通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型。来看一个例子。
function Person(name, age, job){ //属性 this.name = name; this.age = age; this.job = job; //方法 --------------------------------------------- if (typeof this.sayName != "function"){ Person.prototype.sayName = function(){ alert(this.name); }; } -------------------------------------------- } var friend = new Person("Nicholas", 29, "Software Engineer"); friend.sayName();
7、寄生构造函数模式
通常,在前述的几种模式都不适用的情况下,可以使用寄生(parasitic)构造函数模式。这种模式的基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象;但从表面上看,这个函数又很像是典型的构造函数。下面是一个例子。
function Person(name, age, job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ alert(this.name); }; return o; } var friend = new Person("Nicholas", 29, "Software Engineer"); friend.sayName(); //"Nicholas"
在这个例子中,Person 函数创建了一个新对象,并以相应的属性和方法初始化该对象,然后又返回了这个对象。除了使用new 操作符并把使用的包装函数叫做构造函数之外,这个模式跟工厂模式其实是一模一样的。构造函数在不返回值的情况下,默认会返回新对象实例。
8、稳妥构造函数模式
所谓稳妥对象,指的是没有公共属性,而且其方法也不引用this 的对象。稳妥对象最适合在一些安全的环境中(这些环境中会禁止使用this 和new),或者在防止数据被其他应用程序(如Mashup程序)改动时使用。稳妥构造函数遵循与寄生构造函数类似的模式,但有两点不同:一是新创建对象的实例方法不引用this;二是不使用new 操作符调用构造函数。按照稳妥构造函数的要求,可以将前面的Person 构造函数重写如下。
function Person(name, age, job){ //创建要返回的对象 var o = new Object(); //可以在这里定义私有变量和函数 //添加方法 o.sayName = function(){ alert(name); }; //返回对象 return o; }
以上javascript創建物件(類別)的8種方法大家有沒有學會,希望對大家的學習有所幫助。
【相關教學推薦】