# 创建对象方案

# 字面量

const lain = {
    age: 16
}

# 工厂模式:工厂函数

function createMaster(name, age) {
  return  {
    name,
    age,   
    sleeping() {
      console.log(`${this.name} 在睡觉`);
    }
  }
}
const Saber = createMaster("Saber", 16)
const Lain = createMaster("Lain", 20)
const nekoaimer = createMaster("nekoaimer", 30)
// 工厂模式的缺点 (获取不到对象最真实的类型)
console.log(Saber, Lain, nekoaimer)
// {name: 'Saber', age: 16, sleep: ƒ} {name: 'Lain', age: 20, sleep: ƒ} {name: 'nekoaimer', age: 30, sleep: ƒ}
Saber.sleeping() // Saber 在睡觉
Lain.sleeping() // Lain 在睡觉
nekoaimer.sleeping() //nekoaimer 在睡觉

# 构造函数

// 规范:构造函数的首字母一般是大写
function Master(name, age) {
  this.name = name
  this.age = age
  this.sleeping = function() {
    console.log(`${this.name} 在睡觉`)
  }
}
// 在 new 函数的时候 后面的括号如果在没有参数的情况下可以省略
var saber = new Master("Saber", 16)
var lain = new Master("Lain", 16)
console.log(saber) // Master {name: 'Saber', age: 16, sleeping: ƒ}
console.log(lain) // Master {name: 'Lain', age: 16, sleeping: ƒ}
saber.sleeping() // Saber 在睡觉
lain.sleeping() // Lain 在睡觉
// 每次 new 函数调用都会返回一个新对象 这比较浪费性能
console.log(saber === lain) // false

# 对象原型理解

  • JavaScript 当中每个对象都有一个特殊的内置属性 [[prototype]],这个特殊的对象可以指向另外一个对象。
  • 那么这个对象有什么用呢?
    • 当我们通过引用对象的属性 key 来获取一个 value 时,它会触发 [[Get]] 的操作;
    • 这个操作会首先检查该属性是否有对应的属性,如果有的话就使用它;
    • 如果对象中没有改属性,那么会访问对象 [[prototype]] 内置属性指向的对象上的属性;
  • 那么如果通过字面量直接创建一个对象,这个对象也会有这样的属性吗?如果有,应该如何获取这个属性呢?
    • 答案是有的,只要是对象都会有这样的一个内置属性;
  • 获取的方式有两种:
    • 方式一:通过对象的 proto 属性可以获取到(但是这个是早期浏览器自己添加的,存在一定的兼容性问
  • 题);
    • 方式二:通过 Object.getPrototypeOf 方法可以获取到;
// 我们每个对象中都有一个 [[prototype]], 这个属性可以称之为对象的原型 (隐式原型)
const lain = { } // [[prototype]]
// 1. 解释原型的概念和看一下原型
// 早期的 ECMA 是没有规范如何去查看 [[prototype]]
// 给对象中提供了一个属性,可以让我们查看一下这个原型对象 (浏览器提供) __proto__
console.log(lain.__proto__) // {constructor: ƒ ……}
// var lain = {age: "16", __proto__: {} }
// ES5 之后提供的 lainect.getPrototypeOf
console.log(Object.getPrototypeOf(lain)) // {constructor: ƒ ……}
// 2. 原型有什么用呢?
// 当我们从一个对象中获取某一个属性时,它会触发 [[get]] 操作
// 1. 在当前对象中去查找对应的属性,如果找到就直接使用
// 2. 如果没有找到,那么会沿着它的原型去查找 [[prototype]]
lain.__proto__.age = 18
console.log(lain.age) // 18

# 函数的原型理解

  • 那么我们知道上面的东西对于我们的构造函数创建对象来说有什么用呢?
    • 它的意义是非常重大的!
  • 这里我们又要引入一个新的概念:所有函数都有一个 prototype 的属性:
function foo() { }
// 所有的函数都有一个属性:prototype
console.log(foo.prototype)
  • 你可能会问,是不是因为函数是一个对象,所以它有 prototype 的属性呢?

    • 不是的,因为它是一个函数,才有了这个特殊的属性;
    • 而不是它是一个对象,所以有这个特殊的属性
    const obj = {}
    console.log(obj.prototype) //obj 没有这个属性
  • 函数也是一个对象

function foo() {}
//console.log (foo.__proto__) // 函数作为对象来说,它也是有 [[prototype]] 隐式原型
// 因为是一个函数,所以它还会多出来一个显示原型属性: prototype
console.log(foo.prototype) // {constructor: ƒ}
const f1 = new foo()
const f2 = new foo()
console.log(f1.__proto__ === foo.prototype) // true
console.log(f2.__proto__ === foo.prototype) // true

# new 操作符

  • new 关键字的步骤如下:
    • 在内存中创建一个新的对象(空对象);
    • 这个对象内部的 [[prototype]] 属性会被赋值为该构造函数的 prototype 属性;
  • 那么也就意味着我们通过 Person 构造函数创建出来的所有对象的 [[prototype]] 属性都指向 Person.prototype:
