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

属性类型

1.数据属性特性

[[Configurable]](可配置):能否通过delete删除属性重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。

[[Enumerable]](可枚举):能否通过for-in循环返回属性。

[[Writable]](可写):能否修改属性的值。

[[Value]](值):包含这个属性的数据值。读写属性值都再这,默认为undefined。

修改属性默认的特性,必须使用Object.defineProperty()方法。

Object.defineProperty(): 接受三个参数:属性所在的对象,属性的名字,描述符对象。属性描述符对象必须是 configurable、enumberable、writable、value。其中的一或多个。

var person = {};
Object.defineProperty(person, "name", {
    writable: false,               //设置为不可写的。(不能修改name的值)
    configurable: false,           //设置为不可配置的,
    value: "Nicholas"
});
console.log(person.name);
// Nicholas
delete person.name; console.log(person.name); 
// Nicholas
person.name = "Gray"; //修改了name的值 console.log(person.name); //修改的值无效  

// Nicholas

在调用Object.defineProperty(),如果不指定,configurable,wirtable,enumerable默认值为false.

2、访问器属性

访问器属性不包含数据值。包含一对getter函数和setter函数。

[[Configurable]](可配置):能否通过delete删除属性重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。

[[Enumerable]](可枚举):能否通过for-in循环返回属性。

[[Get]]:读取属性时调用的函数。

[[Set]]: 写入属性时调用的函数。

var book = {
  _year: 2004,           //_表示只能通过对象方法访问的属性
   edition: 1
 } 

Object.defineProperty(book, "year", {
     get: function() {
         return this._year;
     },
     set: function(newValue) {
        if(newValue > 2004) {
          this._year = newValue;
          this.edition += newValue - 2004;
        }
    }
});

book.year = 2005;
console.log(book.edition);
// 2

IE9之前创建访问器属性的方法:

var book = {
    _year: 2004,
    edition: 1
}
book.__defineGetter__("year", function(){
     return this._year;
});

book.__defineSetter__("year", function(newValue) {
    if(newValue > 2004) {
      this._year = newValue;
      this.edition +=newValue - 2004;
    }
});

book.year = 2005;
console.log(book.edition);
// 2

Object.defineProperties(): 通过描述符一次定义多个属性。接受2个对象参数:第一个对象是要添加和修改其属性的对象,第二个对象的属性与第一个对象中要添加或修改的属性对应。

var book = {};

Object.defineProperties(book, {
     _year: {
        writbale: true,
        value: 2004
    },
    edition: {
       writable: true,
       value: 1
   },

   year: {
      get: function() {
         return this._year;
      },

     set: function(newValue) {
       if(newValue > 2004) {
         this._year = newValue;
         this.edition += newValue - 2004;
       }
    }
  }

});

book.year = 2007;
console.log(book.edition);
var descriptor = Object.getOwnPropertyDescriptor(book,"_year");
console.log(descriptor.value);
//  4
//  2004
//  false

创建对象

1.工厂模式

工厂模式解决了创建多个相似对象的问题。没有解决对象识别的问题(怎样指定一个对象的类型)。

function createPerson(name, age, job) {
  var o = new Object();
  o.name = name;
  o.age = age;
  o.job = job;
  o.sayName = function() {
    console.log(this.name);
   };
  return o;
}

var person1 = createPerson("zhangsan", 28, "Software Engineer");
var person2 = createPerson("李四", 25, "Docter");
console.log(person1.name);
console.log(person2.name);
//  zhangsan
//  李四

2.构造函数模式


构造函数始终都应该以一个大写字母开头,非构造函数以小写字母开头。以这种方式定义的构造函数是定义再Global对象,再浏览器即window对象下。

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function() {
        console.log(this.name);
    }
}
var person3 = new Person("Nicholas", 27, "Software Enginner");   var person4 = new Person("Gray", 25, "Docter");
console.log(person3.name);
console.log(person4.name);

