读书时间《JavaScript高级程序设计》二:面向对象

  接着上次的进度,开始看第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能否走得更远的重要基础。

时间: 2024-10-06 22:19:50

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

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

一.使用<script>元素 1.<script>元素定义了6个属性: async:可选.表示应该立即下载脚本,但不应该妨碍页面中的其他操作,比如下载其他资源或等待加载其他脚本.只对外部脚本文件有效. charset:可选.表示通过src属性指定的代码的字符集.很少人用. defer:可选.表示脚本可以延迟到文档完全被解析和显示之后再执行.只对外部文件有效. language:已废弃. src:可选.表示包含要执行代码的外部文件. type:可选.表示编写代码使用的脚本语言的内容类

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

第一章 简介   诞生时间 1995 最初用途 客服端验证 第一版标准 注意是标准 1997年 Ecma-262  一个完整的js实现由三部分组成 ECMAScript DOM 文档对象模型 BOM 浏览器对象模型 ECMAScript的宿主环境 web浏览器 Node(服务器js平台) adobe flash js和as的关系 他们都实现了 ECMAScript 五大主流浏览器 IE Firefox Safari Chrome Opera dom介绍 它是针对xml但经过扩展用于html的应用

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

1 <script>的6个属性 async  立即下载当前script标签的外部脚本 但不能影响别的 charset 没用了 defer  文档显示之后再执行脚本,只对外部脚本有效 language 没用了 src type 描述代码内容Mine类型 默认就是text/javascript 没什么用 2 在解释器对<script>元素内部的所有代码求值完毕以前 页面中的其余内容都不会被浏览器加载或显示 3 有两种script 嵌入式 外部引入式 在解析外部引入式的js文件时,页面的

javascript 高级程序设计(面向对象的程序设计) 1

Object构造函数或对象字面量都可以用来创建对象,但这些方式有个明显的缺点:使用相同一个接口创建很多对象,会产生大量重复代码. 工厂模式 //工厂模式 function createDog (name,age) { var o = new Object(); o.name = name; o.age = age; o.sayAge = function () { alert(age); }; return o; } var dog1 = createDog("Bob","1

《JavaScript 高级程序设计》读书笔记二 使用JavaScript

一   <script>元素 a.四个属性: async:立即异步加载外部脚本: defer:延迟到文档完全被解析再加载外部脚本: src:外部脚本路径: type:脚本语言的内容类型: 二   XHTML中用法 a. //<![CDATA[ javascript代码 //]]> 三   <noscript>元素 <JavaScript 高级程序设计>读书笔记二 使用JavaScript

读书时间:《JavaScript高级程序设计》 一

在2011年的时候第一次看了<JavaScript高级程序设计>第二版,那时见到手上的书,第一感觉真是好厚的一本书啊.现在再次回顾一下,看的是<JavaScript高级程序设计>第三版,并记录一些了内容. 第1章:JavaScript简介 JavaScript的历史.版本,可以看到一个有趣的故事. JavaScript是什么? 一个完整的JavaScript应该有3个不同的部分组成: 1. ECMAScript(核心) ECMAScript规定了语言的:语法.类型.语句.关键字.保

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

这个系列之前文字地址:http://www.cnblogs.com/qixinbo/p/6984374.html 这个系列,我会把阅读<Javascript高级程序设计>之后,感觉讲的比较深入,而且实际使用价值较大的内容记录下来,并且注释上我的一些想法.做这个一方面是提升了我的阅读效果以及方便我以后阅读 另一个目的是,Javascript高级程序设计这本书内容很多也很厚,希望其他没有时间的人可以通过看这系列摘录,就可以大体学到书里面的核心内容. 绿色背景的内容是我认为比较值得注意的原著内容.

《JavaScript 高级程序设计》读书笔记一 简介

一   历史 二   实现 a. javascript三个部分: ECMAScript:由ECMA-262定义,提供核心语言功能: DOM:提供HTML的应用程序编程接口/提供访问和操作网页内容的方法和接口: BOM:提供与浏览器交互的方法和接口: 三   版本 <JavaScript 高级程序设计>读书笔记一 简介

JavaScript高级程序设计(读书笔记)(六)

本笔记汇总了作者认为"JavaScript高级程序设计"这本书的前七章知识重点,仅供参考. 第六章 面向对象的程序设计 面向对象(Object-Oriented, OO)的语言有一个标志,那就是它们都有类的概念,而通过类可以创建任意多个具有相同属性和方法的对象.但ECMAScript中没有类的概念,因此它的对象也与基于类的语言中的对象有所不同.ECMAScript把对象定义为:"无序属性的集合,其属性可以包含基本值.对象或者函数."严格来讲,这就相当于说对象是一组没

JavaScript高级程序设计(读书笔记)(五)

本笔记汇总了作者认为"JavaScript高级程序设计"这本书的前七章知识重点,仅供参考. 第五章 引用类型 小结: 对象在JavaScript中被称为引用类型的值,而且有一些内置的引用类型可以用来创建特定的对象,现简要总结如下: 引用类型与传统面向对象程序设计中的类相似,但实现不同: Object是一个基础类型,其他所有类型都从Object继承了基本的行为: Array类型是一组值得有序列表,同时还提供了操作和转换这些值的功能: Date类型提供了有关日期和时间的信息,包括当前日期和