function Person() {}
const p = new Person()
// 上面的操作符相当于会进行如下的操作:
p = {}
p.__proto__ = Person.prototype
// 可以尝试打印比较
console.log(p.__proto__ === Person.prototype) // true
  • 创建对象的内存表现:

p

通过上面的图可以理解下面的代码了:

function Person() {}
const p1 = new Person()
const p2 = new Person()
console.log(p1.__proto__ === p2.__proto__) // true
console.log(p1.__proto__ === Person.prototype) // true
p1.__proto__.friend = 'Saber'
console.log(p2.friend) // Saber
Person.prototype.age = 16 
console.log(p1.__proto__.age) // 16

# 实现 new 方法

function _new(obj, ...rest){
  // 基于 obj 的原型创建一个新的对象
  const newObj = Object.create(obj.prototype)
  
  // 添加属性和传参数到新创建的 newObj 上,并获取 obj 函数执行的结果.
  const result = obj.apply(newObj, rest)
    
  // 如果执行结果有返回值并且是一个对象,返回执行的结果,否则,返回新创建的对象
  return typeof result === 'object' ? result : newObj;
}

# 函数原型上的属性

prototype_object

# constructor 属性

function foo() {}
// 1.constructor 属性
//foo.prototype 这个对象中有一个 constructor 属性
// console.log(foo.prototype)
console.log(Object.getOwnPropertyDescriptors(foo.prototype)) // 获取对象所有属性描述器
// 通过下面修改打印能看到属性描述器
Object.defineProperty(foo.prototype, "constructor", {
  enumerable: true,
  configurable: true,
  writable: true,
  //value: [Function: foo] // 对应的就是 foo 函数
})
console.log(foo.prototype.constructor) // [Function: foo]
//prototype.constructor 指向构造函数本身
console.log(foo.prototype.constructor === foo) // true
console.log(foo.prototype.constructor.name) //foo
//console.log (foo.prototype.constructor.prototype.constructor.prototype.constructor) //prototype 指向 constructor constructor 指向 prototype

# 重写原型对 象

function lain() {}
// 3. 直接修改整个 prototype 对象
lain.prototype = {
  //constructor: lain, //configurable enumerable writable 都是为 true
  name: "lain",
  age: 16,
}
// console.log(Object.getOwnPropertyDescriptors(lain.prototype));
// var p1 = new foo()
// console.log(p1.name, p1.age)
// 真实开发中我们可以通过 Object.defineProperty 方式添加 constructor
Object.defineProperty(lain.prototype, "constructor", {
  enumerable: false,
  configurable: true,
  writable: true,
  value: lain
})
console.log(Object.getOwnPropertyDescriptors(lain.prototype))

lain

# 构造函数和原型组合

function Person(name, age) {
  this.name = name
  this.age = age
}
Person.prototype.sleeping = function() {
  console.log(this.name + " 在呼呼睡大觉~")
}
var lain = new Person("Lain", 16)
var saber = new Person("Saber", 16)
lain.sleeping()
saber.sleeping()

# 对象的方法

# hasOwnProperty

  • 对象是否有某一个属于自己的属性(不是在原型上的属性)
const foo = { name: 'foo' }
foo.__proto__.abc = 123
foo.hasOwnProperty('name')
foo.hasOwnProperty('abc')const foo = { name: 'foo' }
foo.__proto__.abc = 123
foo.hasOwnProperty('name') // true
foo.hasOwnProperty('abc') // false

# in/for in 操作符

  • 判断某个属性是否在某个对象或者对象的原型上
const foo = { name: 'foo' }
foo.__proto__.abc = 123
console.log('name' in foo) // true
console.log('abc' in foo) // true
for (const key in foo) 
  console.log(key) // name // abc

# instanceof

  • 用于检测构造函数的 pototype,是否出现在某个实例对象的原型链上
function A() {}
function B() {}
B.prototype = new A()
B.prototype.constructor = B
const C = new B()
console.log(C instanceof B) // true
console.log(C instanceof A) // true
console.log(C instanceof Object) // true

# isPrototypeOf

  • 用于检测某个对象,是否出现在某个实例对象的原型链上
function A() {}
function B() {}
B.prototype = new A()
B.prototype.constructor = B
const C = new B()
Object.prototype.isPrototypeOf(C) // true
A.prototype.isPrototypeOf(C) // true
B.prototype.isPrototypeOf(C) // true

# isPrototypeOf instanceof 区别

  • A.prototype.isPrototypeOf(C) 判断 A 对象是否存在于 C 对象的原型链之中

  • C instanceof A 判断的是 A.prototype 是否存在与 C 的原型链之中

  • 所以得出一个结论: 如果 A.isPrototypeOf(C) 返回true 则C instanceof A 一定返回true

  • 另外补充一点,比如下面代码,这个时候 instanceof 是不能被调用的,因为右边应该是个构造函数,所以我们可以使用 isPrototypeOf 方法来解决

const foo = {
  name: 'lain'
}
const bar = Object.create(foo)
// console.log(bar instanceof foo) // Uncaught TypeError: Right-hand side of 'instanceof' is not callable
foo.isPrototypeOf(bar) // true