接着上次的进度,开始看第6章。

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

  理解对象

    创建自定义对象最简单的方式就是创建一个 Object 的实例,然后为它添加属性和方法。

  
var person = new Object();
person.name = 'xxx';
person.say = function(){
alert(this.name);
}
//等同于 对象字面量
var person = {
name: 'xxx',
say: function(){
alert(this.name);
}
}

  ECMAScript中有两种属性:数据属性 访问器属性

    数据属性包含一个数据值的位置,在这个位置可以读写。要修改属性默认的特性,必须使用 ECMAScript 5定义的 Object.defineProperty() 方法。这个方法接收3个参数:属性所在的对象、属性的名称、一个描述符对象(configurable、enumerable、writable、value).

var person = {};
Object.defineProperty(person, "name", {
writable: false,
value: "Nicholas"
}); alert(person.name); //Nicholas
person.name = "Michael";
alert(person.name); //Nicholas
//
Object.defineProperty(person, "name", {
configurable: false,
value: "Nicholas"
});

     访问器属性不包含数据值,包含一对 getter setter。访问器属性不能直接定义,必须使用 defineProperty() 来定义。

    读取属性的特性

      ECMAScript 5中的 getOwnPropertyDescriptor() 方法可以取得属性的描述符。这个方法接收2个参数:属性所在的对象、要读取描述符的属性名称。

  创建对象 

    虽然 Object 构造函数或对象字面量都可以创建单个对象,但这些方式有缺点:使用同一个接口创建很多对象,会产生大量的重复代码。

    1. 工厂模式 

//工厂模式
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("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");
person1.sayName(); //"Nicholas"
person2.sayName(); //"Greg"

    2. 构造函数模式

//构造函数模式
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
};
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
person1.sayName(); //"Nicholas"
person2.sayName(); //"Greg"

    构造函数都应该以一个大写字母开头、非构造函数应该以一个小写字母开头。

     要创建一个新实例,必须使用 new 操作符。

    一般经历4个步骤:

      1). 创建一个新对象

      2). 将构造函数的作用域赋给新对象(这时this指向了这个新对象)

      3). 执行构造函数中的代码(为新对象添加属性、方法)

      4). 返回新对象

    创建的新对象都有一个 constructor (构造函数)属性,这个属性指向 那个new实例的构造函数。

    instanceof 操作符用来检测对象类型。

//constructor
alert(person1.constructor == Person); //true
alert(person2.constructor == Person); //true
//instanceof
alert(person1 instanceof Object); //true
alert(person1 instanceof Person); //true
alert(person2 instanceof Object); //true
alert(person2 instanceof Person); //true

    构造函数也是普通的函数, 任何函数通过new操作符调用,那它就可以作为构造函数。

//new操作符 当作构造函数调用
var person = new Person("Nicholas", 29, "Software Engineer");
person.sayName(); //"Nicholas"
//作为普通函数调用
Person("Greg", 27, "Doctor"); //adds to window
window.sayName(); //"Greg"
//在另一个对象作用域调用
var o = new Object();
Person.call(o, "Kristen", 25, "Nurse");
o.sayName();

    3. 原型模式

    新创建的每个函数都有一个 prototype(原型) 属性,这个属性是一个指针,指向一个对象,这个对象的用途是包含可以有特定类型的所有实例共享的属性和方法。

//原型模式
function Person(){}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
}; var person1 = new Person();
person1.sayName(); //"Nicholas" var person2 = new Person();
person2.sayName(); //"Nicholas" alert(person1.sayName == person2.sayName); //true

    理解原型对象

    无论什么时候,只要创建一个新函数,就会根据一组特定的规则为该函数创建一个 prototype 属性,这个属性指向函数的原型对象。 在默认情况下,所有原型对象都会自动获得一个 constructor 属性,这个属性包含一个指向 prototype 属性所在函数的指针。

    创建了自定义构造函数后,其原型对象默认只会取得 constructor 属性;其他方法,则是从 Object 继承而来。 当调用构造函数创建一个新实例,该实例的内部将包含一个指针(内部属性[[prototype]])指向构造函数的原型对象。( 在一些浏览器中这个属性为 __proto__ )

      

   Person的实例:person1, person2,这2个实例都包含一个内部属性( 某些浏览器中:__proto__ ),这个属性指向了 Person.prototype。( 换句话说这个内部属性和构造函数没有直接关系 ) 

   可以通过 isPrototypeOf() 方法来确定对象之间是否存在这种关系(实例内部属性[[prototype]]指向构造函数原型)

    

alert(Person.prototype.isPrototypeOf(person1));  //true
alert(Person.prototype.isPrototypeOf(person2)); //true

    使用 hasOwnProperty() 方法可以检测一个属性是存在于实例中,还是存在于原型中。

