Menu
-
第6章 面向对象的程序设计
- 6.1 理解对象
- 6.1.1 属性类型
- 6.1.2 定义多个属性
- 6.1.3 读取属性的特性
- 6.2 创建对象
- 6.2.1 工厂模式
- 6.2.2 构造函数模式
- 6.2.3 原型模式
- 6.2.4 组合使用构造函数模式和原型模式
- 6.2.5 动态原型模式
- 6.2.6 寄生构造函数模式
- 6.2.7 稳妥构造函数模式
- 6.3 继承 》笔记写到这里
- 6.3.1 原型链
- 6.3.2 借用构造函数
- 6.3.3 组合继承
- 6.3.4 原型式继承
- 6.3.5 寄生式继承
- 6.3.6 寄生组合式继承
- 6.1 理解对象
第6章 面向对象的程序设计
6.1 理解对象
ECMA-262 把对象定义为:“无序属性的集合,其属性可以包含基本值、对象或者函数。” 严格来讲,这就相当于说对象是一组没有特定顺序的值。
- 6.1.1 属性类型
特性是内部值,放在两对儿方括号中,例如[[Enumerable]];
这些特性是为了实现 JavaScript 引擎用的,因此在 JavaScript 中不能直接访问它们。
-
- 数据属性
- 数据属性包含一个数据值的位置, 在这个位置可以读取和写入值。
- 数据属性有 4 个描述其行为的特性:
- [[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。
- [[Enumerable]]:表示能否通过 for-in 循环返回属性。
- [[Writable]]:表示能否修改属性的值。
- [[Value]]:包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。这个特性的默认值为 undefined。
- 对于直接在对象上定义的属性,它们的[[Configurable]]、[[Enumerable]]和[[Writable]]特性都被设置为 true,而[[Value]]特性被设置为指定的值。
- Object.defineProperty()方法:可以修改属性默认的特性;
- 接受3个参数:属性所在的对象、属性的名字和一个描述符对象(在描述符对象内修改4个特性);
var person = {};
Object.defineProperty(person, "name", {
writable: false,
value: "smm"
});
document.write(person.name);//smm
person.name = "xhn";
document.write(person.name);//smm
- [[Configurable]] 特性一旦设置为不可配置(false), 则再也不能设置为可配置(true);此时,再调用 Object.defineProperty()方法修改除 writable 之外的特性,都会导致错误:
var person = {};
Object.defineProperty(person, "age", {
configurable: false,
value:"12"
});
Object.defineProperty(person, "age", {
configurable: true,
value:"12"
});
//抛出异常:can't redefine non-configurable property "age"
- Configurable特性设置为false时, 如果writable也时false, 再调用修改writable为true也会抛出异常。 但是Configurable特性设置为false时, 如果writable设置的是true, 再调用修改writable为false则不会抛出异常;(书里有出入)。
// 以下不会异常
var person = {};
Object.defineProperty(person, "age", {
configurable: false,
writable:true
});
Object.defineProperty(person, "age", {
writable:false
});
// 以下会异常
var person = {};
Object.defineProperty(person, "age", {
configurable: false,
writable:false
});
Object.defineProperty(person, "age", {
writable:true
});
- 在调用 Object.defineProperty()方法时,如果不指定,configurable、 enumerable 和writable 特性的默认值都是 false。多数情况下,可能都没有必要利用 Object.defineProperty()方法提供的这些高级功能。
-
- 访问器属性
- 访问器属性不包含数据值;它们包含一对儿 getter 和 setter 函数;
- 在读取访问器属性时,会调用 getter 函数,这个函数负责返回有效的值;在写入访问器属性时,会调用setter 函数并传入新值,这个函数负责决定如何处理数据。
- 访问器属性 4 个特性:
- [[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为数据属性。对于直接在对象上定义的属性,这个特性的默认值为true。
- [[Enumerable]]:表示能否通过 for-in 循环返回属性。对于直接在对象上定义的属性,这个特性的默认值为 true。
- [[Get]]:在读取属性时调用的函数。默认值为 undefined。
- [[Set]]:在写入属性时调用的函数。默认值为 undefined。
- 访问器属性不能直接定义,必须使用 Object.defineProperty()来定义;
var book = {
_year: 2004,
edition: 1
};
Object.defineProperty(book, "year", {
get: function(){
return this._year;
}, // 通过get函数为属性“year”返回“_year”的值;
set: function(newValue){
if (newValue > 2004) {
this._year = newValue;
this.edition += newValue - 2004;
} // 通过set函数设置如果year赋了新值, 改变_year和edition两个属性值。
}
});
book.year = 2005;
document.write(book.edition); //2
- 只指定 getter 意味着属性是不能写,尝试写入属性会被忽略。
var book = {
_year: 2004,
edition: 1
};
Object.defineProperty(book, "year", {
get: function(){
return this._year;
},
// set: function(newValue){
// if (newValue > 2004) {
// this._year = newValue;
// this.edition += newValue - 2004;
// }
// }
});
book.year = 2005; //被忽略
document.write(book.edition); //1
- 只指定 setter 函数的属性也不能读,会返回 undefined;
var book = {
_year: 2004,
edition: 1
};
Object.defineProperty(book, "year", {
// get: function(){
// return this._year;
// },
set: function(newValue){
if (newValue > 2004) {
this._year = newValue;
this.edition += newValue - 2004;
}
}
});
book.year = 2005;
document.write(book.year); //undefined
document.write(book.edition); //2
- 6.1.2 定义多个属性
- Object.defineProperties()方法
- 利用这个方法可以通过描述符一次定义多个属性
- 这个方法接收两个对象参数:第一个对象是要添加和修改其属性的对象,第二个对象的属性与第一个对象中要添加或修改的属性一一对应。
- Object.defineProperties()方法
// 与上一节中定义的对象相同。唯一的区别是这里的属性都是在同一时间创建的。
var book = {};
Object.defineProperties(book, {
_year: {
value: 2004
},
edition: {
value: 1
},
year: {
get: function(){
return this._year;
},
set: function(newValue){
if (newValue > 2004) {
this._year = newValue;
this.edition += newValue - 2004;
}
}
}
});
re = []
for (i in book){
re.push(i, ">", book[i]);
}
document.write(re);//拿不到东西????
- 6.1.3 读取属性的特性
- Object.getOwnPropertyDescriptor()方法
- 可以取得给定属性的描述符,返回值是一个对象;
- Object.getOwnPropertyDescriptor()方法
var descriptor = Object.getOwnPropertyDescriptor(book, "_year");
document.write(descriptor.value); //2004
document.write(descriptor.configurable); //false
alert(typeof descriptor.get); //"undefined"
var descriptor = Object.getOwnPropertyDescriptor(book, "year");
document.write(descriptor.value); //undefined
document.write(descriptor.enumerable); //false
document.write(typeof descriptor.get); //"function"
6.2 创建对象
- 6.2.2 构造函数模式
- 创建构造函数
// 构造函数首字母大写,相当于其它语言的Class
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
// 如果不写prototype, 那么所有new出来的实例对向都会new一个新的sayName实例对象;加上prototype可以让所有Person的实例对象共享一个方法;
Person.prototype.sayName = function(){ // 定义方法
alert(this.name);
};
}
// 创建构造函数实例
var person1 = new Person("Nicholas", 29, "Software Engineer");
-
在默认情况下,所有原型对象都会自动获得一个 constructor(构造函数)属性,这个属性包含一个指向 prototype 属性所在函数的指针。就拿前面的例子来说,Person.prototype. constructor 指向 Person。而通过这个构造函数,我们还可继续为原型对象添加其他属性和方法。
- 原型与 in 操作符
- in操作符能判断该对象是否有指定的属性;
- 与hasOwnProperty("property_name") 相同作用,hasOwnProperty()只在属性存在于实例中时才返回 true,如实例里没有,原型对象里有,还是返回false;
- example:
// true 会先找person1里的name属性,如person1里没有,就会区原型对象里找name属性,
// 有的话返回true,没有返回false
document.write("name" in person1)
person1.hasOwnProperty("name") // true
- Object.keys()
- 返回对象里可迭代的属性;
function Person(){
this.name = "Nicholas";
this.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);}
};
p = new Person()
// job,sayName 找原型对象Own的可迭代的属性
document.write(Object.keys(Person.prototype));
// name,age 找实例对象自己Own的可迭代属性。
document.write(Object.keys(p));
Object.getOwnPropertyNames(Object) // return all properties
- 原型对象属性的封装语法:
- 为更好地封装原型的功能,更常见的做法是用一个包含所有属性和方法的
对象字面量来重写整个原型对象
- 为更好地封装原型的功能,更常见的做法是用一个包含所有属性和方法的
Person.prototype = {
name : "Nicholas",
age : 29,
job: "Software Engineer",
sayName : function () {
alert(this.name);
}
};
- 为原生对象的原型添加方法
String.prototype.startsWith = function (text) {
return this.indexOf(text) == 0;
};
var msg = "Hello world!";
alert(msg.startsWith("Hello")); //true
- 6.2.7 稳妥构造函数模式
- 稳妥构造函数遵循与寄生构造函数类似的模式,但有两点不同:一是新创建对象的实例方法不引用 this;二是不使用 new 操作符调用构造函数。按照稳妥构造函数的要求,可以将前面的 Person 构造函数重写如下
function Person (age) {
var o = new Object();
var age = age;
o.sayAge = function(){
return age;
};
return o;
}
var friend = Person(17);
document.write(friend.age, "
"); // undefined
// 只有通过方法才能get到内部的值
document.write(friend.sayAge()); // 17
6.3 继承
- 6.3.1 原型链
- 实例的 _proto_ ----> 构造函数的原型对象 ----> Object的原型对象 ----> null
- 构造函数和原型对象和 实例对象之间的关系
- 实例的 _proto_ ----> 构造函数的原型对象 ----> Object的原型对象 ----> null
function F(){
document.write(F.prototype.constructor===F)
}
f()// true
// 构造函数function(.prototype)-> 原型对象(.constructor)->构造函数
// 构造函数(new)-> 实例object (.__proto__)-> 原型对象(.constructor)->构造函数
-
原型链分析
function F1(){
this.name = "nameF1";
}
F1.prototype.getf1name = function(){
return this.name;
}
function F2() {
this.name = "nameF2";
}
F2.prototype = new F1();
F2.prototype.getf2name = function(){
return this.name;
}
F2.prototype.getf1name = function(){
return "newname";
}
var f2instance = new F2();
document.write(f2instance.getf1name());// newname; 重新定义继承自祖辈的方法, 会覆盖原方法
var f1instance = new F1();
document.write(f1instance.getf1name()); //nameF1;重新定义继承自祖辈的方法,不会改变祖辈自己的调用
通过原型链实现继承时,不能使用对象字面量创建原型方法。因为这样做就会重写原型链;
原型链的问题1:.引用类型值的原型属性会被所有实例共享;
function F1(){
this.colors = ["red", "blue", "green"];
}
function F2(){}
F2.prototype = new F1();
var instance1 = new F2();
instance1.colors.push("black");
document.write(instance1.colors); //"red,blue,green,black"
var instance2 = new F2();
document.write(instance2.colors); //"red,blue,green,black"
- 6.3.2 解决办法:借用构造函数;
function F1(){
this.colors = ["red", "blue", "green"];
}
function F2(){
F1.call(this);
}
var instance1 = new F2();
instance1.colors.push("black");
document.write(instance1.colors); //"red,blue,green,black"
var instance2 = new F2();
document.write(instance2.colors); //"red,blue,green"
原型链的问题2:创建子类型的实例时,不能向超类型的构造函数中传递参数。
-
6.3.3 组合继承:最常用的继承方式
- 将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。
- 使用原型链实现对原型属性和方
法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数
复用,又能够保证每个实例都有它自己的属性。
6.3.4 原型式继承:Object.create(o, [描述符]), 传入一个要copy的类型, 返回一个指向这个类型的其它类型的实例, 描述符同Object.defineProperties()的第二个参数。
-缺点:同原型继承, 共享引用类型值
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = Object.create(person, {
name: {
value: "Greg"
}
});
alert(anotherPerson.name); //"Greg"
page180
- ;