面向对象的程序设计

对象是一组没有特定顺序的值
6.1.1 属性类型
ECMAScript中有两种属性:数据属性和访问器属性。
1. 数据属性
Configurable 表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为true。
Enumerable 表示能否通过for-in循环返回属性。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为true。
Writable 表示能否修改属性的值。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为true。
Value 包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。这个特性的默认值为undefined。
对于像前面例子中那样直接在对象上定义的属性,它们的[[Configurable]]、[[Enumerable]]和[[Writable]]特性都被设置为true,而[[Value]]特性被设置为指定的值。例如:

  1. var person = {
  2. name: "Nicholas"
  3. };

这里创建了一个名为name的属性,为它指定的值是"Nicholas"。也就是说,[[Value]]特性将被设置为"Nicholas",而对这个值的任何修改都将反映在这个位置。
要修改属性默认的特性
Object.defineProperty()方法不能重复定义

2. 访问器属性
Configurable
Enumerable
Get 在读取属性时调用的函数。默认值为undefined。
Set 在写入属性时调用的函数。默认值为undefined。
访问器属性不能直接定义,必须使用Object.defineProperty()来定义

  1. var book = {
  2. _year: 2004,
  3. edition: 1
  4. };
  5.  
  6. Object.defineProperty(book,"year",{
  7. get:function(){
  8. return this._year;
  9. },
  10. set:function(newValue){
  11. if(newValue > 2004){
  12. this._year = newValue;
  13. this.edition += newValue - 2004;
  14. }
    }
  15. });
  16.  
  17. book.year = 2010;
  18. console.log(book.edition)//

以上代码创建了一个book对象,并给它定义两个默认的属性:_year和edition。_year前面的下划线是一种常用的记号,用于表示只能通过对象方法访问的属性。而访问器属性year则包含一个getter函数和一个setter函数。
使用访问器属性的常见方式,即设置一个属性的值会导致其他属性发生变化。

6.1.2  定义多个属性

  1. var book = {};
  2. Object.defineProperties(book,{
  3. _year: {
  4. value: 2004
  5. },
  6. edition: {
  7. value: 1
  8. },
  9. year: {
  10. get: function(){
  11. return this._year;
  12. },
  13. set: function(newValue){
  14. if(newValue >2004){
  15. this._year = newValue;
  16. this.edition += newValue - 2004;
  17. }
  18. }
  19. }
    })
  20. console.log(book)

6.2 创建对象
虽然Object构造函数或对象字面量都可以用来创建单个对象,但这些方式有个明显的缺点:使用同一个接口创建很多对象,会产生大量的重复代码。
6.2.1 工厂模式
在ECMAScript中无法创建类,开发人员就发明了一种函数,用函数来封装以特定接口创建对象的细节

  1. function createPerson(name,age,job){
  2. var o = new Object();
  3. o.name = name;
  4. o.age = age;
  5. o.job = job;
  6. o.sayName = function(){
  7. console.log(this.name);
  8. }
  9. return o;
  10. }
  11. var person1 = createPerson('lxf',"23",'fw');
  12. var person2 = createPerson("lxx","21");
  13. console.log(person1);//{name: "lxf", age: "23", job: "fw", sayName: ƒ}
  14. console.log(person2);//{name: "lxx", age: "21", job: undefined, sayName: ƒ}

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

6.2.2 构造函数模式
ECMAScript中的构造函数可用来创建特定类型的对象。

  1. function Person(name, age, job){
  2. this.name = name;
  3. this.age = age;
  4. this.job = job;
  5. this.sayName = new Function("console.log(this.name);");//新实例化了一个对象
  6. // this.sayName = function(){console.log(this.name)}
  7. }
  8. var person1 = new Person('lxf',"23",'fw');
  9. var person2 = new Person('lxx',"23");
  10.  
  11. console.log(person1);
  12. console.log(person2 instanceof Person);//我们在这个例子中创建的所有对象既是Object的实例,同时也是Person的实例
  13. console.log(person2 instanceof Object);
  14. console.log(person1.sayName == person2.sayName);//以这种方式创建函数,会导致不同的作用域链和标识符解析,但创建Function新实例的机制仍然是相同的。因此,不同实例上的同名函数是不相等的