//检测属性
function Person(){}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
}; var person1 = new Person();
var person2 = new Person(); alert(person1.hasOwnProperty("name")); //false
alert("name" in person1); //true person1.name = "Greg";
alert(person1.name); //"Greg" – from instance
alert(person1.hasOwnProperty("name")); //true
alert("name" in person1); //true alert(person2.name); //"Nicholas" – from prototype
alert(person2.hasOwnProperty("name")); //false
alert("name" in person2); //true delete person1.name;
alert(person1.name); //"Nicholas" - from the prototype
alert(person1.hasOwnProperty("name")); //false
alert("name" in person1); //true

    

//判断原型属性
function hasPrototypeProperty(object, name){
return !object.hasOwnProperty(name) && (name in object);
} var person = new Person();
alert(hasPrototypeProperty(person, "name")); //true person.name = "Greg";
alert(hasPrototypeProperty(person, "name")); //false

    在使用for-in循环时,返回是所有能够通过对象访问的、可枚举的属性,包括实例的属性,原型的属性。要取得对象所有可枚举的实例属性,可以使用 ECMAScript 5 中的 Object.keys() 方法。这个方法接收一个对象,返回一个包含可枚举属性的字符串数组。

//属性枚举
function Person(){}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
}; var keys = Object.keys(Person.prototype);
alert(keys); //"name,age,job,sayName" var person1 = new Person();
person1.name = 'xxx';
person1.age = 50;
var person1keys = Object.keys(person1);
alert(person1keys); //"xxx,50"

    可以这样扩展:

if(!Object.create){
Object.create = function(o){
function F(){}
F.prototype = o;
return new F();
}
} if(!Object.keys){
Object.keys = function(o){
var k = [], p;
for(p in o){
if(Object.prototype.hasOwnProperty.call(o,p)){
k.push(p);
}
}
return k;
}
}

     更简单的原型语法

function Person(){}
Person.prototype = {
name : "Nicholas",
age : 29,
job: "Software Engineer",
sayName : function () {
alert(this.name);
}
}; var friend = new Person();
alert(friend instanceof Object); //true
alert(friend instanceof Person); //true
alert(friend.constructor == Person); //false
alert(friend.constructor == Object); //true

    原型的动态性

    由于在原型中查找值的过程是一次搜索,因此对原型对象所做的任何改变都能够立即从实例上反映出来,即时是先创建实例后改变原型。

//原型的动态性
function Person(){}
Person.prototype = {
constructor: Person,
name : "Nicholas",
age : 29,
job : "Software Engineer",
sayName : function () {
alert(this.name);
}
}; var friend = new Person();
Person.prototype.sayHi = function(){
alert("hi");
};
friend.sayHi(); //"hi"

   如果重写整个原型对象,会切断实例中的指针( 调用构造函数会为实例添加一个指向最初原型的指针[[prototype]] )与最初原型之间的联系。

//重写原型
function Person(){}
var friend = new Person();
Person.prototype = {
constructor: Person,
name : "Nicholas",
age : 29,
job : "Software Engineer",
sayName : function () {
alert(this.name);
}
};
friend.sayName(); //error

 

    原生对象的原型

    原型模式的重要性不仅体现在创建自定义类型上,就是所有原生的引用类型,也都是采用原型模式创建的。所有原生引用类型(Object、Array、String...)都是在其构造函数的原型上定义了方法。

//原生对象的原型
alert(typeof Array.prototype.sort); //"function"
alert(typeof String.prototype.substring); //"function" String.prototype.startsWith = function (text) {
return this.indexOf(text) == 0;
};
var msg = "Hello world!";
alert(msg.startsWith("Hello")); //true

     原型模式的问题

     原型模式省略了为构造函数传递初始化参数这一环节,会造成所有实例在默认情况下都取得相同的属性值。原型中所有的属性是被很多实例共享的,实例一般都有属于自己的属性。 

//原型问题
function Person(){}
Person.prototype = {
constructor: Person,
name : "Nicholas",
age : 29,
job : "Software Engineer",
friends : ["Shelby", "Court"],
sayName : function () {
alert(this.name);
}
}; var person1 = new Person();
var person2 = new Person(); person1.friends.push("Van"); alert(person1.friends); //"Shelby,Court,Van"
alert(person2.friends); //"Shelby,Court,Van"
alert(person1.friends === person2.friends); //true

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

   创建自定义类型的最常见方式,就是组合使用构造函数模式和原型模式。构造函数用于定义实例属性,原型模式用于定义方法和共享属性。 这样的组合,每个实例都有自己的一份实例属性,又共享对方法的引用,最大限度的节省了内存。

