博客 / 詳情

返回

《javascript高級程序設計》學習筆記 | 8.1.理解對象

關注前端小謳,閲讀更多原創技術文章

理解對象

  • ECMAScript 定義對象:無序屬性的集合(一組沒有特定順序的值),其屬性可以包含基本值對象函數,整個對象可以想象成一個散列表

相關代碼 →

  • 創建自定義對象的 2 種方法:Object 構造函數對象字面量

    • 用構造函數創建一個 Object 實例,然後為它添加屬性和方法
    var person = new Object()
    person.name = 'Nicholas'
    person.age = 29
    person.job = 'Software Engineer'
    person.sayName = function () {
      console.log(this.name)
    }
  • 對象字面量創建對象
var person = {
  name: 'Nicholas',
  age: 29,
  job: 'Software Engineer',
  sayName: function () {
    console.log(this.name)
  },
}

屬性的類型

  • 為 JavaScript 實現引擎的規範定義,開發者不能直接訪問,用兩個中括號把特性名稱括起來

數據屬性

數據屬性 含義 默認值
[[Configurable]] 能否配置( delete 刪除、修改特性、改為訪問器屬性) 在對象上定義:true / Object.defineProperty()定義:false
[[Enumerable]] 能否通過 for-in 循環返回屬性 在對象上定義:true / Object.defineProperty()定義:false
[[Writable]] 能否修改屬性的值 在對象上定義:true / Object.defineProperty()定義:false
[[Value]] 屬性的數據值 undefined
  • 數據屬性包含一個保存數據值的位置,用 Object.defineProperty() 修改屬性的默認特性,方法接收 3 個參數:

    • 屬性所在對象、屬性名、描述符對象(描述符對象的屬性:configurableenumerablewritablevalue的一個或多個)
    • 嚴格模式下,修改[[Writable]]為 false 的值會報錯
    • 嚴格模式下,用delete刪除[[Configurable]]為 false 的屬性會報錯
var person = {}
Object.defineProperty(person, 'name', {
  writable: false, // 不可修改
  configurable: false, // 不可配置
  value: 'Nicholas',
})
console.log(person.name) // 'Nicholas'

person.name = 'Greg' // 試圖重寫(嚴格模式會報錯)
console.log(person.name) // 'Nicholas',無法重寫
delete person.name // 試圖刪除(嚴格模式會報錯)
console.log(person.name) // 'Nicholas',無法刪除
  • 對同一個屬性多次調用 Object.defineProperty()會有限制:

    • configurablefalseconfigurablewritableenumerable屬性不可再被修改
    • writablefalse ,描述符對象的value屬性不可再被修改
Object.defineProperty(person, 'name', {
  // configurable: true, // 報錯,configurable為true方可修改
  // value: 'Simon', // 報錯,writable為true方可修改
  // writable: true, // 報錯,configurable為true方可修改
  // enumerable: true, // 報錯,configurable為true方可修改
})

訪問器屬性

訪問器屬性 含義 默認值
[[Configurable]] 能否配置( delete 刪除、修改特性、改為訪問器屬性) 在對象上定義:true / Object.defineProperty()定義:false
[[Enumerable]] 能否通過 for-in 循環返回屬性 在對象上定義:true / Object.defineProperty()定義:false
[[Get]] 讀取屬性時調用的函數 undefined
[[Set]] 寫入屬性時調用的函數 undefined
  • 訪問器屬性不包含數據值,用 Object.defineProperty() 定義屬性:方法接收 3 個參數

    • 屬性所在對象、屬性名、描述符對象(和數據屬性用法一樣)
    • 只指定 getter() -> 屬性只讀不寫;只指定 setter() -> 屬性只寫不讀
    • 嚴格模式下,只指定 getter 或 setter 均會報錯
var book = {
  _year: 2017, // 默認屬性
  edition: 1, // 默認屬性
}

Object.defineProperty(book, 'year', {
  // year是訪問器屬性
  get() {
    return this._year
  },
  set(newValue) {
    if (newValue > 2017) {
      this._year = newValue // this._year = 2018
      this.edition += newValue - 2017 // this.edition = 1 + 2018 - 2017
    }
  },
})
book.year = 2018 // 寫入訪問器屬性。調用set()方法
console.log(book) // { _year: 2018, edition: 2 }
  • IE8 或更早,定義訪問器屬性的方法(遺留的方法,可在瀏覽器測試,vscode 會報錯)
book.__defineGetter__('year', function () {
  return this._year
})
book.__defineSetter__('year', function (newValue) {
  if (newValue > 2017) {
    this._year = newValue
    this.edition += newValue - 2017
  }
})
book.year = 2018
console.log(book)

定義多個屬性

  • Object.defineProperties()方法通過多個描述符一次性定義多個屬性,方法接收 2 個參數:

    • 要添加/修改屬性的對象、描述符對象
  • Object.defineProperties()方法定義的所有屬性都是在同一時間定義
var book2 = {}
Object.defineProperties(book2, {
  _year: {
    writable: true,
    value: 2017,
  },
  edition: {
    writable: true,
    value: 1,
  },
  year: {
    get() {
      return this._year
    },
    set(newValue) {
      if (newValue > 2017) {
        this._year = newValue
        this.edition += newValue - 2017
      }
    },
  },
})
book2.year = 2018
console.log(book2.edition) // 2

讀取屬性的特性

  • Object.getOwnPropertyDescriptor()方法取得指定屬性的屬性描述符,接收 2 個參數:

    • 屬性所在對象、要取得其描述符的屬性名
var book3 = {}
Object.defineProperties(book3, {
  _year: {
    value: 2017,
  },
  edition: {
    value: 1,
  },
  year: {
    get: function () {
      return this._year
    },
    set: function (newValue) {
      if (newValue > 2017) {
        this._year = newValue
        this.edition += newValue - 2017
      }
    },
  },
})
var descriptor = Object.getOwnPropertyDescriptor(book3, '_year')
console.log(descriptor) // { value: 2017, writable: false, enumerable: false, configurable: false }
console.log(descriptor.value) // 2017
console.log(descriptor.configurable) // false,Object.getOwnPropertyDescriptor()定義屬性的特性,默認值為false
console.log(typeof descriptor.get) // undefined,數據屬性不含get函數

var descriptor2 = Object.getOwnPropertyDescriptor(book3, 'year')
console.log(descriptor2) // { get: [Function: get], set: [Function: set], enumerable: false, configurable: false }
console.log(descriptor2.value) // undefined,訪問器屬性不含value
console.log(descriptor2.configurable) // false,Object.getOwnPropertyDescriptor()定義屬性的特性,默認值為false
console.log(typeof descriptor2.get) // 'function'
  • Object.getOwnPropertyDescriptors()方法獲取參數對象每個屬性的屬性描述符(在每個屬性上調用Object.getOwnPropertyDescriptor()方法並在一個新對象中返回)
console.log(Object.getOwnPropertyDescriptors(book3))
/* 
  {
    _year: {
      value: 2017,
      writable: false,
      enumerable: false,
      configurable: false
    },
    edition: { value: 1, writable: false, enumerable: false, configurable: false },
    year: {
      get: [Function: get],
      set: [Function: set],
      enumerable: false,
      configurable: false
    }
  }
*/
  • 調用 Object.defineProperty()Object.defineProperties()方法修改或定義屬性時,configurableenumerablewritable的默認值 均為 false(用對象字面量則默認值為 true)
var book4 = {
  year: 2017,
}
var descriptorBook4 = Object.getOwnPropertyDescriptor(book4, 'year')
console.log(
  'book4', // book4
  descriptorBook4.configurable, // true
  descriptorBook4.enumerable, // true
  descriptorBook4.writable, // true
  typeof descriptorBook4.set, // undefined
  typeof descriptorBook4.get // undefined
)
var book5 = {}
Object.defineProperty(book5, 'year', {
  value: 2017,
})
var descriptorBook5 = Object.getOwnPropertyDescriptor(book5, 'year')
console.log(
  'book5', // book5
  descriptorBook5.configurable, // false
  descriptorBook5.enumerable, // false
  descriptorBook5.writable, // false
  typeof descriptorBook4.set, // undefined
  typeof descriptorBook4.get // undefined
)