然而,创建两个完成同样任务的Function实例的确没有必要;况且有this对象在,根本不用在执行代码前就把函数绑定到特定对象上面。因此,大可像下面这样,通过把函数定义转移到构造函数外部来解决这个问题

  1. function Person(name, age, job){
  2. this.name = name;
  3. this.age = age;
  4. this.job = job;
  5. // this.sayName = new Function("console.log(this.name);");//新实例化了一个对象
  6. // this.sayName = function(){console.log(this.name)}
  7. this.sayName = sayName;
  8. }
  9. function sayName(){
  10. console.log(this.name);
  11. }

与工厂模式的区别
没有显式地创建对象;
直接将属性和方法赋给了this对象;
没有return语句。
函数名Person使用的是大写字母P,为了区别于ECMAScript中的其他函数;因为构造函数本身也是函数,只不过可以用来创建对象而已。

要创建Person的新实例,必须使用new操作符。以这种方式调用构造函数实际上会经历以下4个步骤:
(1) 创建一个新对象;
(2) 将构造函数的作用域赋给新对象(因此this就指向了这个新对象);
(3) 执行构造函数中的代码(为这个新对象添加属性);
(4) 返回新对象。

1. 将构造函数当作函数
以这种方式定义的构造函数是定义在Global对象(在浏览器中是window对象)中的。
2. 构造函数的问题
使用构造函数的主要问题,就是每个方法都要在每个实例上重新创建一遍。
可是新问题又来了:在全局作用域中定义的函数实际上只能被某个对象调用,这让全局作用域有点名不副实。而更让人无法接受的是:如果对象需要定义很多方法,那么就要定义很多个全局函数,于是我们这个自定义的引用类型就丝毫没有封装性可言了。好在,这些问题可以通过使用原型模式来解决。
6.2.3 原型模式
ECMA-262第5版中管这个指针叫[[Prototype]]。虽然在脚本中没有标准的方式访问[[Prototype]],但Firefox、Safari和Chrome在每个对象上都支持一个属性__proto__;而在其他实现中,这个属性对脚本则是完全不可见的。不过,要明确的真正重要的一点就是,这个连接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间。

我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。如按照字面意思来理解,那么 prototype就是通过调用构造函数而创建的那个对象实例的原型对象。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。换话说,不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中
Person.prototype 指向了原型对象,而 Person.prototype.constructor 又指回了 Person。原型对象中除了包含 constructor 属性之外,还包括后来添加的其他属性。Person 的每个实例——person1 和 person2 都包含一个内部属性,该属性仅仅指向了 Person.prototype;换句话说,它们与构造函数没有直接的关系。此外,要格外注意的是,虽然这两个实例都不包含属性和方法.

  1. function Person(){
  2. }
  3.  
  4. Person.prototype.name = "lxf";
  5. Person.prototype.age = "23";
  6. Person.prototype.sayName = function(){
  7. console.log("ok");
  8. }
  9.  
  10. var person1 = new Person();
  11. var person2 = new Person();
  12.  
  13. person1.name = "Greg";
  14. console.log(person1.name,person2.name);
  15. console.log(Object.getPrototypeOf(person1));//返回实例的prototype,即原形对象
  16. console.log(Object.getPrototypeOf(person2));
  17. console.log(Person.prototype.isPrototypeOf(person2));//确认实例的prototype,
  18. console.log(person2.hasOwnProperty("name"));//检查属性是否在实例上
  19. console.log("name" in person1);//true,对象person1能访问name属性
    console.log(Object.keys(Person.prototype));//取得对象上所有可枚举的实例属性
  20.  
  21. function hasPrototypeProperty(object, name){
  22. return !object.hasOwnProperty(name) && (name in object);
  23. }
  24. console.log(hasPrototypeProperty(person2,"name"));//true

使用delete操作符则可以完全删除实例属性,从而让我们能够重新访问原型中的属性。

delete person1.name;

  1. function Person(){
  2. }
  3. var person1 = new Person();
  4. Person.prototype = {
  5. constructor : Person,
  6. name : "sad",
  7. sayName:function(){
  8. console.log(this.name)
  9. }
  10. }
  11. console.log(Person.prototype.constructor == Person);
  12. console.log(person1)//error