console.log(person3 instanceof Object);
console.log(person3 instanceof Person);
console.log(person4 instanceof Object);
console.log(person4 instanceof Person);

// Nicholas
// Gray
// true
// true
// true
// true

调用 Person构造函数经历了以下4个步奏:

1.创建一个新对象。

2、将构造函数的作用域赋给新对象(这里的新对象是person3,this指向person3)

3、执行构造函数中的代码。

4.返回新对象。

构造函数的优点:将来可以将它的实例标识为一直特定的类型。

构造函数的缺点:每个方法都要再每个实例上重新创建一遍。函数也是对象,每定义一个函数,即实例化了一个对象。

原型模式

创建的每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象。即原型对象。作用是让所有对象实例共享它包含的属性和方法。

function Person() { }
Person.prototype.name = "Nicholas";
Person.prototype.age = 27;
Person.prototype.job = "Software Enginner";
Person.prototype.sayName = function() {
   console.log(this.name);
};
//将所有属性和方法直接添加到prototype原型中。这里的Person.prototype.constructor指向他的构造函数,即Person

var person1 =new Person();
person1.sayName();
// Nicholas 

var person2 = new Person();
person2.sayName();
// Nicholas

console.log(person1.sayName === person2.sayName);
// true  person1实例和person2实例访问的都是同一组属性和同一个sayName()函数

原型对象自动获得一个constructor属(构造函数)性,constructor包含一个指向prototype属性所在的函数的指针。

EC5 把这个指针叫[[prototype]]。这个链接存在于实例与构造函数的原型对象之间。

可以用isPrototyOf():确认实例和构造函数之间是否存在这种关系。

console.log(Person.prototype.isPrototypeOf(person1));
console.log(Person.prototype.isPrototypeOf(person2));
// true 用原型对象测试person1和person2.它们内部都有一个指向Person.prototype的指针。
// true

Object.getPrototypeOf(): 返回[[prototype]]值。

console.log(Object.getPrototypeOf(person1) == Person.prototype);
console.log(Object.getPrototypeOf(person1).name);
// true
// Nicholas

通过对象的实例可以访问原型中的值,不能通过对象实例重写原型中的值,再实例中定义的同名属性的值,会屏蔽原型中的值。但是不会修改原型中的值。

function Person() { }
Person.prototype.name = "Nicholas";
Person.prototype.age = 27;
Person.prototype.job = "Software Enginner";
Person.prototype.sayName = function() {
   console.log(this.name);
};

var person1 =new Person();
person1.sayName();

var person2 = new Person();
person2.name = "张三";
console.log(person1.name);
console.log(person2.name);
//  Nicholas
//  Nicholas        来着原型
//  张三            来自实例

hasOwnProperty():用于检测一个属性是存在于实例还是原型中。从Object继承而来。在实例中返回true,在原型中返回false;

console.log(person1.hasOwnProperty("name"));
console.log(person2.hasOwnProperty("name"));
// false 来自原型
// true 来自实例

Object.keys(): 取的对象中所有可枚举的实例属性,接受一个参数:一个对象。返回一个包含所有可枚举属性的字符串数组。

function Person() { }
Person.prototype.name = "张三";
Person.prototype.age = 25;
Person.prototype.job = "Software Enginner";
Person.prototype.sayName = function() {
   console.log(this.name);
     };
var keys = Object.keys(Person.prototype);
console.log(keys);

var p1 = new Person();
p1.name = "Rob";
p1.age = 27;
var p1keys = Object.keys(p1);
console.log(p1keys);

//  ["name", "age", "job", "sayName"]
//  ["name", "age"]
//如果要得到所有的实例属性不论是否可枚举,可使用Object.getOwnPropertyNames()
var keys1 = Object.getOwnPropertyNames(Person.prototype);
console.log(keys1);
//[”constructor“, "name", "age", "job", "sayName"]

更简介的原型语法

function Person() {}

Person.prototype = {
  name: "张三",
  age: 27,
  job: "Software Enginner",
  sayName: function() {
      console.log(this.name);
  }
};

