跟着coderwhy学习JavaScript高级(九)

180 阅读4分钟

创建多个对象的方案

  • 如果我们现在希望创建一系列的对象:比如Person
    • 包括张三、李四、王五、赵六等等,他们的信息都不相同

// 字面量方式
var p1 = {
    name: "张三",
    age: 18,
    height: 177,
    address: "深圳"
}

// new Object 方式
var p2 = new Object()
p2.name = "李四";
p2.age = 18;
p2.height = 177;
p2.address = "深圳"

上面这两种方式都有一个很大的弊端,就是如果要创建多个对象,需要写很多重复的代码。

工厂模式创建对象

function factoryObj(name, age, height, address){
    var obj = new Object();

    obj.name = name;
    obj.age = age;
    obj.height = height;
    obj.address = address;

    return obj;
}

var p1 = new factoryObj("zhangsan",17,177,"深圳")
var p2 = new factoryObj("lisi",17,177,"深圳")

工厂方法创建对象有一个比较大的问题:我们在打印对象时,对象的类型都是Object类型

构造函数创建对象

  • 构造函数也是一个普通的函数,从表现形式来说,和千千万万个普通的函数没有任何区别
  • 如果这么一个普通的函数被使用new操作符来调用了,那么这个函数就称之为是一个构造函数
  function Person(name, age, height, address){
      this.name = name;
      this.age = age;
      this.height = height;
      this.address = address;
  } 
  
  var p1 = new Person("zhangsan",17,177,"深圳")
  var p2 = new Person("lisi",17,177,"深圳")
  

构造函数也是有缺点的,它在于我们需要为每个对象的函数去创建一个函数对象实例

对象的原型

  • JavaScript当中每个对象都有一个特殊的内置属性[[prototype]],这个特殊的对象可以指向另外一个对象。

在JavaScript中,当我们通过引用对象上的key获取一个value时,会触发它的Get操作;这个操作首先会检查该对象是否存在对应属性,如果有就使用它;如果该对象没有该属性,那么会沿着它的原型[[prototype]]去查找。

    var obj = {
        name: "zhangsan"
    }
    // 获取对象原型的方式
    // 方式1:__proto__是隐式原型,这个是早期浏览器自己添加的,存在一定的兼容性问题
    console.log(obj.__proto__)
    // 方式2:ES5之后提供的Object.getPrototypeOf
    console.log(Object.getPrototypeof(p1))

函数的原型

  • 所有的函数都有一个prototype的属性
function foo(){}

// 首先,函数也是一个对象,在它的身上也会有一个隐式原型__proto__
console.log(foo.__proto__)

// 函数它因为是一个函数, 所以它还会多出来一个显示原型属性: prototype,只有函数才会有显示原型prototype
console.log(foo.prototype)

new操作符的执行过程

  • 创建一个空的对象
  • 对象的__proto__(隐式原型) = 对象的prototype(显示原型)
function Person(){}
// 那么也就意味着我们通过Person构造函数创建出来的所有对象的[[prototype]]属性都指向Person.prototype
var p1 = new Person()
var p2 = new Person()

console.log(p1.__proto__ === p2.__proto__) // true
console.log(p1.__proto__ == Person.prototype) // true

  • 执行代码
  • 如果构造函数没有返回非空对象,则返回创建出来的新对象

创建对象的内存表现

image.png

// 根据上面的内存图我们可以知道
console.log(Person.prototype.constructor === Person)
console.log(p1.__proto__ === Person.prototype)
console.log(p1.__proto__.constructor === Person.prototype.constructor)

// 因为是循环引用
console.log(Person.prototype === Person.prototype.constructor.prototype)

constructor属性

  • 默认情况下原型上都会添加一个属性叫做constructor,这个constructor指向当前的函数对象
    function Person(){}
    var p1 = new Person();
    
    console.log(Person.prototype.constructor === Person)
    console.log(p1.__proto__.constructor)
    console.log(p1.__proto__.constructor.name)

重写原型对象

    function Person(){}
    
    Person.prototype = {
        name: "zhangsan",
        age: 18
    }
    
  • 每创建一个函数, 就会同时创建它的prototype对象, 这个对象也会自动获取constructor属性
    • 我们这里相当于给prototype重新赋值了一个对象, 那么这个新对象的constructor属性,会指向Object构造函数,而不是Person构造函数了
    function Person(){}
    
    Person.prototype = {
        name: "zhangsan",
        age: 18
    }
    
    // 也就意味着 , 这样显然是不对的
    console.log(Person.prototype.constructor === Object) // false
  • 解决方案1

    重写prototype,constructor指向不正确修复

    function Person(){}
    
    Person.prototype = {
        constructor:Person,
        name: "zhangsan",
        age: 18
    }
    
    console.log(Person.prototype.constructor === Object) // false
    console.log(Person.prototype.constructor === Person) // true
    
  • 上面的方式虽然可以,但是也会造成constructor的[[Enumerable]]特性被设置了true.
    • 默认情况下,原生的constructor属性是不可枚举的

  • 解决方案2 重写prototype,constructor指向不正确修复,enumerable还原为默认值false
    function Person(){}
    
    Person.prototype = {
       name: "zhangsan",
       age: 18
   }
   
   Object.defineProperty(Person.prototype,"constructor",{
       value:Person,
       enumerable:false,
       configurable:true,
      writable:true,
   })

    // 查看所有
    console.log(Object.getOwnPropertyDescriptors(Person.prototype))
    // 查看单个
    console.log(Object.getOwnPropertyDescriptor(Person.prototype,"constructor"))
  • 这样的话,constructor和enumerable都是没有问题的。