javascript
javascript 在对象中使用 定时器_如何使用JavaScript 面向对象编程
學習目標
理解面向?qū)ο箝_發(fā)思想
掌握 JavaScript 面向?qū)ο箝_發(fā)相關模式
面向?qū)ο蠼榻B
什么是對象
Everything is object (一切皆對象)
我們可以從兩個層次來理解對象:
(1) 對象是單個事物的抽象。
????一本書、一輛汽車、一個人都可以是對象,一個數(shù)據(jù)庫、一張網(wǎng)頁、一個與遠程服務器的連接也可以是對象。當實物被抽象成對象,實物之間的關系就變成了對象之間的關系,從而就可以模擬現(xiàn)實情況,針對對象進行編程。
(2) 對象是一個容器,封裝了屬性(property)和方法(method)。
????屬性是對象的狀態(tài),方法是對象的行為(完成某種任務)。比如,我們可以把動物抽象為animal對象,使用“屬性”記錄具體是那一種動物,使用“方法”表示動物的某種行為(奔跑、捕獵、休息等等)。
????在實際開發(fā)中,對象是一個抽象的概念,可以將其簡單理解為:數(shù)據(jù)集或功能集。ECMAScript-262 把對象定義為:無序?qū)傩缘募?#xff0c;其屬性可以包含基本值、對象或者函數(shù)。嚴格來講,這就相當于說對象是一組沒有特定順序的值。對象的每個屬性或方法都有一個名字,而每個名字都 映射到一個值。
提示:每個對象都是基于一個引用類型創(chuàng)建的,這些類型可以是系統(tǒng)內(nèi)置的原生類型,也可以是開發(fā)人員自定義的類型。
什么是面向?qū)ο?/p>
面向?qū)ο蟛皇切碌臇|西,它只是過程式代碼的一種高度封裝,目的在于提高代碼的開發(fā)效率和可維護性。
????面向?qū)ο缶幊?—— Object Oriented Programming,簡稱 OOP ,是一種編程開發(fā)思想。它將真實世界各種復雜的關系,抽象為一個個對象,然后由對象之間的分工與合作,完成對真實世界的模擬。
????在面向?qū)ο蟪绦蜷_發(fā)思想中,每一個對象都是功能中心,具有明確分工,可以完成接受信息、處理數(shù)據(jù)、發(fā)出信息等任務。因此,面向?qū)ο缶幊叹哂徐`活、代碼可復用、高度模塊化等特點,容易維護和開發(fā),比起由一系列函數(shù)或指令組成的傳統(tǒng)的過程式編程(procedural programming),更適合多人合作的大型軟件項目。
面向?qū)ο笈c面向過程區(qū)別:
面向過程就是親力親為,事無巨細,面面俱到,步步緊跟,有條不紊。
面向?qū)ο缶褪钦乙粋€對象,指揮得結(jié)果。
面向?qū)ο髮?zhí)行者轉(zhuǎn)變成指揮者。
面向?qū)ο蟛皇敲嫦蜻^程的替代,而是面向過程的封裝。
面向?qū)ο蟮奶匦?#xff1a;
封裝性
繼承性
多態(tài)性
JavaScript 中面向?qū)ο蟮幕倔w現(xiàn)
????在 JavaScript 中,所有數(shù)據(jù)類型都可以視為對象,當然也可以自定義對象。自定義的對象數(shù)據(jù)類型就是面向?qū)ο笾械念? Class )的概念。
????我們以一個例子來說明面向過程和面向?qū)ο笤诔绦蛄鞒躺系牟煌帯?/p>
????假設我們要處理學生的成績表,為了表示一個學生的成績,面向過程的程序可以用一個對象表示:
var std1 = { name: '張三', score: 98 }var std2 = { name: '李四', score: 81 }????而處理學生成績可以通過函數(shù)實現(xiàn),比如打印學生的成績:
function printScore (student) { console.log('姓名:' + student.name + ' ' + '成績:' + student.score)}????如果采用面向?qū)ο蟮某绦蛟O計思想,我們首選思考的不是程序的執(zhí)行流程, 而是 Student 這種數(shù)據(jù)類型應該被視為一個對象,這個對象擁有 name 和 score 這兩個屬性(Property)。如果要打印一個學生的成績,首先必須創(chuàng)建出這個學生對應的對象,然后,給對象發(fā)一個 printScore 消息,讓對象自己把自己的數(shù)據(jù)打印出來。
抽象數(shù)據(jù)行為模板(Class):
function Student (name, score) { this.name = name this.score = score}Student.prototype.printScore = function () { console.log('姓名:' + this.name + ' ' + '成績:' + this.score)}根據(jù)模板創(chuàng)建具體實例對象(Instance):
var std1 = new Student('張三', 98)var std2 = new Student('李四', 81)實例對象具有自己的具體行為(給對象發(fā)消息):
std1.printScore() // => 姓名:張三 成績:98std2.printScore() // => 姓名:李四 成績 81????面向?qū)ο蟮脑O計思想是從自然界中來的,因為在自然界中,類(Class)和實例(Instance)的概念是很自然的。Class 是一種抽象概念,比如我們定義的 Class——Student ,是指學生這個概念, 而實例(Instance)則是一個個具體的 Student ,比如, 張三 和 李四 是兩個具體的 Student 。
面向?qū)ο蟮脑O計思想是:
抽象出 Class
根據(jù) Class 創(chuàng)建 Instance
指揮 Instance 得結(jié)果
????面向?qū)ο蟮某橄蟪潭缺群瘮?shù)要高,因為一個 Class 既包含數(shù)據(jù),又包含操作數(shù)據(jù)的方法。
JavaScript 如何創(chuàng)建對象
字面量方式
????我們可以直接通過 new Object()?創(chuàng)建:
var person = new Object()person.name = '張三'person.age = 18person.sayName = function () { console.log(this.name)}????每次創(chuàng)建通過 new Object()?比較麻煩,所以可以通過它的簡寫形式對象字面量來創(chuàng)建:
var person = { name: '張三', age: 18, sayName: function () { console.log(this.name) }}????上面的寫法是沒有問題的,但是假如我們要生成兩個 person 實例對象呢?
var person1 = { name: '張三', age: 18, sayName: function () { console.log(this.name) }}var person2 = { name: '李四', age: 16, sayName: function () { console.log(this.name) }}????通過上面的代碼我們不難看出,這樣寫的代碼太過冗余,重復性太高。
簡單方式的改進:工廠函數(shù)
????我們可以寫一個函數(shù),解決上邊代碼重復的問題:
function createPerson (name, age) { return { name: name, age: age, sayName: function () { console.log(this.name) } }}????生成實例對象:
var p1 = createPerson('張三', 18)var p2 = createPerson('李四', 18)????這樣封裝比上邊的方式好多了,通過工廠模式我們解決了創(chuàng)建多個相似對象代碼冗余的問題, 但卻沒有解決對象識別的問題(即怎樣知道一個對象的類型)。
更優(yōu)雅的工廠函數(shù):構(gòu)造函數(shù)
????一種更優(yōu)雅的工廠函數(shù)就是下面這樣,構(gòu)造函數(shù):
function Person (name, age) { this.name = name this.age = age this.sayName = function () { console.log(this.name) }}var p1 = new Person('張三', 18)p1.sayName() // => 張三var p2 = new Person('李四', 23)p2.sayName() // => 李四????在上面的示例中,Person()?函數(shù)取代了 createPerson()?函數(shù),但是實現(xiàn)效果是一樣的。這是為什么呢?
????我們注意到,Person()?中的代碼與?createPerson()?有以下幾點不同之處:
沒有顯示的創(chuàng)建對象
直接將屬性和方法賦給了?this?對象
沒有?return?語句
函數(shù)名使用的是大寫的?Person
構(gòu)造函數(shù)代碼執(zhí)行過程
????要創(chuàng)建 Person 實例,則必須使用 new 操作符。以這種方式調(diào)用構(gòu)造函數(shù)會經(jīng)歷以下 4 個步驟:
創(chuàng)建一個新對象。
將構(gòu)造函數(shù)的作用域賦給新對象(因此 this 就指向了這個新對象)。
執(zhí)行構(gòu)造函數(shù)中的代碼。
返回新對象。
????下面是具體的偽代碼:
function Person (name, age) { // 當使用 new 操作符調(diào)用 Person() 的時候,實際上這里會先創(chuàng)建一個對象 // var instance = {} // 然后讓內(nèi)部的 this 指向 instance 對象 // this = instance // 接下來所有針對 this 的操作實際上操作的就是 instance this.name = name this.age = age this.sayName = function () { console.log(this.name) } // 在函數(shù)的結(jié)尾處會將 this 返回,也就是 instance // return this}構(gòu)造函數(shù)和實例對象的關系
????使用構(gòu)造函數(shù)的好處不僅僅在于代碼的簡潔性,更重要的是我們可以識別對象的具體類型了。在每一個實例對象中的_proto_中同時有一個 constructor 屬性,該屬性指向創(chuàng)建該實例的構(gòu)造函數(shù):
console.log(p1.constructor === Person) // => trueconsole.log(p2.constructor === Person) // => trueconsole.log(p1.constructor === p2.constructor) // => true????對象的 constructor 屬性最初是用來標識對象類型的, 但是,如果要檢測對象的類型,還是使用 instanceof 操作符更可靠一些:
console.log(p1 instanceof Person) // => trueconsole.log(p2 instanceof Person) // => true總結(jié):
構(gòu)造函數(shù)是根據(jù)具體的事物抽象出來的抽象模板。
實例對象是根據(jù)抽象的構(gòu)造函數(shù)模板得到的具體實例對象。
每一個實例對象都具有一個 constructor 屬性,指向創(chuàng)建該實例的構(gòu)造函數(shù)。( 此處constructor 是實例的屬性的說法不嚴謹,具體后面的原型會講到)
可以通過實例的 constructor 屬性判斷實例和構(gòu)造函數(shù)之間的關系。(這種方式不嚴謹,推薦使用 instanceof 操作符,后面學原型會解釋為什么)
構(gòu)造函數(shù)的問題
????使用構(gòu)造函數(shù)帶來的最大的好處就是創(chuàng)建對象更方便了,但是其本身也存在一個浪費內(nèi)存的問題:
function Person (name, age) { this.name = name this.age = age this.type = '學生' this.sayHello = function () { console.log('hello ' + this.name) }}var p1 = new Person('王五', 18)var p2 = new Person('李四', 16)????上邊的代碼,從表面看上好像沒什么問題,但是實際上這樣做,有一個很大的弊端。那就是對于每一個實例對象,type和 sayHello 都是一模一樣的內(nèi)容, 每一次生成一個實例,都必須為重復的內(nèi)容,多占用一些內(nèi)存,如果實例對象很多,會造成極大的內(nèi)存浪費。
console.log(p1.sayHello === p2.sayHello) // => false????對于這種問題我們可以把需要共享的函數(shù)定義到構(gòu)造函數(shù)外部:
function sayHello = function () { console.log('hello ' + this.name)}function Person (name, age) { this.name = name this.age = age this.type = '學生' this.sayHello = sayHello}var p1 = new Person('王五', 18)var p2 = new Person('李四', 16)console.log(p1.sayHello === p2.sayHello) // => true????這樣確實可以了,但是如果有多個需要共享的函數(shù)的話就會造成全局命名空間沖突的問題。如何解決這個問題呢?你肯定想到了可以把多個函數(shù)放到一個對象中用來避免全局命名空間沖突的問題:
var fns = { sayHello: function () { console.log('hello ' + this.name) }, sayAge: function () { console.log(this.age) }}function Person (name, age) { this.name = name this.age = age this.type = '學生' this.sayHello = fns.sayHello this.sayAge = fns.sayAge}var p1 = new Person('王五', 18)var p2 = new Person('李四', 16)console.log(p1.sayHello === p2.sayHello) // => trueconsole.log(p1.sayAge === p2.sayAge) // => true????至此,我們利用自己的方式基本上解決了構(gòu)造函數(shù)的內(nèi)存浪費問題。但是代碼看起來還是那么的格格不入,那有沒有更好的方式呢?
原型
更好的解決方案:prototype
??? Javascript 規(guī)定,每一個構(gòu)造函數(shù)都有一個 prototype 屬性,指向另一個對象。這個對象的所有屬性和方法,都會被構(gòu)造函數(shù)的實例繼承。
????這也就意味著,我們可以把所有對象實例需要共享的屬性和方法直接定義在 prototype 對象上。
function Person (name, age) { this.name = name this.age = age}console.log(Person.prototype)Person.prototype.type = '學生'Person.prototype.sayName = function () { console.log(this.name)}var p1 = new Person(...)var p2 = new Person(...)console.log(p1.sayName === p2.sayName) // => true????這時所有實例的 type 屬性和 sayName()?方法, 其實都是同一個內(nèi)存地址,指向 prototype 對象,因此就提高了運行效率。
?構(gòu)造函數(shù)、實例、原型三者之間的關系:
????任何函數(shù)都有一個 prototype 屬性,該屬性是一個對象。
function F () {}console.log(F.prototype) // => objectF.prototype.sayHi = function () { console.log('hi!')}????構(gòu)造函數(shù)的 prototype 對象默認都有一個 constructor 屬性,指向 prototype 對象所在函數(shù)。
console.log(F.constructor === F) // => true????通過構(gòu)造函數(shù)得到的實例對象內(nèi)部會包含一個指向構(gòu)造函數(shù)的 prototype 對象的指針?__proto__。
var instance = new F()console.log(instance.__proto__ === F.prototype) // => true`__proto__` 是非標準屬性。
實例對象可以直接訪問原型對象成員:
instance.sayHi() // => hi!總結(jié):
任何函數(shù)都具有一個 prototype 屬性,該屬性是一個對象。
構(gòu)造函數(shù)的 prototype 對象默認都有一個 constructor 屬性,指向 prototype 對象所在函數(shù)。
通過構(gòu)造函數(shù)得到的實例對象內(nèi)部會包含一個指向構(gòu)造函數(shù)的 prototype 對象的指針?__proto__。
所有實例都直接或間接繼承了原型對象的成員。
屬性成員的搜索原則:原型鏈
????了解了?構(gòu)造函數(shù)-實例-原型對象?三者之間的關系后,接下來我們來解釋一下為什么實例對象可以訪問原型對象中的成員
????每當代碼讀取某個對象的某個屬性時,都會執(zhí)行一次搜索,目標是具有給定名字的屬性。
搜索首先從對象實例本身開始。
如果在實例中找到了具有給定名字的屬性,則返回該屬性的值。
如果沒有找到,則繼續(xù)搜索指針指向的原型對象,在原型對象中查找具有給定名字的屬性。
如果在原型對象中找到了這個屬性,則返回該屬性的值。
也就是說,在我們調(diào)用 person1.sayName()?的時候,會先后執(zhí)行兩次搜索:
首先,解析器會問:
“實例 person1 有 sayName 屬性嗎?
”答:
“沒有。
然后,它繼續(xù)搜索,再問:“ person1 的原型有 sayName 屬性嗎?”答:“有。
于是,它就讀取那個保存在原型對象中的函數(shù)。
當我們調(diào)用 person2.sayName() 時,將會重現(xiàn)相同的搜索過程,得到相同的結(jié)果。
????這就是多個對象實例共享原型所保存的屬性和方法的基本原理。
總結(jié):
先在自己身上找,找到即返回。
自己身上找不到,則沿著原型鏈向上查找,找到即返回。
如果一直到原型鏈的末端還沒有找到,則返回 undefined。
實例對象讀寫原型對象成員
讀取:
先在自己身上找,找到即返回。
自己身上找不到,則沿著原型鏈向上查找,找到即返回。
如果一直到原型鏈的末端還沒有找到,則返回 undefined。
值類型成員寫入(實例對象.值類型成員 = xx):
當實例期望重寫原型對象中的某個普通數(shù)據(jù)成員時實際上會把該成員添加到自己身上。
也就是說該行為實際上會屏蔽掉對原型對象成員的訪問。
引用類型成員寫入(實例對象.引用類型成員 = xx):同上。
復雜類型修改(實例對象.成員.xx = xx):
同樣會先在自己身上找該成員,如果自己身上找到則直接修改。
如果自己身上找不到,則沿著原型鏈繼續(xù)查找,如果找到則修改。
如果一直到原型鏈的末端還沒有找到該成員,則報錯(實例對象.undefined.xx = xx)。
更簡單的原型語法
????我們注意到,前面例子中每添加一個屬性和方法就要敲一遍 Person.prototype 。?為減少不必要的輸入,更常見的做法是用一個包含所有屬性和方法的對象字面量來重寫整個原型對象:
function Person (name, age) { this.name = name this.age = age}Person.prototype = { type: '學生', sayHello: function () { console.log('我叫' + this.name + ',我今年' + this.age + '歲了') }}????在該示例中,我們將 Person.prototype 重置到了一個新的對象。這樣做的好處就是為 Person.prototype 添加成員簡單了,但是也會帶來一個問題,那就是原型對象丟失了 constructor 成員。
????所以,我們?yōu)榱吮3?constructor 的指向正確,建議的寫法是:
function Person (name, age) { this.name = name this.age = age}Person.prototype = { constructor: Person, // => 手動將 constructor 指向正確的構(gòu)造函數(shù) type: '學生', sayHello: function () { console.log('我叫' + this.name + ',我今年' + this.age + '歲了') }}原生對象的原型
所有函數(shù)都有 prototype 屬性對象。
Object.prototype
Function.prototype
Array.prototype
String.prototype
Number.prototype
Date.prototype
...
????為數(shù)組對象和字符串對象擴展原型方法:
//為內(nèi)置對象添加原型方法//我們在系統(tǒng)的對象的原型中添加方法,相當于在改變源碼//我希望字符串中有一個倒序字符串的方法String.prototype.myReverse = function() {for (var i = this.length - 1; i >= 0; i--) {console.log(this[i]);}};var str = "abcdefg";str.myReverse();//為Array內(nèi)置對象的原型對象中添加方法Array.prototype.mySort = function() {for (var i = 0; i < this.length - 1; i++) {for (var j = 0; j < this.length - 1 - i; j++) {if (this[j] < this[j + 1]) {var temp = this[j];this[j] = this[j + 1];this[j + 1] = temp;} //end if} // end for} //end for};var arr = [100, 3, 56, 78, 23, 10];arr.mySort();console.log(arr);String.prototype.sayHi = function() {console.log(this + "哈哈,我又變帥了");};//字符串就有了打招呼的方法var str2 = "小楊";str2.sayHi();原型對象的一些問題
共享數(shù)組
共享對象
????如果真的希望可以被實例對象之間共享和修改這些共享數(shù)據(jù)那就不是問題。但是如果不希望實例之間共享和修改這些共享數(shù)據(jù)則會出現(xiàn)問題。一個更好的建議是,最好不要讓實例之間互相共享數(shù)組或者對象成員,一旦修改的話會導致數(shù)據(jù)的走向很不明確而且難以維護。
原型對象使用建議:
私有成員(一般就是非函數(shù)成員)放到構(gòu)造函數(shù)中。
共享成員(一般就是函數(shù))放到原型對象中。
如果重置了 prototype 記得修正 constructor 的指向。
總結(jié)
以上是生活随笔為你收集整理的javascript 在对象中使用 定时器_如何使用JavaScript 面向对象编程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python遥感图像处理基础篇(三):a
- 下一篇: jq js json 转字符串_JS中J