在一般的编程语言中,我们使用继承来复用代码,做成良好的数据结构。而在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. 如何两个月刷400道leetcode

    前言随着互联网寒潮的到来, 越来越多的互联网公司提高了面试的难度,其中之一就是加大了面试当中手撕算法题的比例.这里说的算法题不是深度学习,机器学习这类的算法,而是排序,广度优先,动态规划这类既考核数据 ...

  2. C#版剑指Offer-001二维数组中的查找

    题目描述 在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数 ...

  3. java泛型的作用及其基本概念

    一.泛型的基本概念 java与c#一样,都存在泛型的概念,及类型的参数化.java中的泛型是在jdk5.0后出现的,但是java中的泛型与C#中的泛型是有本质区别的,首先从集合类型上来说,java 中 ...

  4. Go 语言基础

    Go 语言基础 特点 常用命令 go run 直接运行, 不会生成可执行文件 go build 生成可执行文件, 推荐 加分特点 UTF-8编码 高并发: go 关键字 管道: pipe := mak ...

  5. JAVA Stirng.format 使用理解

    JAVA Stirng.format 使用理解前言:项目中需要对一些字符串处理发现format方法的神奇之处一.api才是王道第一种二参使用①public static String format(S ...

  6. Python编程菜鸟成长记--A1--03--Python 环境安装(待完成)

    1.重点知识 Windows 上如何安装 Python 3 Linux 上如何安装 Python 3 Mac 上如何安装 Python 3 Windows 上如何安装 Pycharm Mac 上如何安 ...

  7. 利用HtmlAgilityPack插件写的一个抓取指定网页的图片 第一次写 很乱 随便看看就行

    public partial class Form1 : Form { /// <summary> /// 存放图片地址 /// </summary> List<stri ...

  8. c++指针经典题目分析

    首先看一下题目,下列程序会在那一行崩溃,程序如下: #include<iostream> using namespace std; struct S{ int i; int *p; }; ...

  9. Innovus教程 - Flow系列 - MMMC分析环境的配置概述(理论+实践+命令)

    本文转自:自己的微信公众号<集成电路设计及EDA教程> <Innovus教程 - Flow系列 - MMMC分析环境的配置概述(理论+实践+命令)>   轻轻走过,悄悄看过,无 ...

  10. web文件下载(附方案及源码配置)

    1. 场景描述 因项目需查询数据量比较大(需要查询Hbase等nosql数据库),采用用户点击查询后,后台查询并生成查询文件:然后消息通知用户后,用户点击下载的方式来满足用户需求. 2. 解决方案 W ...