合併對象

  • ES6 新增Object.assign()方法,方法接收一個目標對象若干源對象作為參數,將每個源對象中可枚舉自有屬性複製到目標對象:

    • 可枚舉:Object.propertyIsEnmerable()返回 true
    • 自有:Object.hasOwePropety()返回 true
    • 使用源對象上的[[Get]]取得屬性值,使用目標對象上的[[Set]]設置屬性值
    • 目標對象會被修改,方法返回修改後的目標對象
let dest, src, result

// 單個源對象
dest = {}
src = { id: 'src' }
result = Object.assign(dest, src) // 返回修改後的目標對象

console.log(result) // { id: 'src' }
console.log(dest === result) // true,修改後的目標對象
console.log(dest === src) // false,目標對象和源對象

// 多個源對象
dest = {}
result = Object.assign(dest, { a: 'foo' }, { b: 'bar' })
console.log(result) // { a: 'foo', b: 'bar' }

// 獲取函數與設置函數
dest = {
  set a(val) {
    console.log(`Invoked dest setter with param ${val}`)
  },
}
src = {
  get a() {
    console.log(`Invoked src better`)
    return 'foo'
  },
}
Object.assign(dest, src)
/* 
  'Invoked src better',調用源對象上的get方法獲得返回值
  'Invoked dest setter with param foo',再調用目標對象的set()方法傳入值
*/
  • 若多個源對象有相同的屬性,則使用最後一個複製的值
dest = { id: 'dest' }
result = Object.assign(dest, { id: 'src1', a: 'foo' }, { id: 'src2', b: 'bar' })
console.log(result) // { id: 'src2', a: 'foo', b: 'bar' }
  • 從源對象訪問器屬性取得的值,會作為靜態值賦給目標對象,不能在兩個對象間轉移獲取函數和設置函數
dest = {
  set id(x) {
    console.log(x)
  },
}
Object.assign(dest, { id: 'first' }, { id: 'second' }, { id: 'third' }) // first second third,依次賦值給目標對象
console.log(dest) // set id: ƒ id(x),設置函數是不變的
  • Object.assign()實際上是對每個源對象執行淺複製
dest = {}
src = { a: {} }
Object.assign(dest, src)
console.log(dest) // { a: {} }
console.log(dest === src) // false
console.log(dest.a === src.a) // true,對源對象淺複製,複製對象的引用
  • Object.assign()也可以只有一個參數,參數為源對象,調用方法會直接返回源對象自己
  • 只有一個參數的Object.assign()更能體現“淺複製”
console.log(Object.assign(src) === src) // true

src = { a: 1 }
dest = Object.assign(src)
console.log(dest) // { a: 1 }
dest.a = 2
console.log(src) // { a: 2 }
  • 如果賦值期間出錯,操作中之並退出報錯,Object.assign()不會回滾,已完成的修改將保留
dest = {}
src = {
  a: 'foo', // 沒遇到錯誤,執行復制
  get b() {
    throw new Error() // 注入錯誤,操作終止
  },
  c: 'bar', // 已遇到錯誤,不會執行
}
try {
  Object.assign(dest, src)
} catch (e) {}
console.log(dest) // { a: 'foo' },遇到錯誤前已經完成的修改被保留

對象標識及相等判定

  • ES6 新增Object.is(),方法接收 2 個參數,用於判斷兩個值是否是相同的值,如果下列任何一項成立,則兩個值相同:

    • 兩個值都是 undefined
    • 兩個值都是 null
    • 兩個值都是 true 或者都是 false
    • 兩個值是由相同個數的字符按照相同的順序組成的字符串
    • 兩個值指向同一個對象
    • 兩個值都是數字並且:

      • 都是正零 +0
      • 都是負零 -0
      • 都是 NaN
      • 都是除零和 NaN 外的其它同一個數字
console.log(undefined === undefined) // true
console.log(null === null) // true
console.log(+0 === 0) // true
console.log(+0 === -0) // true // true
console.log(-0 === 0) // true
console.log(NaN === NaN) // false

// Object.is()
console.log(Object.is(undefined, undefined)) // true
console.log(Object.is(null, null)) // true
console.log(Object.is(+0, 0)) // true
console.log(Object.is(+0, -0)) // false
console.log(Object.is(-0, 0)) // false
console.log(Object.is(NaN, NaN)) // true