从图中可以看出,重写原型对象切断了现有原型与任何之前已经存在的对象实例之间的联系;它们引用的仍然是最初的原型。

  1. function Person(){
  2. }
  3. Person.prototype = {
  4. constructor : Person,
  5. name : "sad",
  6. friends:["shelby","count"],
  7. sayName:function(){
  8. console.log(this.name)
  9. }
  10. }
  11. var person1 = new Person();
  12. var person2 = new Person();
  13. person1.friends.push("van");
  14. console.log(person1.friends);
  15. console.log(person2.friends);
  16. console.log(person1.friends == person2.friends);

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

创建自定义类型的最常见方式,就是组合使用构造函数模式与原型模式。构造函数模式用于定义实 例属性,而原型模式用于定义方法和共享的属性。结果,每个实例都会有自己的一份实例属性的副本, 但同时又共享着对方法的引用,最大限度地节省了内存。另外,这种混成模式还支持向构造函数传递参 数;可谓是集两种模式之长。下面的代码重写了前面的例子。

  1. function Person(name,age,job){
  2. this.name = name;
  3. this.age = age;
  4. this.job = job;
  5. this.friends = ["xiaohuang","xiaogming"];
  6. }
  7. Person.prototype = {
  8. constructor:Person,
  9. sayName:function(){
  10. console.log(this.name);
  11. }
  12. }
  13. var person1 = new Person("lxf","23","fw");
  14. var person2 = new Person("xm","99","my");
  15. person2.friends.push("aa");
  16.  
  17. console.log(person1.friends == person2.friends);//false
  18. console.log(person1.sayName == person2.sayName);//true
  19. console.log(person1);

6.2.5 动态原型模式

  1. function Person(name,age,job){
  2. this.name = name;
  3. this.age = age;
  4. this.job = job;
  5. this.friends = ["xiaohuang","xiaogming"];
  6. if(typeof this.sayName != "function"){
  7. Person.prototype.sayName = function(){
  8. console.log(this.name);
  9. },

Person.prototype.sayme = function(){
  console.log(this.name);
  }

  1. }
  2. }
  3. var person1 = new Person("asd","23","dd");

var person2 = new Person("asd1","223","ff");
  console.log(person1.name == person2.name);//false
  console.log(person2.sayName == person1.sayName);true

注意构造函数代码中加粗的部分。这里只在 sayName()方法不存在的情况下,才会将它添加到原 型中。这段代码只会在初次调用构造函数时才会执行。此后,原型已经完成初始化,不需要再做什么修 改了。不过要记住,这里对原型所做的修改,能够立即在所有实例中得到反映。因此,这种方法确实可 以说非常完美。其中,if 语句检查的可以是初始化之后应该存在的任何属性或方法——不必用一大堆 if 语句检查每个属性和每个方法;只要检查其中一个即可。对于采用这种模式创建的对象,还可以使 用 instanceof 操作符确定它的类型。

使用动态原型模式时,不能使用对象字面量重写原型。前面已经解释过了,如果 在已经创建了实例的情况下重写原型,那么就会切断现有实例与新原型之间的联系。

6.2.6 寄生构造函数模式

  1. function Person(name,age,job){
  2. var o = new Object();
  3. o.name = name;
  4. o.age = age;
  5. o.job = job;
  6. o.friends = ["xiaohuang","xiaogming"];
  7. o.sayName = function(){
  8. console.log(this.name);
  9. }
  10. return o;
  11. }
  12. var friend = new Person("ad","23","fw");
  13. var friend1 = new Person("33","321","fw1");
  14. console.log(friend1);//{name: "33", age: "321", job: "fw1", friends: Array(2), sayName: ƒ}
  15. console.log(friend);//{name: "ad", age: "23", job: "fw", friends: Array(2), sayName: ƒ}
  16. console.log(friend.name == friend1.name);//false
  17. console.log(friend1.sayName == friend1.sayName);//true

关于寄生构造函数模式,有一点需要说明:首先,返回的对象与构造函数或者与构造函数的原型属 性之间没有关系;也就是说,构造函数返回的对象与在构造函数外部创建的对象没有什么不同。为此, 不能依赖 instanceof 操作符来确定对象类型。由于存在上述问题,我们建议在可以使用其他模式的情 况下,不要使用这种模式。

6.2.7 稳妥构造函数模式

