在一般的编程语言中,我们使用继承来复用代码,做成良好的数据结构。而在JavaScript中,我们使用原型来实现以上的需求。由于JavaScript专注于对象而摒弃了类,我们要明白原型和继承的确是有差异的,但很多人接受不了这个事实,因此用某些语法来模仿类的操作。但如果我们要学习JavaScript,还是要抛开各种写法,先从原理上理解原型。JavaScript中原型并不是一个很难的事,但它是一个全新的概念,因此需要一些时间去接受。

1. 原型在哪儿

  原型指明了一个对象的“身份”。在前面的介绍中我们知道,一个对象被创建完成时会自动生成一个__proto__属性,一个函数被创建完成时会自动生成一个prototype属性,这就是我们所说的原型。所以原型本质上就是对象中的一个不可枚举的属性,值为一个方法可以被我们这个对象共用的对象,只是JavaScript会帮助我们维护这个属性(当然我们也可以改变原型的值)。我们期望被复用的属性和方法都可以放入原型中,而仅自身才有的属性就可以放入构造器里。就像这样:

 var MotherLyn = function(generation, name) {
this.generation = generation;
this.name = name;
} MotherLyn.prototype.show = function() {
return this.generation + this.name;
}

  这样打印出来的结构就是这个对象有两个(generation和name)属性,一个原型;这个原型中又有一个show方法,一个constructor属性(值为前四行声明构造函数的代码),和一个原型;这个原型就是我们的内建对象Object。由此我们可以看出,对象的原型之间形成了链状的关系,这条链最高的端点是我们的Object对象。我们称之为原型链。

2. 原型链

  我在网上查资料的时候翻到了这张图片,非常感激这张图片的作者,它基本讲述了一般对象、原型和构造器间的关系:

  看这个图我们基本上可以明白,Person Prototype的原型为基石,Person构造器为构造方法,由此我们来构建person实例。实例和构造器的原型都是Person Prototype原型;反过来原型中有一个构造器属性和许多被实例继承的属性和方法。

  我们可能会好奇实例中的[[Prototype]]和构造器中的prototype有什么区别。[[Prototype]]这个属性存在于对象中(像图中就存在于person实例中),我们是无法访问到的,只能通过浏览器中的__proto__属性辅助访问到它的值。也就是说__proto__是访问[[Prototype]]属性的一个方式。而prototype是函数对象中的一个属性,只存在于函数中,可以像访问普通属性一样直接访问得到。

  那我们可能又会继续好奇了,构造器作为函数,它本身也是个对象,该怎么办呢。打印构造器观察发现,实际上它既有__proto__属性也有prototype属性。就像人在社会上也有不止一个身份一样,构造器一方面作为函数,另一方面自身也是一个对象。作为函数,构造器是Person类的函数,因此它的prototype属性值为Person的原型;作为对象,构造器本身也是一个Function.prototype的子对象,因此构造器的__proto__属性值为Function。作一个类比,林大妈这个人,在学校中是一个学生,在家庭中是一个孩子,他的原型指明了他的身份属性,因此他既有学生这个原型属性,也有孩子这个原型属性。

  对上面这个图进行扩展,假如我们对Person Prototype继续取原型,一直取,最终我们会到达Object.prototype,里面包含了JavaScript为我们内建的许多方法,例如toString方法、hasOwnProperty方法等。我们刚才一直取原型的操作有点像在沿着一个链表不断向上摸索的感觉,因此我们把它称之为原型链。毫无疑问原型链的顶端是Object.prototype。如果再对Object.prototype取原型,会得到null,这个我们可以忽略。那么,对于原型链上的每个节点,也就是每个原型,理论上说我们是可以实现扩展(也就是给它增加属性或方法)的。

  另外,JavaScript引擎在寻找一个对象的属性时,会以① 先遍历对象自身的属性,② 找不到再遍历原型的属性, ③ 找不到再遍历原型的原型的属性, ……, 直到原型链的顶端Object对象,遍历完了仍然找不到属性时返回一个undefined。找寻方法也是如此。

