第6章 面向对象的程序设计

来源:互联网 发布:中国象棋软件让棋 编辑:程序博客网 时间:2024/04/29 00:35

6.2 创建对象

6.2.1 工厂模式

先定义一个函数,然后在函数中定义一个对象,该对象的各属性值为传入的参数值,最后返回这个对象,再通过定义变量调用该函数来创建对象。
例如:

function a(aa, bb, cc) {     var o = new Object();     o.name = aa;     o.age = bb;     o.job = cc;     o.sayName = function() {      console.log(this.name);     }     return o; } var p = a("top", 20, "singer"); var w = a("lazycat", 25, "font-in"); console.log(w);  // {name: "lazycat", age: 25, job: "font-in"} p.sayName();   // "top" w.sayName();   // "lazycat"

工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)。

6.2.2 构造函数模式

构造一个函数,然后将传入的参数值赋给this对象中相对应的属性,若是定义this对象的方法,可将方法定义在函数外部,然后对其赋值。
例如:

  function A(name, age, job) {    this.name = name;    this.age = age;    this.job = job;    this.sayName = sayName;  }  function sayName() {      alert(this.name);  }  var p1 = new A("w", 20, "student");  var p2 = new A("b", 25, "teacher");  alert(p1.constructor == A);  // true  alert(p2.constructor == A);  // true  alert(p1 instanceof Object);  // true  alert(p1 instanceof A);  // true  alert(p2 instanceof Object);  // true  alert(p2 instanceof A);  // true

p1,p2这两个对象都有一个constructor(构造函数)属性,该属性指向A,如下所示:

惯例,构造函数始终都应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头,主要为了区别于ECMAScript中的其他函数。

6.2.3 原型模式

例如

function Person() {}Person.prototype.name = "top";Person.prototype.age = "26";Person.prototype.job = "font-in";Person.prototype.sayName = function() {  alert(this.name);};var p1 = new Person();p1.sayName();   // topvar p2 = new Person();p2.sayName();   // topalert(p1.sayName == p2.sayName);  // true 通过原型调用的属性和方法是一样的

isPrototypeOf()方法,主要是用来判断实例跟构造函数的原型对象之间是否存在关系,返回一个boolean值。

ECMAScript5中新增了Object.getPrototypeOf()方法,来返回实例中[[Prototype]]的值。

Object.hasOwnProperty()方法可以检测一个属性是存在于实例中,还是存在于原型中。这个方法只在给定属性存在于对象实例中时,才回返回true。
实例p中存在name属性,故返回true;而实例a中的name属性是在原型中找到的,所以返回false。只有当实例重写对应的属性时,才回返回true。

Object.getOwnPropertyDescriptor(object,propertyname),返回一个对象,该属性的所有数据属性.

Object.keys()的用法,返回一个对象中可枚举属性名的数组。

Object.getOwnPropertyNames()的用法,返回实例中所有属性的名称,无论是否可以枚举。

function Person() {}Person.prototype.name = 'w';Person.prototype.age = 25;Person.prototype.job = 'font-in';Person.prototype.sayName = function() {  console.log(this.name);};var p = new Person();p.name = "l";var a = new Person();// isPrototypeOf()的用法,判断实例跟构造函数的原型对象之间的关系console.log(Person.prototype.isPrototypeOf(p));  // true// Object.getPrototypeOf(), 返回实例中[[Prototype]]所指向的原型对象console.log(Object.getPrototypeOf(p));  // Person.prototype的内容console.log(Person.prototype);  // 上下两个语句等价// Object.getOwnPropertyDescriptor(object, propertyname),返回一个对象,该属性的所有数据属性console.log(Object.getOwnPropertyDescriptor(p, "name")); // Object {value: "l", writable: true, enumerable: true, configurable: true}// Object.hasOwnProperty(),判断该属性是存在于实例中还是存在于原型对象中。若为true,则存在于实例中;若为false,则存在于原型对象中console.log(p.hasOwnProperty("name"));  // trueconsole.log(a.hasOwnProperty("name"));  // false// Object.keys()的用法,返回一个对象中可枚举属性名的数组console.log(Object.keys(Person.prototype));  // ["name", "age", "job", "sayName"]console.log(Object.keys(p));  // ["name"]// Object.getOwnPropertyNames()的用法,返回实例中所有属性的名称,无论是否可以枚举console.log(Object.getOwnPropertyNames(Person.prototype));  // ["constructor", "name", "age", "job", "sayName"]console.log(Object.getOwnPropertyNames(p));  // ["name"]

原型与in操作符

