《javascript高级程序设计》学习笔记 | 8.2.创建对象

Posted 小讴

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《javascript高级程序设计》学习笔记 | 8.2.创建对象相关的知识,希望对你有一定的参考价值。

关注前端小讴,阅读更多原创技术文章

创建对象

  • 创建单个对象:Object 构造函数 和 对象字面量
  • 缺点:使用一个接口创建很多对象,产生大量重复代码

相关代码 →

工厂模式

  • 抽象创建特定对象的过程,按照特定接口创建对象
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 一个对象后:

    • 创建了一个新对象(实例)
    • 新对象内部的[[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

原型层级

  • 代码读取对象属性的搜索过程:

    • 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()在属性枚举顺序是确定的:

    • 先以升序枚举数值键
    • 后以插入顺序枚举字符串和符号键
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]]属性指向哪里?为什么?
  • 原生引用类型的方法是如何创建的?为什么不推荐修改原生引用类型的原型?
  • 原型模式的“共享”本性,在修改包含引用类型的属性时,会产生怎样的问题?

以上是关于《javascript高级程序设计》学习笔记 | 8.2.创建对象的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript 高级程序设计学习笔记

javascript 高级程序设计 学习笔记01章 javascript的认知

1 《JavaScript高级程序设计》学习笔记

《JavaScript高级程序设计(第四版)》学习笔记第3章

《JavaScript高级程序设计(第四版)》学习笔记第3章

JavaScript高级程序设计(第三版)学习笔记222425章