一、何为对象
ECMA-262 把对象定义为:“无序属性的集合,其属性可以包含基本值、对象或者函数。” 简单来说:
- 对象是一组无序的key-value组成,
- 属性的值(value)可以是基本值、对象或者函数,即基本类型、引用类型
- 方法也是属性的一种,只是值为函数
属性:数据属性和访问器属性
数据属性:
特性:除了我们常见的value外,还有Configurable/Enumerable/Writable三个、boolean、默认为true
//修改:通过
Object.defineProperty(person,"age",{
configurable:true, //建议不修改,false,则delete perspn[name]报错,另外不能在可配了。
enumerable;true, //建议不修改
writable:false,
value:20
})
访问器属性:特性:Configurable/Enumerable/Get/Set
Get/Set 执行时调用函数,默认undefined
//获取属性的特性
var descriptor=Object.getOwnPropertyDescriptor(person,name);
alert(descriptor.value);
alert(descriptor.configurable);
二、创建函数
1、最基本、常用的方法构建
两种方法
//Object构造函数
var person =new Object()
person.name="张三";// 属性name
person.age=18; //属性age
person.sayWords=function (){ //方法sayWords()
alert(this.name+"永远"+this.age+“岁”);
};
//对象字面量
var person ={
name:"张三",
age:18,
sayWords:function (){
alert(this.name+"永远"+this.age+“岁”);
}};
使用:一般用在创建临时或少量对象时使用,但创建多个同一类型对象时,会出行大量重复的代码(key相同,value不同);因此,将value作为参数创建,工厂模式出现。
2、工厂模式
function createPerson(name,age){
var p =new Object();
p.name=name;
p.age=age;
p.sayWords=function (){
alert(this.name+"永远"+this.age+“岁”);
};
return p
}
var person1=createPerson("张三",18);
var person1=createPerson("李四",18);
问题:person1和person2的对象不是Person,而依旧是Object;
3、构造函数模式,
function Person(name,age){
this.name=name;
this.age=age;
this.sayWords=function (){
alert(this.name+"永远"+this.age+“岁”);
};
}
var person1=new Person("张三",18);
var person1=new Person("李四",18);
person1.constructor ==Person //true
此时,person1和person2的对象即是Person,又是Object。(一切对象都继承于Object)
问题:打个比方:你吃,我吃,都是吃,虽过程一样,但结果不一样。
因此,可以把方法移到构造函数外面。
function Person(name,age){
this.name=name;
this.age=age;
this.sayWords=sayWords;
}
function sayWords(){
alert(this.name+"永远"+this.age+“岁”);
}
但你会发现,方法sayWords就变成全局函数了,不仅仅Person能调用,其他对象也能调用。
4、原型模式
原型:每个函数(构造函数)都有一个 prototype(原型)属性,这个属性是一个指针,指向一个对象 ,而这个对象的用途是包含可以由特定类型的所有实例共享的属性(b包含prototype属性)和方法。
function Person(){} //构造函数
Person.prototype={ //构造函数的prototype指向一个对象
name:"张三",.age:18,sayWords:function (){
alert(this.name+"永远"+this.age+“岁”);
}};
//或者
Person.prototype.name="张三";
Person.prototype.age=18;
Person.prototype.sayWords=function (){
alert(this.name+"永远"+this.age+“岁”);
}
var person1= new Person(); 对象实例化
person1.sayWords(); 实例访问原型对象的属性和方法
var person1= new Person(); 对象实例化
person1.sayWords(); 实例访问原型对象的属性和方法
猜一猜:person1.sayWords() 和person2.sayWords() 的结果会怎样?
比方:你吃,我吃,食物(一个共享的对象)没了
怎样才能不一样呢?
var person1= new Person();
person1.name="李四";//覆盖原型中的name的属性
person1.sayWords()
var person1= new Person();
person2.name="王五";//覆盖原型中的name的属性
person1.sayWords()
补充:如何确定name是对象的属性还是原型的属性
function hasPrototypeProperty(object, name){
return !object.hasOwnProperty(name) && (name in object);//属性不是对象的属性,但又能访问
}
在默认情况下,所有原型对象都会自动获得一个 constructor(构造函数)属性,这个属性包含一个指向prototype 属性所在函数的指针。
function Person(){} //构造函数
Person.prototype. constructor= Person
//或者
Person.prototype={ //构造函数的prototype指向一个对象
constructor:Person,
}};
假如prototype不指向所在函数,而指向另外一个对象呢?
问题:1实例化是都默认取得原型的属性值
2原型的属性是共享的,但如果属性值是引用类型,
比方,我的食物、他的食物、如果我先出生(实例)你后出生,食物就都是你的了,悲剧,反过来就都是我的了,好事。但如果多生几个了,食物的影子都看不到了。
属性、方法在构造函数中,方法重复;属性方法在原型中,属性又不想共享;因此,折中吧。
5、构造函数+原型模式
即构造函数定义实例属性、原型定义方法和共享的属性
function Person(name,age){
this.name=name;
this.age=age;
}
Person.prototype={
constructor:Person,
sayWords:function (){
alert(this.name+"永远"+this.age+“岁”);
}
}
三、对象继承
许多 OO 语言都支持两种继承方式:接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。如前所述,由于函数没有签名,在 ECMAScript 中无法实现接口继承。 ECMAScript 只支持实现继承,而且其实现继承主要是依靠原型链来实现的。
1、原型链
概念:其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。简单回顾一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。那么,假如我们让原型对象等于另一个类型的实例,结果会怎么样呢?显然,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。
简单来说,就是将对象的原型指向另一个对象,不是指向自身。(前面讲原型构造对象的时候提过)
function SuperType(){ / /定义父类属性
this.property = true;
}
SuperType.prototype.getSuperValue = function(){ //定义父类方法
return this.property;
};
function SubType(){ //定义子类属性
this.subproperty = false;
}
//继承了 SuperType
SubType.prototype = new SuperType(); //指定对象原型,继承父类方法
SubType.prototype.getSubValue = function (){ //定义子类方法,
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue()); //true
问题:同样地,对象原型在自定另一个对象时,在该对象实例化的时候,属性和方法就会被共享,当属性不适合共享,因此,将属性和方法分开。
2、构造函数和原型组合
在子类的构造函数中向父类传递参数,在子类的原型中调用父类的原型及方法
function SuperType(name){
this.name = name;
}
function SubType(){
//继承了 SuperType的属性,同时还传递了参数
SuperType.call(this, "张三");
//实例属性
this.age = 18;
}
//继承方法
SubType.prototype = new SuperType(); //原型的对象指向父类,调用父类的方法
SubType.prototype.constructor = SubType; //原型的构造函数指向自身
SubType.prototype.sayAge = function(){
alert(this.age);
};
var instance = new SubType();
alert(instance.name); //"张三";
alert(instance.age); //18
问题:在对象原型指定父类对象时,只需要调用父类的方法,而属性不需要,却被实例化了一次,因此可以进一步优化
3、寄生组合式继承
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
改为:
SubType.prototype=Object.create(SuperType,prototype);
SubType.prototype.constructor = SubType;
四、总结
创建对象,构造函数构建属性、对象原型指定原型对象,并构建方法或重定义(父类)方法
步骤:1、定义构造函数,包括调用父类的构造函数,使用call或者apply
2、创建父类的原型为对象,并作为子类的原型对象
3、子类的原型的构造函数为自身的构造函数
4、创建对象原型的方法
ol.control.Control = function(options) { //对象的构造函数
ol.Object.call(this); //调用父类ol.Object的构造函数,
/**
* @protected
* @type {Element}
*/
this.element = options.element ? options.element : null; //属性element
......
}
ol.inherits(ol.control.Control, ol.Object);
//ol.inherits = function(childCtor, parentCtor) {
// childCtor.prototype = Object.create(parentCtor.prototype); //子类的原型对象指向父类的原型对象
// childCtor.prototype.constructor = childCtor; //子类的原型对象的构造函数
//};
/**
* Get the map associated with this control.
* @return {ol.PluggableMap} Map.
* @api
*/
ol.control.Control.prototype.getMap = function() { //方法
return this.map_;
};
知识来源于《JavaScript高级程序设计》一书,值得推荐