面向对象的程序设计
1. 面向对象具有类的概念,通过类可以创建任意多个相同的属性和方法。
2. ECMA-262把对象定义为:无序属性的集合,其属性包含基本值、对象或函数。
3. ECMAScript中有两种属性:数据属性,访问器属性。
4. 数据属性: configurable, enumerable, writable, value
5. 修改属性默认的特性: Object.defineProperty();
6. Object.defineProperty(person, name, {
writable: false,
value: 'xhk'
})
person.name // 'xhk'
person.name = 'coco';
person.name // 'xhk'
7. 访问器属性不包含数据值,它们包含一堆getter, setter函数(但是他们都不是必须的)。在读取访问器属性的时候,会调用gettter函数,这个函数负责返回有效的值; 在写入访问器属性的时候,调用setter函数,这个函数负责决定如何处理数据。
8. 访问器属性:configurable,enumerable, get, set。访问器属性不能直接定义,需要使用Object.defineProperty();
9. 定义多个属性Object.defineProperties();
10. 读取属性的特性: Object.getOwnPropertyDescriptor(); 获取给定属性的描述符,参数一,属性所在的对象,参数二,要读取其描述符的属性名称。返回值是一个对象,如果是访问器属性,返回configurable,enumerable, get, set;如果是数据属性,则返回configurable,enumerable,writable, value.
11. 创建对象
使用同一个接口创建很多对象,会产生大量的重复代码。
1)工厂模式:用函数来封装以特定接口创建对象的细节,可以创建多个相似的对象。没有解决对象识别问题(即怎样知道一个对象的类型)。
function ceratePerson (name, age, gender) {
var o = {};
o.name = name;
o.age = age;
o.gender = gender;
o.sayName = function() {
console.log(this.name);
}
}
var xhk = createPerson('xhk', 36, '男');
xhk // {name: "xhk", age: 36, gender: "男", sayName: ƒ}
xhk.sayName() // xhk
2)构造函数模式,创建自定义的构造函数,从而定义自定义对象类型的属性和方法。
function Person(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
this.sayName = function() {
console.log(name);
}
}
var p1 = new Person('xhk', 36, '男'); // Person {name: "xhk", age: 36, gender: "男", sayName: ƒ}
构造函数模式和工厂模式的区别:
a. 没有显示的创建对象;
b. 直接将属性和方法赋给了this对象;
c. 没有return语句;
要创建Person的新势力,必须使用new操作符,以这种方式调用构造函数实际上会经历一下四个步骤:
a. 创建一个新对象;
b. 将构造函数的作用域赋给新对象(因此this指向了这个新对象);
c. 执行构造函数中的代码(为这个新对象添加属性);
d. 返回新对象;
p1.constructor // Person
p1 instanceof Object // true
p1 instanceof Person // true
任何函数只要通过new操作符来调用,就可以当做构造函数;而任何函数如果不通过new操作符来调用,跟普通函数没什么两样。
当做普通函数调用:属性和方法都被添加给window对象了。
Person('love', 1, 'null'); // window.sayName() // 'love';
Person.call(o, 'lover', 1, null); // {name: "lover", age: 1, gender: null, sayName: ƒ}
Person.apply(o, ['lover', 1, null]); // {name: "lover", age: 1, gender: null, sayName: ƒ}
function Person(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
this.sayName = sayName;
}
function sayName() {
console.log(this.name);
}
var p1 = new Person('xhk', 36, '男'); // Person {name: "xhk", age: 36, gender: "男", sayName: ƒ}
p1.sayName() // 'xhk'
在全局作用域中定义的函数实际上只能被某个对象使用。
3)原型模式: 我们创建的每一个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象。这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。prototype是通过调用构造函数而创建的那个对象实例的原型对象。好处是让所有对象实例共享它所包含的属性和方法。
function Person () {};
Person.prototype.name = 'xhk';
Person.prototype.age = 36;
Person.prototype.gender = 'man';
Person.prototype.sayName = function() {
console.log(this.name);
}
var p1 = new Person();
p1.name // 'xhk'
所有实例都是同一组属性和同一个sayName方法。
Person.prototype.isPrototypeOf(p1) // true 判断实例与构造函数的原型对象之间的关系
Object.getPrototypeOf(p1).name // 'xhk' 获取属性的值
Object.getPrototypeOf(p1) == Person.prototype 返回的对象实际就是这个对象的原型
function Person () {};
Person.prototype.name = 'xhk';
Person.prototype.age = 36;
Person.prototype.gender = 'man';
Person.prototype.sayName = function() {
console.log(this.name);
}
var p1 = new Person ();
p1.name // 'xhk'
p1.name = 'coco';
p1.name // 'coco'
var p2 = new Person();
p2.name // 'xhk'
delete操作符完全删除实例中的属性,需要继续访问原型中的属性。
delete p1.name // true
p1.name // 'xhk'
p1.hasOwnProperty('name'); // false
p1.name = 'coco';
p1.hasOwnProperty('name'); // true
var o = {
toString: function() {
return 'my husband';
}
}
for (var prop in o) {
if(prop === 'toString') {
console.log('Found toString');
}
} // 'Found toString'
取得对象上所有可枚举的实例属性使用Object.keys() 接收一个对象作为参数,返回一个包含所有可枚举数据的字符串数组。
function Person () {};
Person.prototype.name = 'xhk';
Person.prototype.age = 36;
Person.prototype.gender = 'man';
Person.prototype.sayName = function() {
console.log(this.name);
}
var keys = Object.keys(Person.prototype);
keys // ["name", "age", "gender", "sayName"]
p1.name = 'coco';
p1.age = 24;
var keys = Object.keys(p1);
keys // ["name", "age"]
得到所有实例属性: Object.getOwnPropertyNames();
Object.getOwnPropertyNames(Person.prototype); // ["constructor", "name", "age", "gender", "sayName"]
function Person() {};
Person.prototype = {
name: 'xhk',
age: 36,
gender: 'man',
sayName: function() {
console.log(this.name);
}
}
p1.constructor == Person // false
p1.constructor == Object // true
p1.constructor 不再是Person (因为上面这种语法完全重写了prototype对象),可以使用下面方法将它设置回特定的值。
function Person() {};
Person.prototype = {
constructor: 'Person',
name: 'xhk',
age: 36,
gender: 'man',
sayName: function() {
console.log(this.name);
}
}
12. 创建自定义类型的最常见方式:组合使用构造函数模式和原型模式
构造函数用于定义实力属性,原型模式用于定义方法和共享的属性。
function Person(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
this.friends = ['coco', 'coco1']
};
Person.prototype = {
constructor: 'Person',
sayName: function() {
console.log(this.name);
}
}
p1.friends.pop('coco1');
p1 // Person {name: "xhk", age: 36, gender: "gender", friends: Array(1)}
var p2 = new Person('xsh', 36, 'gender');
p2.friends // ["coco", "coco1"]
13. 动态原型模式
function Person(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
this.friends = ['coco', 'coco1'];
if(typeof this.sayName !== 'function') {
Person.prototype.sayName = function() {
console.log(this.name);
}
}
}
var p1 = new Person('xhk', 36, 'gender');
p1.sayName() // 'xhk'
14. 继承
接口继承:只继承方法签名
实现继承:继承实际方法
ECMAScript只支持实现继承,实现继承主要是依靠原型链来实现。
1)原型链
原型链作为实现继承的主要方法
利用原型让一个引用类型继承另一个引用类型的属性和方法。
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
}
function SubType () {
this.subProperty = false;
}
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function() {
return this.subProperty;
}
var instance = new SubType();
instance.getSuperValue() // true
instance.getSubValue() // false
所有函数的默认原型都是Object实例
原型与实例之间的关系
instance instanceof SubType // true
instance instanceof SuperType // true
instance instanceof Object // true
Object.prototype.isPrototypeOf(instance) // true
SuperType.prototype.isPrototypeOf(instance) // true
SubType.prototype.isPrototypeOf(instance) // true
只要测试的是实例与原型链中出现过的构造函数,结果就会返回true。
给原型添加方法的代码一定要放在替换原型的语句之后。
15. 借用构造函数
在子类型的构造函数内部调用超类型构造函数,使用apply(), call()
function SuperType() {
this.colors = ['red', 'green', 'blue'];
}
function SubType() {
SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push('black');
var instance2 = new SubType();
instance2.colors // ["red", "green", "blue"]
instance1.colors // ["red", "green", "blue", "black"]
SubType的每个实例都有自己的一个colors副本
1)传递参数
function SuperType(name) {
this.name = name;
}
function SubType() {
SuperType.call(this, 'xhk');
this.age = 36;
}
var instance = new SubType();
instance.age // 36
instance.name // 'xhk'