创建对象方式,继承
创建对象的多种方式以及优缺点
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
缺点:
- 引用类型的属性被所有实例共享
- 在创建 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