JS面向對象系列教程 — 對象的基本操作 面向對象概述  面向對象(Object Oriented)簡稱OO,它是一種編程思維,用於指導我們如何應對各種複雜的開發場景。 這裡說的對象(Object),意思就是事物,在面向對象的思維中,它將一切都看作是對象,並以對象為切入點去思考問題。 使用面向對象 ...
JS面向對象系列教程 — 對象的基本操作
面向對象概述

面向對象(Object Oriented)簡稱OO,它是一種編程思維,用於指導我們如何應對各種複雜的開發場景。
這裡說的對象(Object),意思就是事物,在面向對象的思維中,它將一切都看作是對象,並以對象為切入點去思考問題。
使用面向對象的思維去開發程式,我們首先思考的是這個系統中有哪些對象(事物),它們各自有什麼屬性(特征),又有什麼方法(行為),這樣一來,就可以把系統分解為一個一個的對象,然後對每個對象進行單獨研究,以降低系統的整體複雜度。
學習面向對象,我們不僅要學習它的技術知識,更重要的是學習這種思考程式的方式,這種思維方式跟我們過去開發程式有很大的區別。在過去,我們完成一個功能的時候,往往思考的是完成該功能的步驟,先做什麼,再做什麼,如果怎麼樣,就怎麼怎麼樣……,這種過去的思維模式,我們稱之為面向過程。
面向過程並不是錯誤的,只是它面對複雜的問題時顯得有些捉襟見肘。
面向過程和麵向對象最大的區別在於,面向過程思考的重心和切入點是事情,面向對象思考的重心和切入點是事物。
在面向對象的世界中,它將一切都看作是對象。這裡的對象,包羅萬象,它可以是現實世界中看得見摸得著的實體:人、小貓、小狗、飛機、課桌、鉛筆、電視等等等等,都可以看作是對象。它也可以是某些領域中的抽象體:訂單、價格計算器、dom元素、日期等等等等,它們也被看作是對象。
因此,在OO的世界里,有一句至理名言——一切皆為對象。
javascript語言是支持面向對象開發的語言,本系列教程中,將一步步講解如何使用它進行面向對象開發,同時會進一步探尋它背後深層次的原理。
註意:本系列教程的重心,將放在面向對象開發的技術層面(即面向對象編程,Object Oriented Programming,OOP)。
對於面向對象思想層面(即面向對象設計,Object Oriented Design,OOD),本教程不做過多講解。因為面向對象思想的建立並非一朝一夕,它不是靠讀文章讀出來的,雖然好的文章可以對你有啟發作用,但更多的,這種思想是靠常年累月的代碼量練出來的,是靠不斷的分析、重構想出來的。
本教程希望的是,你能夠通過學習面向對象開發,激發你的思考,引起你對另一種開發方式的關註,逐漸建立起面向對象的思維。
創建對象
對象即事物,一切皆對象。
其實你在之前的開發中,應該不止一次的接觸過對象,有些是瀏覽器自帶的對象,比如:dom對象、window對象、document對象等;有些則是你根據功能需要自己創建的,比如:用戶對象、學生對象等。
在JS中,創建一個對象非常的簡單,通過一對{}即可創建一個對象,下麵的代碼你應該並不會陌生:
const user1 = { loginid: "bangbangji", loginpwd: "123456", name: "棒棒雞"};const user2 = { loginid: "xiaobaitu", loginpwd: "654321", name: "小白兔"};
上面的代碼應該這樣理解,創建了兩個對象,分別把它們賦值給了變數user1和user2。
為什麼要強調這一點呢?因為真正的對象不是user1和user2,而是那一對大括弧及其裡面的內容{...},在後面講解對象賦值原理時還會詳細講解這一點。
上面這種創建對象的寫法,即使用兩個大括弧來表示對象{...},叫做對象的字面量表示法。
使用字面量表示法來創建對象的好處是簡單易懂,但它不好的地方在於無法應用面向對象開發中的一些高級特性。
下麵的代碼,展現了另一種創建對象的方式,即使用關鍵字new來創建對象:
const user1 = new Object(); //相當於:const user1 = {};user1.loginid = "bangbangji"; user1.loginpwd = "123456"; user1.name = "棒棒雞";const user2 = new Object(); //相當於:const user2 = {};user2.loginid = "xiaobaitu"; user2.loginpwd = "654321"; user2.name = "小白兔";
這樣的代碼雖然看似繁瑣了很多,但實際上它才是對象原本的創建方式。上面的代碼和第一種方式效果完全一致。
實際上,在JS語言中,所有的對象都是使用new關鍵字創建的。即便你使用的是字面量表示法,JS引擎最終也會將其轉換為這種方式。因此,對象的字面量表示法僅僅是JS語言的一個語法糖,要創建對象,最終都是要使用new關鍵字的。因此,在後文中,每一個示例,我都會用兩種方式來實現對象的創建。
語法糖(Syntactic sugar),也譯為糖衣語法,是指語言層面的一些便捷語法,它僅僅是為開發者提供的一種高效的編碼方式,並不改變底層的實現原理。
屬性和方法
我們過去使用對象,往往是為了將多個變數整合到一個變數上,其實,這種思想就是面向對象的一種核心思想——封裝。
面向對象有三大顯著特征:封裝、繼承、多態
那些整合到一個對象中的變數,我們稱之為對象的屬性。屬性往往是一個名詞,表示對象所擁有的特征。
const user1 = { loginid: "bangbangji", //loginid為對象的屬性,表示用戶賬號 loginpwd: "123456", //loginpwd為對象的屬性,表示用戶密碼 name: "棒棒雞" //name為對象的屬性,表示用戶姓名};const user2 = new Object(); user2.loginid = "xiaobaitu";//loginid為對象的屬性,表示用戶賬號user2.loginpwd = "654321";//loginpwd為對象的屬性,表示用戶密碼user2.name = "小白兔";//name為對象的屬性,表示用戶姓名
對象的屬性還可以是一個對象或者數組或者其他任意類型,用於表示多層次的結構:
const user1 = { loginid: "bangbangji", loginpwd: "123456", name: "棒棒雞", //屬性address也是一個對象 address: { province: "四川省", city: "成都市" } };//輸出:四川省-成都市console.log(`${user1.address.province}-${user1.address.city}`); const user2 = new Object(); user2.loginid = "xiaobaitu"; user2.loginpwd = "654321"; user2.name = "小白兔"; user2.address = new Object(); //屬性address也是一個對象user2.address.province = "四川省"; user2.address.city = "南充市";//輸出:四川省-南充市console.log(`${user2.address.province}-${user2.address.city}`);
對象除了有特征,還會有一些行為,這些行為我們稱之為對象的方法。方法往往是一個動詞,表示對象所擁有的行為,在代碼層面,方法表現為函數。
const user1 = { loginid: "bangbangji", loginpwd: "123456", name: "棒棒雞", //sayHello(打招呼)屬於對象方法,它是一個函數,表示對象的行為 sayHello: function () { console.log("你好!"); } }; user1.sayHello(); //調用對象的方法,輸出:你好!const user2 = new Object(); user2.loginid = "xiaobaitu"; user2.loginpwd = "654321"; user2.name = "小白兔";//sayHello(打招呼)屬於對象方法,它是一個函數,表示對象的行為user2.sayHello = function () { console.log("你好!"); }; user2.sayHello(); //調用對象的方法,輸出:你好!
可以看出,方法和屬性在書寫上並沒有本質的差別,只不過屬性是一個普通的值,方法是一個函數而已。
this關鍵字
我們現在考慮一下,在之前的示例中出現的sayHello方法,假設我們現在要對其功能做一點改造,我們希望該方法要輸出對象的姓名和賬號,於是,得到了下麵的代碼:
const user1 = { loginid: "bangbangji", loginpwd: "123456", name: "棒棒雞", //sayHello(打招呼)屬於對象方法,它是一個函數,表示對象的行為 sayHello: function () { console.log("你好!我是棒棒雞,我的賬號是bangbangji"); } }; user1.sayHello(); //調用對象的方法,輸出:你好!我是棒棒雞,我的賬號是bangbangjiconst user2 = new Object(); user2.loginid = "xiaobaitu"; user2.loginpwd = "654321"; user2.name = "小白兔";//sayHello(打招呼)屬於對象方法,它是一個函數,表示對象的行為user2.sayHello = function () { console.log("你好!我是小白兔,我的賬號是xiaobaitu"); }; user2.sayHello(); //調用對象的方法,輸出:你好!我是小白兔,我的賬號是xiaobaitu
這種方式結果固然正確,但是考慮一下100個用戶的場景……不敢想象是不?這還僅僅是一個打招呼的方法,如果方法裡面代碼多一點,複雜一點,會更加棘手。
由於同一類對象(比如這裡的用戶對象)都具有共同的行為——打招呼,我們何不把這個方法函數提出來呢?就像下麵這樣:
//提取出來的共同方法function sayHello(){ //怎麼辦?輸出哪個對象的姓名和賬號? console.log("你好!我是???,我的賬號是???"); }const user1 = { loginid: "bangbangji", loginpwd: "123456", name: "棒棒雞", sayHello }; user1.sayHello(); //輸出:你好!我是???,我的賬號是???const user2 = new Object(); user2.loginid = "xiaobaitu"; user2.loginpwd = "654321"; user2.name = "小白兔"; user2.sayHello = sayHello; user2.sayHello(); //輸出:你好!我是???,我的賬號是???
這樣雖然解決了代碼重覆的問題,但新的問題又來了,雖然我們知道每個用戶對象都具有打招呼的方法,但是每個對象的屬性值不一樣,打招呼的時候,我到底輸出哪個對象的屬性值呢?
於是,JS給我們提供了一個關鍵字this,它指代的是當前對象,即調用方法的對象,於是我們就可以解決這個問題了:
//提取出來的共同方法function sayHello(){ //this指代當前調用方法的對象 console.log(`你好!我是${this.name},我的賬號是${this.loginid}`); }const user1 = { loginid: "bangbangji", loginpwd: "123456", name: "棒棒雞", sayHello };//user1調用sayHello時,方法運行過程中的this指代user1user1.sayHello(); //輸出:你好!我是棒棒雞,我的賬號是bangbangjiconst user2 = new Object(); user2.loginid = "xiaobaitu"; user2.loginpwd = "654321"; user2.name = "小白兔"; user2.sayHello = sayHello;//user2調用sayHello時,方法運行過程中的this指代user2user2.sayHello(); //輸出:你好!我是小白兔,我的賬號是xiaobaitu
我們在sayHello函數中使用了this關鍵字,當函數運行時,該關鍵字指代調用該函數的對象,即誰在調用我,我裡面的this就指代誰。在閱讀代碼的時候,你可以把this讀作我的,以便於你理解它。
藉助了神奇的this關鍵字,就解決了重覆代碼的問題,之後無論創建多少個用戶對象,我們都可以使用同一個方法了。
關於this關鍵字,目前你需要記住以下兩點:
this關鍵字只能書寫在函數內。
this關鍵字在函數運行的時候才能確定指代的是誰,函數運行前誰也無法知道它將指代誰。
由於javascript語言本身的特性,函數中的this關鍵字會帶來很多坑(比如直接調用sayHello函數,this指代誰呢?)。後面會專門拿一章出來講解this。
總結
面向對象(OO)是一種編程思維,以對象為切入點分析解決問題,這種思維需要長期的練習才能逐漸建立,而我們學習的是面向對象技術層面的東西。
創建對象可以使用字面量表示法和new關鍵字,字面量表示法是一個語法糖,JS引擎最終會將其變為new關鍵字方式創建對象。
對象包含屬性和方法,分別表示對象的特征和行為,它們沒有本質的區別,只是行為提現為一個函數
在函數中可以使用this關鍵字來指代**當前對象(調用函數的對象)