JavaScript是一個(gè)動(dòng)態(tài)的通用面向?qū)ο缶幊陶Z(yǔ)言狂塘,所有的現(xiàn)代Web瀏覽器均包含了JavaScript解釋器爆捞,這使得JavaScript能夠稱得上史上使用最廣泛的編程語(yǔ)言。
特別是自2009年后,隨著Node.js 、ES5的誕生充择,使得JavaScript的功能能夠負(fù)責(zé)“全棧”匪蟀。Node.js是一個(gè)服務(wù)器端框架椎麦,基于Google的V8 JavaScript引擎創(chuàng)建。用Node.js去實(shí)現(xiàn)一層完全配置化的適配HTTP各種協(xié)議材彪,具有緩存策略的接口路由观挎,再通過(guò)配置或少量代碼實(shí)現(xiàn)接口調(diào)用聚合即可完成功能,這些工作前端工程師就能干了段化,使用javascript來(lái)提高團(tuán)隊(duì)整體工作效率嘁捷,完全不需要后端參與。況且在各種評(píng)測(cè)中显熏,看到JavaScript虛擬機(jī)比Java虛擬機(jī)快個(gè)一兩倍雄嚣,甚至幾倍已經(jīng)不是什么新鮮事了。
1996年11月喘蟆,網(wǎng)景公司將JavaScript提交給歐洲計(jì)算機(jī)制造商協(xié)會(huì)進(jìn)行標(biāo)準(zhǔn)化缓升。ECMAScript是由ECMA-262標(biāo)準(zhǔn)化的腳本語(yǔ)言的名稱。到現(xiàn)在已經(jīng)有近20個(gè)年頭了蕴轨,從ECMAScript 1版發(fā)展到了現(xiàn)在的ECMAScript 6(ES6)港谊,經(jīng)歷了5個(gè)版本的更迭(ES4被叫停);近年來(lái)橙弱,基于JavaScript各種架構(gòu)橫空出世歧寺,在后端和移動(dòng)端都有出色的表現(xiàn)。仿佛這個(gè)古老的語(yǔ)言一夜之間咸魚(yú)翻身膘螟。遙想當(dāng)年成福,取名為JavaScript無(wú)非想蹭JAVA的光,誰(shuí)曾想有今日的風(fēng)光荆残。
面向?qū)ο缶幊淌怯贸橄蠓绞絼?chuàng)建基于現(xiàn)實(shí)世界模型的一種編程模式奴艾。它使用先前建立的范例,包括模塊化内斯,多態(tài)和封裝幾種技術(shù)蕴潦。 Javascript并不是一種真正的面向?qū)ο缶幊蹋∣OP)語(yǔ)言像啼,ES6正在朝這方面努力。Javascript是一種基于對(duì)象(object-based)的語(yǔ)言潭苞,你遇到的所有東西幾乎都是對(duì)象忽冻。下面,我們來(lái)看看如何將"屬性"(property)和"方法"(method)此疹,封裝成一個(gè)對(duì)象僧诚,甚至要從原型對(duì)象生成一個(gè)實(shí)例對(duì)象。
一 . 封裝
假設(shè)我們將“人”看成一個(gè)對(duì)象蝗碎,他有名字湖笨、年齡兩個(gè)屬性。
var person={
name:'',
age:0
}
根據(jù)這個(gè)原型對(duì)象蹦骑,我們需要來(lái)生成一個(gè)實(shí)例對(duì)象慈省。
var person1={};
person1.name="jack";
person1.age=18;
以上就是最簡(jiǎn)單的封裝了,但這樣的寫法有一下兩個(gè)缺點(diǎn):
一是如果多生成幾個(gè)實(shí)例眠菇,這樣寫起來(lái)就非常累贅边败;
二是實(shí)例與原型之間,沒(méi)有任何辦法捎废,可以看出有什么聯(lián)系笑窜。
為了解決從原型對(duì)象生成實(shí)例的問(wèn)題,Javascript提供了一個(gè)構(gòu)造函數(shù)(Constructor)模式登疗。
對(duì)構(gòu)造函數(shù)使用new運(yùn)算符怖侦,就能生成實(shí)例,并且this變量會(huì)綁定在實(shí)例對(duì)象上谜叹。
function person(name, age) {
this.name = name;
this.age = age;
}
//生成實(shí)例
var person1 = new person("jack", 18);
var person2 = new person("baby", 17);
console.log(person1.name);//jack
這樣,person1搬葬、person2就同時(shí)擁有constructor 屬性荷腊,指向它們的構(gòu)造函數(shù)。
console.log(person1.constructor == person); //true
console.log(person2.constructor == person); //true
現(xiàn)在我們還需要為person類添加多個(gè)不變的屬性:legs_num(幾條腿),arms_num(幾只手)急凰,以及一個(gè)方法:sayHi()女仰。
function person(name, age) {
this.name = name;
this.age = age;
this.legs_num=2;
this.arms_num=2;
this.sayHi= function(){
console.log("Hi,My name's "+this.name+",I'm"+this.age+"years old now.");
};
}
如果這樣直接加上去,有一個(gè)很大的弊端:那就是對(duì)于每一個(gè)實(shí)例對(duì)象抡锈,屬性和方法都是一樣的內(nèi)容疾忍,每一次生成一個(gè)實(shí)例,都必須為重復(fù)的內(nèi)容床三,多占用一些內(nèi)存一罩,顯得缺乏效率。
Javascript提供了一個(gè)prototype屬性撇簿,每一個(gè)構(gòu)造函數(shù)都有一個(gè)prototype屬性聂渊,指向另一個(gè)對(duì)象差购。這個(gè)對(duì)象的所有屬性和方法,都會(huì)被構(gòu)造函數(shù)的實(shí)例繼承汉嗽。我們可以把那些不變的屬性和方法欲逃,直接定義在prototype對(duì)象上。
function person(name, age) {
this.name = name;
this.age = age;
}
person.prototype.legs_num = 2;
person.prototype.arms_num = 2;
person.prototype.sayHi = function() {
console.log("Hi,My name's " + this.name + ",I'm " + this.age + " years old now.");
};
//生成實(shí)例
var person1 = new person("jack", 18);
var person2 = new person("baby", 17);
person1.sayHi();//Hi,My name's jack,I'm 18 years old now.
person2.sayHi();//Hi,My name's baby,I'm 17 years old now.
為了配合prototype屬性饼暑,Javascript定義了一些輔助方法:isPrototypeOf()稳析、hasOwnProperty()。
isPrototypeOf()方法用來(lái)判斷弓叛,某個(gè)proptotype對(duì)象和某個(gè)實(shí)例之間的關(guān)系彰居。alert(person.prototype.isPrototypeOf(person1)); //true
。
每個(gè)實(shí)例對(duì)象都有一個(gè)hasOwnProperty()方法邪码,用來(lái)判斷是否本地屬性裕菠,false值就表示繼承自prototype對(duì)象的屬性。alert(person1.hasOwnProperty("name")); //true
二 . 繼承
function person(name, age) {
this.name = name;
this.age = age;
}
person.prototype.legs_num = 2;
person.prototype.arms_num = 2;
person.prototype.sayHi = function() {
console.log("Hi,My name's " + this.name + ",I'm " + this.age + " years old now.");
};
function Student(name, age, className){
this.name = name;
this.age = age;
this.className=className;
};
現(xiàn)在闭专,我們用一個(gè)學(xué)生(Student)的構(gòu)造函數(shù)奴潘,如何讓它繼承自人(person)這個(gè)構(gòu)造函數(shù)呢?
function person(name, age) {
this.name = name;
this.age = age;
}
person.prototype.legs_num = 2;
person.prototype.arms_num = 2;
person.prototype.sayHi = function() {
console.log("Hi,My name's " + this.name + ",I'm " + this.age + " years old now.");
};
function Student(name, age, className) {
person.apply(this, arguments);
this.className = className;
};
var Student1 = new Student('jack', 18, 'Class 3,Grade 2');
console.log(Student1.name); //jack
console.log(Student1.age); //18
console.log(Student1.className); //Class 3,Grade 2
Student1.sayHi(); //Student1.sayHi is not a function
使用apply影钉、call簡(jiǎn)單繼承一下画髓,發(fā)現(xiàn)可以繼承到person,而person.prototype.sayHi這顯示沒(méi)有這個(gè)函數(shù)平委,這個(gè)錯(cuò)誤暫時(shí)不用理他奈虾。下面我們使用使用prototype屬性進(jìn)行繼承。
function person(name, age) {
this.name = name;
this.age = age;
}
person.prototype.legs_num = 2;
person.prototype.arms_num = 2;
person.prototype.sayHi = function() {
console.log("Hi,My name's " + this.name + ",I'm " + this.age + " years old now.");
};
function Student(name, age, className) {
this.name=name;
this.age=age;
this.className=className;
};
Student.prototype = new person();
Student.prototype.constructor = Student;
var Student1 = new Student('jack', 18, 'Class 3,Grade 2');
console.log(Student1.name); //jack
console.log(Student1.age); //18
console.log(Student1.className); //Class 3,Grade 2
Student1.sayHi(); //Hi,My name's jack,I'm 18 years old now.
然后再修改一下代碼廉赔,看看:
function person(name, age) {
this.name = name;
this.age = age;
}
person.prototype.legs_num = 2;
person.prototype.arms_num = 2;
person.prototype.sayHi = function() {
console.log("Hi,My name's " + this.name + ",I'm " + this.age + " years old now.");
};
function Student(name, age, className) {
this.name=name;
this.age=age;
this.className=className;
};
Student.prototype =person.prototype;
Student.prototype.constructor = Student;
var Student1 = new Student('jack', 18, 'Class 3,Grade 2');
console.log(Student1.name); //jack
console.log(Student1.age); //18
console.log(Student1.className); //Class 3,Grade 2
Student1.sayHi(); //Hi,My name's jack,I'm 18 years old now.
alert(person.prototype.constructor); // function Student(name, age, className)
有沒(méi)有發(fā)現(xiàn)哪里不同了肉微?Student.prototype = new person();
改為 Student.prototype =person.prototype;
這樣做好像是少用了一個(gè)new節(jié)省了,但實(shí)際上把Animal.prototype對(duì)象的constructor屬性也改掉了蜡塌!在做繼承的時(shí)候千萬(wàn)要注意碉纳,要保護(hù)好父級(jí)的代碼不受影響。
alert(person.prototype.constructor);//function Student(name, age, className)
那么馏艾,我們將它改為:
Student.prototype = Object.create(person.prototype);
輸出來(lái)是不是又好了呢劳曹?好了,現(xiàn)在回到上面繼承第一個(gè)例子琅摩,我們可以來(lái)修補(bǔ)整段代碼了
function person(name, age) {
this.name = name;
this.age = age;
}
person.prototype.legs_num = 2;
person.prototype.arms_num = 2;
person.prototype.sayHi = function() {
console.log("Hi,My name's " + this.name + ",I'm " + this.age + " years old now.");
};
function Student(name, age, className) {
person.apply(this, arguments);
this.className = className;
};
Student.prototype = Object.create(person.prototype);
Student.prototype.constructor = Student;
var Student1 = new Student('jack', 18, 'Class 3,Grade 2');
console.log(Student1.name); //jack
console.log(Student1.age); //18
console.log(Student1.className); //Class 3,Grade 2
Student1.sayHi(); //Student1.sayHi is not a function
alert(person.prototype.constructor); // function person(name, age)
不要被繞暈了铁孵,跟著代碼做一遍就明白了。
最后用拷貝繼承的方式來(lái)實(shí)現(xiàn)繼承房资。這倒不是孔乙己所說(shuō)的茴字到底有幾種寫法蜕劝,有時(shí)候就需要考慮內(nèi)存的資源分配、兼容性等等,實(shí)現(xiàn)同一目標(biāo)有多種方式熙宇,可以找到最適合的一種鳖擒。
function person(name, age) {
this.name = name;
this.age = age;
}
person.prototype.legs_num = 2;
person.prototype.arms_num = 2;
person.prototype.sayHi = function() {
console.log("Hi,My name's " + this.name + ",I'm " + this.age + " years old now.");
};
function Student(name, age, className) {
this.name = name;
this.age = age;
this.className = className;
};
function extend(Child, Parent) {
var p = Parent.prototype;
var c = Child.prototype;
for (var i in p) {
c[i] = p[i];
}
c.uber = p;
}
extend(Student, person);
var Student1 = new Student('jack', 18, 'Class 3,Grade 2');
console.log(Student1.name); //jack
console.log(Student1.age); //18
console.log(Student1.className); //Class 3,Grade 2
Student1.sayHi(); //Hi,My name's jack,I'm 18 years old now.
alert(person.prototype.constructor); // function person(name, age)
這是純粹采用"拷貝"方法實(shí)現(xiàn)繼承:把父對(duì)象的所有屬性和方法,拷貝進(jìn)子對(duì)象烫止,就能夠?qū)崿F(xiàn)繼承蒋荚。c.uber = p;
意思是為子對(duì)象設(shè)一個(gè)uber屬性,這個(gè)屬性直接指向父對(duì)象的prototype屬性馆蠕。這等于在子對(duì)象上打開(kāi)一條通道杭棵,可以直接調(diào)用父對(duì)象的方法舶赔。
最后,我們來(lái)看看普通對(duì)象是如何進(jìn)行繼承操作的。
var area{
nation:'中國(guó)'
}
var person{
name:'jack'
}
現(xiàn)在我們想用person去繼承area礁阁,但這兩個(gè)對(duì)象都是普通對(duì)象语稠,不是構(gòu)造函數(shù)蛤肌,無(wú)法使用構(gòu)造函數(shù)方法實(shí)現(xiàn)"繼承"恩溅。json格式的創(chuàng)始人提出了一個(gè)object()函數(shù),下面看看是如何做到這一點(diǎn)的寺酪。
var area={nation:'中國(guó)'};
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
var person = object(area);
person.career = 'jack';
console.log(person.nation);//中國(guó)
下面我再給添加一個(gè)"出生地"屬性坎背,它的值是一個(gè)數(shù)組。
area.birthPlaces = ['北京','上海','香港'];
var area = {
nation: '中國(guó)',
birthPlaces:['北京', '上海', '香港']
};
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
var person = object(area);
person.career = 'jack';
person.birthPlaces.push('廣州');
console.log(person.nation); //中國(guó)
console.log(area.birthPlaces);//["北京", "上海", "香港", "廣州"]
console.log(person.birthPlaces);//["北京", "上海", "香港", "廣州"]
但是寄雀,這樣的拷貝有一個(gè)問(wèn)題得滤。那就是,如果父對(duì)象的屬性等于數(shù)組或另一個(gè)對(duì)象盒犹,因此存在父對(duì)象被篡改的可能懂更。上面提醒過(guò),繼承要保護(hù)好父級(jí)的代碼不受影響急膀。
請(qǐng)看沮协,現(xiàn)在給area添加一個(gè)"出生地"屬性,它的值是一個(gè)數(shù)組卓嫂。
下面使用深拷貝進(jìn)行繼承:
var area = {
nation: '中國(guó)',
birthPlaces: ['北京', '上海', '香港']
};
function deepCopy(p, c) {
var c = c || {};
for (var i in p) {
if (typeof p[i] === 'object') {
c[i] = (p[i].constructor === Array) ? [] : {};
deepCopy(p[i], c[i]);
} else {
c[i] = p[i];
}
}
return c;
}
var person = deepCopy(area);
person.career = 'jack';
person.birthPlaces.push('廣州');
console.log(person.nation); //中國(guó)
console.log(area.birthPlaces); //["北京", "上海", "香港"]
console.log(person.birthPlaces); //["北京", "上海", "香港", "廣州"]
把父對(duì)象的屬性皂股,全部拷貝給子對(duì)象,也能實(shí)現(xiàn)繼承命黔。同時(shí)又不會(huì)影響到父對(duì)象的數(shù)據(jù)。jQuery庫(kù)使用的就是這種繼承方法就斤。
畫了一個(gè)簡(jiǎn)單的圖悍募,希望能對(duì)你了解這篇文章有幫助:
參考資料:
維基百科
MDN JavaScript
阮一峰的網(wǎng)絡(luò)日志:Javascript 面向?qū)ο缶幊?/p>
《JavaScript 權(quán)威指南》