目录

导语

1.理解对象和面向对象的程序设计

2.创建对象的方式

3.JavaScript的继承机制

3.1 原型对象
3.2 原型链
3.3 与原型对象相关的方法

4.小结


导语

前面的系列文章,基本把JavaScript的核心知识点的基本语法、标准库等章节讲解完;
本章开始进入JavaScript核心知识点的高级部分——面向对象的程序设计,这一部分的内容将会对对象这一数据类型做进一步的深化理解,并且讲述几种创建对象的设计模式以及JavaScript独特的继承机制;


1.理解对象和面向对象的程序设计

1.1 面向对象的程序设计

"面向对象编程"(Object Oriented Programming,缩写为OOP)本身是一种编程的思维模式,它把世界的一切看作是对象的集合,世界的运转就是靠一个个对象分工、合作的结果,体现一切皆“对象”思想;
而在程序设计当中,面向对象编程就可以看做编写各个具有特定功能的对象(模块)并将它们进行有机的分工合作,即目前的模块化编程就是面向对象的程序设计的实际应用;

1.2 理解对象

对象在前面的系列文章中曾经提到,从数据特征上看,对象是无序属性(键值对)的集合;
我们可以使用字面量和构造函数的方式去创建一个最为简单的对象:

var person = new Object();
person.name = "teren";
person.age = 18;
person.greet = function(){console.log("hello "+this.name);
}var teren = {name:"teren",age:18,greet:function(){console.log("hello "+this.name);}
}

通常创建一个简单的对象,都是采用字面量的方式;
上面的对象就是对现实对象的一种抽象表达;

1.3 对象的属性类型

前面章节中我们使用delete命令可以删除一些对象的属性,有一些又不可以,使用Object.keys()方法只能遍历可枚举属性,那么对象的属性是否有一些特性是我们尚未了解的呢?
ES5提供了一种只有内部才用的特性(attribute)去描述对象的属性(property)的各种特性,使用[[attribute]]表示,在JavaScript中不能直接访问它们;
一个我们非常熟悉的栗子就是Number构造函数构造出来的实例对象;

我们无法直接访问num.[[PrimitiveValue]],这一属性,只能通过num.valueOf()访问该值;

ES5中定义对象属性的两种特性,数据特性和访问器特性,对象属性可以兼备这两种特性;

数据特性定义对象的属性值的特性,一个属性值可以包括以下四个数据特性:

[[Value]]:存放属性值;
[[Writable]]:是否可写属性;
[[Enumerable]]:是否为可枚举属性;
[[Configurable]]:是否可用delete命令删除;

访问器特性定义对象的属性在访问属性和设置属性时调用的两个函数getter和setter;

[[Get]]:访问属性时调用的函数;
[[Set]]:设置属性时调用的函数;

下面以一个实例对象直接讲解这两个特性:

//数据特性;
var teren = {};
Object.defineProperty(teren,{value:"teren",writable:false,enumerable:true,configurable:true
})//访问器特性;
//html
<div id="name"></div>
//js
var obj = Object.defineProperty({},"name",{
set:function(name){document.getElementById('name').innerHTML=name
},
get:function(){console.log( document.getElementById('name').innerHTML) },
})
obj.name = "hello world"
obj.name

【demo】
Object.defineProperties可以一次性配置对象的多个属性;


2. 创建对象的方式

上一节我们对面向对象的程序设计思想和对象有了初步理解,这一节我们深入探讨一下对象的创建方式及其优缺点;

创建对象的不同方式也可以简单的称作设计模式,不同的设计模式在实际编程应用中起到不同的作用;

2.1 单例模式

单例模式就是产生一个类的唯一实例对象,它能够确保您只有一个对象实例能够实际派上用场;

单例模式下,创建对象方式如下:

var singleton = {attr:1,method:function(){return this.attr}
}
var ex1 = singleton;
var ex2 = singleton;
ex1 === ex2//true

上述创建单例的方式:

优点:使用非常简捷;
缺点:缺乏封装,成员暴露,初始化时占用资源;

可以使用闭包方式解决这一问题:

var substance = (function(){var unique; function init(){var type;   return {setType:function(t){return type = t;}}}return {getInstance:function(){if(!unique){unique = init();}return unique;}  }
})();var Adam = substance.getInstance();
var Eve = substance.getInstance();
Adam === Eve
Adam.setType('Man')//Man

2.2 工厂模式

单例模式只能创作单个实例对象,也就是说如果将该实例对象赋予多个变量时,会存在对象的引用问题,即修改其中一个变量会影响到另一个变量;
有时我们需要创造多个结构相似的对象,只有部分属性有所区别,这时候工厂模式派上用场;

工厂模式的设计思想就是能够像工厂一样批量生产出相似属性和方法的对象,使用工厂模式能解决多个相似的问题,例如创造多个弹窗(只是标题不同);

function person(name,age){var obj = new Object();obj.name = name;obj.age = age;obj.greet = function(){return "hello "+this.name;};return obj
}
var Adam = person("Adam",18);
var Eve = person("Eve",20);

上述工厂模式:

优点:能批量生产结构类似的对象;封装创建对象的细节;

缺点:未能解决对象的类型,即由哪个构造函数创建的;

2.3 构造函数模式

构造函数可以创建特定类型的对象,类似之前的Array、RegExp等原生对象都能创造特定类型的实例对象;

function Person(name,age){this.name = name;this.age = age;this.greet = function(){return "hello "+this.name;}
}
var p1 = new Person('Adam',18);
var p2 = new Person('Eve',20);

使用构造函数模式就能够解决实例对象由谁创建的问题;

上述代码和工厂模式的区别在于:

1.没有显示创建新对象;

2.直接将属性和方法赋给this对象;
3.没有return语句;
4.函数名开头大写以区别普通函数;
5.使用new操作符去创建对象实例;

new操作符的原理
使用new操作符去调用函数和直接调用函数不同,其new操作符的运行函数的过程为:

  1. 创建一个新对象;

  1. 将构造函数的作用域赋给新对象并执行构造函
    内的代码;

  2. 返回新对象;

使用代码表示如下:

function Person(name,age){this.name = name;this.age = age;this.greet = function(){return "hello "+this.name;}
}
function createPerson(name,age){var o = new Object();Person.call(o,name,age);return o;
}
var p1 = createPerson('Adam',18);
var p2 = createPerson('Eve',20);

使用构造函数模式创建对象的优缺点在于:

优点:能够识别对象属于的构造函数;

缺点:如果存在不同实例对象共享的属性和方法,使用构造函数模式则会浪费内存;

【注】
关于this关键字的更多知识点可以参见【what's this】;
构造函数如果不用new操作符调用和普通函数是一样的;

2.4 原型模式

每个函数都有一个prototype原型属性,这个原型属性可以部署特定类型的实例共享的属性和方法;

function Person(){}
Person.prototype.greet = function(){return "hello "+this.name;
}

将原来的greet函数部署在Person函数的prototype原型属性上,这样p1和p2可以共享该方法,而不像构造函数模式每创建一个实例就增加一个greet方法浪费内存;

【注】
关于原型对象的更多理解详见下一节——JavaScript的继承机制;

使用原型模式创建对象的优缺点在于:

优点:对于每个实例的共享属性和方法能够较好实现;

缺点:单独采用原型模式将无法区分不同实例的私有属性;

2.5 混合模式

混合模式,就是综合构造函数模式和原型模式的优缺点,构造函数模式部署实例的私有属性,原型模式部署实例的公有属性;

function Person(name,age){this.name = name;this.age = age;
}
Person.prototype.greet = function(){return "hello "+this.name;
}
var p1 = new Person("Adam",18);
var p2 = new Person("Eve",20);

混合模式是目前使用最广泛、认同度最高的一种创建自定义类型(类)的设计模式;

【注】
当然,设计模式不仅仅上述所提到,还有更加精深可以参考《设计模式》一书以及之前小羊写的一篇文章《设计模式梗概》;


3.JavaScript的继承机制

上一节我们通过创建对象的不同模式,隐式引出了原型对象的概念,这一节中我们将详细了解一下原型对象、原型链及其实现的继承机制;

前面,我们从数据特征上看,知道对象是无序属性(键值对)的集合;
现在,我们可以从面向对象的角度看,任何对象都是更为抽象的对象的实例,可以理解为类的概念;
从这个角度理解,我们现在可以重新定义一下对象和类的含义:
对象可以说是对现实事物的抽象,对象封装了属性和方法,属性值指的是对象的状态,方法指的是对象的行为;
是提供一种模板的‘对象’,它是对象的抽象
举个简单的栗子:

function Person(name,age){this.name = name;this.age = age;
}
Person.prototype.greet = function(){return "hello "+this.name;
}
var p1 = new Person("Adam",18);
var p2 = new Person("Eve",20);

上述代码表明,p1和p2两个实例是对现实Adam和Eve的抽象,而“类”Person又是对2个实例的抽象,为创建相似结构的人提供标准的模板;
[注]ES6之前JavaScript中没有类,在ES6中定义了类;

3.1 原型对象

在上一节的原型模式中,我们提到每个函数都有一个prototype属性,这个属性指向函数的原型对象,可以部署特定类型的实例共享的属性和方法;

更为深入理解prototype原型对象,prototype原型对象不仅可以部署特定类型的实例共享的属性和方法,而且还是实现JavaScript继承的关键;

只要创建一个新函数就会为该函数创建一个prototype属性,每个prototype属性自动获得一个constructor属性,该属性指向prototype属性所在的函数指针;

当使用构造函数创建一个实例时,该实例内部包含一个内部属性__proto__指向构造函数的原型对象;

由此,一个简单的继承便产生了;
以下面代码为例:

function Person(name,age){this.name = name;this.age = age;
}
Person.prototype.greet = function(){return "hello "+this.name;
}
var p1 = new Person("Adam",18);
var p2 = new Person("Eve",20);

构造函数创建之后,自动创建一个prototype属性,prototype原型对象下有一个默认的constructor属性指向prototype属性所在的函数Person中;
在prototype原型对象上部署greet方法,实例p1的内部属性__proto__指向构造函数Person.prototype,由此继承了构造函数的原型对象上的greet方法;

【注意】

  • 实例的__proto__指向构造函数的prototype原型对象实现继承,这种联系存在于实例与构造函数的原型对象之间而不是构造函数之间;

  • 当js引擎解析对象的属性时,先会搜索对象本身的属性,如果没有则会去搜索__proto__指向的原型对象上的属性,直到找到为止,如果在对象本身定义的属性和原型对象上的具有相同属性名,则在读取该属性时,自身属性会屏蔽原型对象上的属性;

function Person(name,age){this.name = name;this.age = age;
}
Person.prototype.greet = function(){return "hello "+this.name;
}
var p1 = new Person("Adam",18);
p1.greet()//hello Adam;
p1.greet = function(){return "hello world"
}

  • 修改构造函数的原型对象可以直接使用点操作,效果是直接在原来的原型对象上增加属性,有时需要增加的属性太多是,点操作就显得太麻烦,可以采用重置原型对象的方法:

function Person(name,age){this.name = name;this.age = age;
}
Person.prototype = {constructor:Person,greet1:function(){},greet2:function(){},greet3:function(){}
};
var p1 = new Person("Adam",18);
Person.prototype.constructor.name//"Object"

需要注意的是,重置原型对象后,要重新为原型对象的constructor的属性指回Person构造函数;
如果不重置constructor的话,那么此时的Person.prototype是由字面量创建的对象,字面量创建的对象默认的构造函数是Object;

3.2 原型链

上面我们只定义一个构造函数,实现一次继承;如果存在多个构造函数,它们之间也存在继承关系,那么就会形成一条关于继承的原型链;

function SuperType(name,age){this.name = name;this.age = age
}
SuperType.prototype.greet = function(){return "hello "+this.name
}
function SubType(name,age,height){SuperType.call(this,name,age);this.height = height;
}
SubType.prototype = Object.create(SuperType.prototype);
SubType.prototype.constructor = SubType;
SubType.prototype.method = function(){return 1;}var instance = new SubType('teren',18,180)

上面就是一个最为常用的实现多个类间继承的设计模式;
使用Object.create(SuperType.prototype)的优缺点在于:

优点:能够创建一个新的SuperType.prototype对象赋给SubType.prototype,修改SubType.prototype这个而不影响原来构造函数SuperType.prototype;

缺点:虽然拥有子类的prototype和父类的prototype值是相同的,但内存不同,从而切断子类和父类之间的类型;

还可以使用SubType.prototype =new SuperType()实现相同效果,其优缺点在于:

优点:能够体现子类和父类的继承关系;

缺点:子类具有父类的私有属性;


所以,一般在实际实现原型链时使用Object.create()方法,而理解原型链时使用new SuperType()方法;

3.3 与原型对象相关的方法

遍历对象属性方法
Object.keys()Object.getOwnPropertyNames()用于遍历对象自身而不是继承的属性名,返回一个数组,其中Object.keys()只返回可枚举属性;


in用于检查一个对象是否具有某个属性。它不区分该属性是对象自身的属性,还是继承的属性;
for...in用于遍历对象的所有可枚举属性(不管是自身的还是继承的)


如果只遍历自身的属性,可以使用如下代码:

for (var key in instance){if(instance.hasOwnProperty(key)){console.log(key)}
}

判断属性是否为自身的方法
Object.prototype.hasOwnProperty()返回一个布尔值,用于判断某个属性定义在对象自身,还是定义在原型链上;

设置和获取实例对象的原型对象的方法
Object.getPropertyOf()返回一个实例对象的原型对象;
Object.setPropertyOf(obj,prototype)可传两个参数,第1个为现有参数,第2个为原型对象;

判断一个对象是否为另一个对象的原型对象
Object.prototype.isPrototypeOf()用于判断一个对象是否是另一个对象的原型;


小结

通读本文,我们可以知道:

  • 面向对象编程时一种思维模式,它把世界的一切看做是对象的集合,世界的运作是一个个对象分工合作的结果;映射到程序设计中,就是编写各个具有特定功能的对象(模块),并将它们有机整合使程序得以运作;

  • 对象从数据特征角度看,是无序键值对的集合,对象的属性具有两种特性——数据特性和访问器特性;

  • 创建对象的不同方式可以称为设计模式,本文简单讲解了单例模式、工厂模式、构造函数模式、原型模式、混合模式等;

  • 从面向对象角度看,对象可以说是对现实事物的抽象,类是对对象的抽象;

  • 每个函数都有一个原型对象prototype,既可以部署特定类型实例的共享属性和方法,也是JavaScript实现继承的关键;

  • prototype原型对象有一个constructor属性,该属性指向prototype所在的函数指针;

  • 每当使用构造函数创建一个实例时,该实例内部包含一个内部属性__proto__指向构造函数的原型对象,由此实现简单的继承;

  • 当A构造函数是B构造函数的实例时,由此就会形成一条原型链,即
    A构造函数的实例对象C的__proto__指向A构造函数的原型对象prototype,A构造函数prototype的__proto__指向B构造函数的原型对象prototype,B构造函数prototype的__proto__指向Function构造函数的prototype,Function的prototype的__proto__指向Object的prototype;

  • 与原型对象的相关方法包括:Object.keys()和Object.getPropertyNames()、for...in方法,Object.getPrototypeOf()和Object.setPrototypeOf()方法,Object.prototype.hasOwnProperty()和Object.prototype.isPrototypeOf()方法;


参考资料

  • 《JavaScript高级程序设计(第3版)》

  • 《JavaScript标准参考教程》——阮一峰