这种形式的原型语法和之前的原型语法结果一样,但是constructor不在指向原型对象的函数。可以通过显式的设置,让它指向正确。

但是Enumerable特性被设置为:true;

function Person() {}

Person.prototype = {
 constructor: Person,  //显式的设置constructor值
  name: "张三",
  age: 27,
  job: "Software Enginner",
  sayName: function() {
      console.log(this.name);
  }
};
Object.defineProperty(Person.prototype,"constructor", {
       enumerable: false,
       value: Person
});

原型模式的问题

1、所有实例默认情况下都将取的相同的值。

2、在其中一个实例中的修改,会影响其他的实例的属性。像一个实例中添加属性,不是同名的字符串,都是再原型中添加。

function Person() {}

Person.prototype = {
  name: "张三",
  age: 27,
  job: "Software Enginner",
  friends: ["Shelby", "Court"],
  sayName: function() {
      console.log(this.name);
  }
};

Object.defineProperty(Person.prototype,"constructor", {
       enumerable: false,
       value: Person
});

var person3 = new Person();
var person4 = new Person();

person3.friends.push("Van");
console.log(person3.friends);
console.log(person4.friends);
// ["Shelby", "Court", "Van"]
// ["Shelby", "Court", "Van"]

构造函数与原型混成的模式,是最广泛,认同度最高的一直创建自定义类型的方法。

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["Shely", "Court"];
}

Person.prototype = {
      constructor: Person,
      sayName: function() {
           console.log(this.name);
      }
};

var person1 = new Person("张三", 24, "Docter");
var person2 = new Person("李四", 25, "后台");

person1.friends.push("刘五");
console.log(person1.friends);
console.log(person2.friends);
console.log(person1.friends === person2.friends);
// ["Shely", "Court", "刘五"]
// ["Shely", "Court"]
// false

继承

EC只支持实现继承,实现继承依靠原型链来实现。利用原型让一个引用类型继承另一个引用类型的属性和方法。

实现原型链的基本模式

function SuperType() {
   this.property = true;
   }

SuperType.prototype.getSuperValue = function() {
    return this.property;
};           //在SuperType的原型中定义一个方法

function SubType() {
    this.subproperty = false;
  }

SubType.prototype = new SuperType();              //把superType当作实例,赋给SubType的原型,(实例对象包含一个指针[[prototype]],指向构造函数的原型对象),SubType继承了 SuperType
SubType.prototype.getSubValue  = function() {     //为SubType添加了一个新方法
       return this.subproperty;
    };                                                 //重新父原型中的方法 代码需放在替换原型的语句之后
SuperType.prototype.getSuperValue = function() {       return false;

}

   var instance = new SubType();
console.log(instance.getSuperValue());
//   false

 原型链继承的缺点:

1.包含引用类型值的原型属性会被所有实例共享;

2.没有办法在不影响所有对象实例的情况下,给父原型对象的构造函数传递参数。

function SuperType() {
  this.color = ["yellow", "blue", "green", "red"];
}

function SubType() {}
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.color.push("black");

console.log(instance1.color);

var instance2 = new SubType();
console.log(instance2.color);

//  ["yellow", "blue", "green", "red", "black"]
//  ["yellow", "blue", "green", "red", "black"]

借用构造函数

即在子类型构造函数的内部调用超类型构造函数。函数只不过是在特定环境中执行代码的对象,通过使用call()或apply()方法在新创建的对象上执行构造函数。

function colorsItem() {
   this.color = ["red", "blue", "green"];
}

function colorsItem2 () {
   colorsItem.call(this);                 //借调“colorsItem”构造函数,实际上是在colorItem2的实例下调用“colorsItem”构造函数
}

var instance3 = new colorsItem2();       // 实例化一个对象
instance3.color.push("black");           //  在 colorsItem2的其中一个实例中执行colorsItem2的初始化代码,顺便把一个值推进副本中去。每个实例化对象都拥有各自的color属性的副本
console.log(instance3.color);

