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

创建对象

  • 创建单个对象: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 等原生构造函数,还可以创建自定义的构造函数
function Person(name, age, job) {
  this.name = name
  this.age = age
  this.job = job
  this.sayName = function () {
    console.log(this.name)
  }
}
var person1 = Person('Nicholas', 29, 'Software Engineer')
var person2 = Person('Greg', 27, 'Doctor')
  • 构造函数模式 vs 工厂模式:① 不显式的创建对象;② 直接将属性和方法赋给 this 对象;③ 没有 return
  • 构造函数 new 一个对象后:① 创建了一个新对象;② 将构造函数的作用域(即 this)赋给新对象;③ 执行构造函数中的代码(即:为这个对象添加新属性);④ 返回新对象
  • 构造函数用大写字母开头,创建实例时用 new 操作符
  • 创建的对象的 constructor 属性指向构造函数
  • 创建的对象既是 Object 的实例,又是构造函数的实例
console.log(person1.constructor === Person) // true,constructor 属性指向构造函数
console.log(person2.constructor === Person) // true,constructor 属性指向构造函数
console.log(person1 instanceof Object) // true,是 Object 的实例
console.log(person1 instanceof Person) // true,也是 Person 的实例
console.log(person2 instanceof Object) // true,是 Object 的实例
console.log(person2 instanceof Person) // true,也是 Person 的实例
  • 可以将自定义构造函数的实例标识为一种特定的类型,这是构造函数模式胜过工厂模式的地方
  • 以该方法定义的构造函数是定义在 Global 对象中的,在浏览器中则是 window 对象
// 构造函数vs普通函数
var person3 = new Person('Nicholas', 29, 'Software Engineer') // 用构造函数创建对象
person3.sayName() // 'Nicholas'
Person('Greg', 27, 'Doctor') // 不使用new操作符,直接调用
global.sayName() // 直接调用函数时,this指向Global对象(浏览器中指向window对象)
var o = new Object() // 新对象o
var p = new Object() // 新对象p
Person.call(o, 'Kristen', 25, 'Nurse') // 扩充作用域,在对象o中调用Person()函数,call()分别传入每个参数
Person.apply(p, ['Kristen', 25, 'Nurse']) // 扩充作用域,在对象p中调用Person()函数,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(构造函数)属性,指向 prototype 属性所在函数的指针,即构造函数
console.log(PersonPrototype.prototype.constructor) // Function: PersonPrototype构造函数
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构造函数
  • 原型对象的 isPrototypeOf()方法,检测实例中否有指向原型对象的指针
console.log(PersonPrototype.prototype.isPrototypeOf(person5)) // true,person5包含指向PersonPrototype的原型对象的指针
console.log(PersonPrototype.prototype.isPrototypeOf(person1)) // false,person1不包含指向PersonPrototype的原型对象的指针
  • ES5 追加 Object.getPrototypeOf()方法,参数为实例,返回实例的构造函数的原型对象
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构造函数
  • 代码读取对象属性的搜索过程:

    • 1.搜索对象实例本身 -> 有属性 → 返回属性值 -> 结束
    • 2.对象实例本身无属性 -> 搜索原型对象 → 有/无属性 → 返回属性值/undefined → 结束
  • 可以通过实例访问原型中属性的值,但无法通过实例重写原型中属性的值
  • 如果添加的实例属性与原型的属性同名,则实例属性屏蔽原型中的属性
  • 删除同名的实例属性,可恢复被屏蔽的原型的属性
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('属性') 检测属性存在于实例 or 原型,存在于实例返回 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,判断属性存在于 对象 or 原型
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 循环使用 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' ]

更简单的原型语法

  • 包含所有属性和方法新对象字面量来重写整个原型对象
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 属性属于“直接在对象上定义的属性”,会导致 PersonLiteral2.prototype 的 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
var keys = Object.keys(PersonLiteral2.prototype)
console.log(keys) // [ 'constructor', 'name', 'age', 'job', 'sayName' ],因为constructor是“直接在对象上定义的属性”
  • 用 Object.defineProperty()修改对象字面量中 constructor 属性的特性,以兼容 ES5 的 javascript 引擎