function Person() {}Person.prototype.name = "top";Person.prototype.age = "26";Person.prototype.job = "font-in";Person.prototype.sayName = function() {  alert(this.name);};var p = new Person();p.name = "l";console.log("name" in p);  // truedelete p.name;console.log("name" in p);  //true

只要原型中存在的该属性,无论实例中是否存在该属性,用in操作符时都会返回true。

用对象字面量方式简写原型对象

function Person() {}// Person.prototype.name = 'w';// Person.prototype.age = 25;// Person.prototype.job = 'font-in';// Person.prototype.sayName = function() {//  console.log(this.name);// };// 用对象字面量方法简写原型对象,将Person.prototype设置为等于一个以对象字面量形式创建的新对象,此时,constructor属性不在指向Person。Person.prototype = {  name: "w",  age: 25,  job: "font-in",  sayName: function() {    console.log(this.name);  }};var p = new Person();p.name = "l";var a = new Person();console.log(p instanceof Object);  // trueconsole.log(p instanceof Person);  // trueconsole.log(p.constructor == Person);  // falseconsole.log(p.constructor == Object);  // true

prototype对象被重写成为新的对象,因此constructor属性也变成了新对象的constructor属性(指向Object构造函数),不再指向Person函数。

若要保留constructor属性的原始值,可以通过将该属性赋值完成。
例如:

// 第一种方式Person.prototype = {  constructor: Person,  name: "w",  age: 25,  job: "font-in",  sayName: function() {    console.log(this.name);  }};// 此时constructor属性的[[Enumerable]]特性会被设置为true,从而可以通过for-in循环枚举出来,而原生的constructor属性是不可枚举的,因此可以用第二种方式。// 第二种方式,通过Object.defineProperty()Person.prototype = {  name: "w",  age: 25,  job: "font-in",  sayName: function() {    console.log(this.name);  }};Object.defineProperty(Person.prototype, "constructor", {  enumerable: false,  value: Person});var p = new Person();p.name = "l";var a = new Person();console.log(p instanceof Object);  // trueconsole.log(p instanceof Person);  // trueconsole.log(p.constructor == Person);  // trueconsole.log(p.constructor == Object);  // false

原型的动态性

定义了实例之后,不可重写整个原型对象,否则调用实例属性的时候,会搜索不到内容。

function Person() {}var p = new Person();Person.prototype = {  constructor: Person,  name: "w",  age: 25,  job: "font-in",  sayName: function() {    console.log(this.name);  }};var a = new Person();// p.sayName();  // 单独运行时,报错a.sayName(); // 单独运行时,返回 w

重写原型对象切断了现有原型与任何之前已经存在的对象实例之间的联系;该实例们引用的仍然是最初的原型。

原生对象的原型

所有原生引用类型(Object、Array、String等等)都在其后遭函数的原型上定义了方法。通过原生对象的原型,不仅可以取得所有默认方法的引用,而且也可以定义新方法;可以像修改自定义对象的原型一样修改原生对象的原型,因此可以随时添加方法。例如:给基本包装类型String添加了一个名startsWith()的方法

String.prototype.startsWith = function(text) {  return this.indexOf(text) == 0;};var msg = "lazycat";console.log(msg.startsWith("l")); // trueconsole.log(msg.startsWith("y")); // false

原型对象的问题

  1. 省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都取得相同的属性值。
  2. 原型模式的最大问题是由其共享的本性所导致的,尤其是对于包含信用类型值的属性来说。例如:
function Person() {}Person.prototype = {  constructor: Person,  name: "w",  age: 25,  job: "font-in",  friends: ["top", "yy"],  sayName: function() {    console.log(this.name);  }};var p1 = new Person();var p2 = new Person();// p1.friends = Person.prototype.friends; 无效,除非p1.friends有自己的初始属性值p1.friends.push("tom");console.log(Person.prototype.friends);  // ["top", "yy", "tom"]console.log(p1.friends);  // ["top", "yy", "tom"]console.log(p2.friends);  // ["top", "yy", "tom"]

由于p1实例中没有friends属性,因而会到原型对象中去搜索friends属性。因此这里做的push操作,实际上是在原型对象中进行的,因为会影响到p2实例中的friends属性。

6.2.4 组合使用构造函数模式和原型模式

创建自定义类型的最常见方式,就是组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性。

function Person(name, age, job) {  this.name = name;  this.age = age;  this.job = job;  this.friends = ["a", "b"];}Person.prototype = {  constructor: Person,  sayName: function() {    console.log(this.name);  }};var p1 = new Person("w", 20, "font-in");var p2 = new Person("l", 25, "student");p1.friends.push("c");console.log(p1.friends);  // ["a", "b", "c"]console.log(p2.friends);  // ["a", "b"]console.log(p1.friends == p2.friends);  // falseconsole.log(p1.sayName == p2.sayName);  //true

