第6章 面向对象的程序设计
# 第6章 面向对象的程序设计
# 6.1 理解对象
# 6.2 创建对象
# 6.2.1 工厂模式
// 工厂模式
function createPerson(name, age, job) {
// var o = {};
// o.name = name;
// o.age = age;
// o.job = job;
// o.sayName = function() {console.log(this.name)}
var o = {
name:name,
age:age,
job:job,
sayName = function() {
console.log(this.name)
}
}
return o;
}
var person1 = createPerson('张三',29,'设计师')
var person2 = createPerson('李四',22,'程序员')
# 6.2.2 构造函数
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName
}
function sayName() { console.log(this.name) }
var person1 = new Person('张三', 29, '设计师');
var person2 = new Person('李四',22,'程序员');
要创建Person
实例,必须使用new
操作符。new
方式调用构造函数会经历四个步骤:
- 创建一个新对象
- 将构造函数的作用域赋予新对象,
this
指向该对象 - 执行构造函数中的代码
- 返回新对象
任何函数,只要通过new调用,都可以当做构造函数(构造函数首字母大写)
任何函数,如果不通过new
调用,就和普通函数一样,注意:不使用new
要注意this
的指向问题
# 6.2.3 原型模式
创建一个新函数,该函数会创建一个prototype属性,这个属性指向函数的原型对象
所有原型对象默认都会有一个constructor属性,这个属性是一个指向prototype属性所在函数的指针
调用构造函数创建实例后,该实例内部包含一个指针,指向构造函数的原型对象,也就是__proto__属性
isPrototypeOf()方法用于检测一个对象是否存在于另一个对象的原型链上
function Foo() {}
function Bar() {}
function Baz() {}
Bar.prototype = Object.create(Foo.prototype);
Baz.prototype = Object.create(Bar.prototype);
var baz = new Baz();
console.log(Baz.prototype.isPrototypeOf(baz)); // true
console.log(Bar.prototype.isPrototypeOf(baz)); // true
console.log(Foo.prototype.isPrototypeOf(baz)); // true
console.log(Object.prototype.isPrototypeOf(baz)); // true
function Person() {
}
Person.prototype.name = '张三';
Person.prototype.age = 29;
Person.prototype.job = '设计师';
Person.prototype.sayName = function() {
console.log(this.name)
}
var person1 = new Person();
person1.sayName() // "设计师"
var person2 = new Person();
person2.sayName() // "设计师"
原型对象,创建的实例对象会具有相用的属性和方法。与构造函数不同的是,新对象的这些属性和方法是有所有实例共享的。
使用Object.getPrototypeOf()可以获取一个对象的原型
console.log(Object.getPrototypeOf(person1) === Person.prototype) // true
为实例对象添加一个属性时,这个属性会屏蔽掉原型对象中保存的同名属性
使用delete操作符可以删除实例属性,从而能够重新访问原型的中属性
function Person() {
}
Person.prototype.name = '张三';
Person.prototype.age = 29;
Person.prototype.job = '设计师';
Person.prototype.sayName = function() {
console.log(this.name)
}
var person1 = new Person();
var person2 = new Person();
person1.name = '李四'
console.log(person1.name) // "李四" --来自实例
console.log(person2.name) // "张三" -- 来自原型
// 删除实例属性
delete person1.name
console.log(person1.name) // "张三" -- 重新从原型中获取
属性查找规则:首先会从当前实例中查找,如果没找到则会向原型对象上查找
使用hasOwnProperty()方法可以检测一个属性是存在于实例中,还是存在于原型中,如果给定属性存在实例中就返回true
# 6.2.4 构造函数和原型模式组合
原型对象缺点:所有实例默认都取得相同的属性值,所以原型模式定义的是公共的属性,对于定义的引用类型属性,其中一个实例修改了属性也会反映到其他实例上,实例一般情况下都需要定义自己的私有属性,所以单单使用原型模式是不够的。
构造函数与原型模式的结合,是创建自定义类型的最常见的方式,构造函数定义实例属性,原型模式定义公共方法和属性。每个实例拥有自己的一份实例副本,同时又共享着对方法的引用。
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.friends = ['王五','赵六']
}
Person.prototype = {
constructor: Person,
sayName: function() {console.log(this.name)}
}
var person1 = new Person('张三', 29, '设计师')
var person2 = new Person('李四', 24, '程序员')
# 6.2.5 动态原型模式
将所有的信息封装到构造函数中,通过构造函数中初始化原型,又保持了同时使用构造函数和原型的优点。
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
if(typeof this.sayName != 'function') {
person.prototype.sayName = function() {
console.log(this.name)
}
}
}
使用动态原型模式时,不能使用对象字面量重写原型。如果在已经创建了实例的情况下重写原型,那么就会切断现有实例与新原型之间的联系。
# 6.2.6 寄生构造函数模式
创建一个函数,该函数的作用仅仅是封装创建对象的代码。然后返回新创建的对象
function Person(name, age, job) {
var o = {
name: name,
age: age,
job:job,
sayName: function(){console.log(this.name)}
};
return o;
}
var friend = new Person('张三', 29, '设计师')
friend.sayName() // "张三"
这个模式可以在特殊的情况下用来为对象创建构造函数。创建一个具有额外方法的特殊数组。由于不能直接修改Array构造函数,因此可以使用这个模式
function SpecialArray() {
// 创建数组
var values = []
// 添加值
values.push.apply(values, arguments);
// 添加方法
values.toPipedString = function() {
return this.join('|')
}
// 返回数组
return values;
}
var colors = new SpecialArray('red','blue','green');
console.log(colors.toPipedString()) // red | blue | green
# 6.2.7 稳妥构造函数模式
所谓稳妥对象,指的是没有公共属性,而且其方法也不应用this对象。
稳妥构造函数和寄生构造函数不同点:
- 新创建对象的实例方法不引用this
- 不使用new操作符调用构造函数
function Person(name, age, job) {
// 创建要返回的对象
var o = {};
// 可以在这里定义私有变量和函数
// 添加方法
o.sayName = function() { console.log(name)};
return o;
}
# 6.3 继承
# 6.3.1 原型链
# 6.3.2 借用构造函数
在子类型构造函数的内部调用超类构造函数。通过使用apply()和call()方法在新创建的对象上执行构造函数
可以在子类型构造函数中向超类型构造函数传递参数
缺点:只能继承超类型的实例属性,对于原型上的方法和属性无法继承。
function SuperType(name) {
this.name = name;
this.colors = ['red','blue','green']
}
function SubType() {
// 继承SuperType
SuperType.call(this,'ronghui');
this.age = 19;
}
var instance1 = new SubType();
instance1.colors.push('black');
console.log(instance1.colors) // ["red", "blue", "green", "black"]
console.log(instance.name) // ronghui
console.log(instance.age) // 19
# 6.3.3 组合继承
组合继承也叫伪经典继承,指的是将原型链和急用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的的继承。这样,既通过原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。
缺点:
- 无论在什么情况下,都会调用两次超类型构造函数
- 一次是在创建子类型原型的时候,另一次是在子类型构造函数内部,子类型会继承超类型对象的全部实例属性;
- 这些实例属性可能不是我们想要的,因为我们还需要定义自己私有的属性,
function SuperType(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
SuperType.prototype.sayName = function () { console.log(this.name) }
function SubType(name, age) {
// 继承属性
SuperType.call(this, name);
this.age = age;
}
// 继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function () { console.log(this.age) }
var instance1 = new SubType('张三', 29);
instance1.colors.push('block')
console.log(instance1.colors) // ["red", "blue", "green", "block"]
instance1.sayName() // 张三
instance1.sayAge() // 29
var instance2 = new SubType('李四', 24);
console.log(instance2.colors); // ["red", "blue", "green"]
instance2.sayName() // 李四
instance2.sayAge() // 24
# 6.3.4 原型式继承
function object(o) {
function F()
F.prototype = o;
return new F()
}
var person = {
name: '张三',
f
}
ES5的Object.create()实现原型式继承,接受两个参数:一个用做新对象原型的对象和一个为新对象定义额外属性的对象。
var person = {
name: 'Nicholas',
friends: ['Shelby', 'Court', 'Van']
}
var anotherPerson = Object.create(person);
anotherPerson.name = 'Greg';
anotherPerson.friends.push('Rob');
var yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = 'Linda';
yetAnotherPerson.friends.push('Barbie');
console.log(person.friends); // ["Shelby", "Court", "Van", "Rob", "Barbie"]
# 6.3.5 寄生式继承
function createAnother(original) {
let clone = Object.create(original);
clone.sayHi = function() { console.log( 'hi')}
return clone
}
# 6.3.6 寄生组合式继承
组合继承缺点:无论在什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数颞部。
寄生组合继承,是通过借用构造函数来继承属性,通过原型链的混成形式来继承方法
function inheritPrototype(subType, superType) {
var prototype = Object.create(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;;
}
function SuperType(name) {
this.name = name;
this.colors = ['red', 'blue', 'green']
}
SuperType.prototype.sayName = function () {
console.log(this.name)
}
function SubType(name, age) {
SuperType.call(this, name);
this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function () {
console.log(this.age)
}
var instance1 = new SubType('张三', 18)
instance1.sayName() // 张三
instance1.sayAge(); // 18
# 6.4 拷贝继承
function Animal() { }
Animal.prototype.species = '动物';
function extends2(SubType, SupType) {
var p = SupType.prototype;
var c = SubType.prototype;
for (var i in p) {
c[i] = p[i]
}
c.uber = p;
}
extends2(Cat, Animal);
var cat1 = new Cat('大毛', '黄色')
console.log(cat1.species)
# 6.5 小结
ECMAScript支持面向对象(OO)编程,但不使用类或者接口。对象可以在代码执行过程中创建和增强,因此具有动态而非严格定义的实体。在没有类的情况下,可以采用下列模式创建对象。
- 工厂模式,使用简单的函数创建对象,为对象添加属性和方法,然后返回对象。这个模式后来被构造函数模式所取代。
- 构造函数模式,可以创建自定义引用类型,可以像创建内置对象实例一样使用new操作符。不过,构造函数模式也有缺点,即它的每个成员都无法得到复用,包括函数。由于函数可以不局限于任何对象(即与对象具有松散耦合的特点),因此没有理由不在多个对象间共享函数
- 原型模式,使用构造函数的prototype属性来指定那些应该共享的属性和方法。组合使用构造函数模式和原型模式时,使用构造函数定义实例属性,而使用原型定义共享的属性和方法。
JavaScript主要通过原型链实现继承。原型链的构建是通过一个类型的实例赋值给另一个构造函数的原型实现的。这样,子类型就能够访问超类型的所有属性和方法,这一点与基于类的继承很相似。原型链的问题是对象实例共享所有继承的属性和方法,因此不适宜单独使用。解决这个问题的技术是借用构造函数,即在子类型构造函数的内部调用超类型构造函数。这样就可以做到每个实例都具有自己的属性,同时还能保证只使用构造函数模式来定义类型。使用最多的继承模式是组合继承,这种模式使用原型链继承共享的属性和方法,而通过借用构造函数继承实例属性。
此外,还存在下列可供选择的继承模式:
- 原型式继承,可以在不必预先定义构造函数的情况下实现继承,其本质是执行对给定对象的浅复制。而复制得到的副本还可以得到进一步改造。
- 寄生式继承,与原型式继承非常相似,也是基于某个对象或某些信息创建一个对象,然后增强对象,最后返回对象。为了解决组合继承模式由于对此调用超类型构造函数而导致的低效率问题,可以将这个模式与组合继承一起使用。
- 寄生组合式继承,集寄生式继承和组合继承的优点于一身,是实现基于类型继承的最有效方式。