增強的對象語法

屬性值簡寫

  • 使用變量名(不再寫冒號)會自動被解釋為同名屬性鍵
var name = 'Matt'
var person = {
  name: name,
}
var person = { name }

可計算屬性

  • 可以在對象字面量中完成動態屬性賦值
var nameKey = 'name'
var ageKey = 'age'
var jobKey = 'job'
var person = {
  [nameKey]: 'Matt',
  [ageKey]: 27,
  [jobKey]: 'Software engineer',
}
console.log(person) // { name: 'Matt', age: 27, job: 'Software engineer' }
  • 可計算屬性本身可以是複雜的表達式,實例化時再求值
var uniqueToken = 0
function getUniqueKey(key) {
  return `${key}_${uniqueToken++}`
}
var person = {
  [getUniqueKey(nameKey)]: 'Matt',
  [getUniqueKey(ageKey)]: 27,
  [getUniqueKey(jobKey)]: 'Software engineer',
}
console.log(person) // { name_0: 'Matt', age_1: 27, job_2: 'Software engineer' }

簡寫方法名

  • 放棄給函數表達式明名,明顯縮短方法聲明
var person = {
  sayName: function (name) {
    console.log(`My name is ${name}`)
  },
}
var person = {
  sayName(name) {
    console.log(`My name is ${name}`)
  },
}
  • 對獲取函數和設置函數也適用
var person = {
  name_: '',
  get name() {
    return this.name_
  },
  set name(name) {
    this.name_ = name
  },
  sayName() {
    console.log(`My name is ${this.name_}`)
  },
}
person.name = 'Matt'
person.sayName() // 'My name is Matt'
  • 可計算屬性鍵相互兼容
var methodKey = 'sayName'
var person = {
  [methodKey](name) {
    console.log(`My name is ${name}`)
  },
}
person.sayName('Matt') // 'My name is Matt'