Object.defineProperty(PersonLiteral2.prototype, 'constructor', {
  enumerable: false,
  value: PersonLiteral2,
})
var keys = Object.keys(PersonLiteral2.prototype)
console.log(keys) // [ 'name', 'age', 'job', 'sayName' ],constructor的enumerable已被设置为false

原型的动态性

  • 对原型对象所做的任何修改都立即从实例上反映出来,即使先创建实例后修改原型
function Person4() {}
var friend3 = new Person4()
Person4.prototype.sayHi = function () {
  console.log('Hi')
}
friend3.sayHi() // 'Hi',先在friend3实例中搜索sayHi属性,没有找到则继续找原型对象
  • 重写整个原型对象,会切断构造函数与最初原型之间的联系,而实例的[[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] },最初的原型对象
console.log(friend3.__proto__ === Person4.prototype) // false,实例的__proto__指向最初的原型对象,重写整个原型切断了构造函数与最初原型之间的联系
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' ],从原型中获得

组合使用构造函数模式和原型模式

  • 构造函数模式用于定义实例属性,原型模式用域定义方法共享的属性
  • 每个实例都有自己的一份实例属性的副本,同时共享着对方法的引用,最大限度节省内存
  • 该模式是目前 ECMAScript 中使用最广泛、认同度最高的创建自定义类型的方法
function PersonMix(name, age, job) {
  // 在构造函数定义实例属性
  this.name = name
  this.age = age
  this.job = job
  this.friends = ['Shelby', 'Court']
}
PersonMix.prototype = {
  // 在原型定义方法和共享的属性
  constructor: PersonMix, // 直接在对象上定义constructor属性,指向构造函数,[[Enumerable]]为true
  sayName: function () {
    console.log(this.name)
  },
}
var person16 = new PersonMix('Nicholas', 29, 'Software Engineer')
var person17 = new PersonMix('Greg', 27, 'Doctor')
person16.friends.push('Van') // 仅向person16实例本身的friends数组push数据
console.log(person16.friends) // [ 'Shelby', 'Court', 'Van' ]
console.log(person17.friends) // [ 'Shelby', 'Court' ]
console.log(person16.friends === person17.friends) // false,person16实例的friends数组push了数据
console.log(person16.sayName === person17.sayName) // true,共享方法sayName

动态原型模式

  • 将所有信息(属性、方法)都封装在构造函数中,通过检查某个方法是否有效,来决定是否需要初始化原型
function PersonDynamic(name, age, job) {
  // 属性
  this.name = name
  this.age = age
  this.job = job
  // 方法:①只有方法不存在时才添加到原型;②只在初次调用构造函数时执行;③会立即在实例中体现
  if (typeof this.sayName !== 'function') {
    PersonDynamic.prototype.sayName = function () {
      console.log(this.name)
    }
  }
}
var person18 = new PersonDynamic('Nicholas', 29, 'Software Engineer')
person18.sayName() // 'Nicholas'
console.log(person18 instanceof PersonDynamic) // true,person18是PersonDynamic的实例
  • 只有在方法不存在时,才会将方法添加到原型
  • 只在初次调用构造函数时才会执行,此后原型已经完成初始化
  • 对原型所做的修改,会立即在实例中体现
  • 原型的动态性同理,如果已经创建了实例后再用对象字面量重写原型,会切断实例与新原型之间的联系,导致修改无效
PersonDynamic.prototype = {
  newName:
    typeof this.newName !== 'function'
      ? function () {
          console.log('prototype:', this.name)
        }
      : this.newName,
}
person18.newName() // error,person18指向最初的原型,没有newName方法
var person19 = new PersonDynamic('Greg', 27, 'Doctor') // person19是重写原型后创建的实例
person19.newName() // prototype: Greg
person19.sayName() // Greg

console.log(person18 instanceof PersonDynamic) // false,person18不是重写原型后的PersonDynamic的实例,person18指向最初的原型
console.log(person19 instanceof PersonDynamic) // true,person19是重写原型后的PersonDynamic的实例