//组合构造函数模式和原型模式
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["Shelby", "Court"];
}
Person.prototype = {
constructor: Person,
sayName : function () {
alert(this.name);
}
}; var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor"); person1.friends.push("Van"); alert(person1.friends); //"Shelby,Court,Van"
alert(person2.friends); //"Shelby,Court"
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true

    这种构造函数与原型混合的模式,是目前是用最广泛、认同度最高的一种创建自定义类型的方法。这是用来自定义类型的一种默认模式。

   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("Nicholas", 29, "Software Engineer");
friend.sayName();

    使用动态原型模式,不能使用对象字面量重写原型。

   6. 寄生构造函数模式

   7. 稳妥构造函数模式 

  继承   

  许多OO语言支持两种继承:接口继承、实现继承。接口继承只继承方法签名,实现继承则继承实际的方法。在ECMAScript中无法实现接口继承,因为函数没有签名。所以ECMAScript只支持实现继承,实现继承主要是依靠原型链来实现的。

  1. 原型链 

  原型链的基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。 

  构造函数、原型、实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针( constructor ),实例都包一个指向原型对象的内部指针( [[prototype]] __proto__ )。

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

//原型链

//SuperType
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
}; //SubType
function SubType(){
this.subproperty = false;
}
//继承了SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
return this.subproperty;
}; //instance
var instance = new SubType();
alert(instance.getSuperValue()); //true
//原型和实例的关系 1
alert(instance instanceof Object); //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true
//原型和实例的关系 2
alert(Object.prototype.isPrototypeOf(instance)); //true
alert(SuperType.prototype.isPrototypeOf(instance)); //true
alert(SubType.prototype.isPrototypeOf(instance)); //true

  //原型链关系图

    所有的引用类型都继承了 Object,这个继承也是通过原型链实现的。

  

  SubType继承了SuperType, SuperType继承了Object. 当调用instance.toString()时,实际上调用的是保存在Object.prototype中的那个toString()方法.

  //谨慎的定义方法

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

   //原型链的问题

//原型链的问题
function SuperType(){
this.colors = ["red", "blue", "green"];
} function SubType(){}
SubType.prototype = new SuperType(); var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black" var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green,black"

    SuberType构造函数定义了一个colors属性,SubType通过原型链继承SuperType的一个实例,因此SubType.prototype变成了SuperType的一个实例,也拥有一个自己的colors属性, 所有的SubType实例都共享这个属性。对instance1.colors的修改也会通过instance2.colors反映出来。

    创建子类型的实例时,不能向超类型的构造函数传递参数。实际上是没有办法在不影响所有对象实例的情况下,给超类的构造函数传递参数。

  2. 借用构造函数

  3. 组合继承

  4. 原型式继承 - ‘现代’无类继承模式

function object(o){
function F(){}
F.prototype = o;
return new F();
}
if(!Object.create){
Object.create = function(o){
function F(){}
F.prototype = o;
return new F();
}
}

    ECMAScript新增了 Object.create() 方法规范化了原型式继承。这方法介绍两个参数:一个用作新对象原型的对象、一个为新对象定义额外属性的对象。在传入一个参数的情况下,Object.create() 和 object()方法类似。

   5. 寄生式继承

   6. 寄生组合式继承

function object(o){
function F(){}
F.prototype = o;
return new F();
}
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype); //create object
prototype.constructor = subType; //augment object
subType.prototype = prototype; //assign object
} 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;
} inheritPrototype(SubType, SuperType); SubType.prototype.sayAge = function(){
alert(this.age);
}; var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29 var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27

  

    在这个例子中,它只调用了一次 SuperType 构造函数,避免了在 SubType.prototype 上创建不必要的属性。原型链也保持不变,还能正常使用 instanceof 和 isPrototypeOf()。普遍认为寄生组合式继承是引用类型最理想的继承方式。

  第6章主要介绍了面向对象相关知识。创建对象:工厂模式、构造函数模式、原型模式、组合使用构造函数模式和原型模式... 实现继承:原型式、寄生式、寄生组合式... 这一章需要深刻理解,是JS能否走得更远的重要基础。

