JavaScript系列之面向?qū)ο?/h1>
作者: __?mxin
來源:SegmentFault 思否社區(qū)
什么是面向?qū)ο螅?/span>
概念起源
面向?qū)ο蟪绦蛟O(shè)計(jì)的雛形,早在1960年的 Simula 語(yǔ)言中即可發(fā)現(xiàn),當(dāng)時(shí)的程序設(shè)計(jì)領(lǐng)域正面臨著一種危機(jī):在軟硬件環(huán)境逐漸復(fù)雜的情況下,軟件如何得到良好的維護(hù)?面向?qū)ο蟪绦蛟O(shè)計(jì)在某種程度上通過強(qiáng)調(diào)可重復(fù)性解決了這一問題。--引自維基百科
面向?qū)ο螅∣OP)
面向?qū)ο缶幊淌且猿橄蠓绞絼?chuàng)建基于現(xiàn)實(shí)世界模型的一種編程模式;在此種模式下將對(duì)象作為擁有明確職責(zé)的基本單元,對(duì)象本身可以接收消息,處理數(shù)據(jù)以及發(fā)送數(shù)據(jù)給其他對(duì)象;OOP 有以下幾點(diǎn)特性:
Inheritance 繼承 子類可以繼承父類的特征(屬性和方法)? ??
Encapsulation 封裝 是一種把數(shù)據(jù)和相關(guān)的方法綁定在一起使用的方法
Abstraction 抽象 結(jié)合復(fù)雜的繼承,屬性,方法的對(duì)象能夠模擬現(xiàn)實(shí)的模型
Polymorphism 多態(tài) 不同類可以定義相同的屬性和方法
基于原型和基于類
基于原型的編程提倡我們?nèi)リP(guān)注一系列對(duì)象實(shí)例的行為,而后才去關(guān)心如何將這些對(duì)象,劃分到最近的使用方式相似的原型對(duì)象,而不是將它們分成類
基于原型的面向?qū)ο笙到y(tǒng)通過復(fù)制的方式來創(chuàng)建新對(duì)象一些語(yǔ)言的實(shí)現(xiàn)中,還允許復(fù)制一個(gè)空對(duì)象,這實(shí)際上就是創(chuàng)建一個(gè)全新的對(duì)象
原型系統(tǒng)的“復(fù)制操作”有兩種實(shí)現(xiàn)思路:
并不真的去復(fù)制一個(gè)原型對(duì)象,而是使得新對(duì)象持有一個(gè)原型的引用 切實(shí)地復(fù)制對(duì)象,從此兩個(gè)對(duì)象再無關(guān)聯(lián)
基于類的編程提倡使用一個(gè)關(guān)注分類和類之間關(guān)系開發(fā)模型
在這類語(yǔ)言中,總是先有類,再?gòu)念惾?shí)例化一個(gè)對(duì)象 類與類之間又可能會(huì)形成繼承、組合等關(guān)系 類又往往與語(yǔ)言的類型系統(tǒng)整合,形成一定編譯時(shí)的能力
基于原型和基于類的區(qū)別