6.2.5 动态原型模式

通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型。

// 构造函数中没有定义sayName方法 function Person(name, age, job) {  this.name = name;  this.age = age;  this.job = job;  this.friends = ["a", "b"];  if (typeof this.sayName != "function") {    Person.prototype.sayName = function() {        console.log(this.name);    };  }}var p = new Person("w", 20, "font-in");console.log(Person.prototype.sayName);  // 返回原型中sayName的函数体// 构造函数中定义了sayName方法 function Person(name, age, job) {  this.name = name;  this.age = age;  this.job = job;  this.friends = ["a", "b"];// 若函数中定义了sayName方法,则不会再原型中添加sayName方法this.sayName = function(){    console.log("aaa");};  // 只有在sayName方法不存在的情况下,才会被添加到原型中  if (typeof this.sayName != "function") {    Person.prototype.sayName = function() {        console.log(this.name);    };  }}var p = new Person("w", 20, "font-in");console.log(Person.prototype.sayName);  // undefined

6.2.6 寄生构造函数模式

function Person(aa, bb, cc) {     var o = new Object();     o.name = aa;     o.age = bb;     o.job = cc;     o.sayName = function() {        console.log(this.name);     }     return o; } var p = new Person("top", 20, "singer"); p.sayName();  // top

除了使用new操作符并把使用的包装函数叫构造函数之外,这个模式跟工厂模式其实是一模一样的。

