《javascript高級程序設計》學習筆記 | 8.2.建立對象

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

建立對象

  • 建立單個對象:Object 構造函數 和 對象字面量
  • 缺點:使用一個接口建立不少對象,產生大量重複代碼

相關代碼 →javascript

工廠模式

  • 抽象建立特定對象的過程,按照特定接口建立對象
function createPerson(name, age, job) {
  var o = new Object()
  o.name = name
  o.age = age
  o.job = job
  o.sayName = function () {
    console.log(this.name)
  }
  return o
}
var person1 = createPerson('Nicholas', 29, 'Engineer')
var person2 = createPerson('Greg', 27, 'Doctor')
console.log(person1)
console.log(person2)
  • 工廠模式解決了建立多個類似對象的問題,但沒有解決對象識別問題(怎樣知道一個對象的類型)

構造函數模式

  • 除了 Object 和 Array 等原生構造函數,還能夠建立自定義的構造函數
  • 構造函數模式 vs 工廠模式前端

    • 不顯式的建立對象
    • 直接將屬性和方法賦給 this 對象
    • 沒有 return
function Person(name, age, job) {
  this.name = name
  this.age = age
  this.job = job
  this.sayName = function () {
    console.log(this.name)
  }
}
var person1 = new Person('Nicholas', 29, 'Software Engineer')
var person2 = new Person('Greg', 27, 'Doctor')
  • 構造函數用大寫字母開頭,建立實例時用 new 操做符
  • 構造函數 new 一個對象後:java

    • 建立了一個新對象(實例)
    • 新對象內部的[[Prototype]]特性被賦值爲構造函數的prototype屬性(共同指向原型)
    • 將構造函數的做用域(即 this)賦給新對象
    • 執行構造函數中的代碼(即:爲這個對象添加新屬性)
    • 返回新對象或非空對象
  • 建立的對象(實例)既是 Object 的實例,又是構造函數的實例,其 constructor 屬性指向構造函數
  • 能夠確保自定義構造函數的實例被標識爲特定的類型,是構造函數模式賽過工廠模式的地方
console.log(person1.constructor === Person) // true,constructor 屬性指向構造函數
console.log(person2.constructor === Person) // true,constructor 屬性指向構造函數
console.log(person1 instanceof Object) // true,person1是Object的實例
console.log(person1 instanceof Person) // true,person1是Person的實例
console.log(person2 instanceof Object) // true,person2是Object的實例
console.log(person2 instanceof Person) // true,person2是Person的實例
  • 構造函數也可使用函數表達式表示,實例化不傳參數時,構造函數後面的括號可加可不加
var PersonExpression = function () {
  // 構造函數的函數表達式
  this.name = 'Jake'
  this.sayName = function () {
    console.log(this.name)
  }
}
var personNoBrackets = new PersonExpression() // 實例化不傳參數,可不加括號

構造函數也是函數

  • 構造函數與普通函數惟一的區別調用方式不一樣:使用new操做符調用的就是構造函數,不使用的是普通函數
  • 默認狀況下,調用函數時的 this 指向 Global 對象(瀏覽器中指向 window 對象)
var person3 = new Person('Nicholas', 29, 'Software Engineer') // 用構造函數建立對象
person3.sayName() // 'Nicholas'
Person('Greg', 27, 'Doctor') // 'Greg',不使用new操做符,直接調用
global.sayName() // 直接調用函數,this指向Global對象(瀏覽器中指向window對象)

var o = new Object() // 新對象o
var p = new Object() // 新對象p
Person.call(o, 'Kristen', 25, 'Nurse') // 將對象o指定爲Person()內部的this值,call()分別傳入每一個參數
Person.apply(p, ['Kristen', 25, 'Nurse']) // 將對象o指定爲Person()內部的this值,apply()傳入參數數組
o.sayName() // 'Kristen'
p.sayName() // 'Kristen'

構造函數的問題

  • 定義的方法會在每一個實例上都建立一遍,每定義一個函數,就實例化一個對象,建立 2 個完成一樣任務的 Function 實例沒有必要
