一? 實例對象與new命令
1. 什么是對象?
面向?qū)ο缶幊蹋∣bject Oriented Programming,縮寫為 OOP)是目前主流的編程范式。它將真實世界各種復雜的關(guān)系,抽象為一個個對象,然后由對象之間的分工與合作,完成對真實世界的模擬。
每一個對象都是功能中心,具有明確分工,可以完成接受信息、處理數(shù)據(jù)、發(fā)出信息等任務。對象可以復用,通過繼承機制還可以定制。因此,面向?qū)ο缶幊叹哂徐`活、代碼可復用、高度模塊化等特點,容易維護和開發(fā),比起由一系列函數(shù)或指令組成的傳統(tǒng)的過程式編程(procedural programming),更適合多人合作的大型軟件項目。
那么,“對象”(object)到底是什么?我們從兩個層次來理解
? ??1.1 對象是單個實物的抽象
????????一本書、一輛汽車、一個人都可以是對象,一個數(shù)據(jù)庫、一張網(wǎng)頁、一個與遠程服務器的連接也可以是對象。當實物被抽象成對象,實物之間的關(guān)系就變成了對象之間的關(guān)系,從而就可以模擬現(xiàn)實情況,針對對象進行編程。
? ??1.2 對象是一個容器,封裝了屬性(property)和方法(method)。
????????屬性是對象的狀態(tài),方法是對象的行為(完成某種任務)。舉個例子:我們可以把動物抽象為 animal對象,使用“屬性”來記錄具體是哪一種動物,使用“方法”表示動物的某種行為(奔跑,捕獵,交配,休息...等等)
2. 構(gòu)造函數(shù)
面向編程的第一步,就是要生成對象,前面說過,對象是單個實物的抽象。通常需要一個模板,表示某一類實物的共同特征,然后對象根據(jù)這個模板生成。典型的面向?qū)ο笞兂烧Z言(java 和c++),都有‘類’(class)的概念。所謂“類”就是對象的模板,對象就是“類”的實例。但是Javascript不是基于“類”的,而是基于構(gòu)造函數(shù)(constructor)和原型鏈(prototype)。
Javascript語言使用構(gòu)造函數(shù)(constructor)作為對象的模板。所謂的“構(gòu)造函數(shù)",就是專門用來生成實例對象的函數(shù)。它就是對象的模板,描述實例對象的基本結(jié)構(gòu)。一個構(gòu)造函數(shù)可以生成多個實例對象,這些實例對象都具有相同的結(jié)構(gòu)。
構(gòu)造函數(shù)(constructor)就是一個很普通的函數(shù),但是有自己的特征和用法。

上面的代碼 Person就是一個構(gòu)造函數(shù),為了與普通函數(shù)區(qū)別,構(gòu)造函數(shù)的名字的第一個字母通常大寫。
構(gòu)造函數(shù)(constructor)的兩個特點:
? ??????????????函數(shù)體內(nèi)使用了 this關(guān)鍵字,代表了所要生成的對象實例。
? ??????????????生成對象時候,必須使用 new 命令。
3. new命令
3.1 基本用法
new命令的作用,就是執(zhí)行構(gòu)造函數(shù),返回一個實例對象。

上面代碼通過new命令,讓構(gòu)造函數(shù)Person生成一個實例對象,保存在變量person1中。這個新生成的實例對象,從構(gòu)造函數(shù)Person得到了name屬性。new命令執(zhí)行時,構(gòu)造函數(shù)內(nèi)部的this,就代表了新生成的實例對象,this.name表示實例對象有一個name屬性,值是 Yahiko。
使用new命令時候,構(gòu)造函數(shù)也是可以接受參數(shù)的。

3.2 new命令的原理
使用new命令時,它后面的函數(shù)依次執(zhí)行下面的步驟:
1)創(chuàng)建了一個空對象,作為要返回的實例對象。
2)將這個空對象的原型,指向構(gòu)造函數(shù)的 prototype屬性。
3)將這個空對象賦值給函數(shù)內(nèi)部的this關(guān)鍵字。
4)開始執(zhí)行函數(shù)內(nèi)部代碼。
也就是說,構(gòu)造函數(shù)內(nèi)部,this指的是一個新生成的空對象,所有針對this的操作,都會發(fā)生在這個空對象上。構(gòu)造函數(shù)之所以叫“構(gòu)造函數(shù)”,就是說這個函數(shù)的目的,就是操作一個空對象(即this對象),將其“構(gòu)造”為需要的樣子。
如果構(gòu)造函數(shù)內(nèi)部有return語句,而且return后面跟著一個對象,new命令會返回return語句指定的對象;否則,就會不管return語句,返回this對象。

但是,如果return語句返回的是一個跟this無關(guān)的新對象,new命令會返回這個新對象,而不是this對象。這一點需要特別引起注意。

上面代碼中,構(gòu)造函數(shù)Vehicle的return語句,返回的是一個新對象。new命令會返回這個對象,而不是this對象。
另一方面,如果對普通函數(shù)(內(nèi)部沒有this關(guān)鍵字的函數(shù))使用new命令,則會返回一個空對象。

上面代碼中,getMessage是一個普通函數(shù),返回一個字符串。對它使用new命令,會得到一個空對象。這是因為new命令總是返回一個對象,要么是實例對象,要么是return語句指定的對象。本例中,return語句返回的是字符串,所以new命令就忽略了該語句
3.3? new.target
函數(shù)內(nèi)部可以使用new.target屬性。如果函數(shù)是new命令調(diào)用的,new.target指向當前函數(shù),否則為undefined。

new.target這個屬性可以判斷函數(shù)調(diào)用時,是否使用了new命令。

4. Object.create()創(chuàng)建實例對象
構(gòu)造函數(shù)作為模板,可以生成實例對象。但是,有時候拿不到構(gòu)造函數(shù),只能拿到一個現(xiàn)有的對象。我們希望能拿這個現(xiàn)有的對象作為模板,生成新的實例對象,這時候就可以使用 Object.create()方法了。

二? this關(guān)鍵字
1.涵義
this 關(guān)鍵字是一個非常重要的語法點,毫不夸張的講,不理解它的含義,大部分開發(fā)任務都無法完成。
前面已經(jīng)提到了,this在構(gòu)造函數(shù)中,表示實例對象。除此外,this還可以用在別的場合里。不管什么場合,this都有一個共同特點,返回一個對象。
簡單點說,this就是屬性或者方法‘當前’的對象。

上面代碼中,this.name表示name屬性所在的那個對象。由于this.name是在describe方法中調(diào)用,而describe方法所在的當前對象是person,因此this指向person,this.name就是person.name。
由于對象的屬性是可以賦值給另一個對象的,所以屬性所在對象是會發(fā)生改變的,即this的指向是可變的。

拆分一下上面的例子,重構(gòu)一下:

總結(jié)一下,JavaScript 語言之中,一切皆對象,運行環(huán)境也是對象,所以函數(shù)都是在某個對象之中運行,this就是函數(shù)運行時所在的對象(環(huán)境)。這本來并不會讓用戶糊涂,但是 JavaScript 支持運行環(huán)境動態(tài)切換,也就是說,this的指向是動態(tài)的,沒有辦法事先確定到底指向哪個對象,這才是最讓初學者感到困惑的地方。
教你個笨招數(shù),你不是很明確this指向的時候,看的暈頭轉(zhuǎn)向的時候,別再靠猜了!不妨console.log()打印一下這個this,你看看當前它到底指向誰。
2. this實質(zhì)
javascript語言之所以有this設(shè)計,跟內(nèi)存里面的數(shù)據(jù)結(jié)構(gòu)有關(guān)。
var? obj={ foo:5}
上面的代碼,將一個對象賦值給了變量obj。Javascipt引擎會現(xiàn)在內(nèi)存里面,生成一個對象{foo:5},然后再把這個對象的內(nèi)存地址賦值給變量obj。
也就是說,變量obj 是一個地址 。后面要讀取 obj.foo,引擎先從obj拿到內(nèi)存地址,再從該地址讀取原始對象,返回了foo屬性。
3.使用場景
1.全局環(huán)境
全局環(huán)境使用this ,它的指向就是window。
2.構(gòu)造函數(shù)
構(gòu)造函數(shù)中的this,指的是實例對象。
3.對象的方法
如果對象的方法里包含了this,this的指向就是該方法運行時所在的對象。該方法賦值給另一個對象,就會改變this的指向。(這種情況不好把握)
4.注意事項
1.this 盡量避免多層
由于this的指向不確定,所以切勿在函數(shù)中多層this,當然了 也有辦法搞,你就想多層套這咋辦呢?使用一個變量固定this的值,然后內(nèi)層函數(shù)調(diào)用這個變量。舉個例子:

這時候我們可以在第二個this 稍微改動一下讓 第二個this也指向當前對象o:

2.避免數(shù)組處理方法中的this
數(shù)組的map和foreach方法,允許提供一個函數(shù)作為參數(shù)。這個函數(shù)內(nèi)部不應該使用this。

解決這個問題的一種方法,就是前面提到的,使用中間變量固定this。

或者固定運行環(huán)境的辦法也可以。

3.綁定this的方法
JavaScript提供了call apply bind三個方法可以切換/固定 this指向。
1)Function.prototype.call()
? 格式 func.call(thisValue, arg1, arg2, ...)

call方法的參數(shù),應該是一個對象。如果參數(shù)為空、null和undefined,則默認傳入全局對象,也可以傳入第多個參數(shù),第一個參數(shù)是this指向的對象,后面的參數(shù)則是函數(shù)調(diào)用時所需參數(shù)。

2)Function.prototype.apply()
格式 func.apply(thisValue, [arg1, arg2, ...])
apply方法的作用與call方法類似,也是改變this指向,然后再調(diào)用該函數(shù)。apply方法的第一個參數(shù)也是this所要指向的那個對象,如果設(shè)為null或undefined,則等同于指定全局對象。第二個參數(shù)則是一個數(shù)組,該數(shù)組的所有成員依次作為參數(shù),傳入原函數(shù)。原函數(shù)的參數(shù),在call方法中必須一個個添加,但是在apply方法中,必須以數(shù)組形式添加。
三 對象的繼承
面向?qū)ο缶幊毯苤匾囊粋€方面,就是對象的繼承。A對象通過繼承B對象,就能直接擁有B對象的所有屬性和方法,這對代碼復用很有用。
大部分面向?qū)ο笳Z言都是通過“類”實現(xiàn)對象繼承。傳統(tǒng)上,Javascript語言不通過class,而是通過“原型對象”(prototype)實現(xiàn)。
es6引入了class語法 ,先暫時不說。后續(xù)再專門寫關(guān)于ES6部分的。
1 原型對象概述
????1.1構(gòu)造函數(shù)的缺點
JavaScript通過構(gòu)造函數(shù)申城新對象,因此構(gòu)造函數(shù)可以視為對象的模板。實例對象的屬性和方法,可以定義在構(gòu)造函數(shù)內(nèi)部。

上面代碼中,Cat函數(shù)是一個構(gòu)造函數(shù),函數(shù)內(nèi)部定義了name屬性和color屬性,所有實例對象(上例是cat1)都會生成這兩個屬性,即這兩個屬性會定義在實例對象上面。
同一個構(gòu)造函數(shù)的多個實例之間,無法共享屬性,從而造成對系統(tǒng)資源的浪費。

這個問題的解決方法,就是 JavaScript 的原型對象(prototype)
????1.2 prototype 屬性的作用
JavaScript 繼承機制的設(shè)計思想就是,原型對象的所有屬性和方法,都能被實例對象共享。也就是說,如果屬性和方法定義在原型上,那么所有實例對象就能共享,不僅節(jié)省了內(nèi)存,還體現(xiàn)了實例對象之間的聯(lián)系
JavaScript規(guī)定,每個函數(shù)都有一個prototype屬性,指向一個對象。

對于普通函數(shù)來說,該屬性基本無用。但是對于構(gòu)造函數(shù)來說,生成實例的時候,該屬性會自動成為實例對象的原型。

Animal的prototype屬性,就是實例對象cat1和cat2的原型對象。原想對象添加color屬性,實例對象都共享了該屬性。
原型對象的屬性不是實例對象自身的屬性。只要修改原型對象,變動就立刻會體現(xiàn)所有實例對象上。

原型對象的color屬性的值變?yōu)閥ellow,兩個實例對象的color屬性立刻跟著變了。這是因為實例對象其實沒有color屬性,都是讀取原型對象的color屬性。也就是說,當實例對象本身沒有某個屬性或方法的時候,它會到原型對象去尋找該屬性或方法。這就是原型對象的特殊之處。
如果實例對象自身就有某個屬性或方法,它就不會再去原型對象尋找這個屬性或方法。

總結(jié)一下,原型對象的作用,就是定義所有實例對象共享的屬性和方法。這也是它被稱為原型對象的原因,而實例對象可以視作從原型對象衍生出來的子對象。
????1.3 原型鏈
JavaScript規(guī)定,所有對象都有自己的原想對象(prototype)。一方面,任何一個對象,的都可以充當其他對象的原型,另一方面,由于原型對象也是對象,所以他特有自己的原型。因此,就會形成一個“原型鏈”:對象到原型對象,原型對象到原型對象的原型對象...
如果一層層地上溯,所有對象的原型最終都可以上溯到Object.prototype,即Object構(gòu)造函數(shù)的prototype屬性。也就是說,所有對象都繼承了Object.prototype的屬性。這就是所有對象都有valueOf和toString方法的原因,因為這是從Object.prototype繼承的。
那么,Object.prototype對象有沒有它的原型呢?回答是Object.prototype的原型是null。null沒有任何屬性和方法,也沒有自己的原型。因此,原型鏈的盡頭就是null。
讀取對象的某個屬性時,JavaScript 引擎先尋找對象本身的屬性,如果找不到,就到它的原型去找,如果還是找不到,就到原型的原型去找。如果直到最頂層的Object.prototype還是找不到,則返回undefined。如果對象自身和它的原型,都定義了一個同名屬性,那么優(yōu)先讀取對象自身的屬性,這叫做“覆蓋”(overriding)。
注意,一級級向上,在整個原型鏈上尋找某個屬性,對性能是有影響的。所尋找的屬性在越上層的原型對象,對性能的影響越大。如果尋找某個不存在的屬性,將會遍歷整個原型鏈。
var A=function(){};
var a=new A();
A是構(gòu)造函數(shù),a是構(gòu)造函數(shù)A的實例。A.prototype可以看作一個整體 他就是原型對象。
掛在A.prototype上的屬性或方法,都可以被實例a調(diào)用。
實例a.__proto__=(構(gòu)造函數(shù)A.prototype)原型對象? ??
下面圖便于理解:

????1.4 constructor 屬性
prototype對象有一個constructor屬性,默認指向prototype對象所在的構(gòu)造函數(shù)。

由于constructor屬性定義在prototype對象上面,意味著可以被所有實例對象繼承。

上面代碼中,f1是構(gòu)造函數(shù)F的實例對象,但是f1自身沒有constructor屬性最后一行代碼就返回了false,該屬性其實是讀取原型鏈上面的F.prototype的constructor屬性 。
constructor屬性表示原型對象與構(gòu)造函數(shù)之間的關(guān)聯(lián)關(guān)系,如果修改了原型對象,一般會同時修改constructor屬性,防止引用的時候出錯。

上面代碼中,構(gòu)造函數(shù)Person的原型對象改掉了,但是沒有修改constructor屬性,導致這個屬性不再指向Person。由于Person的新原型是一個普通對象,而普通對象的constructor屬性指向Object構(gòu)造函數(shù),導致Person.prototype.constructor變成了Object。
所以,修改原型對象時,一般要同時修改constructor屬性的指向。

????2. instanceof運算符
instanceof運算符返回一個布爾值,表示對象是否為某個構(gòu)造函數(shù)的實例。

instanceof運算符的左邊是實例對象,右邊是構(gòu)造函數(shù)。它會檢查右邊構(gòu)建函數(shù)的原型對象(prototype),是否在左邊對象的原型鏈上。因此,下面兩種寫法是等價的。

由于instanceof檢查整個原型鏈,因此同一個實例可能會對 多個構(gòu)造函數(shù)返回true。

有一種情況比較特殊,就是做左邊對象的原型鏈上,只有null對象,這時候,instanceof就會判斷失誤。

上面代碼中,Object.create(null)返回一個新對象obj,它的原型是null(Object.create的詳細介紹見后文)。右邊的構(gòu)造函數(shù)Object的prototype屬性,不在左邊的原型鏈上,因此instanceof就認為obj不是Object的實例。但是,只要一個對象的原型不是null,instanceof運算符的判斷就不會失真。
instanceof運算符還可以判斷值的類型。

注意:instanceof只能用于對象,不適用原始類型的值(String 布爾值 數(shù)值 三個原始類型 不能再細分了 ,對象是一個合成型值)。

????3. 構(gòu)造函數(shù)的繼承
讓一個構(gòu)造函數(shù)繼承另一個構(gòu)造函數(shù),是非常常見的需求。這可以分成兩步實現(xiàn)。第一步再子類的構(gòu)造函數(shù)中,調(diào)用父類構(gòu)造函數(shù)。第二步讓子類的原型指向父親的原型,這樣子類就能繼承父親的原型。

????4.多重繼承
JavaScript不提供多重繼承功能,即不允許一個對象繼承多個對象。但是,通過變通的辦法,實現(xiàn)。

????5.模塊
隨著網(wǎng)站逐漸變成“互聯(lián)網(wǎng)應用程序”,潛入網(wǎng)頁的Js代碼越來越大,越來越復雜。網(wǎng)頁越來越像桌面程序,需要一個團隊分割寫作,進度管理,單元測試等等...開發(fā)者必須使用軟件工程的方法,管理網(wǎng)頁的業(yè)務邏輯。
JavaScript模塊化編程,已經(jīng)編程一個迫切需求。理想情況下,開發(fā)正只需要實現(xiàn)核心業(yè)務邏輯,其他的都可以加載被人已經(jīng)寫好的模塊。
但是,JavaScript并不是一種模塊化編程語言,ES6才開始支持“類”和“模塊”。下面介紹傳統(tǒng)的做法,如何利用對象實現(xiàn)模塊效果。
????????5.1模塊基本的實現(xiàn)方法
模塊是實現(xiàn)特定功能的一組屬性和方法的封裝。
簡單的做法就是把模塊寫成一個對象,所有模塊的成員都放到這個對象里。

但是!這樣的寫法會暴漏所有模塊成員,內(nèi)部狀態(tài)可以被外部改寫。比如外部代碼可以直接改變內(nèi)部計數(shù)器的值。

這怎么辦呢?我們可以利用構(gòu)造函數(shù),封裝私有變量。
????????5.2 封裝私有變量:構(gòu)造函數(shù)寫法

上面的代碼,buffer是模塊的私有變量。一單生成實例對象,外部是無法訪問buffer的。但是,這種方法將私有變量封裝在構(gòu)造函數(shù)中,倒是構(gòu)造函數(shù)與實例對象是一體的,總是存在內(nèi)存之中,無法在使用完成后清除。這意味著,構(gòu)造函數(shù)有雙重作用,既用來塑造實例對象,又保存實例對象的數(shù)據(jù),違背了構(gòu)造函數(shù)與實例對象在數(shù)據(jù)相分離的原則(即實例對象的數(shù)據(jù)不應該保存在實例對象以外)同時又非常消耗內(nèi)存。

有沒有更好的辦法???慢慢往下看??
????????5.3 封裝私有變量:立即執(zhí)行函數(shù)的寫法
另一種做法就是使用‘立即執(zhí)行函數(shù)’,將相關(guān)的屬性和方法封裝在一個函數(shù)作用域里面,可以達到不暴漏私有成員目的。

上面的module就是 JavaScript 模塊的基本寫法。下面,再對這種寫法進行加工。再來??
????????5.4模塊的放大模式
如果一個模塊很大,必須分成幾個部分,或者一個模塊需要繼承另一個模塊,這時就有必要采用‘放大模式’。

上面的代碼為module1模塊添加了一個新方法m3(),然后返回新的module1模塊。
在瀏覽器環(huán)境中,模塊的各個部分通常都是從網(wǎng)上獲取的,有時無法知道哪個部分會先加載。如果采用上面的寫法,第一個執(zhí)行的部分有可能加載一個不存在空對象,這時就要采用"寬放大模式"(Loose augmentation)。

與"放大模式"相比,“寬放大模式”就是“立即執(zhí)行函數(shù)”的參數(shù)可以是空對象。
????????5.5輸入全局變量
獨立性是模塊的重要特點,模塊內(nèi)部最好不要與程序進行直接交互。
為了在模塊內(nèi)調(diào)用全局變量,必須顯式的將其他變量輸入模塊。

上面的moudle模塊需要使用jQuery庫和YUI庫,就把這兩個庫(起始式兩個模塊)當作參數(shù)輸入moudle。這樣做除了保證模塊獨立性,還使得模塊之間的依賴關(guān)系變得明顯。
立即執(zhí)行函數(shù)還可以起到命名空間的作用:

上面代碼中,finalCarousel對象輸出到全局,對外暴露init和destroy接口,內(nèi)部方法go、handleEvents、initialize、dieCarouselDie都是外部無法調(diào)用的。
四 Object對象的相關(guān)方法
1.Object.getPrototypeOf()
Object.getPrototypeOf()方法返回參數(shù)對象的原型。這是獲取原型對象的標準方法。

下面是幾種特殊的對象原型:

2.Object.setPrototypeOf()
Object.setPrototypeOf() 方法為參數(shù)對象設(shè)置原型對象,返回該參數(shù)對象。它接受兩個參數(shù),第一個是現(xiàn)有對象,第二個是原型對象。

new命令可以使用Object.setPrototypeOf()方法模擬:

上面代碼,new命令新建實例對象,其實可以分成兩步。第一步,將一個空對象的原型設(shè)為構(gòu)造函數(shù)的prototype屬性(上面的例子? 就是 F構(gòu)造函數(shù)的prototype屬性,? ?F.prototype原型對象);第二部將構(gòu)造函數(shù)內(nèi)部的this綁定這個空對象,然后執(zhí)行構(gòu)造函數(shù),使得定義在this上面的方法和屬性(上例就是this.name),轉(zhuǎn)移到這個空對象上。
3.Object.create()
生成實例對象的常用方法是,使用new命令讓構(gòu)造函數(shù) 返回一個實例。但是很多時候,只能拿到一個實例對象,它可能根本不是由構(gòu)函數(shù)生成的,那么能不能從一個實例對象,生成另一個實例對象呢?
JavaScript提供了Object.create方法,用來滿足這種需求。該方法接受一個對象作為參數(shù),然后以它為原型,返回一個實例對象。該實例對象完全繼承原型對象的屬性。