JAVASCRIPT高程笔记-------第六章 面向对象的程序设计
理解对象的概念 js中的对象与其他 编程语言中的类不一样 ECMAscript 没有类的概念 ECMA-262 把对象定义为 “无序属性的集合,其属性可以包含基本值,对象或者函数” 也就是键值对 可以理解成散列表 示例简单的创建一个对象
var person = new Object(); person.name = "zhangsan" person.age = 20; person.sayhi = function (){ return "myName is "+this.name; } alert(person.sayhi()); // myName is zhangsan
6.1 属性类型 ---ECMAscript包含两种属性: 数据属性和访问器属性
6.1.1数据属性 ---简单来理解就是 描述对象属性的属性 相当于java中的元数据信息
[[Configgurable]] 表示属性是否可以通过delete 删除属性 从而重新定义属性 或者能否将属性修改为访问器属性 示例中定义的person对象的name、age以及sayhi()方法 默认都是为true
[[Enumerable]] 表示对象的该属性是否可以通过 for-in循环返回该属性 示例中 定义的person对象的name、age以及sayhi()方法 默认都是为true
[[Waitable]] 表示能否修改 该对象的属性的值 相当于java中 final 关键字修饰的常量 示例中 定义的person对象的name、age以及sayhi()方法 默认都是为true
[[Value]] 表示 该对象属性的实际值 例如示例中 person.name 的实际值就是等于"zhangsan" 这个特性的的值默认为undefined
r如何修改对象属性默认的特性 则必须使用ECMAscript5的 Object.defineProperty()方法 此方法接收三个参数 第一个参数为对象 第二个参数为对象的属性,第三个参数为对象属性的描述 示例
var person ={}; Object.defineProperty(person,'name',{ writable: false, //设置person对象的name属性为只读 configurable: true, Enumerable: true, value: "zhangsan" }); alert(person.name); // ‘zhangsan’ person.name = 'lisi'; //因为person对象name属性已经变成只读 所以此处修改无效,并且在严格模式下的js解析将会报错 alert(person.name); // ‘zhangsan’ Object.defineProperty(person,'name',{ writable: true, configurable: true, Enumerable: true, value: "zhangsan" }); person.name = 'lisi'; alert(person.name); // lisi
当对象属性的 [[Configgurable]] 特性 被修改后 将 无法还原 因为再次修改时会报错
6.1.2 访问器属性 -----相当于java中的属性私有化 提供get和set方法 示例
var person ={ name : 'zhangsan', _age: 25 }; Object.defineProperty(person,'age',{ get : function(){ return this._age; }, set : function(newValue){ if(newValue>0){ this._age = newValue; } } }); alert(person.age); // 25 person.age = -18; //由于设定了年龄不能为负数 所以此处的修改无效 alert(person.age); // 25
get和set 属性 也可以单独 设置 但若只设置了get 那么意味着该属性为只读 而不能写入 严格模式下 尝试写入操作则会报错
6.1.3 获取对象属性的特性描述 ECMAscript5中 可以设置对象属性的特性,亦可以通过 Object.getOwnPropertyDescriptor()方法来获取这些 特性信息 该方法返回的是一个对象 如果是访问器属性 则这个对象的属性为 configurable、enumerable、get和set 如果是 数据属性 则为 configurable、enumerable、waitable、value
6.2创建对象 简单理解成java中自定义对象
//工厂模式创建对象 function createPerson(name,age){ var o = new Object(); o.name = name; o.age = age; o.sayhi = function(){ alert('my name is ' + this.name); } return o; } var person1 = createPerson('zhangsan',27); var person2 = createPerson('lisi',25); //---------缺点 无法指定对象的类型------------------------ //构造函数模式 为了表示与普通函数的区别 函数首字母大写 function Person(name,age){ this.name = name; this.age = age; this.sayhi = function(){ alert('my name is ' + this.name); } } var person1 = new Person('zhangsan',27); var person2 = new Person('lisi',25);
构造函数与工厂模式创建对象的最大区别 就是 对象的类型鉴别
通过工厂模式创建的对象一定是Object类型 而通过构造函数创建的对象 既属于Object对象类型 也属于自定义对象类型 因为任何对象都是由Object对象继承而来的
构造函数模式创建对象带来的问题 由于每个函数都是一个特殊对象 因此person1和 person2的sayhi方法实际上引用了2个不同的对象 而只完成了相同的事情 就是告诉别人我叫啥 当 存在N个person对象的同时 也存在 N个sayh 对象 造成内存浪费
为解决这个问题 ------- 原型模式
function PersonF(name,age){ this.name = name; this.age = age; this.sayhi = function(){ alert('my name is ' + this.name); }; } var person1 = new PersonF('zhangsan',27); var person2 = new PersonF('lisi',25); alert(person1.sayhi === person2.sayhi); // false 因为每创建一个新的person对象 然后也创建了一个新的sayhi函数对象 所以他们不相等 //原型模式 function PersonP(name,age){ this.name = name; this.age = age; } PersonP.prototype.sayhi= function(){ alert('my name is ' + this.name); } var person1 = new PersonP('zhangsan',27); var person2 = new PersonP('lisi',25); alert(person1.sayhi === person2.sayhi) // true 原型模式下 所有被创建的person对象 将共享sayhi函数对象,所以这里相等
无论任何时候创建一个新的函数 js解析器默认会创建这个函数的prototype对象 默认情况下 这个prototype只包含 函数的构造器constructor属性 而其他方法 都是从Object继承而来 或者 后期手动添加进去 例如示例中我们给PersonP()这个对象的prototype中添加了sayhi方法
当一个实例对象中添加了与原型中重名方法时 则实际使用的是实例对象中的方法 ------相当于java的子类重写父类方法 示例
//原型模式 function PersonP(name,age){ this.name = name; this.age = age; } PersonP.prototype.sayhi= function(){ alert('my name is ' + this.name); } var person1 = new PersonP('zhangsan',27); var person2 = new PersonP('lisi',25); person1.sayhi(); // my name is zhangsan 来自于原型中的sayhi方法 person1.sayhi = function(){ //修改当前实例对象的sayhi方法 alert("my age = " + this.age); }; person1.sayhi(); // my age = 27 来自于当前实例中的sayhi方法 alert(person1.hasOwnProperty("sayhi")); // true 此方法是person1对象实例的方法 alert(person2.hasOwnProperty("sayhi")); // false 此方法来自PersonP 原型中
hasOwnProperty() 检测对象的方法是由原型继承而来还是属于本身实例
原型与in操作符
in 操作符 用于判断 某个属性 是否 属于 指定 对象 示例 alert(('name' in person1)); // 执行结果为true in 操作符 不管对象的属性 是实例对象本身的属性 还是由原型中继承而来 都会返回true
for-in 循环 时 返回的都是能通过对象访问的、可以枚举的属性 ,也包括原型中存在的属性 首先遍历的是对象构造函数内的属性 然后再往原型中查找属性
function PersonP(name,age){ this.name = name; this.age = age; this.sayhi= function(){ alert('my name is ' + this.name); } } PersonP.prototype.saybay= function(){ alert('my name is ' + this.name); } var person2 = new PersonP('lisi',25); person2.money = 50; for(var property in person2){ console.log(property); // 结果 name age sayhi money saybay 依次出现 } var propertys = Object.keys(person2); console.log(propertys); // 结果 ["name", "age", "sayhi", "money"]
Object.keys() 方法 接收一个对象参数 返回该对象上所有可枚举的属性
利用原型创建对象的简写方式
function PersonP(){ } PersonP.prototype ={ constructor : PersonP, // 此属性表明构造器 为 PersonP 对象的构造器 否则PersonP构造器默认由Object对象继承而来 是属于Object对象的构造器 但是会导致对象的构造器的[[enumerable]]特性 设置为true name : 'zhangsan', age : 18, sayhi : function(){ alert('my name is ' + this.name); } }
在ECMAscript 5 js解析引擎下 可以重设 对象的对象构造器为不可枚举
Object.definedProperty(PersonP.prototype,'constructor',{ enumerable : false, value: PersonP });
原型的动态性 ------js引擎 在原型中查找值的过程是一次搜索 因此 对对象原型的任何修改 都会立即在对象的实例上体现出来
当 对象创建以后 再重写其原型 则会出现 无法调用原有函数的现象 参考示例 原因在于 对象实例原有的原型对象已经被切断与对象联系 因此原型查找时无法获取对象的sayhi方法
function PersonP(){ } var person1 = new PersonP(); PersonP.prototype = { constructor : PersonP, // 此属性表明构造器 为 PersonP 对象的构造器 否则PersonP 由于是一个空对象 构造器默认由Object对象继承而来 name : 'zhangsan', age : 18, sayhi : function(){ alert('my name is ' + this.name); } } person1.sayhi(); // error
6.2.4组合使用构造函数模式和原型模式
将对象私有的属性放在构造函数中,而通用的方法放在原型中 例如 以person为例子 name、age、sex可能因人而已 但都会有打招呼 做自我介绍的方法sayhi方法 是通用的可以放在原型中
动态原型模式
function Person(name,age,sex){ this.name = name; this.age = age; this.sex = sex; if(typeof this.sayhi != 'function'){ Person.prototype.sayhi = function(){ alert(this.name); } } }
寄生构造函数模式----- 与工厂模式一致 在一个对象有多个实例的时候 而其中某一个实例需要特殊处理 ,在不影响其他实例属性数据的情况下可以使用此模式来完成操作 有点类似与java中的包装设计模式
function Person(name,age,sex){ var o = new Object(); o.name = name ; o.age = age; o.sex = sex; o.sayhi =function(){ alert(this.name); } return o; }
稳妥模式---- 没有公共属性,而且其他方法也不引用this对象 ,稳妥对象适合在一些安全的环境中使用(这些环境会禁用this和new) ,感觉像java中常量一样,一旦初始化就不可修改
function Person(name,age,sex){ var o = new Object(); o.sayhi =function(){ alert(name); //不使用this } return o; } var person = Person('zhangsan',18,'男'); //不使用new操作符创建对象 person.sayhi(); //除此方法 之外 没有任何其他方法可以访问 person对象的name属性
6.3 继承
一、原型链式继承 其本质 是重写原型对象,
function SuperType(){ this.property = true; } SuperType.prototype.getSuperValue = function(){ return this.property; } function SubType(){ this.subproperty = false; } SubType.prototype = new SuperType(); var sub = new SubType(); alert(sub.getSuperValue()); //true 通过原型继承而来的方法
原型继承注意的问题: 一、共享原型中的属性和方法 二、不能向超类型的构造函数中传递参数
二、借用构造函数
利用 apply() 和 call() 方法在新创建的对象上执行构造函数 此方式可以传递参数给超类的构造函数 问题点 一、不能继承超类原型的属性和方法,二、方法都在构造函数当中,无法实现函数复用
function SuperType(){ this.colors = ['red','blue','green']; } function SubType(){ SuperType.call(this); } var instance1 = new SubType(); instance1.colors.push('black'); alert(instance1.colors); //red,blue,green,black
三、组合式继承 ---又叫做伪经典继承
将原型链和借用构造函数组合到一块 利用原型链继承超类的原型属性和方法,而借用构造函数来实现对实例属性的继承
function SuperType(name){ this.name = name; this.colors = ['red','blue','green']; } SuperType.prototype.sayName = function(){ alert(this.name); } function SubType(name,age){ SuperType.call(this,name); this.age = age; } SubType.prototype = new SuperType(); SubType.prototype.sayAge = function(){ alert(this.age); } var instance1 = new SubType('zhangsan',28); instance1.colors.push('black'); alert(instance1.colors); //red,blue,green,black 继承自超类的属性 instance1.sayName(); //zhangsan 传递给超类的参数 超类原型中的方法 instance1.sayAge(); //28 自定义的方法
四、原型式继承
先建立一个临时性的构造函数 然后将传入的对象当做这个构造函数的原型,最后返回这个临时类型的新实例 从本质上看 是object对传入对象的一次浅复制
function object(o){ function F(){} F.prototype = o; return new F(); } var person = { name : 'zhangsan', friends : ['lisi','wangwu'] } var anotherPerson = object(person); anotherPerson.name = 'zhaotiezhu'; anotherPerson.friends.push('zhangxiaohua'); //找了个女朋友赵小花 alert(person.friends); //lisi,wangwu,zhangxiaohua
ECMA5.0 中 新增了 Object.create() 方法 规范了原型式继承 此方法接收两个参数,第一个参数为创建新对象的原型 另外一个用于修饰新对象中元数据的描述(可选)如下面代码
var person = { name : 'zhangsan', friends : ['lisi','wangwu'] } var person1 = Object.create(person); person1.name = 'lisi'; alert(person1.name); var person2 = Object.create(person,{ name :{ //writable : false, value : 'wangwu' } }); //第二个参数为新对象的元数据修饰参数 此处由于未设置 person2对象的name属性 为可写的 所以导致 在修改 person2的name属性不成功 alert(person2.name); //wangwu person2.name = 'zhaoliu'; alert(person2.name); //wangwu
寄生式继承 ----接收一个对象参数 在函数内部对此对象进行增强 然后返回这个函数--------------------------感觉像java中的实现接口????
function createAnother(original){ var clone = object(original); clone.sayhi = function(){ // alert("hi"); } return clone; } var person ={ name:'zhangsan', friends:['lisi','wangwu'] } var anotherPerson = createAnother(person); anotherPerson.sayhi(); //hi
6.3.6 寄生组合式继承
组合式继承的缺陷 ---无论任何条件下都需要调用两次超类型的构造函数 第一次为指定子类原型 第二次为子类构造函数中 借用超类的构造函数 第一次调用已经拥有了超类的属性 第二次调用 则是在子类上重新创建了与原型中相同属性 从而屏蔽了原型的属性
寄生组合式继承 通过借用构造函数来继承超类属性,通过原型链的混成形式来继承方法---------此方式体现在只调用一次超类的构造函数 避免了subtype.prototype上面创建多余的属性 ,原型链还能保持不变,因此能够正常使用 instanceof和isPrototypeOf()方法去确定类型
function inheritPrototype(subType,superType){ var prototype = Object(superType.prototype); //创建对象 prototype.constructor = subType; //增强对象 subType.prototype = prototype; //指定对象 } function SuperType(name){ this.name = name; this.friends = ['xiaohua','xiaoming'] } SuperType .prototype.sayhi = function (){ alert(this.name); } function SubType(name,age){ SuperType.call(this,name); this.age = age; } inheritPrototype(SubType,SuperType); SubType.prototype.sayAge = function(){ alert(this.age); } var person = new SubType('zhangsan',28); person.friends.push('xiaohong'); //找了个女朋友 小红 person.sayAge(); //28 person.sayhi(); //zhangsan alert(person.friends); //xiaohua,xiaoming,xiaohong
小结 : ECMAscript支持面向对象编程,但没有类和接口的概念 。对象可以在代码执行过程中创建和增强,因此具有动态性而非严格的定义的实体对象,
创建对象 1.工厂模式 定义一个函数 接收指定参数 函数内部创建Object对象 添加传入的参数作为属性,然后返回对象
2.构造函数模式 可以创建自定义引用类型 可以向内置对象一样使用new 操作符 缺点 每个成功都无法得到复用 包括方法
3.原型模式,使用构造函数的prototype属性来指定共有的属性和方法
对象继承 ECMAscript 主要通过原型链实现继承 原型链的构造是将一个实例赋值给另一个构造函数 ,这样通过构造函数 new处理的对象的原型指向超类的实例 实现 属性和方法复用
通常使用 组合式寄生继承 避免了多次调用超类的构造函数 以及属性的唯一性