所谓稳妥对象,指的是没有公共属性,而且其方法也不引用 this 的对象。

  1. function Person(name,age,job){
  2. var o = new Object();//创建要返回的对象
  3. //可以在这里定义私有变量和函数
  4. o.sayName = function(){
  5. console.log(name);
  6. }
  7. return o;//返回对象
  8. }
  9. var person1 = Person("lxfa","23","front-end");
  10. var person2 = Person("lxf","23","front-end");
  11. console.log(person1.name == person2.name);//true
  12. console.log(person1.sayName == person2.sayName);//false

在以这种模式创建的对象中,除了使用 sayName()方法之外,没有其他办法访问 name 的值。 可以像下面使用稳妥的 Person 构造函数。

变量 friend 中保存的是一个稳妥对象,而除了调用 sayName()方法外,没有别的方式可 以访问其数据成员。即使有其他代码会给这个对象添加方法或数据成员,但也不可能有别的办法访问传 入到构造函数中的原始数据。

与寄生构造函数模式类似,使用稳妥构造函数模式创建的对象与构造函数之间也 没有什么关系,因此 instanceof 操作符对这种对象也没有意义。

6.3 继承

许多 OO 语言都支持两种继承方式:接口继承和 实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。如前所述,由于函数没有签名, 在 ECMAScript 中无法实现接口继承。ECMAScript 只支持实现继承,而且其实现继承主要是依靠原型链 来实现的。

6.3.1 原型链

ECMAScript 中描述了原型链的概念,并将原型链作为实现继承的主要方法。其基本思想是利用原 型让一个引用类型继承另一个引用类型的属性和方法。简单回顾一下构造函数、原型和实例的关系:每 个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型 对象的内部指针。那么,假如我们让原型对象等于另一个类型的实例,结果会怎么样呢?显然,此时的 原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数 的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实 例与原型的链条。这就是所谓原型链的基本概念。

  1. function SuperType(){
  2. this.property = true;
  3. }
  4. SuperType.prototype.getSuperValue = function(){
  5. return this.property;
  6. }
  7.  
  8. function SubType(){
  9. this.subproperty = false;
  10. }
  11. ////继承了 SuperType
  12. SubType.prototype = new SuperType();
  13. SubType.prototype.getSubValue = function(){
  14. return this.subproperty;
  15. }
  16.  
  17. var instance = new SubType();
  18. console.log(instance.getSuperValue());//true
  19. console.log(instance);

以上代码定义了两个类型:SuperType 和 SubType。每个类型分别有一个属性和一个方法。它们 的主要区别是 SubType 继承了 SuperType,而继承是通过创建 SuperType 的实例,并将该实例赋给 SubType.prototype 实现的。实现的本质是重写原型对象,代之以一个新类型的实例。换句话说,原 来存在于 SuperType 的实例中的所有属性和方法,现在也存在于 SubType.prototype 中了。在确立了 继承关系之后,我们给 SubType.prototype 添加了一个方法,这样就在继承了 SuperType 的属性和方 法的基础上又添加了一个新方法。这个例子中的实例以及构造函数和原型之间的关系如图 6-4所示。

完整的原型链:

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

① 实际上,不是 SubType 的原型的 constructor 属性被重写了,而是 SubType 的原型指向了另一个对象—— SuperType 的原型,而这个原型对象的 constructor 属性指向的是 SuperType。

3. 谨慎地定义方法

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