读书时间《JavaScript高级程序设计》二:面向对象的更多相关文章

  1. JavaScript高级程序设计(二):在HTML中使用JavaScript

    一.使用<script>元素 1.<script>元素定义了6个属性: async:可选.表示应该立即下载脚本,但不应该妨碍页面中的其他操作,比如下载其他资源或等待加载其他脚本 ...

  2. javascript 高级程序设计 二

    这里我们直接进入主题: 在JS刚刚开始的时候,必须面临一个问题,那就是如何使的JS的加载和执行不会影响web核心语言HTML的展示效果,和HTML和谐共存. 在这个背景下<script>标 ...

  3. 读书笔记-JavaScript高级程序设计(1)

    1.组合继承 (JavaScript 中最常用的继承模式 ) (position: page168) (书中定义了两个变量名 SuperType   SubType  乍一看 感觉不太能区分,我将改为 ...

  4. 读书笔记 - javascript 高级程序设计 - 第二章 在Html中使用JavaScript

    1 <script>的6个属性 async  立即下载当前script标签的外部脚本 但不能影响别的 charset 没用了 defer  文档显示之后再执行脚本,只对外部脚本有效 lan ...

  5. 读书笔记 - javascript 高级程序设计 - 第一章 简介

      第一章 简介   诞生时间 1995 最初用途 客服端验证 第一版标准 注意是标准 1997年 Ecma-262  一个完整的js实现由三部分组成 ECMAScript DOM 文档对象模型 BO ...

  6. 读书时间《JavaScript高级程序设计》一:基础篇

    第一次看了<JavaScript高级程序设计>第二版,那时见到手上的书,第一感觉真是好厚的一本书啊.现在再次回顾一下,看的是<JavaScript高级程序设计>第三版,并记录一 ...

  7. 《JavaScript高级程序设计》读书笔记--前言

    起因 web编程过程使用javascript时感觉很吃力,效率很低.根本原因在于对javascript整个知识体系不熟,看来需要找些书脑补一下,同时欢迎众网友监督. 大神推荐书籍 看了博客大神们推荐的 ...

  8. 《Javascript高级程序设计》阅读记录(二):第四章

    这个系列之前文字地址:http://www.cnblogs.com/qixinbo/p/6984374.html 这个系列,我会把阅读<Javascript高级程序设计>之后,感觉讲的比较 ...

  9. 《JavaScript高级程序设计》学习笔记(5)——面向对象编程

    欢迎关注本人的微信公众号"前端小填填",专注前端技术的基础和项目开发的学习. 本节内容对应<JavaScript高级程序设计>的第六章内容. 1.面向对象(Object ...

  10. 《Javascript高级程序设计》读书笔记之对象创建

    <javascript高级程序设计>读过有两遍了,有些重要内容总是会忘记,写一下读书笔记备忘 创建对象 工厂模式 工厂模式优点:有了封装的概念,解决了创建多个相似对象的问题 缺点:没有解决 ...

随机推荐

  1. Deploy Oracle 10.2.0.5 DataGuard on Red Hat Enterprise Linux 6.4

    系统:Red Hat Enterprise Linux 6.4 数据库:Oracle 10.2.0.5.0 Patch Set 4 主机:10dg1 192.168.1.91 10dg2192.168 ...

  2. C#的百度地图开发(一)发起HTTP请求

    原文:C#的百度地图开发(一)发起HTTP请求 百度地图的开发文档中给出了很多的事例,而当用到具体的语言来开发时,又会有些差异.我是使用C#来开发的.在获取相应的数据时,需要通过URL传值,然后获取相 ...

  3. 多个UpdatePanel控件相互引发刷新的使用

    原文:多个UpdatePanel控件相互引发刷新的使用 ScriptManager和UpdatePanel控件联合使用可以实现页面异步局部更新的效果.其中的UpdatePanel就是设置页面中异 步局 ...

  4. poj3140(树的dfs)

    题目链接:http://poj.org/problem?id=3140 题意:给定一棵n棵节点的树,求删去某条边后两个分支的最小差异值. 分析:num[u]表示以u点为根节点的子树的总人数,那么不在该 ...

  5. NPC

    这里的想说的NPC不是Non-Player-Controled,非玩家控制角色,而是Non-determinisitc Polynomial complete problem,它属于一类很特殊的问题, ...

  6. UVA 11324 - The Largest Clique(强连通分量+缩点)

    UVA 11324 - The Largest Clique 题目链接 题意:给定一个有向图,要求找一个集合,使得集合内随意两点(u, v)要么u能到v,要么v能到u,问最大能选几个点 思路:强连通分 ...

  7. tomcat启动Flash退出错误不能被视为解决该错误信息

    tomcat 当有错误 启动startup.bat闪存在退出解决方案 打开 startup.bat 文件 最后 该start 阅读run watermark/2/text/aHR0cDovL2Jsb2 ...

  8. maven 项目中使用 jstl标签

    在pom.xml文件下面增加如下的依赖包: <dependency> <groupId>javax.servlet</groupId> <artifactId ...

  9. 编译安装LNMP Centos 6.5 x64 + Nginx1.6.0 + PHP5.5.13 + Mysql5.6.19

    (来自:http://www.cnblogs.com/vicowong/archive/2011/12/01/2116212.html) 环境: 系统硬件:vmware vsphere (CPU:2* ...

  10. 使用C++名单在文档处理和学生成绩管理系统相结合

    对于学生成绩管理系统,我并不陌生,几乎学习C人的语言.做项目会想到学生成绩管理系统,我也不例外.在研究中的一段时间C语言之后,还用C语言到学生管理系统,然后做几个链接.计数,这个系统是以前的系统上的改 ...