在一般的编程语言中,我们使用继承来复用代码,做成良好的数据结构。而在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. 视频编解码的理论和实践2:Ffmpeg视频编解码

    近几年,视频编解码技术在理论及应用方面都取得了重大的进展,越来越多的人想要了解编解码技术.因此,网易云信研发工程师为大家进行了归纳梳理,从理论及实践两个方面简单介绍视频编解码技术. 相关阅读推荐 &l ...

  2. MCtalk对话抱抱星英语:从Diss在线英语教学乱象到回归教育本原

    在百度搜索输入“在线英语”四字,清一色的线上英语培训品牌琳琅满目,“免费英语学习”.“外教口语一对一培训”.“四级听力”.“专属外教”,竞争之激烈可见一斑,创业公司绞尽脑汁挖掘细分市场,试图在一片红海 ...

  3. JavaWeb入门_模仿天猫整站Tmall_SSH实践项目

    Tmall_SSH 技术栈 Struts2 + Hibernate + Spring + Jsp + Tomcat , 是 Java Web 入门非常好的练手项目 效果展示: 模仿天猫前台 模仿天猫后 ...

  4. 如何安装xenserver

    xenserver安装 选择键盘 是否同意协议 清理磁盘 选择本地磁盘安装 选择本地镜像文件 输入管理密码 配置IP地址 配置DNS服务器地址 选择地点 配置NTP服务器地址 开始安装 安装完成

  5. linux命令积累

    lsof -i: //根据端口号查相关信息 //杀进程 ps -ef|grep appName //根据进程名称查找相关信息 grep -r "关键词" 目录 //在制定目录下根据 ...

  6. 【POJ - 1426】Find The Multiple(dfs)

    -->Find The Multiple 原文是英语,直接上中文了 Descriptions: 给定一个正整数n,请编写一个程序来寻找n的一个非零的倍数m,这个m应当在十进制表示时每一位上只包含 ...

  7. Shiro中@RequiresAuthentication等等注解介绍

    使用前请先开启Shiro的controller层注解,如果已经设置请下滑绕过 要在spring-mvc.xml中写. <!--下面的用于开启shiro的权限注解--> <bean c ...

  8. 页面的隐藏或显示:hidden与visibilityState

    我们在很多地方都需要判断用户是不是在当前页面,如果离开了当前页面我们需要捕捉到并进行一些操作. 例如:当视频处于播放状态时,我们需要判断用户是不是在当前页面以继续播放,如果离开了我们需要暂停播放. 有 ...

  9. 探寻 webpack_bundle_analyzer 原理

    webpack_bundle_analyzer 是什么? 这是webpack官方出品的,对项目中模块依赖关系及体积的分析插件,其界面如下: 问题来了,这是如何来进行统计的? 这里提供一个插件的代码,可 ...

  10. 使用Gitlab-CI 实现NetCore项目Docker化并部署到阿里云K8S

    使用Gitlab-CI 实现NetCore项目Docker化并部署到阿里云K8S 先行条件: 1.了解NetCore项目基础命令,如dotnet publish   等几个常用命令. 2.了解Dock ...