在一般的编程语言中,我们使用继承来复用代码,做成良好的数据结构。而在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. java中更新文件时,指定原文件的编码格式,防止编码格式不对,造成乱码

    1.pom中添加引入cpdetector(一个可以自动检测文本编码格式的项目) //pom中添加引入cpdetector(一个可以自动检测文本编码格式的项目) <dependency> & ...

  2. 15 | 过不了的坎:聊聊GUI自动化过程中的测试数据

  3. mysql 正确清理binlog日志的两种方法

    前言: MySQL中的binlog日志记录了数据库中数据的变动,便于对数据的基于时间点和基于位置的恢复,但是binlog也会日渐增大,占用很大的磁盘空间,因此,要对binlog使用正确安全的方法清理掉 ...

  4. Java基础中字符串与字符的注意点!

    在Java中,字符的表达与字符串的表达是不一样的!话不多说,直接上代码!!! String  a="a"; char a='a'; 其中他们的引号是不一样的

  5. GET,POST,PUT,DELETE,OPTIONS等请求方式简单总结

    之前做的java web项目,基本上只使用get和post的请求方式,但是现在新项目额外增加了put,delete,查了点资料,做个简单的总结. 1.GET-安全且幂等 get请求是用来获取数据的,只 ...

  6. div+css 布局技巧总计

    一.css 样式 1.float 首先需要了解块级元素(block element).每个块级元素都默认占用一行,在同一行只能添加一个块元素(float 除外).块级元素一般可以嵌套块级元素或者行内元 ...

  7. 关于Keepalive的那些事

    服务端很多同学包括自己对keepalive理解不清晰,经常搞不清楚,TCP也有keepalive,HTTP也有keepalive,高可用也叫keepalive,经常混淆这几个概念.做下这几个概念的简述 ...

  8. kuangbin专题 专题一 简单搜索 非常可乐 HDU - 1495

    题目链接:https://vjudge.net/problem/HDU-1495 题意:有两个空杯(分别是N升和M升)和一罐满的可乐S升,S = N + M,三个容器可以互相倾倒,如果A倒入B,只有两 ...

  9. 哈工大计算机网络Week1-网络应用

    目录 哈工大计算机网络Week1-网络应用 2.1网络应用的体系结构 特点 应采取什么结构 C/S结构 客户机/服务器 P2P CS vs P2P 混合结构 思考题目 2.2网络应用的基本原理 网络应 ...

  10. 第二章 Session会话管理

    采用网址重写的缺点: 在有些Web浏览器中,URL限制为2000个字符. 仅当有链接要插入值时,值才能转换成后面的资源.此外,要把值添加到静态页面的链接中,可不是一件容易的事情. 网址重写必须在服务器 ...