建立對象
建立一個對象,然後給這個對象建立屬性和方法。
var box = new Object(); //建立一個 Object 對象
box.name = 'Lee'; //建立一個 name 屬性并指派
box.age = 100; //建立一個 age 屬性并指派
box.run = function () { //建立一個 run()方法并傳回值
return this.name + this.age + '運作中...';
};
alert(box.run()); //輸出屬性和方法的值
上面建立了一個對象,并且建立屬性和方法,在 run()方法裡的 this,就是代表 box 對象
本身。這種是 JavaScript 建立對象最基本的方法,但有個缺點,想建立一個類似的對象,就
會産生大量的代碼。
var box2 = box; //得到 box 的引用
box2.name = 'Jack'; //直接改變了 name 屬性
alert(box2.run()); //用 box.run()發現 name 也改變了
var box2 = new Object();
box2.name = 'Jack';
box2.age = 200;
box2.run = function () {
alert(box2.run()); //這樣才避免和 box 混淆,進而保持獨立
為了解決多個類似對象聲明的問題,我們可以使用一種叫做工廠模式的方法,這種方法
就是為了解決執行個體化對象産生大量重複的問題。
function createObject(name, age) { //集中執行個體化的函數
var obj = new Object();
obj.name = name;
obj.age = age;
obj.run = function () {
return obj;
}
var box1 = createObject('Lee', 100); //第一個執行個體
var box2 = createObject('Jack', 200); //第二個執行個體
alert(box1.run());
alert(box2.run()); //保持獨立
工廠模式解決了重複執行個體化的問題,但還有一個問題,那就是識别問題,因為根本無法
搞清楚他們到底是哪個對象的執行個體。
alert(typeof box1); //Object
alert(box1 instanceof Object); //true
ECMAScript 中可以采用構造函數(構造方法)可用來建立特定的對象。類型于 Object 對
象。
function Box(name, age) { //構造函數模式
this.name = name;
this.age = age;
this.run = function () {
var box1 = new Box('Lee', 100); //new Box()即可
var box2 = new Box('Jack', 200);
alert(box1 instanceof Box); //很清晰的識别他從屬于 Box
使用構造函數的方法,即解決了重複執行個體化的問題,又解決了對象識别的問題,但問題
是,這裡并沒有 new Object(),為什麼可以執行個體化 Box(),這個是哪裡來的呢?
使用了構造函數的方法,和使用工廠模式的方法他們不同之處如下:
1.構造函數方法沒有顯示的建立對象(new Object());
2.直接将屬性和方法指派給 this 對象;
3.沒有 renturn 語句。
構造函數的方法有一些規範:
1.函數名和執行個體化構造名相同且大寫,(PS:非強制,但這麼寫有助于區分構造函數和
普通函數);
2.通過構造函數建立對象,必須使用 new 運算符。
既然通過構造函數可以建立對象,那麼這個對象是哪裡來的,new Object()在什麼地方
執行了?執行的過程如下:
1.當使用了構造函數,并且 new 構造函數(),那麼就背景執行了 new Object();
2.将構造函數的作用域給新對象,(即 new Object()建立出的對象),而函數體内的 this 就
代表 new Object()出來的對象。
3.執行構造函數内的代碼;
4.傳回新對象(背景直接傳回)。
關于 this 的使用,this 其實就是代表目前作用域對象的引用。如果在全局範圍 this 就代
表 window 對象,如果在構造函數體内,就代表目前的構造函數所聲明的對象。
var box = 2;
alert(this.box); //全局,代表 window
構造函數和普通函數的唯一差別,就是他們調用的方式不同。隻不過,構造函數也是函
數,必須用 new 運算符來調用,否則就是普通函數。
var box = new Box('Lee', 100); //構造模式調用
alert(box.run());
Box('Lee', 20); //普通模式調用,無效
var o = new Object();
Box.call(o, 'Jack', 200) //對象冒充調用
alert(o.run());
探讨構造函數内部的方法(或函數)的問題,首先看下兩個執行個體化後的屬性或方法是否相
等。
var box1 = new Box('Lee', 100); //傳遞一緻
var box2 = new Box('Lee', 100); //同上
alert(box1.name == box2.name); //true,屬性的值相等
alert(box1.run == box2.run); //false,方法其實也是一種引用位址
alert(box1.run() == box2.run()); //true,方法的值相等,因為傳參一緻
可以把構造函數裡的方法(或函數)用 new Function()方法來代替,得到一樣的效果,更加
證明,他們最終判斷的是引用位址,唯一性。
function Box(name, age) { //new Function()唯一性
this.run = new Function("return this.name + this.age + '運作中...'");
我們可以通過構造函數外面綁定同一個函數的方法來保證引用位址的一緻性,但這種做
法沒什麼必要,隻是加深學習了解:
function Box(name, age) {
this.run = run;
function run() { //通過外面調用,保證引用位址一緻
雖然使用了全局的函數 run()來解決了保證引用位址一緻的問題,但這種方式又帶來了
一個新的問題,全局中的 this 在對象調用的時候是 Box 本身,而當作普通函數調用的時候,
this 又代表 window。
原型
我們建立的每個函數都有一個 prototype(原型)屬性,這個屬性是一個對象,它的用途是
包含可以由特定類型的所有執行個體共享的屬性和方法。邏輯上可以這麼了解:prototype 通過
調用構造函數而建立的那個對象的原型對象。使用原型的好處可以讓所有對象執行個體共享它所
包含的屬性和方法。也就是說,不必在構造函數中定義對象資訊,而是可以直接将這些資訊
添加到原型中。
function Box() {} //聲明一個構造函數
Box.prototype.name = 'Lee'; //在原型裡添加屬性
Box.prototype.age = 100;
Box.prototype.run = function () { //在原型裡添加方法
比較一下原型内的方法位址是否一緻:
var box1 = new Box();
var box2 = new Box();
alert(box1.run == box2.run); //true,方法的引用位址保持一緻
我們可以通過 hasOwnProperty()方法檢測屬性是否存在執行個體中,也可以通過 in 來判斷
執行個體或原型中是否存在屬性。那麼結合這兩種方法,可以判斷原型中是否存在屬性。
function isProperty(object, property) { //判斷原型中是否存在屬性
return !object.hasOwnProperty(property) && (property in object);
var box = new Box();
alert(isProperty(box, 'name')) //true,如果原型有
為了讓屬性和方法更好的展現封裝的效果,并且減少不必要的輸入,原型的建立可以使
用字面量的方式:
function Box() {};
Box.prototype = { //使用字面量的方式
name : 'Lee', age : 100, run : function () {
使用構造函數建立原型對象和使用字面量建立對象在使用上基本相同,但還是有一些區
别,字面量建立的方式使用 constructor 屬性不會指向執行個體,而會指向 Object,構造函數建立
的方式則相反。
alert(box instanceof Box);
alert(box instanceof Object);
alert(box.constructor == Box); //字面量方式,傳回 false,否則,true
alert(box.constructor == Object); //字面量方式,傳回 true,否則,false
如果想讓字面量方式的 constructor 指向執行個體對象,那麼可以這麼做:
Box.prototype = {
constructor : Box, //直接強制指向即可
PS:字面量方式為什麼 constructor 會指向 Object?因為 Box.prototype={};這種寫法其實
就是建立了一個新對象。而每建立一個函數,就會同時建立它 prototype,這個對象也會自
動擷取 constructor 屬性。是以,新對象的 constructor 重寫了 Box 原來的 constructor,是以會
指向新對象,那個新對象沒有指定構造函數,那麼就預設為 Object。
原型的聲明是有先後順序的,是以,重寫的原型會切斷之前的原型。
Box.prototype = { //原型被重寫了
constructor : Box, name : 'Lee', age : 100, run : function () {
age = 200
var box = new Box(); //在這裡聲明
alert(box.run()); //box 隻是最初聲明的原型
原型對象不僅僅可以在自定義對象的情況下使用,而 ECMAScript 内置的引用類型都可
以使用這種方式,并且内置的引用類型本身也使用了原型。
alert(Array.prototype.sort); //sort 就是 Array 類型的原型方法
alert(String.prototype.substring); //substring 就是 String 類型的原型方法
String.prototype.addstring = function () { //給 String 類型添加一個方法
return this + ',被添加了!'; //this 代表調用的字元串
alert('Lee'.addstring()); //使用這個方法
PS:盡管給原生的内置引用類型添加方法使用起來特别友善,但我們不推薦使用這種
方法。因為它可能會導緻命名沖突,不利于代碼維護。
原型模式建立對象也有自己的缺點,它省略了構造函數傳參初始化這一過程,帶來的缺
點就是初始化的值都是一緻的。而原型最大的缺點就是它最大的優點,那就是共享。
原型中所有屬性是被很多執行個體共享的,共享對于函數非常合适,對于包含基本值的屬性
也還可以。但如果屬性包含引用類型,就存在一定的問題:
constructor : Box, name : 'Lee', age : 100, family : ['父親', '母親', '妹妹'], //添加了一個數組屬性
run : function () {
return this.name + this.age + this.family;
box1.family.push('哥哥'); //在執行個體中添加'哥哥' alert(box1.run());
alert(box2.run()); //共享帶來的麻煩,也有'哥哥'了
PS:資料共享的緣故,導緻很多開發者放棄使用原型,因為每次執行個體化出的資料需要
保留自己的特性,而不能共享。
為了解決構造傳參和共享問題,可以組合構造函數+原型模式:
function Box(name, age) { //不共享的使用構造函數
this. family = ['父親', '母親', '妹妹'];
Box.prototype = { //共享的使用原型模式
constructor : Box, run : function () {
PS:這種混合模式很好的解決了傳參和引用共享的大難題。是建立對象比較好的方法。
原型模式,不管你是否調用了原型中的共享方法,它都會初始化原型中的方法,并且在
聲明一個對象時,構造函數+原型部分讓人感覺又很怪異,最好就是把構造函數和原型封裝
到一起。為了解決這個問題,我們可以使用動态原型模式。
function Box(name ,age) { //将所有資訊封裝到函數體内
if (typeof this.run != 'function') { //僅在第一次調用的初始化
Box.prototype.run = function () {
var box = new Box('Lee', 100);
當第一次調用構造函數時,run()方法發現不存在,然後初始化原型。當第二次調用,就
不會初始化,并且第二次建立新對象,原型也不會再初始化了。這樣及得到了封裝,又實作
了原型方法共享,并且屬性都保持獨立。
if (typeof this.run != 'function') {
alert('第一次初始化'); //測試用
var box = new Box('Lee', 100); //第一次建立對象
alert(box.run()); //第一次調用
alert(box.run()); //第二次調用
var box2 = new Box('Jack', 200); //第二次建立對象
alert(box2.run());
PS:使用動态原型模式,要注意一點,不可以再使用字面量的方式重寫原型,因為會
切斷執行個體和新原型之間的聯系。
以上講解了各種方式對象建立的方法,如果這幾種方式都不能滿足需求,可以使用一開
始那種模式:寄生構造函數。
寄生構造函數,其實就是工廠模式+構造函數模式。這種模式比較通用,但不能确定對
象關系,是以,在可以使用之前所說的模式時,不建議使用此模式。
在什麼情況下使用寄生構造函數比較合适呢?假設要建立一個具有額外方法的引用類
型。由于之前說明不建議直接 String.prototype.addstring,可以通過寄生構造的方式添加。
function myString(string) {
var str = new String(string);
str.addstring = function () {
return this + ',被添加了!';
return str;
var box = new myString('Lee'); //比直接在引用原型添加要繁瑣好多
alert(box.addstring());
在一些安全的環境中,比如禁止使用 this 和 new,這裡的 this 是構造函數裡不使用 this,
這裡的 new 是在外部執行個體化構造函數時不使用 new。這種建立方式叫做穩妥構造函數。
function Box(name , age) {
return name + age + '運作中...'; //直接列印參數即可
var box = Box('Lee', 100); //直接調用函數
PS:穩妥構造函數和寄生類似。
繼承
繼承是面向對象中一個比較核心的概念。其他正統面向對象語言都會用兩種方式實作繼
承:一個是接口實作,一個是繼承。而 ECMAScript 隻支援繼承,不支援接口實作,而實作
繼承的方式依靠原型鍊完成。
function Box() { //Box 構造
this.name = 'Lee';
function Desk() { //Desk 構造
this.age = 100;
Desk.prototype = new Box(); //Desc 繼承了 Box,通過原型,形成鍊條
var desk = new Desk();
alert(desk.age);
alert(desk.name); //得到被繼承的屬性
function Table() { //Table 構造
this.level = 'AAAAA';
Table.prototype = new Desk(); //繼續原型鍊繼承
var table = new Table();
alert(table.name); //繼承了 Box 和 Desk
在 JavaScript 裡,被繼承的函數稱為超類型(父類,基類也行,其他語言叫法),繼承的
函數稱為子類型(子類,派生類)。繼承也有之前問題,比如字面量重寫原型會中斷關系,使
用引用類型的原型,并且子類型還無法給超類型傳遞參數。
為了解決引用共享和超類型無法傳參的問題,我們采用一種叫借用構造函數的技術,或
者成為對象冒充(僞造對象、經典繼承)的技術來解決這兩種問題
function Box(age) {
this.name = ['Lee', 'Jack', 'Hello']
function Desk(age) {
Box.call(this, age); //對象冒充,給超類型傳參
var desk = new Desk(200);
alert(desk.name);
desk.name.push('AAA'); //添加的新資料,隻給 desk
借用構造函數雖然解決了剛才兩種問題,但沒有原型,複用則無從談起。是以,我們需
要原型鍊+借用構造函數的模式,這種模式成為組合繼承。
return this.name + this.age;
Box.call(this, age); //對象冒充
Desk.prototype = new Box(); //原型鍊繼承
var desk = new Desk(100);
alert(desk.run());
還有一種繼承模式叫做:原型式繼承;這種繼承借助原型并基于已有的對象建立新對象,
同時還不必是以建立自定義類型。
function obj(o) { //傳遞一個字面量函數
function F() {} //建立一個構造函數
F.prototype = o; //把字面量函數指派給構造函數的原型
return new F(); //最終傳回出執行個體化的構造函數
var box = { //字面量對象
name : 'Lee', arr : ['哥哥','妹妹','姐姐']
var box1 = obj(box); //傳遞
alert(box1.name);
box1.name = 'Jack';
alert(box1.arr);
box1.arr.push('父母');
var box2 = obj(box); //傳遞
alert(box2.name);
alert(box2.arr); //引用類型共享了
寄生式繼承把原型式+工廠模式結合而來,目的是為了封裝建立對象的過程。
function create(o) { //封裝建立過程
var f= obj(o);
f.run = function () {
return this.arr; //同樣,會共享引用
return f;
組合式繼承是 JavaScript 最常用的繼承模式;但,組合式繼承也有一點小問題,就是超
類型在使用過程中會被調用兩次:一次是建立子類型的時候,另一次是在子類型構造函數的
内部。
function Box(name) {
this.arr = ['哥哥','妹妹','父母'];
return this.name;
function Desk(name, age) {
Box.call(this, name); //第二次調用 Box
Desk.prototype = new Box(); //第一次調用 Box
以上代碼是之前的組合繼承,那麼寄生組合繼承,解決了兩次調用的問題。
function obj(o) {
function F() {}
F.prototype = o;
return new F();
function create(box, desk) {
var f = obj(box.prototype);
f.constructor = desk;
desk.prototype = f;
Box.call(this, name);
inPrototype(Box, Desk); //通過這裡實作繼承
var desk = new Desk('Lee',100);
desk.arr.push('姐姐');
alert(desk.arr);
alert(desk.run()); //隻共享了方法
var desk2 = new Desk('Jack', 200);
alert(desk2.arr); //引用問題解決