類
面向?qū)ο笳Z(yǔ)言中都有類的概念,如 Java、C++ 都是基于類的語(yǔ)言,而 JavaScript 是基于原型的語(yǔ)言,在舊的 ES 版本沒有類的定義,所以我們要做一系列模擬類的操作去定義類
ES6中提供了 class ,使我們不用再去模擬類的操作,但是這個(gè) class 并非真正的類,其實(shí)是基于原型繼承方式的語(yǔ)法糖
function Person() { }// 或var Person = function(){ }// 或class Person{ }
構(gòu)造函數(shù)
在實(shí)例化時(shí)構(gòu)造函數(shù)被調(diào)用 (也就是對(duì)象實(shí)例被創(chuàng)建時(shí))
構(gòu)造函數(shù)是對(duì)象中的一個(gè)方法
在 JavaScript 中函數(shù)就可以作為構(gòu)造器使用,因此不需要特別地定義一個(gè)構(gòu)造器方法,每個(gè)聲明的函數(shù)都可以在實(shí)例化后被調(diào)用執(zhí)行(在ES6規(guī)范中 class 內(nèi)的 constructor 起到同樣的作用)
構(gòu)造器常用于給對(duì)象的屬性賦值或者為調(diào)用函數(shù)做準(zhǔn)備
function Person(name) { this.name = name alert('Person instantiated')}// 或class Person{ constructor(name){ this.name = name alert(`Person instantiated`) }}
var person = new Person('mxin');
JavaScript對(duì)象
對(duì)象在計(jì)算機(jī)中是一個(gè)具有唯一標(biāo)識(shí)的內(nèi)存地址,參考 Grandy Booch 的《面向?qū)ο蠓治雠c設(shè)計(jì)》來看,對(duì)象具有下列幾個(gè)特點(diǎn):
唯一標(biāo)識(shí)性:即使完全相同的兩個(gè)對(duì)象,也并非同一個(gè)對(duì)象 狀態(tài):對(duì)象具有狀態(tài),同一對(duì)象可能處于不同狀態(tài)之下 行為:即對(duì)象的狀態(tài),可能因?yàn)樗男袨楫a(chǎn)生變遷
唯一標(biāo)識(shí)性
function Person() { }var person_1 = new Person();var person_2 = new Person();console.log(person_1 == person_2) // false
const obj_1 = { a: 1 };const obj_2 = { a: 1 };console.log(obj_1 == obj_2); // false
狀態(tài)和行為
分別對(duì)應(yīng)了屬性和方法這兩個(gè)概念,在 JavaScript 被抽象為屬性,其屬性可以為任何類型
const obj = { name: 'mxin', age: 99, getInfo() { return `name: ${this.name} , age: ${this.age}` }}
繼承
JavaScript 通過將構(gòu)造函數(shù)與原型對(duì)象相關(guān)聯(lián)的方式來實(shí)現(xiàn)繼承,下面將以 ES5 及 ES6 兩種方式展示
ES5
// 定義Person構(gòu)造器function Person(firstName) { this.firstName = firstName; }
// 在Person.prototype中加入方法 Person.prototype.walk = function(){ console.log("I am walking!"); }; Person.prototype.sayHello = function(){ console.log("Hello, I'm " + this.firstName); };
// 定義Student構(gòu)造器 function Student(firstName, subject) { // 調(diào)用父類構(gòu)造器, 確保(使用Function#call)"this" 在調(diào)用過程中設(shè)置正確 Person.call(this, firstName);
// 初始化Student類特有屬性 this.subject = subject; };
// 建立一個(gè)由Person.prototype繼承而來的Student.prototype對(duì)象. // 注意: 常見的錯(cuò)誤是使用 "new Person()"來建立Student.prototype. // 這樣做的錯(cuò)誤之處有很多, 最重要的一點(diǎn)是我們?cè)趯?shí)例化時(shí) // 不能賦予Person類任何的FirstName參數(shù) // 調(diào)用Person的正確位置如下,我們從Student中來調(diào)用它 Student.prototype = Object.create(Person.prototype); // See note below
// 設(shè)置"constructor" 屬性指向Student Student.prototype.constructor = Student;
// 更換"sayHello" 方法 Student.prototype.sayHello = function(){ console.log("Hello, I'm " + this.firstName + ". I'm studying " + this.subject + "."); };
// 加入"sayGoodBye" 方法 Student.prototype.sayGoodBye = function(){ console.log("Goodbye!"); };
// 測(cè)試實(shí)例: var student = new Student("Janet", "Applied Physics"); student.sayHello(); // "Hello, I'm Janet. I'm studying Applied Physics." student.walk(); // "I am walking!" student.sayGoodBye(); // "Goodbye!"
// Check that instanceof works correctly console.log(student instanceof Person); // true console.log(student instanceof Student); // true
ES6
class Person { constructor(firstName) { this.firstName = firstName }
walk() { console.log(`I am walking!`) }
sayHello() { console.log(`Hello, I'm ${this.firstName}`) }}
class Student extends Person { constructor(firstName, subject) { super(firstName) this.subject = subject }
sayHello() { console.log(`Hello, I'm ${this.firstName}. I'm studying ${this.subject}.`) }
sayGoodBye() { console.log(`Goodbye!`) }}
// 測(cè)試實(shí)例:const student = new Student(`Janet`, `Applied Physics`)student.sayHello() // "Hello, I'm Janet. I'm studying Applied Physics."student.walk() // "I am walking!"student.sayGoodBye() // "Goodbye!"
// Check that instanceof works correctlyconsole.log(student instanceof Person) // trueconsole.log(student instanceof Student) // true
封裝
上面例子中Student類雖然不需要知道 Person 類的 walk() 方法是如何實(shí)現(xiàn)的,但是仍然可以使用這個(gè)方法,Student類不需要明確地定義這個(gè)方法,除非我們想改變它,這就叫做封裝
抽象
抽象是允許模擬工作問題中通用部分的一種機(jī)制,這可以通過繼承或組合( JavaScript 讓類的實(shí)例是其他對(duì)象的屬性值來實(shí)現(xiàn)組合)來實(shí)現(xiàn):
JavaScript Function 類繼承自 Object 類(繼承)
Function.prototype 的屬性是一個(gè) Object 實(shí)例(組合)
var foo = function(){};
console.log(foo instanceof Function) // trueconsole.log(foo.prototype instanceof Object) // true
多態(tài)
就像所有定義在原型屬性內(nèi)部的方法和屬性一樣,不同的類可以定義具有相同名稱的方法,方法是作用于所在的類中(并且這僅在兩個(gè)類不是父子關(guān)系時(shí)成立)
總結(jié)
結(jié)合上面的知識(shí),可以了解到JavaScript一種基于原型設(shè)計(jì)的面向?qū)ο笳Z(yǔ)言,而且JavaScript的對(duì)象操作靈活,是具有高度動(dòng)態(tài)性的屬性集合
可以深入了解一下基于原型的設(shè)計(jì)思路以及JavaScript對(duì)象模型的細(xì)節(jié)
參考資料:
JavaScript面向?qū)ο蠛?jiǎn)介:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Introduction_to_Object-Oriented_JavaScript 對(duì)象模型的細(xì)節(jié):https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Details_of_the_Object_Model 多繼承:https://zhuanlan.zhihu.com/p/34693209
點(diǎn)擊左下角閱讀原文,到?SegmentFault 思否社區(qū)?和文章作者展開更多互動(dòng)和交流。

