《javascript高级程序设计》学习笔记 | 8.2.创建对象
发布于 4 年前 作者 simon9124 1908 次浏览 来自 分享

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

创建对象

  • 创建单个对象: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]]属性指向哪里?为什么?
  • 原生引用类型的方法是如何创建的?为什么不推荐修改原生引用类型的原型?
  • 原型模式的“共享”本性,在修改包含引用类型的属性时,会产生怎样的问题?
回到顶部