function A() {  var v = new Array();  v.push.apply(v, arguments);  // arguments = ["red", "blue", "yellow"]  v.toPipedString = function() {    return this.join("|");  };  return v;}var c = new A("red", "blue", "yellow"); // c = ["red", "blue", "yellow"]console.log(c.toPipedString());  // red|blue|yellow

6.2.7稳妥构造函数模式

指的是没有公共属性,而且其方法也不引用this的对象。稳妥构造函数模式不使用new操作符调用构造函数。

function Person(name, age, job) {  // 创建要返回的对象  var o = new Object();  o.sayName = function() {    console.log(name);  };  return o;}var p = Person("lazycat", 20, "font-in");  // 类似工厂模式的p.sayName();  // lazycat

6.3继承

ECMAScript只支持实现继承,而且其实现继承主要是依靠原型链来实现的。

6.3.1 原型链

让原型对象等于另一个类型的实例,此时原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。如此层层递进,就构成了实例与原型的链条,这就是所谓原型链的基本概念。

1.默认的原型

所有引用类型默认都继承了Object,而这个继承也是通过原型链实现的。所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototyoe。

2.确定原型和实例的关系

有两种方式:instanceof 和 isPrototypeOf()方法,只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型。
例如:

function SuperType() {  this.property = true;}SuperType.prototype.getSuperValue = function() {  return this.prototype;}function SubType() {  this.subproperty = false;}SubType.prototype = new SuperType();SubType.prototype.getSubValue = function() {  return this.subproperty;}var instance = new SubType();console.log( instance instanceof Object);  // trueconsole.log( instance instanceof SuperType);  // trueconsole.log( instance instanceof SubType);  // trueconsole.log(Object.prototype.isPrototypeOf(instance));  // trueconsole.log(SuperType.prototype.isPrototypeOf(instance));  // trueconsole.log(SubType.prototype.isPrototypeOf(instance));  // true

3.谨慎地定义方法

子类型有时候需要覆盖超类型中的某个方法,或者需要添加超类型中不存在的某个方法时,一定要将给原型添加方法的代码放在替换原型的语句之后。
即:

function SuperType() {  this.property = true;}SuperType.prototype.getSuperValue = function() {  return this.prototype;}function SubType() {  this.subproperty = false;}SubType.prototype = new SuperType();  // 必须为第一步SubType.prototype.getSubValue = function() {  // 再给该原型添加方法  return this.subproperty;};SubType.prototyp.getSuperValue = function() {  // 或者是重写超类型中的方法  return false;};var instance = new SubType();console.log(instace.getSuperValue());  // false

==通过原型链实现继承时,不能使用对象字面量创建原型方法。因为这样做会重写原型链。==
例如;

function SuperType() {  this.property = true;}SuperType.prototype.getSuperValue = function() {  return this.prototype;}function SubType() {  this.subproperty = false;}SubType.prototype = new SuperType();// 对象字面量方法重写了SubType.prototype,从而会导致上面的语句无效,此时SubType不再继承SuperTypeSubType.prototype = {  getSubValue: function() {    return this.subproperty;  },  getSuperValue: function() {    return false;  }};var instance = new SubType();console.log(instance.getSuperValue());  // false

4.原型链的问题

1.对于共享的引用类型的属性,会导致其他实例的属性值也发生改变,例如:

function SuperType() {  this.colors = ["red", "blue", "yellow"];}function SubType() {  this.subproperty = false;}SubType.prototype = new SuperType();var v1 = new SubType();v1.colors.push("black");  // 影响了SubType.prototype.colors的值,从而会使其他实例的colors属性值改变console.log(v1.colors);  // ["red", "blue", "yellow", "black"]console.log(SubType.prototype.colors);  // ["red", "blue", "yellow", "black"]var v2 = new SubType();console.log(v2.colors);  // ["red", "blue", "yellow", "black"]

2.在创建子类型的实例时,不能向超类型的构造函数中传递参数。

6.3.2 借用构造函数(伪造对象或经典继承)

通过使用apply()和call()方法也可以在新创建的对象上执行构造函数,例如:

function SuperType(name) {  this.name = name;  this.colors = ["red", "blue", "yellow"];}function SubType(name) {  // 继承了SuperType  SuperType.call(this, name);}var instance1 = new SubType("lazycat");instance1.colors.push("black");console.log(instance1.colors);  // ["red", "blue", "yellow", "black"]console.log(instance1.name);  // "lazycat"var instance2 = new SubType("top");console.log(instance2.colors);  // ["red", "blue", "yellow"]console.log(instance2.name);  // "top"

借用构造函数相对于原型链的一个优势是,可以在子类型构造函数中向超类型构造函数传递参数。

6.3.3 组合继承

组合继承,有时候也叫作伪经典继承,指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。

function SuperType(name) {  this.name = name;  this.colors = ["red", "blue", "yellow"];}SuperType.prototype.sayName = function() {  alert(this.name);}function SubType(name, age) {  // 继承了SuperType  SuperType.call(this, name);  this.age = age;}SubType.prototype = new SuperType();SubType.prototype.constructor = SubType;SubType.prototype.sayAge = function() {  alert(this.age);};var instance1 = new SubType("w", 26);instance1.colors.push("black");console.log(instance1.colors);  // ["red", "blue", "yellow", "black"]instance1.sayName();  // "w"instance1.sayAge();  // 26var instance2 = new SubType("l",29);  console.log(instance2.colors);  // ["red", "blue", "yellow"]instance2.sayName();  // "l"instance2.sayAge();  // 29

组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为JavaScript中最常用的继承模式。

6.3.4 原型是继承

借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。通过以下函数实现:

function object(o) {  function F() {}  F.prototype = o;  return new F();}

在Object()函数内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。
例如:

  var person = {    name: "lazycat",    friends: ["top", "ab", "weiwei"]  // 实例共享的属性  };  var anotherPerson = object(person);  // 调用之前定义的函数  anotherPerson.name = "w";  anotherPerson.friends.push("loveweiwei");  var an = object(person);  an.name = "luowei";  an.friends.push("loveyou");  console.log(person.friends); // ["top", "ab", "weiwei", "loveweiwei", "loveyou"]  console.log(person.name);  // "lazycat"

ECMAScript5中新增了Object.create()方法,省略了函数的定义,该方法接收两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。例如:

var person = {    name: "lazycat",    friends: ["top", "ab", "weiwei"]  // 实例共享的属性};var anotherPerson = Object.create(person);  // 直接使用ES5定义的方法即可anotherPerson.name = "w";anotherPerson.friends.push("loveweiwei");var an = Object.create(person, {name: {    value: "luowei"}}); // an.name = "luowei"; an.friends.push("loveyou"); console.log(person.friends); // ["top", "ab", "weiwei", "loveweiwei", "loveyou"] console.log(person.name);  // "lazycat"

6.3.6 寄生组合式继承

所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混合形式来继承方法。其背后的基本思路是:不必为了指定子类型的原型而调用超类型的构造函数,所需要的无非就是超类型原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。

 function inheriPrototype(subType, superType) {    var prototype = Object(superType.prototype);    prototype.constructor = subType;    subType.prototype = prototype; } 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; } inheriPrototype(SubType, SuperType); SubType.prototype.sayAge = function() {    alert(this.age); }

该方法只调用了一次超类型构造函数,避免了多次调用。


——来自《JavaScript高级程序设计》

0 0
原创粉丝点击