var instance4 = new colorsItem2();
console.log(instance4.color);
// ["red", "blue", "green", "black"]
// ["red", "blue", "green"]

组合继承

使用原型链实现原型属性和方法的继承,通过借用构造函数实现对实例属性的基础。

在原型上定义方法实现函数复用,又能够保证每个实例有自己的属性。

function SuperType1(name) {
  this.name = name;
 this.colors = ["red", "blue", "yellow"];
}
SuperType1.prototype.sayName = function () {
      console.log(this.name);
}
function SubType1(name, age) {
   SuperType1.call(this, name);
   this.age = age;
 }

SubType1.prototype = new SuperType1();
SubType1.prototype.consctructor = SubType1;
SubType1.prototype.sayAge = function() {
   console.log(this.age);
}

var instances = new SubType1("张三", 27);
instances.colors.push("black");
console.log(instances.colors);
instances.sayName();
instances.sayAge();

// ["red", "blue", "yellow", "black"]
// 张三
// 27
var instances1 = new SubType1("李斯", 25);
console.log(instances.colors);
instances1.sayName();
instances1.sayAge();

// ["red", "blue", "yellow", "black"]
// 李斯
// 25

优点: 可以让不同的实例分别拥有自己的属性又可以使用不同的方法。instanceof和isPrototypeOf()也能识别基于组合继承创建的对象。也是最常用的继承模式。

缺点: 会调用俩次超类型函数,一次是创建子类型函数,另一次在子类型函数内部。

原型式继承

EC5的Object.create()方法:接受2个参数:用作新对象原型的对象和(可选)一个为心对定义额外属性的对象;

var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
 };

var anotherPerson = Object.create(person, {
     name: {
        value: "Greg"
     }
});

console.log(anotherPerson.name);
// Greg

寄生式继承

创建一个用于封装继承过程的函数, 该函数在内部以某种方式来增强对象,最后再像针对是它做了所有工作一样返回对象。

function createAnother(original) {
   var clone = object(original);
   clone.sayHi = function() {
        console.log("Hi");
    };
    return clone;
}

var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
 };

var anotherPerson = createAnother(person);
anotherPerson.sayHi();

// Hi

寄生组合继承

组合继承会调用俩次超类型函数,一次是创建子类型函数,另一次在子类型函数内部。

function SuperType (name) {
   this.name = name;
   this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function() {
     console.log(this.name);
 };

function SubType (name, age) {
   SuperType.call(this, name);          //第二次调用

      this.age = age;
  }

SubType.prototype = new SuperType();    //第一次调用
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function () {
    console.log(this.age);
};

第一次调用,SubType得到俩个属性,SuperType构造函数的color 和name属性。调用SubType又会再一次调用SuperType构造函数。这一次在新对象上创建实例属性name和color,这俩个同名属性屏蔽了原型中的同名属性。

寄生组合式继承: 通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。

思路:使用寄生式继承来继承超类型的原型,将结果指定给子类型的原型。

寄生组合式继承的模式 是引用类型最理想的继承范式

function inheritPrototype(SubType, SuperType) {
        var prototype = Object(SuperType.prototype);       //创建对象,创建超类型原型的一个副本
        prototype.consctructor = SubType;                   //增强对象  为创建的副本添加constructor属性
        SubType.prototype = prototype;                      //指定对象  将新创建的对象赋值给子类型的原型。
   }
 function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "yellow"];
}
SuperType.prototype.sayName = function() {
 console.log(this.name);
};
function SubType(name, age) {
SuperType.call(this, name);
this.age = age;
}
inheritPrototype(SubType, SuperType);
Subtype.prototype.sayAge = function () {
console.log(this.age);
};
时间: 2024-11-03 19:36:04

第六章 面向对象的程序设计的相关文章

第六章 面向对象的程序设计 (1 理解对象)

