最近再次看起了 《JavaScript高级程序设计》,在此做下笔记,总结一下 创建对象与继承的主要方式。
虽然Object构造函数或对象字面量都可以用来创建对象,但这些方式都有个明显的缺点:使用同个接口创建很多对象,会产生大量的重复代码。为解决这个问题,人们开始使用工厂模式的一种变体。 ——《JavaScript高级程序设计》
1.工厂模式
这种模式抽象了创建具体对象的过程。缺点:无法解决对象类型识别的问题
function createPerson(name, age, job) {
let obj = new Object();
obj.name = name;
obj.age = age;
obj.job = job;
obj.sayName = function() {
console.log(this.name)
}
return obj
}
let person = createPerson('张三', 26, 'teacher');
2.构造函数模式
优点:解决了对象类型识别的问题 缺点:还是会有 同样的方法在每个实例上都要创建 没有复用。
function Person(name, age) {
this.name = name;
this.age = age;
this.sayName = function () {
console.log(this.name)
}
}
let person = new Person('张三', 28)
person.sayName()
console.log(person instanceof Person) //true
3.原型模式
每个函数都有一个prototype属性,指向该函数的原型对象,该原型对象有一个constructor属性指向函数, 通过构造函数生成的实例也会有一个指针 __proto__ 指向它的原型对象。构造函数、原型对象、实例三者关系如下图所示。对象属性查找时,如果没有找到,会沿着原型链向上查找它的原型对象,如果找到属性这个查找过程会终止,否则会查询到原型链的终点null.
优点:解决了方法重复创建的问题 缺点:引用类型数据相互影响的问题;创建时没有办法传递参数
function Person() { }
Person.prototype = {
constructor: Person,
name: '张三',
age: 28,
like: ['food', 'cake'],
sayName: function () {
console.log(this.name)
}
}
let person1 = new Person();
let person2 = new Person();
person1.like.push('milk')
console.log(person1.like) //['food', 'cake', 'milke]
console.log(person2.like) //['food', 'cake', 'milke]
4.组合使用构造函数和原型模式
集合两种模式的长处,并没有它们的缺点。 缺点:构造函数和其原型分离,封装性不够。
function Person(name, age, like) {
this.name = name;
this.age = age;
this.like = like;
}
Person.prototype.sayName = function () {
console.log(this.name)
}
let person1 = new Person('张三', 26, ['food', 'cake']);
let person2 = new Person('李四', 26, ['food', 'cake']);
person1.like.push('milk')
console.log(person1.like) //['food', 'cake', 'milke]
console.log(person2.like) //['food', 'cake']
person1.sayName() //张三
解决了组合模式 封装性不够的问题
function Person(name, age, like) {
this.name = name;
this.age = age;
this.like = like;
//判断一个应该存在于原型上的属性或方法就知道是否已经添加过
if (typeof this.sayName !== 'function') {
//不能用字面量形式 或则会重写原型
Person.prototype.sayName = function () {
console.log(this.name)
}
}
}
let person1 = new Person('张三', 26, ['food', 'cake']);
let person2 = new Person('李四', 26, ['food', 'cake']);
person1.like.push('milk')
console.log(person1.like) //['food', 'cake', 'milke]
console.log(person2.like) //['food', 'cake']
person1.sayName() //张三
1.原型链继承
缺点 1)、数据共享问题 引用类型数据 实例之间相互影响 2).创建子类型实例时没办法向超类传递参数 3.只能单继承( 只能继承一个父类)
function SuperType() {
this.name = '张三';
this.like = ['food', 'cake'];
}
SuperType.prototype.sayName = function () {
console.log(this.name)
}
function SubType() {
this.age = '26';
}
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function () {
console.log(this.age)
}
let person1 = new SubType();
let person2 = new SubType();
person1.sayAge() //26
person1.sayName() // 张三
person1.like.push('flower')
console.log(person1.like) // ["food", "cake", "flower"]
console.log(person2.like) // ["food", "cake", "flower"]
2.构造函数继承
优点:解决原型链继承引用类型数据共享和没办法传递参数问题;可以多继承 。 缺点:方法多次重复定义
function SuperType(name) {
this.name = name;
this.like = ['food', 'cake'];
this.sayName = function () {
console.log(this.name)
}
}
function SuperType2(date) {
this.birthday = date;
this.sayDate = function () {
console.log(this.birthday)
}
}
function SubType(name, age, date) {
//多继承
SuperType.call(this, name);
SuperType2.call(this, date)
this.age = age;
this.sayAge = function () {
console.log(this.age)
}
}
let person1 = new SubType('张三', 26, '2013-12');
let person2 = new SubType('李四', 28, '2013-11');
person1.sayName() // 张三
person2.sayName() // 李四
person1.sayDate() //2013-12
person1.like.push('flower')
console.log(person1.like) // ["food", "cake", "flower"]
console.log(person2.like) // ["food", "cake"]
3.组合继承(原型链 + 构造函数)
优点:避免了原型链和构造函数的缺陷,能实现函数复用,每个实例也都有自己的属性。 缺点:超类构造函数执行了两次
function SuperType(name, like) {
this.name = name;
this.like = like
}
SuperType.prototype.sayName = function () {
console.log(this.name)
}
function SubType(name, age, like) {
SuperType.call(this, name, like)
this.age = age;
}
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function () {
console.log(this.age)
}
let person1 = new SubType('张三', '28', ['food', 'cake']);
let person2 = new SubType('李四', '28', ['food', 'cake']);
person1.sayName() //张三
person2.sayName() //李四
person1.like.push('flower');
console.log(person1.like) // ["food", "cake", "flower"]
console.log(person2.like) // ["food", "cake"]
4.寄生组合式
优点: 解决超类构造函数调用两次问题。 我们只需要让子类能访问到超类的原型的就可以。
//其他地方和组合继承一致
SubType.prototype = Object.create(SuperType.prototype)
//Object.create 传一个参数时和如下object函数行为一致
function object(obj) {
function F() { }
F.prototype = obj;
return new F()
}
有些人可能会有疑惑,我们为什么不直接让 子类原型等于超类原型呢?
//这样不是更省事吗?
SubType.prototype = SuperType.prototype
//这样如果在SubType原型上添加属性 SuperType原型上也会影响到
//如
SubType.prototype.sayAge = function () {
console.log(this.age)
}
//SuperType.prototype 也会存在sayAge方法
5.ES6继承
ES5 的继承,实质是先创造子类的实例对象
this
,然后再将父类的方法添加到this
上面(Parent.apply(this)
)。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this
上面(所以必须先调用super
方法),然后再用子类的构造函数修改this
。 —— 《ECMAScript 6入门》阮一峰
class A {
p() {
return 2;
}
}
class B extends A {
constructor() {
//作为函数调用 代表父类构造函数
super();
//作为对象调用 指向父类原型对象
console.log(super.p()); // 2
}
}