對象解構

  • 一條語句中使用嵌套數據實現一個或多個賦值操作(使用與對象相匹配的結構實現對象屬性賦值
var person = {
  name: 'Matt',
  age: 27,
}
var { name: personName, age: personAge } = person
console.log(personName, personAge) // 'Matt' 27
  • 變量直接使用屬性名稱,可進一步簡化語法
var { name, age } = person // 變量直接使用屬性名稱
console.log(name, age) // 'Matt' 27
  • 引用的屬性不存在,則該變量的值為 undefined
var { name, job } = person // job不存在
console.log(name, job) // 'Matt' undefined
  • 可在解構賦值時定義默認值
var { name, job = 'Sofrware engineer' } = person // 定義job的默認值
console.log(name, job) // 'Matt' 'Sofrware engineer'
  • 解構內部使用函數ToObject()(不能直接在運行環境訪問)把元數據轉換為對象,因此原始值會被當做對象,null 和 undefined 不能被解構(會報錯)
var { length } = 'foobar' // 'foobar'轉換為String包裝對象
console.log(length) // 6,字符串長度
var { constructor: c } = 4 // 4轉換為Number包裝對象
console.log(c === Number) // true,constructor指向構造函數

var { _ } = null // TypeError
var { _ } = undefined // TypeError
  • 事先聲明的變量賦值時,賦值表達式必須包含在一對括號中
var person = {
  name: 'Matt',
  age: 27,
}
let personName2,
  personAge2 // 事先聲明的變量
;({ name: personName2, age: personAge2 } = person) // 給實現聲明的變量賦值,賦值表達式必須包含在一對括號中
console.log(personName, personAge) // 'Matt' 27

嵌套解構

  • 可以通過解構來複製對象屬性
var person = {
  name: 'Matt',
  age: 27,
  job: {
    title: 'Software engineer',
  },
}
var personCopy = {}
;({ name: personCopy.name, age: personCopy.age, job: personCopy.job } = person) // 解構賦值,複製對象屬性

person.job.title = 'Hacker' // 修改屬性,源對象和賦值對象都受影響
console.log(person) // { name: 'Matt', age: 27, job: { title: 'Hacker' } }
console.log(personCopy) // { name: 'Matt', age: 27, job: { title: 'Hacker' } }
  • 可以使用嵌套結構,以匹配嵌套的屬性
var {
  job: { title },
} = person
console.log(title) // 'Hacker',嵌套解構,title = person.job.title
  • 無論源對象還是目標對象,外層屬性未定義時,不能使用嵌套結構
var person = {
  job: {
    title: 'Software engineer',
  },
}
var personCopy = {}

;({
  foo: { bar: personCopy.bar },
  // personCopy.bar = person.foo.bar,foo在源對象person上是undefined
} = person) // TypeError: Cannot read property 'bar' of undefined
;({
  job: { title: personCopy.job.title },
  // personCopy.job.title = person.job.title,job在目標對象persoCopy上是undefined
} = person) // TypeError: Cannot set property 'title' of undefined

部分解構

  • 如果一個解構表達式涉及多個賦值,開始的賦值成功而後面的賦值出錯,解構賦值只會完成一部分,出錯後面的不再賦值
var person = {
  name: 'Matt',
  age: 27,
}
var personName, personBar, personAge
try {
  ;({
    name: personName, // personName = person.name,賦值成功,
    foo: { bar: personBar }, // personBar = person.foo.bar,foo未在person定義,賦值失敗操作中斷
    age: personAge, // 操作已中斷,賦值失敗
  } = person)
} catch (e) {}
console.log(personName, personBar, personAge) // 'Matt' undefined undefined

參數上下文匹配

  • 函數參數列表也可以進行解構賦值,且不影響 arguments 對象,可以在函數簽名中聲明在函數體內使用局部變量
var person = {
  name: 'Matt',
  age: 27,
}
function printPerson(foo, { name, age }, bar) {
  console.log(arguments)
  console.log(name, age)
}
printPerson('1st', person, '2nd')
// ["1st", {name: "Matt", age: 27}, "2nd"]
// 'Matt' 27
function printPerson2(foo, { name: personName, age: personAge }, bar) {
  console.log(arguments)
  console.log(name, age)
}
printPerson2('1st', person, '2nd')
// ["1st", {name: "Matt", age: 27}, "2nd"]
// 'Matt' 27

總結 & 問點

API 含義 參數 返回值
Object.defineProperty() 修改屬性的默認特性/定義屬性 ① 屬性所在對象 ② 屬性名 ③ 描述符對象
Object.defineProperties() 一次性(同時)定義多個屬性 ① 屬性所在對象 ② 描述符對象
Object.getOwnPropertyDescriptor() 取得指定屬性的屬性描述符 ① 屬性所在對象 ② 要取得描述符的屬性名 屬性描述符對象
Object.getOwnPropertyDescriptors() 獲取參數對象每個屬性的屬性描述符 ① 屬性所在對象 ② 要取得描述符的屬性名 每個屬性描述符對象組成的新對象
Object.assign() 源對象可枚舉且自有屬性複製到目標對象 ① 目標對象(選) ② 源對象 修改後的目標對象
Object.is() 判斷兩個值是否是相同的值 ① 值 1 ② 值 2 true / false
  • JS 的對象是什麼?其屬性可以包含什麼?創建自定義對象有哪些方法?
  • 對象的數據屬性有哪些特性?其含義和默認值分別是什麼?
  • 如何修改對象的數據屬性?對同一個屬性多次修改有哪些限制?
  • 對象的訪問器屬性有哪些特性?其含義和默認值分別是什麼?
  • 如何定義對象的訪問器屬性?同時定義多個屬性呢?
  • 如何獲取指定屬性的屬性描述符?全部屬性的屬性描述符呢?
  • Object.assign()的用法和實質含義是什麼?其能否轉移 get()函數和 set()函數?
  • 請用代碼證明 Object.assign()是“淺複製”以及“不回滾”
  • ES6 有哪些增強對象的語法?其用法分別是什麼?
  • 什麼是解構賦值?原始值可以當作解構源對象麼?為什麼?
  • 請用代碼依次舉例嵌套解構、部分解構和參數上下文匹配
user avatar guizimo 頭像 frontoldman 頭像 liyl1993 頭像 huanjinliu 頭像 qianduanlangzi_5881b7a7d77f0 頭像 lidalei 頭像 wukongnotnull 頭像 gfeteam 頭像
8 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.