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關鍵字來指代**當前物件(呼叫函式的物件)