js006-面向对象的程序设计
面向对象(Object-Oriented,OO)的语言有一个标志,那就是他们都有类的概念。而通过类可以创建多个具有相同属性和方法的对象。
ECMA-262把对象定义为:无序属性的集合,其属性可以包含基本值、对象或者函数。(对象是一组没有特定顺序的值)。对象每个方法或者属性都有一个特定的名字,每个名字都映射到一个特定的值。对象可以想象为一个散列表,一组名值对,其中之可以是数据或者函数。
每个对象都是基于一个引用类型创建的。
6.1理解对象
创建自定义对象的最简单凡事就是创建一个Object的实例,再为它添加属性和方法。
var person = new Object(); person.name = "meimei"; person.age = 18; person.job = "software enginer"; person.sayName = function(){ alert(this.name); }; |
创建了一个名为person的对象,添加三个属性并对其赋值,和一个方法sayName用于显示this.name |
6.1.1属性类型
ECMAScript中有两种属性:数据属性和访问器属性
数据属性:包含一个数据值的位置,这个位置可以读取和写入值。一下四个特性描述数据属性的行为
[[Configurable]] |
表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。默认值为:true |
[[Enumerable]] |
表示能否通过for-in循环返回属性。默认值为:true |
[[Writable]] |
表示能否修改属性的值。默认值为:true |
[[Value]] |
包含这个属性的数据值,读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存到这个位置,这个特性的默认值为Undefined |
要修改属性的默认特性必须使用Object.defineroperty()方法。该方法接收三个参数:属性所在的对象,属性的名字和一个描述符对象。描述符对象的属性必须是:Configurable、Enumerable、Writable、Value,设置其中的一个或者多个值,可以修改其默认值。
访问器属性:访问器属性不包含数据值;它包含一对getter和setter函数(这两函数都不是必须的)
getter函数 |
读取访问器属性时调用,返回有效值 |
setter函数 |
写入访问器属性是调用,传入新值 |
访问器属性有以下四个特性:
[[Configurable]] |
表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。默认值为:true |
[[Enumerable]] |
表示能否通过for-in循环返回属性。默认值为:true |
[[Get]] |
读取访问器属性时调用的函数,默认值为:Undefined |
[[Set]] |
写入访问器属性是调用的函数,默认值为:Undefined |
访问器属性不能直接定义,必须使用Object.defineProperty()来定义:
var book = { _year: 2015, _edition:1 }; Object.defineProperty(book, "year", { get:function(){ if (newValue > 2015) { this.year = newValue; this.edition += newValue - 2015; } } }); book.year = 2016; alert(book.edition); //2 |
创建了一个book对象,并定义了连个默认属性, _(添加下划线)表示只能通过对象访问的属性。 |
6.1.2定义多个属性
通过Object.defineProperties()方法可以依稀定义多个属性。该方法接收两个参数,第一:对象要添加及修改其属性的对象;第二对象的属性与第一个对象中要添加或修改的属性一一对应。
如
var book ={}; Object.defineProperties(book,{ _year:{ writable:true, value:2015 }, edition:{ value:1 }, year:{ get.function(){ return this._year; }, set:function(newValue){ if (newValue > 2015) { this.year = newValue; this.edition += newValue - 2015; } } } }); |
定义了个book独享的两个数据属性_year和edition和一个访问器属性(year).最终的对象与上一节中定义的对昂相同,唯一的区别是这里的属性都是在同一时间创建的。 |
6.1.3读取属性的特性
var book ={}; Object.defineProperties(book,{ _year:{ value:2015; }, edition:{ value:1 }, year:{ get:function(newValue){ if (newValue > 2015) { this._year = newValue; this.edition += newValue - 2015; } } } }); var descriptor = Object.getOwnPropertyDescriptor(book, "_year"); alert(descriptor.value); //2015 alert(descriptor.configurable); //false |
使用Object.getOwnPropertyDescriptor()方法,可以取得给定属性的描述符,该方法接收两个参数:属性所在的对象和要读取其描述符的属性的名称。返回值是一个对象,如果是访问器属性,这个对象的属性有:Configurable、Enumerable、Get、Set 如果是数据属性,这个对象的属性有:Configurable、Enumerable、Writable、Value |
6.2 创建对象
工厂模式的一个变体是为了解决Object构造函数或者对象字面量创建单个对象的缺点:使用同一个借口创建很多对象,会产生大量的重复代码。
6.1.1 工厂模式
这种模式抽象了创建具体对象的过程。因为ECMAScript中无法创建类,所以用函数来封装特定接口创建对象的细节。
例子:
function createPerson(name, age, job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ alert(this.name); }; return o; } var person1 = createPerson("meimei", 18, "teacher"); var person2 = createPerson("lal-Alice", 18, "software enginer"); |
可以多次调用createPerson()函数 在控制台运行并测试: console.log(person2); Object {name: "lal- Alice", age: 18, job: "software enginer"} 但是工程模式没有解决对象识别的问题(怎样知道一个对象的类型) |
6.2.2构造函数模型
像Object和Array这样的原生构造函数,在运行时会自动出现在执行环境中。可以创建自定义的构造函数,从而定义自定义对象类型的属性和方法。例如:可以使用构造函数模式将前面的例子重写如下:
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; o.sayName = function(){ alert(this.name); }; } var person1 = new Person("meimei", 18, "teacher"); var person2 = new Person("lal", 18, "software enginer"); |
Person()取代了createPerson()函数。相对来说不同的是:Person()中 1、没有显式地创建对象; 2、直接将属性和方法赋给了this对象; 3、没有return语句。 |
构造函数(只要是通过new操作符来调用的,它就可以作为构造函数)的首字母一般为大写字母。非构造函数应该以一个小写字母开头。这样主要是为了区别于ECMAScript中的其他函数;因为构造函数本身也是韩式,不过,它可以用来创建对象。。
要创建一个Person新实例,必须使用new操作符。以这种方式调用构造函数会经历以下4个步骤:
创建一个新对象 |
将构造函数的作用域赋给新对象 |
执行构造函数中的代码(添加新属性) |
返回新对象 |
person1和 person2分别保存person的一个不同实例。它们都有一个属性:constructor(构造函数),该属性指向person。该属性是用来标识对象类型的。对于对象类型检测最好还是用:instanceof操作符。
1、将构造函数当做函数。
之前的Person()函数可以通过以下任意一种方式来调用
当做构造函数使用 |
var person2 = new Person("lal-Alice", 18, "software enginer"); person.sayName();//"lal-Alice" |
作为普通函数调用 |
person("lal-Alice" , 18, "software enginer"); window.sayName();//"lal-Alice" |
在另一个对象的作用域中调用 |
var o = new Object(); Person.call(o, "lal-Alice", 18, "software enginer"); o.sayName();//"lal-Alice" |
2、构造函数的问题
主要问题:每个方法都要在每个实例上重新创建一遍。
6.2.3原型模式
我们创建的每个函数都有一个原型属性,这个属性是一个指针,,指向一个对象。
(该部分笔记,之后补上来,先认真看书。2016/1/20 15:41)
6.2.4组合使用构造函数模式和原型模式
创建自定义类型的最常见的方式就是组合使用构造函数模式和原型模式。构造函数模式用于定义实例属性,原型模式用于定义方法和共享属性。结果:每个实例都有自己的一份实力属性副本,同时又共享着对方法的引用,最大限度的节省了内存。
如下代码:
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.fiiends = ["shelby", "Court"]; } Person.prototype = { constructor:person; sayName:function(){ alert(this.name); } } var person1 = new Person("meimei", 18, "teacher"); var person2 = new Person("lal", 18, "software enginer"); person1.fiiends.push("van"); alert(person1.fiiends); //"shelby,Court,van" alert(person2.fiiends); //"shelby,Court" alert(person1.fiiends == person2.fiiends); //false alert(person1.sayName == person2.sayName); //true |
实例属性是在构造函数中定义的,而由所有实例共享的属性constructor和方法sayName()则是在原型中定义的。而修改了person1.fiiends,并不会影响到person2.fiiends |
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(){ alert(this.name); } }; } var friend = new Person("lal-Alice" , 18, "software enginer"); friend.sayName(); |
加粗代码只有在sayName()方法不存在的情况下,才会添加到原型中,这段代码只有在初次构造函数时才会调用。 |
6.2.6寄生构造函数
之前的集中模式都不适用的情况下可以使用寄生(parasitic)模式。它的基本思想是创建一个函数,作用是封装创建对象的代码,然后再返回新创建的对象。
6.2.7寄生构造函数
所谓稳妥对象,指的是没有公共属性,而且其方法也不引用this的对象。
(版权声明:该博客为http://www.cnblogs.com/lal-fighting/原创发表,未经允许,不得转载或抄袭!!! )
6.3 继承
一般有两种继承方式:借口继承和实现继承。
ECMAScript只支持实现继承,而实现继承主要是通过原型链来实现的。
6.3.1原型链
基本思想:利用原型让一个引用类型继承另一个引用类型的属性和方法。
实现原型链有一种基本模式,其代码大致如下:
//原型链 function SupperType(){ this.property = true; SupperType.property.getSuperValue = function(){ return this.property; }; } function SubType(){ this.subproperty = false; } //继承了SuperType SubType.propertype = new SupperType(); SubType.propertype.getSuperValue = function() { return this.subproperty; }; var instance = new SupperType(); alert(instance.getSuperValue()); //true |
定义了两个类型:SuperType SubType。每个类型分别有一个属性和一个方法。 |
(未完成,笔记之后补上)
1、别忘记默认的原型
2、确定原型和实例的关系
3、谨慎地定义方法
6.3.2借用构造函数
也叫伪造对象或经典继承。
基本思想:在子类型构造函数的内部调用超类型构造函数。通过apply() 和call()方法也可以再新创建的对象上执行构造函数(将来)
1、传递参数:相对于原型链而言,借用构造函数有一个很大的优势,可以在子类型构造函数中向抄类型构造函数传递参数。
2、借用构造函数的问题:无法避免构造函数模式存在的问题—方法都在构造函数中定义,所以函数没法复用。而且,在超类型的原型定义的方法,对子类型而言是不可见的。结果所有类型都只能使用构造函数模式
6.3.3组合继承 (最常用的继承)
也叫伪经典继承,指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。
思路:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。
组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,,成为javaScript中最常用的继承
(笔记未更新上来)
6.3.4原型式继承
6.3.5寄生式继承
6.3.6寄生组合式继承
版权声明:该博客为http://www.cnblogs.com/lal-fighting/原创发表,未经允许,不得转载或抄袭!!!