瀏覽
122
作者: __?mxin
來源:SegmentFault 思否社區(qū)
什么是面向?qū)ο螅?/span>
概念起源
面向?qū)ο蟪绦蛟O(shè)計(jì)的雛形,早在1960年的 Simula 語(yǔ)言中即可發(fā)現(xiàn),當(dāng)時(shí)的程序設(shè)計(jì)領(lǐng)域正面臨著一種危機(jī):在軟硬件環(huán)境逐漸復(fù)雜的情況下,軟件如何得到良好的維護(hù)?面向?qū)ο蟪绦蛟O(shè)計(jì)在某種程度上通過強(qiáng)調(diào)可重復(fù)性解決了這一問題。--引自維基百科
面向?qū)ο螅∣OP)
面向?qū)ο缶幊淌且猿橄蠓绞絼?chuàng)建基于現(xiàn)實(shí)世界模型的一種編程模式;在此種模式下將對(duì)象作為擁有明確職責(zé)的基本單元,對(duì)象本身可以接收消息,處理數(shù)據(jù)以及發(fā)送數(shù)據(jù)給其他對(duì)象;OOP 有以下幾點(diǎn)特性:
Inheritance 繼承 子類可以繼承父類的特征(屬性和方法)? ?? Encapsulation 封裝 是一種把數(shù)據(jù)和相關(guān)的方法綁定在一起使用的方法 Abstraction 抽象 結(jié)合復(fù)雜的繼承,屬性,方法的對(duì)象能夠模擬現(xiàn)實(shí)的模型 Polymorphism 多態(tài) 不同類可以定義相同的屬性和方法
基于原型和基于類
基于原型的編程提倡我們?nèi)リP(guān)注一系列對(duì)象實(shí)例的行為,而后才去關(guān)心如何將這些對(duì)象,劃分到最近的使用方式相似的原型對(duì)象,而不是將它們分成類
基于原型的面向?qū)ο笙到y(tǒng)通過復(fù)制的方式來創(chuàng)建新對(duì)象一些語(yǔ)言的實(shí)現(xiàn)中,還允許復(fù)制一個(gè)空對(duì)象,這實(shí)際上就是創(chuàng)建一個(gè)全新的對(duì)象
原型系統(tǒng)的“復(fù)制操作”有兩種實(shí)現(xiàn)思路:
并不真的去復(fù)制一個(gè)原型對(duì)象,而是使得新對(duì)象持有一個(gè)原型的引用 切實(shí)地復(fù)制對(duì)象,從此兩個(gè)對(duì)象再無關(guān)聯(lián)
基于類的編程提倡使用一個(gè)關(guān)注分類和類之間關(guān)系開發(fā)模型
在這類語(yǔ)言中,總是先有類,再?gòu)念惾?shí)例化一個(gè)對(duì)象 類與類之間又可能會(huì)形成繼承、組合等關(guān)系 類又往往與語(yǔ)言的類型系統(tǒng)整合,形成一定編譯時(shí)的能力
基于原型和基于類的區(qū)別