3. 尝试用原型扩展内建对象

  当我们明白了原型是这么一个对象时,我们就要开始想它能怎么使用了。最浅显的用法显然是用来扩展Object,Array,Function这些内建的对象了,下面我们尝试为Array对象扩展一个乱序排序的方法:

 if(!Array.prototype.shuffle) {
Array.prototype.shuffle = function() {
for(var i = this.length, j = Math.floor(Math.random() * i), x; i; j = Math.floor(Math.random() * i)) {
x = this[--i];
this[i] = this[j];
this[j] = x;
}
return this;
}
}

  或者我们要做很多扩展工作,觉得.prototype太长了,不想打这么多次,也不美观,尝试为Function对象扩展函数提供一个简单的写法:

 if(!Function.prototype.method) {
Function.prototype.method = function(name, code) {
if(!Function.prototype[name]) {
this.prototype[name] = func;
return this;
}
}
}

  这样,我们扩展时只需要调用method函数,传入函数名和代码段(匿名函数也可以),就可以把函数扩展到对象里面了。

4. 尝试使用原型链实现继承

  假设我们有一个公司(不可能的,我不可能有公司的),要做一个非常非常非常简单基础的雇员管理,需求是:

    ① 有一个Employee雇员对象(不是实例)作为最高原型,有属性name人名和属性salary薪水,有方法show以字符串形式展示name和salary;

    ② 有一个Manager经理对象(不是实例),它的原型是雇员,有属性人名、薪水和手下数组inferiors,还有一个方法getInferiors以字符串形式展示inferiors;

    ③ 有一个Secretary秘书对象(不是实例),它的原型也是雇员,有属性人名、薪水和上司superior,其中上司必为经理,还有一个方法getSuperior以字符串形式展示superior。

  首先创建一个雇员对象:

 function Employee (name, salary) {
this.name = name;
this.salary = salary; this.show = function () {
return this.name + ": $" + this.salary;
}
}

  然后创建经理和秘书对象:

 function Manager (name, salary, inferiors) {
Manager.prototype.name = name;
Manager.prototype.salary = salary;
this.inferiors = inferiors; this.getInferiors = function () {
return this.inferiors;
}
} function Secretary (name1, salary1, name2, salary2) {
Secretary.prototype.name = name1;
Secretary.prototype.salary = salary1;
this.superior = new Manager(name2, salary2); this.getSuperior = function () {
return this.superior;
}
}

  以上这些都不是关键,下面我们要把它们以原型形式连接起来。由于JavaScript是纯基于对象的语言,它不像C++和Java一样有“类”的概念,因此即使是原型,我们也要使用对象来表示,而不是直接连接到构造函数:

 Manager.prototype = new Employee();
Secretary.prototype = new Employee();

  连接后创建manager和secretary实例,它们作为对象,__proto__属性成功连接到了Employee实例上。

  总结:① 原型是我们创建对象后JavaScript自动帮我们生成和维护的一个不可枚举对象,它指明了对象的“身份”,可以通过原型实现代码复用,但也要注意使用for in遍历时是否要过滤掉原型上的属性和方法;

     ② 不断取一个对象的原型,最终一定会到达Object.prototype(Object.prototype的原型为null,不予讨论),我们称之为原型链。在JavaScript中找寻对象和属性时会沿着原型链一直找,直到顶端遍历完如果仍找不到,最终返回undefined。

     ③ 对于每个原型对象,包括JavaScript的内建对象,我们都可以进行代码的扩展。

     ④ 原型的作用类似继承,可以帮助我们面向对象编程,复用代码。

