前端進階-深入瞭解物件

moduzhang發表於2018-11-13
  • 如何建立、訪問和修改物件?
  • 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 運算子來刪除,它會直接改變物件。

‘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 物件上的一個​​屬性!

全域性變數和 varletconst

JavaScript 中使用關鍵字 varletconst 來宣告變數。只有使用 var 關鍵字來宣告變數才會將其新增到 window 物件中。如果你用 letconst 在函式外部宣告一個變數,它將不會被作為屬性新增到 window 物件中

全域性函式是 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() 呢?請檢視瀏覽器相容性表!

Object.keys
Object.values

相關文章