function Person2(name, age, job) {
  this.name = name
  this.age = age
  this.job = job
  this.sayName = new Function(console.log(this.name)) // 與聲明函數邏輯等價,每建立一個對象就要建立一個Function實例
}
console.log(person1.sayName === person2.sayName) // false,新對象的2個方法的做用域鏈和標識符解析不一樣
  • 將對象的方法移到構造函數外部,避免屢次建立 Function 實例
function Person3(name, age, job) {
  this.name = name
  this.age = age
  this.job = job
  this.sayName = sayName
}
function sayName() {
  console.log(this.name) // 將sayName設置成全局函數
}
var person4 = new Person('Nicholas', 29, 'Software Engineer')
  • 構造函數仍未解決的問題:① 建立的全局函數實際上只須要被某個對象中調用;② 若對象有多個方法,需建立不少全局方法

原型模式

  • 每一個函數都有 prototype 屬性,該屬性是一個指針,指向函數(經過調用構造函數而建立的那個對象實例的)原型對象
  • 使用原型對象的好處是,其全部對象實例共享其所包含的屬性和方法
function PersonPrototype() {}
PersonPrototype.prototype.name = 'Nicholas' // 爲PersonPrototype的原型對象添加屬性
PersonPrototype.prototype.age = 29 // 爲PersonPrototype的原型對象添加屬性
PersonPrototype.prototype.job = 'Software Engineer' // 爲PersonPrototype的原型對象添加屬性
PersonPrototype.prototype.sayName = function () {
  // 爲PersonPrototype的原型對象添加方法
  console.log(this.name)
}

var person5 = new PersonPrototype()
var person6 = new PersonPrototype()

person5.sayName() // 'Nicholas'
person6.sayName() // 'Nicholas'
console.log(person5.sayName === person6.sayName) // true,原型對象上建立的屬性和方法,由全部實例共享

理解原型對象

  • 只要建立一個函數,就會爲函數建立prototype屬性指向原型對象,(默認狀況下)原型對象自動得到constructor屬性,指回與之關聯的構造函數
console.log(PersonPrototype.prototype.constructor) // PersonPrototype構造函數,原型對象的constructor屬性指向與之關聯的構造函數
console.log(PersonPrototype === PersonPrototype.prototype.constructor) // true,都指向構造函數
  • 實例內部包含[[Prototype]]指針,指向實例的構造函數的原型對象,但沒有標準的方式訪問[[Prototype]]
  • 在瀏覽器中,可用 __proto__ 屬性實現[[Prototype]]的功能
console.log(person5.__proto__) // 原型對象,PersonPrototype {name: 'Nicholas',age: 29,job: 'Software Engineer',sayName: [Function] }
console.log(person5.__proto__ === PersonPrototype.prototype) // true,都指向原型對象
console.log(person5.__proto__.constructor) // Function: PersonPrototype構造函數
console.log(person5.__proto__ === person6.__proto__) // true,共享同一個原型對象
  • instanceof檢查實例的原型鏈中,是否包含指定構造函數的原型
console.log(person5 instanceof PersonPrototype) // true,person5是PersonPrototype的實例
console.log(person5 instanceof Object) // true,person5是Object的實例
console.log(PersonPrototype.prototype instanceof Object) // true,全部實例對象和原型對象都是Object的實例
  • 原型對象的 isPrototypeOf()方法,檢測實例是否有指向原型對象的指針
console.log(PersonPrototype.prototype.isPrototypeOf(person5)) // true,person5包含指向PersonPrototype的原型對象的指針
console.log(PersonPrototype.prototype.isPrototypeOf(person1)) // false,person1不包含指向PersonPrototype的原型對象的指針
  • Object.getPrototypeOf()方法(參數通常爲實例),返回參數的[[Prototype]]的值(通常爲原型對象)
console.log(Object.getPrototypeOf(person5)) // 原型對象
console.log(Object.getPrototypeOf(person5) === person5.__proto__) // true,都指向原型對象
console.log(Object.getPrototypeOf(person5) === PersonPrototype.prototype) // true,都指向原型對象
console.log(Object.getPrototypeOf(person5).name) // 'Nicholas'
console.log(Object.getPrototypeOf(person5).constructor) // Function: PersonPrototype構造函數
  • Object.setPrototypeOf()方法,向實例(參數一)的[[Prototype]]寫入一個新值(參數二),從而重寫一個對象的原型繼承關係