林大妈的JavaScript基础知识(三):JavaScript编程(3)原型的更多相关文章

  1. 【javascript基础知识】javascript中的转义序列和特殊数值常量

    javascript的转义序列 \0 NUL字符(\u0000) \b 退格符(\u0008) \t 水平制表符(\u0009) \n 换行符(\u000A) \v 垂直制表符(\u000B) \f ...

  2. Javascript 基础知识学习--javascript中的参数传递都是按值传递的

    ECMAScript中所有函数的参数传递都是按值传递的,无论参数是值类型还是引用类型的.过去我跟大多数人一样觉得跟传值类型相关. 自己写了一个测试的例子,确实如此 function add(a) { ...

  3. 学习javascript基础知识系列第二节 - this用法

    通过一段代码学习javascript基础知识系列 第二节 - this用法 this是面向对象语言中的一个重要概念,在JAVA,C#等大型语言中,this固定指向运行时的当前对象.但是在javascr ...

  4. 学习javascript基础知识系列第三节 - ()()用法

    总目录:通过一段代码学习javascript基础知识系列 注意: 为了便于执行和演示,建议使用chrome浏览器,按F12,然后按Esc(或手动选择)打开console,在console进行执行和演示 ...

  5. Javascript基础知识总结一

    Javascript基础知识总结一 <!DOCTYPE html> <html> <head lang="en"> <meta chars ...

  6. JavaScript基础知识(一)

    一.JavaScript基础 1.JavaScript用法: HTML 中的脚本必须位于 <script> 与 </script> 标签之间. 脚本可被放置在 HTML 页面的 ...

  7. JavaScript基础(三)

    十三.JS中的面向对象 创建对象的几种常用方式 1.使用Object或对象字面量创建对象 2.工厂模式创建对象 3.构造函数模式创建对象 4.原型模式创建对象 1.使用Object或对象字面量创建对象 ...

  8. javascript 基础知识学习1

    JavaScript 是脚本语言.浏览器会在读取代码时,逐行地执行脚本代码.而对于传统编程来说,会在执行前对所有代码进行编译.基础知识:1).JavaScript 对大小写敏感.JavaScript ...

  9. JavaScript 基础知识 - BOM篇

    前言 本篇文章是JavaScript基础知识的BOM篇,如果前面的<JavaScript基础知识-DOM篇>看完了,现在就可以学习BOM了. 注意: 所有的案例都在这里链接: 提取密码密码 ...

  10. java 基础知识三 java变量

    java  基础知识 三 变量 1.作用域 {} 包围起来的代码 称之为代码块,在块中声明的变量只能在块中使用 2.常量 就是固定不变的量,一旦被定义,它的值就不能再被改变. 3.变量 变量必须在程序 ...

随机推荐

  1. Linux不重启识别新添加的磁盘

    现网的一台EXSI 下的虚拟机一般在进行配置变更后都会通过重启来识别新增的配置,不过业务侧某台主机因为业务需要无法重启,想通过不重启直接识别护容上去的新磁盘.经测试,发现如下方可以解决. 1.通过ex ...

  2. 设计和编写一个异步通用Picker选择器,用于时间日期、城市、商品分类的选择

    目录 一.功能规划 二.最底层基础实现 (1)Picker界面和功能实现 (2)不同类型的选择器基础实现 三.数据源层 (1)时间日期 (2)多级同步分类,如:城市 (3)多级异步分类,如:城市 四. ...

  3. BZOJ 1878:[SDOI2009]HH的项链(莫队算法)

    http://www.lydsy.com/JudgeOnline/problem.php?id=1878 题意:…… 思路:比上题还简单很多.数字很小,开一个数组哈希记录出现次数(记得数组要开1e6) ...

  4. C++学习书籍推荐《Exceptional C++(英文)》下载

    百度云及其他网盘下载地址:点我 作者简介 Herb Sutter is the author of three highly acclaimed books, Exceptional C++ Styl ...

  5. 3. Django每日一码 之 Serializers 源码

    2019-7-6 今日源码:rest-framework 序列化Serializers 序列化组件Serializers 源码分析 首先,它需要 data .many . instance,其中 in ...

  6. java学习笔记(基础篇)—抽象与接口的区别

    抽象与接口的区别 一.抽象(abstract) 1. 抽象方法 1) 作用:定义规范 2) 抽象方法用来描述具有什么功能,但不提供实现. 3) 如果类中一个方法没有实现就要定义一个抽象方法. 2. 抽 ...

  7. Java调用方法参数究竟是传值还是传址?

    之前阅读<Head First Java>的时候,记得里面有提到过,Java在调用方法,传递参数的时候,采用的是pass-by-copy的方法,传递一份内容的拷贝,即传值.举一个最简单的例 ...

  8. 【HDU - 1560】DNA sequence (dfs+回溯)

    DNA sequence 直接中文了 题目描述 21世纪是生物科技飞速发展的时代.我们都知道基因是由DNA组成的,而DNA的基本组成单位是A,C,G,T.在现代生物分子计算中,如何找到DNA之间的最长 ...

  9. poi解析excel文件报错

    getFileMagic() only operates on streams which support mark(int) 使用 bis 解决 BufferedInputStream bis = ...

  10. Excel催化剂开源第24波-较VBA更强大的.Net环境的正则表达式

    在VBA上可以调用正则表达式库,从而编写正则表达式自定义函数,这个相信不少VBA开发者已经熟知,但VBA的VBScript正则表达式库毕竟是一个过时的产品,不像.Net那样是与时俱进的,所以两者实现出 ...