跳到主要内容

创建对象方式,继承

创建对象的多种方式以及优缺点

1.工厂模式

function createPerson(name) {
const o = new Object()
o.name = name
o.getName = function () {
console.log(this.name)
}

return o
}

const person1 = createPerson('kevin')

缺点:对象无法识别,因为所有的实例都指向一个原型

2.构造函数模式

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

const person1 = new Person('kevin')

优点:实例可以识别为一个特定的类型

缺点:每次创建实例时,每个方法都要被创建一次

原型模式

function Person(name) {}

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

const person1 = new Person()

优点:方法不会重新创建

缺点:1. 所有的属性和方法都共享 2. 不能初始化参数

组合模式

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

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

const person1 = new Person()

优点:该共享的共享,该私有的私有,使用最广泛的方式

缺点:有的人就是希望全部都写在一起,即更好的封装性

继承

原型链继承

function Parent() {
this.name = 'kevin'
}

function Child() {}

Child.prototype = new Parent()

const child1 = new Child()

console.log(child1.name) // kevin

缺点:

  1. 引用类型的属性被所有实例共享
  2. 在创建 Child 的实例时,不能向 Parent 传参

借用构造函数(经典继承)

使用父类的构造函数来增强子类实例,等同于复制父类的实例给子类(不使用原型)

function Parent() {
this.names = ['kevin', 'daisy']
}

function Child() {
// 继承自Parent
Parent.call(this)
}

const child1 = new Child()

child1.names.push('yayu')

console.log(child1.names) // ["kevin", "daisy", "yayu"]

const child2 = new Child()

console.log(child2.names) // ["kevin", "daisy"]

核心代码是 Parent.call(this),创建子类实例时调用 Parent 构造函数,于是 Child 的每个实例都会将 Child 中的属性复制一份。

缺点:

  • 只能继承父类的实例属性和方法,不能继承原型属性/方法
  • 无法实现复用,每个子类都有父类实例函数的副本,影响性能

组合继承

组合上述两种方法就是组合继承。用原型链实现对原型属性和方法的继承,用借用构造函数技术来实现实例属性的继承。

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

function Child(name, age) {
// 第二次调用
Parent.call(this, name)

this.age = age
}

Child.prototype = new Parent() // 第一次调用
Child.prototype.constructor = Child

const child1 = new Child('kevin', '18')

缺点:

  • 第一次调用 Parent():给 Child.prototype 写入两个属性 name,colors。
  • 第二次调用 Parent():给 child1 写入两个属性 name,colors。 组合模式的缺点就是在使用子类创建实例对象时,其原型中会存在两份相同的属性/方法。

原型式继承

function object(obj) {
function F() {}
F.prototype = obj
return new F()
}

就是 ES5 Object.create 的模拟实现,将传入的对象作为创建的对象的原型。

缺点:

包含引用类型的属性值始终都会共享相应的值,这点跟原型链继承一样。

寄生式继承

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

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

缺点:跟借用构造函数模式一样,每次创建对象都会创建一遍方法。

寄生组合式继承

function inheritPrototype(Child, Parent) {
const prototype = Object.create(Parent.prototype) // 创建对象,创建父类原型的一个副本
prototype.constructor = Child // 增强对象,弥补因重写原型而失去的默认的constructor 属性
Child.prototype = prototype // 指定对象,将新创建的对象赋值给子类的原型
}

对象方法

Object.create(proto, propertiesObject)
// 一个现有对象作为原型,创建一个新对象。

Object.hasOwn(obj, prop)
// 如果指定的对象自身有指定的属性,则静态方法 Object.hasOwn() 返回 true。如果属性是继承的或者不存在,该方法返回 false。
// 它不像 in 运算符,这个方法不检查对象的原型链中的指定属性。
// Object.hasOwn() 旨在取代Object.prototype.hasOwnProperty()

hasOwn 与 hasOwnProperty 区别

使用 Object.create(null) 创建的对象不从 Object.prototype 继承,使得 hasOwnProperty() 不可访问。

const foo = Object.create(null)
foo.prop = 'exists'
if (Object.hasOwn(foo, 'prop')) {
console.log(foo.prop) // true——无论对象是如何创建的,它都可以运行。
}
foo.hasOwnProperty('prop')
// Uncaught TypeError: foo.hasOwnProperty is not a function

JavaScript 并不保护属性名称 hasOwnProperty;具有此名称属性的对象可能会返回不正确的结果:

const foo = {
hasOwnProperty() {
return false
},
bar: 'Here be dragons',
}

foo.hasOwnProperty('bar') // 始终返回 false
Object.hasOwn(foo, 'bar') // true

参考

https://github.com/mqyqingfeng/Blog/issues/15

https://github.com/mqyqingfeng/Blog/issues/16

https://github.com/yygmind/blog/issues/7

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain