以下内容参考《JavaScript高级教程第三版中文版》
理解对象
属性类型
说到对象就不得不先说对象的属性了。正是因为有了属性,才赋予对象各种特征。属性是描述对象的特征的,那么有没有描述属性的特征呢?当然有!这种描述属性(property)的特征的,称之为特性(attribute)。这些特性是为了实现JavaScript的引擎的,因此在JS内部是无法访问到的。为了表示特性是内部值,因此把它们放到[[]]中表示,eg:[[Enumerable]]。特性解释完毕,接着说属性。
ES中有两种属性:数据属性和访问器属性。
数据属性
数据属性包含一个数据值的位置,通俗地讲就是属性名;数据属性包含4个描述其行为的特性:
特性名 | 说明 |
---|---|
[[Configurable]] | 表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。 |
[[Enumerable]] | 表示能否通过for-in循环返回属性 |
[[Writable]] | 能否修改属性的值 |
[[Value]] | 此属性的数据值 |
在直接在对象上定义的属性前三个属性值默认为true。
若要修改属性默认的特性须通过ES5提供的defineProperty(), 此方法接收三个参数:属性所在对象,属性名和一个描述符对象(以Configurable,Enumerable等特性作为属性的对象)。
eg:
var person = {};
Object.defineProperty(person, "name", {
writable: false,
value: "Nill"});
alert(person.name);//Nill
person.name = "Crow";//严格模式下修改会报错
alert(person.name);//Nill
访问器属性
访问器属性不包含数据值,
特性名 | 说明 |
---|---|
[[Configurable]] | 表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。 |
[[Enumerable]] | 表示能否通过for-in循环返回属性 |
[[Get]] | 读取属性时调用的函数 |
[[Set]] | 写入属性时调用的函数 |
访问器属性不能直接定义,须通过Object.defineProperty()来定义。
为对象定义多个属性
Object.defineProperties(),此方法接收两个参数,一个是要修改和添加属性的对象,一个是由该对象要修改和添加的属性组成的对象。
读取属性的特性
通过Object.getOwnPropertyDescriptor()方法,可以取得给定属性的描述符,该方法接收两个参数一个包含该属性的对象,一个便是该属性名。
创建对象
工厂模式
就是以函数来封装以特定接口创建对象的细节
举个例子即可:
function createPerson(name, age, job) {
var obj = new Object();
obj.name = name;
obj.age = age;
obj.job = job;
obj.sayName = function() {
alert(this.name);}
return obj;}
var person = createPerson("Crow", "23", "se");
这种方法创建的对象无法知道其类型。
此方法不推荐。
构造函数模式
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() {
alert(this.name);}
}
var person = new Person("Crow", "23", "se");
此方法和前面的工厂模式差不多,但是还有很多不一样的的地方:
1.没有显式地创建对象
2.直接将属性和方法赋值给this对象
3.没有返回值
通过构造函数创建的示例其constructor属性指向其构造函数。拿上面的例子:
alert(person.constructor == Person)//true
补充一下:构造函数与其他函数的唯一区别就在于调用方式不同。并且不存在创造构造函数的特俗语法,任何函数只要通过new来调用都可以当作构造函数。
eg:
//当构造函数使用
var person = new Person("Crow", "23", "se");
person.sayName();//"Crow"
//当普通函数使用
Person("Crow", "23", "se");//添加到window上
window.sayName()//"Crow"
//在另一个对象的作用域中使用
var o = new object();
Person.call(o, "Crow", "23", "se");
o.sayName();//"Crow"
上例中的构造函数模式的缺点便是创建多个实例时其方法不能共享;当然也可以这样改:
`function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
function sayName() {
alert(this.name);
}
var person = new Person(“Crow”, “23”, “se”);`
但是这样一来便没有封装性可言了,并且将只能被某个对象调用的方法放在全局作用域中也不太好。
接下来看原型模式。
原型模式
每个函数都包含一个prototype属性,该属性是一个指针,指向一个对象。这个对象的用途就是用来包含 由特定类型的所有实例所共享的
属性和方法。
eg:
function Person() {};
Person.prototype.name = "Crow";
Person.prototype.age = 21;
Person.prototype.job = "SE";
Person.prototype.sayName = function() {
alert(this.name);
}
var person = new Person();
person.sayName()//"Crow'
这种方式的结果就是所有的属性和方法都会被共享。但是实例一般要有属于自己的全部属性的,所以这种方法也不常用。
于是我们就结合构造函数模式和原型的模式,接着看组合使用构造函数模式和原型模式
组合使用构造函数模式和原型模式
这种方式就是用构造函数定义实例属性,用原型模式定义方法和共享的属性。eg:
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
}
Person.prototype = {
constructor: Person,//注意
sayName: function() {
alert(this.name);
}
}
var person1 = new Person("Crow", "23", "se");
var person2 = new Person("Phy", "21", "se");
通过对象字面量的形式为原型添加新的属性和方法等同于将Person.prototype 指向了一个新的对象,会导致其构造函数属性指向创建这个新的对象的构造函数Object(),因此需要显式的将其constructor重现指向Person。
动态原型模式
上面的方法将构造函数和原型分开写,看起来不好。动态原型就是把所有信息封装在构造函数中。通过检查某个该存在的方法是否有效,来决定是否初始化原型。
eg:
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
if(typeof this.sayName != function) {
Person.prototype.sayName = function(){
alert(this.name);
}
}
}
寄生构造函数模式
和工厂模式差不多,只不过在创建实例的时候使用new。
eg:
function Person(name, age, job) {
var obj = new Object();
obj.name = name;
obj.age = age;
obj.job = job;
obj.sayName = function() {
alert(this.name);}
return obj;}
var person = new Person("Crow", "23", "se");
构造函数在不返回值的情况下,默认返回新对象实例。通过在构造函数末尾添加return语句可以重写调用函数时的返回值。
稳妥构造函数模式
道格拉斯.克劳福德发明了JS中稳妥对象这个概念,所谓稳妥对象是指没有公共属性,其方法也不引用this的对象。稳妥对象最适合在安全的环境中或者防止对象的数据被其他程序改动时使用。
eg:
function Person(name, age, job) {
var o = new Object();
//此处可以定义私有变量和函数
//添加方法
o.sayName = function() {
alert(name);
}
return o;
}
var person = Person("Crow", "23", "se");
person.sayName()//"Crow"
继承
ES中的继承主要依靠原型链来实现。
通过原型链
先说一下构造函数,原型和实例的关系:每个函数都有一个原型对象,原型对象包含一个指向构造函数的指针,而每个实例都包含一个指向原型的内部指针。假如让一个原型对象等于另一个类型的实例,其结果就是:此时该原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含指向另一个构造函数的指针。假如这里的另一个原型又是另一个类型的实例,那么上述关系依然成立。如此层层递进,就构成来实例与原型之间的链条,这就是所谓原型链的基本概念。
eg:
function Super() {
this.pro = true;
}
Super.prototype.getSuperValue = function() {
return this.pro;
}
function Sub(){
this.SubPro = false;
}
Sub.prototype = new Super();
Sub.prototype.getSubValue = function(){
return this.SubPro;
}
var ins = new Sub();
alert(ins.getSuperValue);//true
借用构造函数(又叫伪造对象或经典继承)
function Super() {
this.color = ["red", "blue"];
}
function Sub() {
Super.call(this);
}
var ins1 = new Sub();
ins1.color.push("green");
alert(ins1.color);//"red", "blue", green"
var ins2 = new Sub();
alert(ins2.color);//"red", "blue"
上例中便是通过call(apply也行),在创建Sub的实例时调用了Super的构造函数。
组合继承(也叫伪经典继承)
它是结合原型链和借用构造函数发挥两者之长的一种继承模式。
eg:
function Super(name) {
this.name = name;
this.color = ["red", "blue"];
}
Super.prototype.sayName = function() {
alert(this.name);
}
function Sub(name, age) {
//继承属性
Super.call(this, name);
this.age = age;
}
//继承方法
Sub.prototype = new Super();
Sub.prototype.sayAge = function() {
alert(this.age);
}
var ins1 = new Sub();
ins1.color.push("green");
alert(ins1.color);//"red", "blue", green"
var ins2 = new Sub();
alert(ins2.color);//"red", "blue"
原型式继承
道格拉斯.克劳福德主张的原型式继承
function object(o) {
function F(){};
F.prototype = o;
return new F();
}
ES5中通过新增Object.create()方法规范这种继承。该方法接收两个参数一个作为新对象原型的对象,一个可选的为新对象定义额外属性的对象。
如果只是想让一个对象与另一个对象保持类似的情况下,原型式继承完全可以继承。但要记住一点引用类型的属性会被共享。
寄生式继承
寄生式继承是与原型式继承紧密相连的一种思路。该方法创建一个仅用于封装继承过程的函数,在函数内部以某种方式增强对象,最后返回对象。
eg:
function createAnother(ori) {
var clone = object(ori);//原型式继承中的那个方法
clone.sayHi = function(){
alert("hi");
}
return clone;
}
此例中的object函数不是必需的,任何能够返回新对象的函数都能替换它。
寄生组合式继承
寄生组合式继承的思想就是通过构造函数继承属性,通过原型链混成方式继承方法,基本模式如下:
function inheritPrototype(sub, sup){
var prototype = object(sup.prototype);//创建对象
prototype.constructor = sub;//增强对象
sub.prototype = prototype;//指定对象
}
eg:
function Super(name) {
this.name = name;
this.color = ["red", "blue"];
}
Super.prototype.sayName = function() {
alert(this.name);
}
function Sub(name, age) {
//继承属性
Super.call(this, name);
this.age = age;
}
//继承方法
inheritPrototype(Super, Sub);
Sub.prototype.sayAge = function() {
alert(this.age);
}
var ins1 = new Sub();
ins1.color.push("green");
alert(ins1.color);//"red", "blue", green"
var ins2 = new Sub();
alert(ins2.color);//"red", "blue"
寄生组合式继承为最理想的继承方式。