JavaScript面向对象的程序设计相关推荐

  1. javascript:面向对象的程序设计

    一:理解对象属性 对象有两种属性:数据属性和访问器属性. 1)数据属性 数据属性有四个描述其行为的特性,[[configurable]] [[enumerable]] [[writable]] [[v ...

  2. JavaScript 面向对象的程序设计1

    一.理解对象 1.创建一个对象,然后给这个对象新建属性和方法. ①常见的创建方式 var person = new Object(); //创建一个Object 对象person.name = 'XI ...

  3. javascript高级程序设计第3版——第6章 面向对象的程序设计

    第六章--面向对象的程序设计 这一章主要讲述了:面向对象的语言由于没有类/接口情况下工作的几种模式以及面向对象语言的继承: 模式:工厂模式,构造函数模式,原型模式 继承:原型式继承,寄生式继承,以及寄 ...

  4. 《JavaScript面向对象精要》读书笔记

    JavaScript(ES5)的面向对象精要 标签: JavaScript 面向对象 读书笔记 2016年1月16日-17日两天看完了<JavaScript面向对象精要>(参加异步社区的活 ...

  5. javascript面向对象系列第一篇——构造函数和原型对象

    前面的话 一般地,javascript使用构造函数和原型对象来进行面向对象编程,它们的表现与其他面向对象编程语言中的类相似又不同.本文将详细介绍如何用构造函数和原型对象来创建对象 构造函数 构造函数是 ...

  6. 《JavaScript面向对象编程指南》——第1章 引言1.1 回顾历史

    本节书摘来自异步社区<JavaScript面向对象编程指南>一书中的第1章,第1.1节,作者: [加]Stoyan Stefanov 译者: 凌杰 更多章节内容可以访问云栖社区" ...

  7. 面向对象的程序设计——理解对象

    面向对象的程序设计 ECMA-262 把对象定义为:无须属性的集合,其属性可以包含基本值.对象或者函数. 理解对象 var person = new Object(); //创建对象 person.n ...

  8. Javascript 面向对象全新理练之数据的封装

    JavaScript 是一种非常灵活的面向对象程序设计语言,它与传统的强类型的面向对象程序设计语言(如 C++,Java,C# 等)有很大不同,所以要实现如 C++.java.C# 当中的一些特性就需 ...

  9. 《JavaScript面向对象的编程指南》--读书笔记

    第一章.引言 1.5 面向对象的程序设计常用概念 对象(名词):是指"事物"在程序设计语言中的表现形式. 这里的事物可以是任何东西,我们可以看到它们具有某些明确特征,能执行某些动作 ...

  10. JS面向对象的程序设计之继承-继承的实现-借用构造函数

    JS面向对象的程序设计之继承-继承的实现-借用构造函数 前言:最近在细读Javascript高级程序设计,对于我而言,中文版,书中很多地方翻译的差强人意,所以用自己所理解的,尝试解读下.如有纰漏或错误 ...

最新文章

  1. 安装librosa遇到LLVM问题原因是numba问题
  2. 利用位运算和指针实现的交换两个数的程序
  3. Express res.render 学习笔记
  4. C++学习之路 | PTA乙级—— 1056 组合数的和 (15 分)(精简)
  5. Going Deeper in Spiking Neural Networks: VGG and Residual Architectures
  6. 上海服务器虚拟机系统,服务器虚拟机系统重装系统
  7. 计算机不能代替人类英语作文,2013年雅思写作范文:电脑翻译能取代人吗?
  8. 若依图片上传成功不能显示的解决办法?
  9. 计算机组装的虚拟仿真实验报告,组装计算机的虚拟实验室
  10. Project Office 9.0 for mac(任务项目管理软件)
  11. 番茄时间--C#实现
  12. HCIE-Datacom V1.0 考试大纲
  13. 百度地图经纬度获取标点与城市编码
  14. 这个是没事的时候做出来看的一系列算数表
  15. 解构SPA模式 ZARA、UNIQLO秘诀
  16. IO进程——系统IO与文件IO
  17. 2020-06-08
  18. DotNetCore深入了解之二HttpContext类
  19. 2019最新某图灵系列丛书(交互设计、编程、大数据、人工智能等)
  20. 一个应届生在深圳不平凡的一年

热门文章

  1. Servlet和JSP学习指南
  2. Inside Google’s language detection tool
  3. Android 四大组件学习之Service七
  4. Android 9.0 HIDL接口添加
  5. 苹果xsmax怎么开机_苹果XS/xs max按钮浮标怎么设置?
  6. git本地项目推动到gitlab远端服务器
  7. 编写可靠shell脚本的八个建议
  8. python中常用的模块_Python中常用的模块
  9. html 块 隐藏 显示,bootstrap如何显示或隐藏内容?
  10. centos运行jar包需要的环境_SpringBoot+Vue前后端一次打包为一个Jar包运行