寄生构造函数模式

  • 构造函数仅封装创建对象的代码,在构造函数内部用原生引用类型创建新对象,经过操作后再返回这个新对象
  • 构造函数内部跟工厂模式一模一样,调用时用 new 操作符,类似于工厂模式与构造函数模式的结合体
function PersonParasitic(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 person20 = new PersonParasitic('Nicholas', 29, 'Software Engineer')
person20.sayName() // 'Nicholas'
  • 寄生构造函数模式创建的实例,与构造函数或构造函数的原型没有关联,不能依赖 instanceof 确定对象类型
  • 如果可以用其他模式,不建议该模式创建自定义对象
function SpecialArray() {
  var values = new Array() // 用原生引用类型创建数组
  values.push.apply(values, arguments) // 添加值
  // 添加方法
  values.toPipedString = function () {
    return this.join('|')
  }
  return values // 返回经过操作后的数组
}
var colors = new SpecialArray('red', 'blue', 'green')
console.log(colors.toPipedString()) // red|blue|green
console.log(SpecialArray.prototype) // SpecialArray{},构造函数的原型对象
console.log(colors.__proto__) // [],构造函数内部通过new Array()重新初始化,其原型对象是原生对象Array
console.log(SpecialArray.prototype === colors.__proto__) // false,二者无关联
console.log(colors instanceof SpecialArray) // false,二者无关联

稳妥构造函数模式

  • 与计生构造函数类似类似,在构造函数中用原生引用类型创建新对象,但没有公共属性其方法也不引用 this不使用 new 调用构造函数
  • 除了构造函数内部的方法,没有其他办法可访问到构造函数内部的原始数据(即便有其他代码给对象添加方法或属性),该模式适合在安全环境下使用
  • 寄生构造函数模式,该模式下创建的实例,与构造函数或构造函数的原型没有关联,不能依赖 instanceof 确定对象类型
function PersonSafe(name, age, job) {
  var o = new Object() // 用原生引用类型创建对象
  o.sayName = function () {
    console.log(name)
  }
  return o
}
var person21 = new PersonParasitic('Nicholas', 29, 'Software Engineer')
person21.sayName() // 'Nicholas'

总结 & 问点

创建对象过程缺点
Object 构造函数1.创建 Objecty 实例 2.添加属性和方法同一接口创建多个对象,大量重复代码
对象字面量直接创建包含属性和方法的对象同一接口创建多个对象,大量重复代码
工厂模式1.用函数封装创建 Object 实例的过程(添加属性和方法、返回该实例对象) 2.调用该函数没有解决对象识别问题,即怎样知道一个对象的类型
构造函数模式1.构造函数封装(不显示的创建对象、属性和方法赋给 this 对象、无 return) 2.new 调用构造函数每个实例重新创建方法,机制相同的 Function 对象被多次实例化
原型模式1.构造函数封装(空的,无属性和方法) 2.原型对象上添加属性和方法 3.new 调用构造函数对实例的引用类型属性修改而非重新定义时,会对原型的引用类型属性造成影响
组合使用构造函数模式和原型模式1.构造函数封装实例属性 2.原型模式定义方法和共享属性 3.new 调用构造函数无明显缺点,目前最广泛认同度最高,是默认模式
动态原型模式1.构造函数封装所有信息(属性、检查某个方法是否有效来决定是否初始化原型) 2.new 调用构造函数无明显缺点,不要使用对象字面量重写原型
寄生构造函数模式1.构造函数封装(原生引用类型创建新对象、添加值或方法、返回新对象) 2.new 调用构造函数实例与构造函数或构造函数的原型没有关联,不能依赖 instanceof 确定对象类型
稳妥构造函数模式1.同寄生构造函数模式的构造函数封装(但方法不引用 this) 2.作为普通函数调用构造函数实例与构造函数或构造函数的原型没有关联,不能依赖 instanceof 确定对象类型
对象属性指向用法
任何函数prototype原型对象Person.prototype → 构造函数的原型对象
实例、原型constructor构造函数person1.constructor === Person.prototype.constructor === Person
实例[[Prototype]]原型对象person1.__proto__ === Person.prototype(没有标准方式访问[[Prototype]],但可用 __proto__
操作符含义用法
new创建构造函数的实例(四个步骤var person = new Person()
in能否通过对象访问到属性(无论属性在实例还是原型中)console.log('name' in person)
for-in返回所有能通过对象访问到的、可枚举的属性(无论属性在实例还是原型中)for(var attr in person){console.log(attr)}
delete删除实例属性delete person.name
对象方法含义参数返回值用法
Object 对象getPrototypeOf()获取实例对象的原型实例原型对象Object.getPrototypeOf('person') === Person.prototype
任何对象hasOwnProperty()对象自身(不包括原型)是否含有该属性属性true/falseconsole.log(Person.hasOwnProperty('name'))
Object 对象keys()获取对象上所有可枚举的属性(仅对象自身)对象属性的字符串数组Object.keys(person)
Object 对象getOwnPropertyNames()获取对象上所有属性(仅对象自身,无论是否可枚举)对象属性的字符串数组Object.getOwnPropertyNames(person),原型对象会包含 constructor 属性
  • 创建单个对象有哪些方法?这些方法有什
    么缺点?
  • 工厂模式做出了怎样的优化?该模式有什么缺点?
  • 相比工厂模式,构造函数模式有哪些区别?如何创建其实例?
  • 构造函数在 new 的过程中都发生了什么?
  • 构造函数创建出的对象,其 construtor 属性指向哪里?这样的对象是哪些构造函数的实例?
  • 相比工厂模式,构造函数有什么优势?
  • 构造函数与普通函数有什么相同点和区别?
  • 自定义对象的方法时,构造函数模式有什么缺点?
  • 用全局函数代替构造函数内部的对象属性的方法,仍有什么缺点?
  • prototype 属性的含义和用法?使用原型对象的好处是什么?
  • 原型对象的 constructor 属性的含义和用法?
  • 用什么方法检测实例是否含有指向原型对象的指针?
  • 构造函数、实例的哪些属性或方法可获得原型对象?如何通过实例获得构造函数?
  • 代码读取对象属性时,经历了怎样的搜索过程?
  • 是否可以通过实例访问和修改原型中的属性值?
  • 在实例中添加与原型的同名属性会怎样?再删除这个实例中的属性呢?
  • 用什么方法检测属性存在与实例 or 原型?
  • 用什么方法获取原型属性的描述符?
  • 单独使用 in 的用法是什么?其和 hasOwnProperty()方法的区别是什么?
  • for-in 的用法是什么?其返回哪些属性屏蔽哪些属性?
  • Object.keys()的和 Object.getOwnPropertyNames()用法分别是什么?
  • Object.getOwnPropertyNames()、Object.leys()、for-in 的区别是什么?
  • 用一个对象字面量的新对象重写整个原型对象时,原型对象的 constructor 指向发生了怎样的改变?
  • 写一段代码,用对象字面量重写构造函数的原型对象,且让原型对象的 constructor 仍指向原构造函数,并保留 construtor 的[[Enumerable]]特性为 false
  • 创建实例后再修改原型的属性,实例会受到影响么?为什么?
  • 重写整个原型对象后,构造函数的 prototype 指向哪里?实例的[[Prototype]]属性指向哪里?为什么?
  • 原生引用类型的方法是如何创建的?为什么不推荐修改原生引用类型的原型?
  • 原型模式的“共享”本性,在修改包含引用类型的属性时,有怎样的问题?
  • ECMAScript 使用最广泛、认同度最高的创建自定义类型的方法是什么?其原理和优势是什么?
  • 动态原型模式是如何初始化原型的?其对原型的修改有哪些注意点?
  • 寄生构造函数模式的原理是什么?为什么其创建的实例,与其构造函数或构造函数的原型没有关联?
  • 稳妥构造函数模式的原理是什么?为什么该模式比较“稳妥”?

小讴
217 声望16 粉丝