類
面向?qū)ο笳Z(yǔ)言中都有類的概念,如 Java、C++ 都是基于類的語(yǔ)言,而 JavaScript 是基于原型的語(yǔ)言,在舊的 ES 版本沒有類的定義,所以我們要做一系列模擬類的操作去定義類
ES6中提供了 class ,使我們不用再去模擬類的操作,但是這個(gè) class 并非真正的類,其實(shí)是基于原型繼承方式的語(yǔ)法糖
function Person() { }// 或var Person = function(){ }// 或class Person{ }
構(gòu)造函數(shù)
在實(shí)例化時(shí)構(gòu)造函數(shù)被調(diào)用 (也就是對(duì)象實(shí)例被創(chuàng)建時(shí))
構(gòu)造函數(shù)是對(duì)象中的一個(gè)方法
在 JavaScript 中函數(shù)就可以作為構(gòu)造器使用,因此不需要特別地定義一個(gè)構(gòu)造器方法,每個(gè)聲明的函數(shù)都可以在實(shí)例化后被調(diào)用執(zhí)行(在ES6規(guī)范中 class 內(nèi)的 constructor 起到同樣的作用)
構(gòu)造器常用于給對(duì)象的屬性賦值或者為調(diào)用函數(shù)做準(zhǔn)備
function Person(name) {this.name = namealert('Person instantiated')}// 或class Person{constructor(name){this.name = namealert(`Person instantiated`)}}var person = new Person('mxin');
JavaScript對(duì)象
對(duì)象在計(jì)算機(jī)中是一個(gè)具有唯一標(biāo)識(shí)的內(nèi)存地址,參考 Grandy Booch 的《面向?qū)ο蠓治雠c設(shè)計(jì)》來看,對(duì)象具有下列幾個(gè)特點(diǎn):
唯一標(biāo)識(shí)性:即使完全相同的兩個(gè)對(duì)象,也并非同一個(gè)對(duì)象 狀態(tài):對(duì)象具有狀態(tài),同一對(duì)象可能處于不同狀態(tài)之下 行為:即對(duì)象的狀態(tài),可能因?yàn)樗男袨楫a(chǎn)生變遷
唯一標(biāo)識(shí)性
function Person() { }var person_1 = new Person();var person_2 = new Person();console.log(person_1 == person_2) // falseconst obj_1 = { a: 1 };const obj_2 = { a: 1 };console.log(obj_1 == obj_2); // false
狀態(tài)和行為
分別對(duì)應(yīng)了屬性和方法這兩個(gè)概念,在 JavaScript 被抽象為屬性,其屬性可以為任何類型
const obj = {name: 'mxin',age: 99,getInfo() {return `name: ${this.name} , age: ${this.age}`}}
繼承
JavaScript 通過將構(gòu)造函數(shù)與原型對(duì)象相關(guān)聯(lián)的方式來實(shí)現(xiàn)繼承,下面將以 ES5 及 ES6 兩種方式展示
ES5
// 定義Person構(gòu)造器function Person(firstName) {this.firstName = firstName;}// 在Person.prototype中加入方法Person.prototype.walk = function(){console.log("I am walking!");};Person.prototype.sayHello = function(){console.log("Hello, I'm " + this.firstName);};// 定義Student構(gòu)造器function Student(firstName, subject) {// 調(diào)用父類構(gòu)造器, 確保(使用Function#call)"this" 在調(diào)用過程中設(shè)置正確Person.call(this, firstName);// 初始化Student類特有屬性this.subject = subject;};// 建立一個(gè)由Person.prototype繼承而來的Student.prototype對(duì)象.// 注意: 常見的錯(cuò)誤是使用 "new Person()"來建立Student.prototype.// 這樣做的錯(cuò)誤之處有很多, 最重要的一點(diǎn)是我們?cè)趯?shí)例化時(shí)// 不能賦予Person類任何的FirstName參數(shù)// 調(diào)用Person的正確位置如下,我們從Student中來調(diào)用它Student.prototype = Object.create(Person.prototype); // See note below// 設(shè)置"constructor" 屬性指向StudentStudent.prototype.constructor = Student;// 更換"sayHello" 方法Student.prototype.sayHello = function(){console.log("Hello, I'm " + this.firstName + ". I'm studying " + this.subject + ".");};// 加入"sayGoodBye" 方法Student.prototype.sayGoodBye = function(){console.log("Goodbye!");};// 測(cè)試實(shí)例:var student = new Student("Janet", "Applied Physics");student.sayHello(); // "Hello, I'm Janet. I'm studying Applied Physics."student.walk(); // "I am walking!"student.sayGoodBye(); // "Goodbye!"// Check that instanceof works correctlyconsole.log(student instanceof Person); // trueconsole.log(student instanceof Student); // true
ES6
class Person {constructor(firstName) {this.firstName = firstName}walk() {console.log(`I am walking!`)}sayHello() {console.log(`Hello, I'm ${this.firstName}`)}}class Student extends Person {constructor(firstName, subject) {super(firstName)this.subject = subject}sayHello() {console.log(`Hello, I'm ${this.firstName}. I'm studying ${this.subject}.`)}sayGoodBye() {console.log(`Goodbye!`)}}// 測(cè)試實(shí)例:const student = new Student(`Janet`, `Applied Physics`)student.sayHello() // "Hello, I'm Janet. I'm studying Applied Physics."student.walk() // "I am walking!"student.sayGoodBye() // "Goodbye!"// Check that instanceof works correctlyconsole.log(student instanceof Person) // trueconsole.log(student instanceof Student) // true
封裝
上面例子中Student類雖然不需要知道 Person 類的 walk() 方法是如何實(shí)現(xiàn)的,但是仍然可以使用這個(gè)方法,Student類不需要明確地定義這個(gè)方法,除非我們想改變它,這就叫做封裝
抽象
抽象是允許模擬工作問題中通用部分的一種機(jī)制,這可以通過繼承或組合( JavaScript 讓類的實(shí)例是其他對(duì)象的屬性值來實(shí)現(xiàn)組合)來實(shí)現(xiàn):
JavaScript Function 類繼承自 Object 類(繼承)
Function.prototype 的屬性是一個(gè) Object 實(shí)例(組合)
var foo = function(){};console.log(foo instanceof Function) // trueconsole.log(foo.prototype instanceof Object) // true
多態(tài)
就像所有定義在原型屬性內(nèi)部的方法和屬性一樣,不同的類可以定義具有相同名稱的方法,方法是作用于所在的類中(并且這僅在兩個(gè)類不是父子關(guān)系時(shí)成立)
總結(jié)
結(jié)合上面的知識(shí),可以了解到JavaScript一種基于原型設(shè)計(jì)的面向?qū)ο笳Z(yǔ)言,而且JavaScript的對(duì)象操作靈活,是具有高度動(dòng)態(tài)性的屬性集合
可以深入了解一下基于原型的設(shè)計(jì)思路以及JavaScript對(duì)象模型的細(xì)節(jié)
參考資料:
JavaScript面向?qū)ο蠛?jiǎn)介:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Introduction_to_Object-Oriented_JavaScript 對(duì)象模型的細(xì)節(jié):https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Details_of_the_Object_Model 多繼承:https://zhuanlan.zhihu.com/p/34693209