《JavaScript高级程序设计第三版》——细碎知识痛点整理(第六章)的更多相关文章

  1. JavaScript高级程序设计第三版.CHM【带实例】

    从驱动全球商业.贸易及管理领域不计其数的复杂应用程序的角度来看,说 JavaScript 已经成为当今世界上最流行的编程语言一点儿都不为过. JavaScript 是一种非常松散的面向对象语言,也是 ...

  2. 《Javascript高级程序设计》阅读记录(五):第六章 上

    这个系列以往文字地址: <Javascript高级程序设计>阅读记录(一):第二.三章 <Javascript高级程序设计>阅读记录(二):第四章 <Javascript ...

  3. javascript高级程序设计第三版书摘

    在HTML 中使用JavaScript <script>元素 在使用<script>元素嵌入 JavaScript 代码时,只须为<script>指定 type 属 ...

  4. DOM 操作技术【JavaScript高级程序设计第三版】

    很多时候,DOM 操作都比较简明,因此用JavaScript 生成那些通常原本是用HTML 代码生成的内容并不麻烦.不过,也有一些时候,操作DOM 并不像表面上看起来那么简单.由于浏览器中充斥着隐藏的 ...

  5. 10.2 DOM 操作技术【JavaScript高级程序设计第三版】

    很多时候,DOM 操作都比较简明,因此用JavaScript 生成那些通常原本是用HTML 代码生成的内容并不麻烦.不过,也有一些时候,操作DOM 并不像表面上看起来那么简单.由于浏览器中充斥着隐藏的 ...

  6. 22.1 高级函数【JavaScript高级程序设计第三版】

    函数是JavaScript 中最有趣的部分之一.它们本质上是十分简单和过程化的,但也可以是非常复杂和动态的.一些额外的功能可以通过使用闭包来实现.此外,由于所有的函数都是对象,所以使用函数指针非常简单 ...

  7. 21.1 XMLHttpRequest 对象【JavaScript高级程序设计第三版】

    IE5 是第一款引入XHR 对象的浏览器.在IE5 中,XHR 对象是通过MSXML 库中的一个ActiveX对象实现的.因此,在IE 中可能会遇到三种不同版本的XHR 对象,即MSXML2.XMLH ...

  8. JavaScript高级程序设计第三版-读书笔记(1-3章)

    这是我第一次用markdown,也是我第一次在网上记录我自己的学习过程. 第一章 JavaScript主要由以下三个不同的部分构成 ECMAScript   提供核心语言功能 DOM     提供访问 ...

  9. 14.5 富文本编辑【JavaScript高级程序设计第三版】

    富文本编辑,又称为WYSIWYG(What You See Is What You Get,所见即所得).在网页中编辑富文本内容,是人们对Web 应用程序最大的期待之一.虽然也没有规范,但在IE 最早 ...

随机推荐

  1. python_元组 学习

    一.创建元组 代码: name=(‘chinese’,’gansu’,’beijing’) 创建空元组 name=() 元组中只包含一个元素时,需要在玄素后面加逗号(,)消除歧义: name=(‘ch ...

  2. (转)Memcached用法--参数和命令详解

    Memcached用法--参数和命令详解 1. memcached 参数说明: # memcached -h 1.1 memcached 的参数 常用参数 -p <num> 监听的TCP端 ...

  3. pat1101. Quick Sort (25)

    1101. Quick Sort (25) 时间限制 200 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CAO, Peng There is a ...

  4. pat1068. Find More Coins (30)

    1068. Find More Coins (30) 时间限制 150 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHEN, Yue Eva l ...

  5. JEECMS站群管理系统-- 首页的加载过程

    在浏览器中输入http://localhost:8080/jeecms,回车 首先进入配置文件web.xml, <context-param> <param-name>cont ...

  6. Gradient Boosting算法简介

    最近项目中涉及基于Gradient Boosting Regression 算法拟合时间序列曲线的内容,利用python机器学习包 scikit-learn 中的GradientBoostingReg ...

  7. Java基于jdbc链接mysql数据库步骤示列

    用JDBC来链接MYSQL数据库,基本步骤都大同小异,只不过不同的数据库之间的URL地址有些不同.其基本步骤可分为以下几点: 1.加载相应的数据库的JDBC驱动程序. 2.利用驱动管理器DriverM ...

  8. 基于表单布局:分析过时的table结构与当下的div结构

    一些话在前面 最近做了百度前端学院一个小任务,其中涉及到表单布局的问题, 它要处理的布局问题:左边的标签要右对齐,右边的输入框.单选按钮等要实现左对齐. 从开始入门就被告知table布局已经过时了,当 ...

  9. 快速配置$XX_TOP方法

    查找配置文件名 执行:env | grep CONTEXT 得到: CONTEXT_FILE=/dev01/oracle/UAT/inst/apps/UAT_ksebsdt/appl/admin/UA ...

  10. Linux命令之查看内存和CPU消耗命令TOP使用

    1)输入top ,按enter键,即可查看服务器内存消耗情况 注意:其中PID表示进程号 :%cpu表示cpu消耗情况:%M表示内存消耗情况:通常在做性能测试的时候用到该命令: 默认为实时刷新:按s键 ...