var biped = {
  numLegs: 2,
}
var person = {
  name: 'Matt',
}
Object.setPrototypeOf(person, biped)
console.log(person.name) // 'Matt'
console.log(person.numLegs) // 2
console.log(person.__proto__) // { numLegs: 2 },person的[[Prototype]]指針指向biped
  • 爲避免Object.setPrototypeOf()可能嚴重影響代碼性能,可以使用Object.create()建立一個新對象,同時爲其指定原型(參數)
var biped2 = {
  numLegs: 3,
}
var person = Object.create(biped2)
console.log(person.numLegs) // 3
console.log(person.__proto__) // { numLegs: 3 },person的[[Prototype]]指針指向biped2

原型層級

  • 代碼讀取對象屬性的搜索過程:git

    • 1.搜索對象實例自己 -> 有屬性 → 返回屬性值 -> 結束
    • 2.對象實例自己無屬性 -> 搜索原型對象 → 有/無屬性 → 返回屬性值/undefined → 結束
  • 能夠經過實例訪問原型中屬性的值(如 constructor 屬性),但沒法經過實例重寫原型中屬性的值
  • 若是添加的實例屬性與原型的屬性同名,則實例屬性屏蔽原型中的屬性
var person7 = new PersonPrototype()
person7.name = 'Greg'
console.log(person7.name) // 'Greg',來自實例
console.log(person5.name) // 'Nicholas',來自原型
  • 刪除同名的實例屬性,可恢復被屏蔽的原型的屬性
delete person7.name
console.log(person7.name) // 'Nicholas',來自原型
  • 使用 hasOwnProperty()方法,檢測屬性是否存在於實例中(存在返回 true),參數爲要檢測的屬性
var person8 = new PersonPrototype()
var person9 = new PersonPrototype()
console.log(person8.hasOwnProperty('name')) // false,name不存在在person8的實例中
person8.name = 'Simon'
console.log(person8.name) // 'Simon',來自實例
console.log(person8.hasOwnProperty('name')) // true,name存在在person8的實例中
console.log(person9.name) // 'Nicholas',來自原型
console.log(person9.hasOwnProperty('name')) // false,name不存在在person8的實例中
delete person8.name
console.log(person8.name) // 'Nicholas',來自原型
console.log(person8.hasOwnProperty('name')) // false,person8實例的name屬性已被刪除
  • 可在原型對象上調用 Object.getOwnPropertyDescriptor(),獲取原型屬性的描述符
console.log(Object.getOwnPropertyDescriptor(person8, 'name')) // undefined,person8實例上沒有name屬性
console.log(Object.getOwnPropertyDescriptor(person8.__proto__, 'name')) // {value: 'Nicholas',writable: true,enumerable: true,configurable: true},原型對象的name屬性描述符

原型和 in 操做符

  • 單獨使用in操做符:對象可以訪問指定屬性則返回 true,不管屬性在實例中仍是原型中
function PersonIn() {}
PersonIn.prototype.name = 'Nicholas'
PersonIn.prototype.age = 29
PersonIn.prototype.job = 'Software Engineer'
PersonIn.prototype.sayName = function () {
  console.log(this.name)
}
var person9 = new PersonIn()
var person10 = new PersonIn()

console.log(person9.hasOwnProperty('name')) // false,實例person9中不含name屬性
console.log('name' in person9) // true,經過person9能夠訪問到name屬性

person9.name = 'Greg'
console.log(person9.name); // 'Greg',來自實例
console.log(person9.hasOwnProperty('name')) // true,實例person9中包含name屬性
console.log('name' in person9) // true,經過person9能夠訪問到name屬性
console.log(person10.name); // 'Nicholas',來自原型
console.log(person10.hasOwnProperty('name')) // false,實例person10中不含name屬性
console.log('name' in person10) // true,經過person10能夠訪問到name屬性

