前端進階-深入瞭解物件
- 如何建立、訪問和修改物件?
- JavaScript 函式為什麼是一級函式?
- JavaScript 對傳統類和繼承的抽象?
前端入門回顧
陣列
陣列是 JavaScript 中最有用的資料結構之一。在本質上,陣列就是一個由方括號(即 [ ]
)括起來的有序
元素集合。陣列可以儲存許多不同型別的資料,而不僅僅是字串!
const mixedArray = [0, "hello", true, ['apple', 'banana', 'orange', 'grape', 'lychee']];
物件
物件是 JavaScript 中最重要的資料結構之一。從根本上來說,物件就是一個有關聯的鍵/值對
的集合。我們使用大括號(即 { }
)來建立物件。陣列中的元素被數字索引所引用,而物件中的鍵則必須直接命名。
const car = { // 賦給該物件的變數被命名為 car
color: 'red', // 每個鍵均與一個值相關聯。這些鍵值對通過冒號連線起來
year: 1992, // 每個獨特的鍵值對(稱為該物件的屬性)均通過逗號與其他屬性分隔開來
isPreOwned: true
}; // 使用大括號來定義 car 物件
與陣列不同,物件是無序集合。
物件屬性語法
另外要注意的是,鍵
(即物件屬性的名稱)是字串
,但是圍繞這些字串的引號是可選的
,前提是該字串也是一個有效的 JavaScript 識別符號(即可以將其用作變數名稱或函式名稱)。因此,以下三個物件是等價的:
const course = { courseId: 711 }; // ← 沒有引號
const course = { 'courseId': 711 }; // ← 單引號
const course = { "courseId": 711 }; // ← 雙引號
對比Python字典
Python 字典與 JavaScript 中的物件也有一些類似功能,並有一些顯著差異。首先,Python 字典中的鍵必須是可雜湊的
(例如字串、數字、浮點數等)。以下是 JavaScript 中的一個有效物件:
const javascriptObject = { name: 'George Orwell', year: 1984 }
然而,它作為一個 Python 字典則是無效的:
python_dictionary = {name: 'George Orwell', year: 1984}
# Traceback (most recent call last):
# NameError: name 'name' is not defined
一個快速對策是將 Python 字典的鍵轉換為字串
:
my_dictionary = {'name': 'George Orwell', 'year': 1984}
訪問物件屬性
有兩種方法:點表示法
和方括號表示法
。
const bicycle = {
color: 'blue',
type: 'mountain bike',
wheels: {
diameter: 18,
width: 8
}
};
bicycle.color; // 'blue'
bicycle['color']; // 'blue'
bicycle.wheels.width; // 8
bicycle['wheels']['width']; // 8
點表示法的侷限性
請注意,儘管點表示法可能更易於讀寫,但它並不適用於所有情況。
例如,假設上面的 bicycle 物件中有一個鍵是數字
。那麼,像 bicycle.1
; 這樣的表示式將會導致錯誤;而 bicycle[1]
,則可以返回預期的值:
bicycle.1; // Uncaught SyntaxError: Unexpected number
bicycle[1]; // (returns the value of the `1` property)
另一個問題在於將變數賦給屬性名稱。假設我們宣告瞭 myVariable
,並將其賦給字串 'color'
:
const myVariable = 'color';
bicycle[myVariable]; // 'blue'
bicycle.myVariable; // undefined
要記得,JavaScript 物件中的所有屬性鍵都是字串,即使省略了引號
也是如此。當使用點表示法
時,JavaScript 直譯器將在 bicycle 中查詢一個值為 'myVariable' 的鍵
。由於該物件中並未定義這樣一個鍵,因此這個表示式將會返回 undefined
。
建立和修改屬性
建立物件
const myObject = {}; // 使用字面量表示法(推薦):
const myObject = new Object(); // 使用 Object() 建構函式:
雖然這兩個方法最終都會返回一個沒有自己屬性的物件,但是 Object()
建構函式相對較慢,而且較為冗長。因此,在 JavaScript 中建立新物件的推薦方法是使用字面量表示法
。
修改屬性
const cat = {
age: 2,
name: 'Bailey',
meow: function () {
console.log('Meow!');
},
greet: function (name) {
console.log(`Hello ${name}`);
}
};
cat.age += 1;
cat.age; // 3
cat.name = 'Bambi';
cat.name; // 'Bambi'
// 現在的 cat 物件
{
age: 3,
name: 'Bambi',
meow: function () {
console.log('Meow!');
},
greet: function (name) {
console.log(`Hello ${name}`);
}
};
新增屬性
const printer = {};
// 點表示法來新增屬性
printer.on = true;
printer.mode = 'black and white';
// 方括號表示法
printer['remainingSheets'] = 168;
// 新增一個方法
printer.print = function () {
console.log('The printer is printing!');
};
// 最終 printer 物件
{
on: true,
mode: 'black and white',
remainingSheets: 168,
print: function () {
console.log('The printer is printing!');
}
};
移除屬性
由於物件是可變的
,我們不僅可以修改現有屬性(或新增新屬性),還可以從物件中刪除屬性。
delete printer.mode; // true
delete
會直接改變當前的物件。如果呼叫一個已被刪除的方法,JavaScript 直譯器將無法再找到 mode
屬性,因為 mode 鍵(及其值 true)已被刪除:
printer.mode; // undefined
傳遞引數
傳遞一個原始型別
,在 JavaScript 中,原始型別(例如字串、數字、布林值
等)是不可變
的。換句話說,對函式中的引數所作的任何更改都會有效地為該函式建立一個區域性副本,而不會影響該函式外部的原始型別
。
function changeToEight(n) {
n = 8; // 無論 n 是什麼,它此刻都是 8... 但僅僅是在這個函式中!
}
let n = 7;
changeToEight(n);
console.log(n); // 7
傳遞一個物件
,JavaScript 中的物件是可變的
。如果你向函式傳遞一個物件,Javascript 會傳遞一個引用給該物件。如果我們向函式傳遞一個物件,然後修改一個屬性。
let originalObject = {
favoriteColor: 'red'
};
function setToBlue(object) {
object.favoriteColor = 'blue';
}
setToBlue(originalObject);
originalObject.favoriteColor; // blue
這是怎麼發生的?答案是,由於JavaScript 中的物件是通過引用傳遞的
,因此如果我們修改那個引用,我們其實是在直接修改原始物件本身!
更重要的是:同樣的規則適用於將一個物件重新賦給新的變數,然後改變那個副本
。同樣,由於物件是通過引用傳遞的,因此原始物件也被改變了。
const iceCreamOriginal = {
Andrew: 3,
Richard: 15
};
const iceCreamCopy = iceCreamOriginal;
iceCreamCopy.Richard; // 15
iceCreamCopy.Richard = 99;
iceCreamCopy.Richard; // 99
iceCreamOriginal.Richard; // 99
物件比較
const parrot = {
group: 'bird',
feathers: true,
chirp: function () {
console.log('Chirp chirp!');
}
};
const pigeon = {
group: 'bird',
feathers: true,
chirp: function () {
console.log('Chirp chirp!');
}
};
parrot === pigeon; // false
const myBird = parrot;
myBird === parrot; // true
事實證明,只有在將對同一個物件的兩個引用進行比較時
,這個表示式才會返回 true
。
原始型別(例如數字、字串、布林值等)是不可變的值。
let string = 'orange';
function changeToApple(string) {
string = 'apple';
}
changeToApple(string);
console.log(string); // orange
預設情況下,物件是可變的(除了少數例外),因此其中的資料可以被改變。既可以新增新屬性,也可以通過指定屬性名稱並賦值(或重新賦值)來輕鬆修改現有屬性。此外,物件的屬性和方法還可以使用 delete
運算子來刪除,它會直接改變物件。
函式與方法
const developer = {
name: 'Andrew',
sayHello: function () {
console.log('Hi there!');
}
};
呼叫方法
developer.sayHello(); // 'Hi there!'
developer['sayHello'](); // 'Hi there!'
物件引用自身的屬性
const triangle = {
type: 'scalene',
identify: function () {
console.log(`This is a ${this.type} triangle.`);
}
};
triangle.identify(); // 'This is a scalene triangle.'
this
的值
由於物件既包括資料,又包括對這些資料進行操作的手段,因此方法可以使用特別的 this
關鍵字來訪問被呼叫的物件。當方法被呼叫時,this 的值將被確定,它的值就是呼叫該方法的物件
。由於 this
是 JavaScript 中的一個保留字,因此它的值不能用作識別符號。
this
詳解
const chameleon = {
eyes: 2,
lookAround: function () {
console.log(`I see you with my ${this.eyes} eyes!`);
}
};
chameleon.lookAround();
// 'I see you with my 2 eyes!'
該函式體內部是程式碼 this.eyes
。由於 lookAround()
方法在 chameleon
物件上作為 chameleon.lookAround()
被呼叫,因此 this
的值就是 chameleon
物件本身!相應地,this.eyes
就是數字 2,因為它指向 chameleon
物件的 eyes
屬性。
函式/方法中的 this
const chameleon = {
eyes: 2,
lookAround: function () {
console.log(`I see you with my ${this.eyes} eyes!`);
}
};
function whoThis () {
this.trickyish = true
}
chameleon.lookAround();
whoThis();
在這兩種情況下,this 的使用基本上是相同的。chameleon
程式碼中使用 this
來檢索一個屬性
,而在 whoThis
程式碼中使用 this
來設定一個屬性
。
函式如何呼叫決定了函式內的 this
的值。
whoThis()
函式中的 this
的值是什麼呢?這是 JavaScript 語言一個有趣的特點。當一個常規函式被呼叫時,this
的值就是全域性 window
物件。
const chameleon = {
eyes: 2,
lookAround: function () {
debugger
console.log(`I see you with my ${this.eyes} eyes!`);
}
};
chameleon.lookAround();
function whoThis () {
this.trickyish = true
}
whoThis();
注意全域性變數
window
物件
如果你還沒有用過 window
物件,該物件是由瀏覽器環境提供的
,並可使用識別符號 window
在 JavaScript 程式碼中進行全域性訪問
。該物件不是 JavaScript 規範(即 ECMAScript)的一部分,而是由 W3C 開發的。
這個 window
物件可以訪問大量有關頁面本身的資訊,包括:
- 頁面的 URL (
window.location;
) - 頁面的垂直滾動位置 (
window.scrollY
) - 滾動到新位置(
window.scroll(0, window.scrollY + 200);
,從當前位置向下滾動 200 個畫素) - 開啟一個新的網頁 (
window.open("https://www.udacity.com/");
)
示例:
const car = {
numberOfDoors: 4,
drive: function () {
console.log(`Get in one of the ${this.numberOfDoors} doors, and let's go!`);
}
};
const letsRoll = car.drive;
letsRoll();
this
指向 window
物件。雖然 car.drive
是一個方法,但我們還是將該函式儲存在一個變數 letsRoll
中。由於 letsRoll()
是作為一個常規函式呼叫
的,因此 this
將指向它內部的 window
物件。
全域性變數是 window
上的屬性
每個在全域性級別(在函式外部)進行的變數宣告都會自動成為 window
物件上的一個屬性!
全域性變數和 var
、let
及 const
JavaScript 中使用關鍵字 var
、let
和 const
來宣告變數。只有使用 var
關鍵字來宣告變數才會將其新增到 window
物件中。如果你用 let
或 const
在函式外部宣告一個變數,它將不會被作為屬性新增到 window 物件中
。
全域性函式是 window
上的方法
與全域性變數可以作為 window 物件上的屬性進行訪問類似,任何全域性函式宣告都可以作為 window 物件上的方法進行訪問。
避免全域性變數
全域性變數和函式並不理想。這是由於很多原因,不過我們要看的兩種是:
- 緊密耦合
- 名稱衝突
緊密耦合
緊密耦合是開發者用來表示程式碼過於依賴彼此細節的一個短語。“耦合”一詞是指“將兩樣東西配對”。在緊密耦合中,程式碼片斷以一種緊密的方式連線在一起,使得更改一段程式碼會無意中改變其他程式碼的功能。
名稱衝突
一個主要問題是,兩個函式都會嘗試更新變數和/或設定變數,但是這些更改將被相互覆蓋。
提取屬性和值
在本質上,物件只是一個鍵/值對的集合。 如果我們只想從物件中提取鍵呢?假設我們有以下物件,表示一個字典:
const dictionary = {
car: 'automobile',
apple: 'healthy snack',
cat: 'cute furry animal',
dog: 'best friend'
};
當 Object.keys()
被賦予一個物件時,它只會提取該物件的鍵
,並將它們返回為一個陣列
:
Object.keys(dictionary);
// ['car', 'apple', 'cat', 'dog']
如果我們需要一個物件的值列表
,我們可以使用 Object.values()
:
Object.values(dictionary);
// ['automobile', 'healthy snack', 'cute furry animal', 'best friend']
Object.keys()
結果陣列的元素是字串,陣列元素的順序與 for...in
迴圈的順序相同。Object.values()
結果陣列元素的順序與 for...in
迴圈的順序相同。
支援現狀
Object.keys()
已經存在很長時間了,因此每個瀏覽器都完全支援它。
與此相反,Object.values()
則是新近才出現的。它在 2017 年被正式新增到了語言規範中。但是,僅僅因為它已經被新增到規範中,並不意味著你的瀏覽器就一定支援它!
如何確定你的瀏覽器_確實_支援 Object.values()
呢?請檢視瀏覽器相容性表!
相關文章
- 前端進階-類和物件前端物件
- python進階(16)深入瞭解GIL鎖(最詳細)Python
- 深入瞭解JavaScript中的物件JavaScript物件
- 前端進階課程之物件屬性特性詳解前端物件
- 詳解前端進階指南教程前端
- 深入瞭解ConcurrentHashMapHashMap
- JavaScript——深入瞭解thisJavaScript
- 深入瞭解原型原型
- 高階前端進階(七)前端
- 高階前端進階(五)前端
- 高階前端進階(三)前端
- js物件建立進階JS物件
- 中高階前端必須瞭解的--陣列亂序前端陣列
- web前端雜記:深入瞭解原理,擴充套件學習Web前端套件
- 深入瞭解Synchronized原理synchronized
- 深入瞭解SCN(轉)
- 深入瞭解babel(一)Babel
- [譯] 深入瞭解 FlutterFlutter
- 前端進階(二)JS高階講解物件導向,原型,繼承,閉包,正規表示式,讓你徹底愛上前端前端JS物件原型繼承
- 高階前端進階系列 - webview前端WebView
- Flutter進階:深入探究 TextFieldFlutter
- 深入瞭解解析Https - 從瞭解到放棄HTTP
- 深入瞭解 Vue.js 是如何進行「依賴收集]Vue.js
- 前端進階-樣式前端
- 前端進階之困前端
- 深入瞭解 Object.definePropertyObject
- 深入瞭解 Builder 模式 - frankelUI模式
- 深入瞭解Zookeeper核心原理
- 深入瞭解Object.definePropertyObject
- 深入瞭解MySQL的索引MySql索引
- Java小白進階筆記(5)-進階物件導向Java筆記物件
- 【2019 前端進階之路】深入 Vue 響應式原理,從原始碼分析前端Vue原始碼
- JavaScript進階知識點——函式和物件詳解JavaScript函式物件
- 「MoreThanJava」Day 5:物件導向進階——繼承詳解Java物件繼承
- 前端進階課程之跨域問題詳解前端跨域
- 技術管理進階——你瞭解成長的全貌嗎?
- 深入瞭解如何對IPA包進行有效的混淆處理
- 使用 JUnit 5.7 進行引數化測試:深入瞭解 @EnumSource