面向对象(Object-Oriented,OO)的语言有一个标志,那就是它们都有类的概念,而通过类可以创建任意多个具有相同属性和方法的对象. ECMA-262 把对象定义为:"无序属性的集合,其属性可以包含基本值.对象或者函数."严格来讲,这就相当于说对象是一组没有特定顺序的值.对象的每个属性或方法都有一个名字,而每个名字都映射到一个值.正因为这样,我们可以把ECMAScript 的对象想象成散列表:无非就是一组名值对,其中值可以是数据或函数. 每个对象都是基于一个引用类型创建的. 6

第六章 面向对象的程序设计 (3 继承)

6.3 继承 许多OO 语言都支持两种继承方式:接口继承和实现继承.接口继承只继承方法签名,而实现继承则继承实际的方法.如前所述,由于函数没有签名,在ECMAScript 中无法实现接口继承.ECMAScript 只支持实现继承,而且其实现继承主要是依靠原型链来实现的. 6.3.1 原型链 ECMAScript 中描述了原型链的概念,并将原型链作为实现继承的主要方法.其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法.简单回顾一下构造函数.原型和实例的关系:每个构造函数都有一个原型

第六章-面向对象的程序设计—创建对象

使用对象字面量和或者构造函数都可以创建对象,但是都有缺点:使用同一个借口创建对象,会导致代码重复.为解决这个问题,人们开始使用工厂模式. 1 工厂模式 1 function create(name,age,job){ 2 var o = new Object(); 3 o.name = name; 4 o.age = age; 5 o.sayName = function(){ 6 alert(this.name); 7 } 8 } 9 var person1 = create('XU',27)

第六章-面向对象的程序设计(理解对象)

ESMA把对象定义为:"无序属性的集合,其属性可以包含基本值,对象和函数".严格的讲,这相当于说对象是一组没有特定顺序的值,对象的每个属性或方法都有一个名字,而每一个名字都映射到一个值. 1.理解对象 创建对象有对象字面量的方法,这种方法比较常用,也是比较推荐的. 1 var person ={ 2 name:'xu', 3 age:27, 4 sayName:function(){ 5 alert(this.name); 6 } 7 }; 还有创建对象的实例的方法: 2.属性的类型

JavaScript高级程序设计-第六章面向对象的程序设计

创建对象主要的两种形式,创建Object实例和创建对象字面量 对象包含属性和方法 数据 .属性有四个特性,特性是为了描述属性行为的,他们是: Configurable(可配置的)是否能删除或是否能修改为访问器属性 Enumerable(枚举)是否能够for-in Writeable(可写)能否修改属性值 Value(值)默认为undefined,从这个位置上读数据或把新值保存到这个位置上 eg: var person = {}'; Object.defineproperty(person,"na

JavaScript高级程序设计(第三版)第六章 面向对象的程序设计

6.1 理解对象 var person = new Object(); person.name = "Nicholas"; person.age = 29; person.job = "Software Engineer"; person.sayName = function(){ alert(this.name); }; person.sayName(); 6.1.1 属性类型 1.数据属性 [[Configurable]]:表示能否通过delete删除属性从而重

读书笔记 - js高级程序设计 - 第六章 面向对象的程序设计

EcmaScript有两种属性 数据属性 和 访问器属性 数据属性有4个特性 Configurable Enumerable Writable Value 前三个值的默认值都为false 举例 Object.defineProperty( person, "name", { writable:false, value:"niko"} ) ; 一旦属性定义为不可配置的,就不能再把它变回可配置的了 读取属性 的特性 var descriptor  = Object.ge

第六章 面向对象的程序设计 (2 创建对象)

6.2 创建对象 6.2.1 工厂模式 工厂模式是软件工程领域一种广为人知的设计模式,这种模式抽象了创建具体对象的过程.考虑到在ECMAScript 中无法创建类,开发人员就发明了一种函数,用函数来封装以特定接口创建对象的细节. function createPerson(name, age, job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ alert(thi

第六章-面向对象编程

面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想 面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行, 面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度 面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递. 在Python中, 所有数据类型都可以视为对象, 同时也可以自定义对象. 自定