delete person9 'name'
console.log(person9.name); // 'Nicholas',來自原型
console.log(person9.hasOwnProperty('name')) // false,實例person9中不含name屬性
console.log('name' in person9) // true,經過person9能夠訪問到name屬性
  • 同時使用hasOwnProperty()in操做符,判斷屬性存在於實例仍是原型
function hasPrototypeProperty(object, name) {
  return !object.hasOwnProperty(name) && name in object // 不存在於實例 && 能訪問到 → 存在於原型
}
var person11 = new PersonIn()
console.log(hasPrototypeProperty(person11, 'name')) // true,!false && true
person11.name = 'Greg'
console.log(hasPrototypeProperty(person11, 'name')) // false,!true && true
  • for-in 循環:返回對象全部可以訪問的可枚舉的屬性(不管來自實例仍是原型),屏蔽不可枚舉([[Enumerable]]爲 false)的屬性(如:原型的 constructor、構造函數的 prototype)
for (var attr in person11) {
  console.log(`${attr}:${person11[attr]}`)
  /*  
    name:Greg
    age:29
    job:Software Engineer
    sayName:function () {
      console.log(this.name)
    } 
  */
}
  • Object.keys()方法返回對象(自身可枚舉的屬性的數組,參數爲該對象
var keys = Object.keys(PersonIn.prototype) // 原型對象的全部可枚舉屬性
console.log(keys) // [ 'name', 'age', 'job', 'sayName' ]
var person12 = new PersonIn()
person12.name = 'Bob'
person12.age = 31
var p12keys = Object.keys(person12) // person12的全部可枚舉屬性
console.log(p12keys) // [ 'name', 'age' ]
  • Object.getOwnPropertyNames()返回對象(自身全部屬性(不管是否可枚舉)的數組,參數爲該對象
var keys = Object.getOwnPropertyNames(PersonIn.prototype) // 原型對象的全部屬性,包含不可枚舉
console.log(keys) // [ 'constructor', 'name', 'age', 'job', 'sayName' ],原型對象都包含constructor屬性,指向構造函數
var p12keys = Object.getOwnPropertyNames(person12) // person12的全部屬性,包含不可枚舉
console.log(p12keys) // [ 'name', 'age' ]
console.log(Object.getOwnPropertyNames(PersonIn)) // [ 'length', 'name', 'arguments', 'caller', 'prototype' ]
  • ES6 新增Object.getOwnPropertySymbols()方法,返回對象(自身全部符號鍵屬性(不管是否可枚舉)的數組,參數爲該對象
var k1 = Symbol('k1')
var k2 = Symbol('k2')
var o = {
  [k1]: 'k1', // 符號做爲屬性,需使用「計算屬性」語法,即[屬性名]
  [k2]: 'k2',
}
console.log(Object.getOwnPropertySymbols(o)) // [ Symbol(k1), Symbol(k2) ]

屬性枚舉順序

  • for-in循環和Object.keys()屬性枚舉順序不肯定的,取決於瀏覽器的 JS 引擎
  • Object.getOwnPropertyNames()Object.getOwnPropertySymbols()Object.assign()屬性枚舉順序肯定的:github

    • 升序枚舉數值鍵
    • 插入順序枚舉字符串和符號鍵
var k1 = Symbol('k1')
var k2 = Symbol('k2')
var o = {
  1: 1,
  first: 'first',
  [k2]: 'sym2',
  third: 'third',
  0: 0,
}
o[k1] = 'sym1'
o[3] = 3
o.second = 'second'
o[2] = 2
console.log(Object.getOwnPropertyNames(o)) // [ '0', '1', '2', '3', 'first', 'third', 'second' ]
console.log(Object.getOwnPropertySymbols(o)) // [ Symbol(k2), Symbol(k1) ]

對象迭代

  • ES7 新增Object.values()Object.entries()方法,接收參數對象,分別返回對象值對象鍵/值對數組
var o = {
  foo: 'bar',
  baz: 1,
  qux: {},
}
console.log(Object.values(o)) // [ 'bar', 1, {} ],迭代值
console.log(Object.entries(o)) // [ [ 'foo', 'bar' ], [ 'baz', 1 ], [ 'qux', {} ] ],迭代鍵值對
  • 非字符串屬性轉換爲字符串,方法執行對象的淺複製
var o = {
  qux: {},
}
console.log(Object.values(o)) // [ {} ]
console.log(Object.entries(o)) // [ [ 'qux', {} ] ]
console.log(Object.values(o)[0] === o.qux) // true,淺複製,複製對象的引用
console.log(Object.entries(o)[0][1] === o.qux) // true,淺複製,複製對象的引用
  • 符號屬性會被忽略
var sym = Symbol()
var o = {
  [sym]: 'foo', // 符號屬性
}
console.log(Object.values(o)) // [],符號屬性被忽略
console.log(Object.entries(o)) // [],符號屬性被忽略

其餘原型語法

  • 包含全部屬性和方法新對象字面量來重寫整個原型對象
function PersonLiteral() {}
PersonLiteral.prototype = {
  name: 'Nicholas',
  age: 29,
  job: 'Software Engineer',
  sayName: function () {
    console.log(this.name)
  },
}
  • 將構造函數的prototype屬性設置爲一個以對象字面量形式建立新對象,其constructor屬性再也不指向原構造函數,而是新對象的constructor屬性,即Object 構造函數
var friend = new PersonLiteral()
console.log(friend instanceof Object) // true,friend是Object的實例
console.log(friend instanceof PersonLiteral) // true,friend是PersonLiteral的實例
console.log(friend.constructor === PersonLiteral) // false,constructor屬性變成了新對象——即對象字面量的constructor
console.log(friend.constructor === Object) // true,新對象的constructor指向Object構造函數
  • 能夠在對象字面量裏設置constructor屬性,讓其指向原構造函數
  • 這樣設置constructor屬性屬於直接在對象上定義的屬性,會致使constructor屬性的[[Enumerable]]爲 true,能夠被枚舉出來
function PersonLiteral2() {}
PersonLiteral2.prototype = {
  constructor: PersonLiteral2, // 直接在對象上定義constructor,指向原構造函數
  name: 'Nicholas',
  age: 29,
  job: 'Software Engineer',
  sayName: function () {
    console.log(this.name)
  },
}
var friend2 = new PersonLiteral2()
console.log(friend2.constructor === PersonLiteral2) // true,constructor再次指向原構造函數
console.log(friend2.constructor === Object) // false
console.log(Object.keys(PersonLiteral2.prototype)) // [ 'constructor', 'name', 'age', 'job', 'sayName' ],由於constructor是「直接在對象上定義的屬性」,可被枚舉出來
  • 不在對象字面量內設置constructor屬性,而用Object.defineProperty()修改對象字面量中constructor屬性的特性,以兼容 JavaScript 引擎
Object.defineProperty(PersonLiteral2.prototype, 'constructor', {
  enumerable: false,
  value: PersonLiteral2,
})
console.log(Object.keys(PersonLiteral2.prototype)) // [ 'name', 'age', 'job', 'sayName' ],constructor的enumerable已被設置爲false

原型的動態性

  • 原型對象所作的任何修改當即從實例上反映出來,即便先建立實例後修改原型
function Person4() {}
var friend3 = new Person4() // 先建立實例
Person4.prototype.sayHi = function () {
  // 後修改原型對象
  console.log('Hi')
}
friend3.sayHi() // 'Hi',實例受影響,實例指向原型
  • 重寫整個原型,會切斷構造函數與最初原型之間的聯繫,(重寫原型前建立的)實例的[[Prototype]]指針指向最初的原型(重寫原型後建立的實例指向新原型)
Person4.prototype = {
  // 重寫原型
  constructor: Person4,
  name: 'Nicholas',
  age: 29,
  job: 'Software Engineer',
  sayName: function () {
    console.log(this.name)
  },
}
console.log(friend3.__proto__) // Person4 { sayHi: [Function] },friend3在重寫原型前建立,[[Prototype]]指向最初的原型對象
console.log(friend3.__proto__ === Person4.prototype) // false,重寫整個原型切斷了構造函數與最初原型之間的聯繫
friend3.sayName() // error:friend3.sayName is not a function

原生對象原型

  • 全部原生的引用類型(Array、Object、String...)都是用原型模式建立的,在其構造函數的原型上定義了方法
console.log(Array.prototype) // 在瀏覽器中查看Array的原型對象,包含sort()等方法
console.log(String.prototype) // 在瀏覽器中查看Array的原型對象,包含substring()等方法
  • 能夠像修改自定義對象的原型同樣,修改原生對象的原型,添加或刪除方法
  • 不推薦修改原生對象的原型,可能會引發衝突或重寫原生方法
String.prototype.startsWith = function (text) {
  // 給String的原型對象添加startsWith方法
  return this.indexOf(text) === 0
}
var msg = 'Hello World'
console.log(msg.startsWith('Hello')) // true
console.log(msg.startsWith('World')) // false
delete String.prototype.startsWith
console.log(msg.startsWith('Hello')) // error

原型的問題

  • 原型模式最大的問題是由其共享的本性致使的,尤爲對於包含引用類型的屬性,對實例的數組、對象等引用類型的屬性進行增刪改而非從新定義時,會對原型的引用類型屬性形成影響
function PersonProblem() {}
PersonProblem.prototype = {
  constructor: PersonProblem,
  name: 'Nicholas',
  age: 29,
  job: 'Software Engineer',
  friends: ['Shelby', 'Court'],
  sayName: function () {
    console.log(this.name)
  },
}
var person13 = new PersonProblem()
var person14 = new PersonProblem()
person13.name = 'Greg' // 從新定義,在實例中屏蔽原型的屬性
person13.friends.push('Van') // 非從新定義,而是向原型的數組中添加一個字符串
console.log(person13.name) // 'Greg',從實例得到
console.log(person14.name) // 'Nicholas',從原型中得到
console.log(person13.friends) // [ 'Shelby', 'Court', 'Van' ],從原型中得到
console.log(person14.friends) // [ 'Shelby', 'Court', 'Van' ],從原型中得到
console.log(person13.friends === person14.friends) // true
var person15 = new PersonProblem()
person15.friends = [] // 從新定義,在實例中屏蔽原型的屬性
console.log(person15.friends) // [],從實例得到
console.log(person13.friends) // [ 'Shelby', 'Court', 'Van' ],從原型中得到
console.log(person14.friends) // [ 'Shelby', 'Court', 'Van' ],從原型中得到

總結 & 問點

建立對象 過程 缺點
Object 構造函數 1.建立 Objecty 實例 2.添加屬性和方法 同一接口建立多個對象,大量重複代碼
對象字面量 直接建立包含屬性和方法的對象 同一接口建立多個對象,大量重複代碼
工廠模式 1.用函數封裝建立 Object 實例的過程(添加屬性和方法、返回該實例對象) 2.調用該函數 沒有解決對象識別問題,即怎樣知道一個對象的類型
構造函數模式 1.構造函數封裝(不顯示的建立對象、屬性和方法賦給 this 對象、無 return) 2.new 調用構造函數 每一個實例從新建立方法,機制相同的 Function 對象被屢次實例化
原型模式 1.構造函數封裝(空的,無屬性和方法) 2.原型對象上添加屬性和方法 3.new 調用構造函數 對實例來自原型的引用類型屬性修改而非從新定義時,會對原型形成影響
對象 屬性 默認指向 用法
任何函數 prototype 原型對象 Person.prototype → 構造函數的原型對象
實例、原型 constructor 構造函數 person1.constructor === Person.prototype.constructor === Person
實例 [[Prototype]] 原型對象 person1.__proto__ === Person.prototype(沒有標準方式訪問[[Prototype]],但可用 __proto__
操做符 含義 用法
new 建立構造函數的實例(四個步驟 var person = new Person()
delete 刪除實例屬性 delete person.name
in 可否經過對象訪問到屬性(不管屬性在實例仍是原型中) console.log('name' in person)
for-in 返回全部能經過對象訪問到的、可枚舉的屬性(不管屬性在實例仍是原型中) for(var attr in person){console.log(attr)}
方法 含義 參數 返回值
isPrototypeOf() 實例是否有指向原型對象的指針 實例 true/false
Object.getPrototypeOf() 獲取實例[[Prototype]]的值 實例 原型對象
Object.setPrototypeOf() 向實例的[[Prototype]]寫入新值(指定原型) ① 實例 ② 指定原型
Object.create() 建立一個新對象,同時爲其指定原型 指定原型
hasOwnProperty() 屬性是否存在於實例中(非原型中) 屬性 true/false
Object.keys() 獲取對象(自身)全部可枚舉的屬性 對象 屬性的字符串數組
Object.getOwnPropertyNames() 獲取對象(自身)全部屬性(不管是否可枚舉) 對象 屬性的字符串數組(原型對象包含 constructor 屬性)
Object.getOwnPropertySymbols() 獲取對象(自身)全部符號鍵屬性(不管是否可枚舉) 對象 屬性的符號鍵數組(原型對象包含 constructor 屬性)
方法/操做符 枚舉順序
for-in 枚舉順序不肯定,取決於瀏覽器的 JS 引擎
Object.keys() 枚舉順序不肯定,取決於瀏覽器的 JS 引擎
Object.getOwnPropertyNames() 枚舉順序肯定:先以升序枚舉數值鍵,後以插入順序枚舉字符串和符號鍵
Object.getOwnPropertySymbols() 枚舉順序肯定:先以升序枚舉數值鍵,後以插入順序枚舉字符串和符號鍵
  • 建立單個對象有哪些方法?這些方法有什麼缺點?
  • 工廠模式作出了怎樣的優化?該模式有什麼缺點?
  • 相比工廠模式,構造函數模式有哪些區別和優點?其在 new 的過程當中都發生了什麼?
  • 構造函數建立出的對象,其 construtor 屬性指向哪裏?這樣的對象是哪些構造函數的實例?
  • 相比普通函數,構造函數有什麼相同點和區別?
  • 構造函數模式有什麼缺點?用全局函數代替構造函數內部對象的方法,仍有什麼缺點?
  • 函數的 prototype 屬性是什麼?使用原型對象的好處是什麼?如何理解原型對象的 constructor 屬性?
  • 構造函數、實例、原型對象之間,分別能夠用什麼方式相互獲取?用什麼方法檢測實例是否含有指向原型對象的指針?
  • Object.getPrototypeOf()、Object.setPrototypeOf()、Object.create()分別的含義和用法是什麼?
  • 代碼讀取對象屬性時,經歷了怎樣的搜索過程?是否能夠經過實例訪問和修改原型中的屬性值?
  • 在實例中添加與原型的同名屬性會怎樣?再刪除這個實例中新增的屬性呢?
  • 單獨使用 in 操做符的含義是什麼?其和 hasOwnProperty()方法的區別是什麼?
  • 請寫一段代碼,判斷某個屬性存在於實例仍是原型
  • for-in 的用法是什麼?其返回哪些屬性屏蔽哪些屬性?
  • Object.keys()、Object.getOwnPropertyNames()、Object.getOwnPropertySymbols()、Object.values()、Object.entries()的用法分別是什麼?
  • for-in、Object.keys()、Object.getOwnPropertyNames()、Object.getOwnPropertySymbols()在屬性枚舉時的順序有什麼區別?
  • 用一個對象字面量的新對象重寫整個原型對象時,原型對象的 constructor 指向發生了怎樣的改變?
  • 寫一段代碼,用對象字面量重寫構造函數的原型對象,且原型對象的 constructor 仍指向原構造函數,並保留 construtor 屬性「不可被枚舉」的特性
  • 建立實例後再修改原型的屬性,實例會受到影響麼?爲何?
  • 重寫整個原型對象後,構造函數的 prototype 指向哪裏?重寫前建立的實例的[[Prototype]]屬性指向哪裏?爲何?
  • 原生引用類型的方法是如何建立的?爲何不推薦修改原生引用類型的原型?
  • 原型模式的「共享」本性,在修改包含引用類型的屬性時,會產生怎樣的問題?
相關文章
相關標籤/搜索