谈谈创建对象的方法和继承

119 阅读3分钟

今天继续巩固基础,看下创建对象的几种方法和优缺点,以及继承的几种方法和优缺点吧

创建对象的多种方法和优缺点

工厂函数

function createPersn(name) {
  var o = new Object()
  o.name = name
  o.getName = function () {
    console.log(this.name)
  }
  return o
}

var person1 = createPerson("randy")

从我们创建的过程可以看到这种方式非常的简单,但是有一定的缺陷,那就是对象无法识别,因为所有的实例都指向了一个原型

  • 优点:简单
  • 缺点:所有的实例都指向了一个原型

构造函数模式

function Person(name) {
  this.name = name
  this.getName = function () {
    console.log(this.name)
  }
}

var person1 = new Person("randy")
  • 优点:实例可以识别为一个特定的类型
  • 缺点:每次创建实例时,每个方法都要创建一次

优化

function Person(name) {
  this.name = name
  this.getName = getName
}

function getName() {
  console.log(this.name)
}

var person1 = new Person("randy")

解决了每个方法都要被重新创建的问题,但是每次都要返回一个新的实例

原型模式

function Person(name) {}

Person.prototype.name = "randy"
Person.prototype.getName = function () {
  console.log(this.name)
}

var person1 = new Person()
  • 优点:方法不会被重新创建
  • 缺点:
    • 所有的属性和方法都被共享
    • 不能初始化参数

原型优化1

function Person(name) {}

Person.prototype = {
  name: "randy",
  getName: function () {
    console.log(this.name)
  }
}
var person = new Person()
  • 优点:封装了清晰点
  • 缺点:重写了原型 constructor属性丢失

原型优化2

function Person(name) {}

Person.prototype = {
  constructor: Person,
  name: "randy",
  getName: function () {
    console.log(this.name)
  }
}
var person = new Person()
  • 优点:实例可以通过constructor属性找到所属的构造函数
  • 缺点:
    • 所有的属性和方法都共享
    • 不能初始化参数
    • 原型链断了

组合模式

function Person(name) {
  this.name = name
}

Person.prototype = {
  constructor: Person,
  getName: function () {
    console.log(this.name)
  }
}

var person = new Person()
  • 优点:该共享的共享,该私有的私有
  • 缺点:希望写在一个地方,即更好的封装性

动态原型模式

function Person(name) {
  this.name = name
  if (typeof this.getName != "function") {
    Person.prototype.getName = function () {
      console.log(this.name)
    }
  }
}

var person = new Person()

注意: 使用动态原型模式时,不能用对象字面量重写原型

function Person(name) {
  this.name = name
  if (typeof this.getName != "function") {
    Person.prototype = {
      constructor: Person,
      getName: function () {
        console.log(this.name)
      }
    }
  }
}

var person1 = new Person("randy")
var person2 = new Person("mick")

person1.getName() // 报错,并没有该方法

new的执行步骤:

  1. 首先创建一个对象
  2. 将对象的原型指向Person.prototype
  3. Person.apply(obj)
  4. return 对象
    执行 apply 的时候,会执行 obj.Person,if 判断的时候,构造函数的 prototype 属性指向了实例的原型,使用字面量方式覆盖 Person.prototype 并不会更改实例的原型的值,person1 依然指向了以前的原型

继承的多种方式&优缺点

原型链继承

function Parent() {
  this.name = "randy"
}

Parent.prototype.getName = function () {
  console.log(this.name)
}

function Child() {}

Child.prototype = new Parent()

var child1 = new Child()

child1.getName()

问题:

  • 引用类型的属性被所有的实例共享
  • 不能给父构造函数传参
function Parent() {
  this.names = ["randy", "mick"]
}

function Child() {}

Child.prototype = new Parent()

var child1 = new Child()

child1.names.push("hahah")

console.log(child1.names) //[ 'randy', 'mick', 'hahah' ]

var child2 = new Child()
console.log(child2.names) // [ 'randy', 'mick', 'hahah' ]

借用构造函数

function Parent() {
  this.names = ["randy", "mick"]
}

function Child() {
  Parent.call(this)
}

var child1 = new Child()

child1.names.push("hahah")
console.log(child1.names) // [ 'randy', 'mick', 'hahah' ]

var child2 = new Child()
console.log(child2.names) // [ 'randy', 'mick' ]
  • 避免了引用类型的所有属性被所有实例共享
  • 可以在Child中向Parent传参
function Parent(name) {
  this.name = name
}

function Child(name) {
  Parent.call(this, name)
}

var child1 = new Child("randy")

console.log(child1.name) // randy

var child2 = new Child("mick")

console.log(child2.name) // mick

缺点:方法都在构造函数中定义,每次创建实例都会创建一遍方法

组合继承

function Parent(name) {
  this.name = name
  this.colors = ["red", "blue", "green"]
}

Parent.prototype.getName = function () {
  console.log(this.name)
}

function Child(name, age) {
  Parent.call(this, name)
  this.age = age
}

Child.prototype = new Parent()
Child.prototype.constructor = Child

var child1 = new Child("randy", 18)

child1.colors.push("black")

console.log(child1.name) //randy
console.log(child1.age) // 18
console.log(child1.colors) //[ 'red', 'blue', 'green', 'black' ]

var child2 = new Child("mick", 20)

console.log(child2.name) //mick
console.log(child2.age) // 20
console.log(child2.colors) //[ 'red', 'blue', 'green']

组合继承融合了原型链继承和构造函数的优点

原型继承

function createObj(o) {
  function F() {}
  F.prototype = o
  return new F()
}

缺点:包含引用类型的属性值始终都会共享相应的值

var person = {
  name: "randy",
  friends: ["mick", "qin"]
}

var person1 = createObj(person)
var person2 = createObj(person)

person1.name = "person1"
console.log(person2.name) // randy

person1.friends.push("ran")
console.log(person2.friends) // [ 'mick', 'qin', 'ran' ]

寄生式继承

创建一个仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象,最后返回对象

function createObj(o) {
  var clone = Object.create(o)
  clone.sayName = function () {
    console.log("hi")
  }
  return clone
}