JavaScript原型和继承用法详解

一、函数创建过程

在了解原型链之前我们先来看看一个函数在创建过程中做了哪些事情,举一个空函数的例子:
function A() {};
当我们在代码里面声明这么一个空函数,js解析的本质是(肤浅理解有待深入):
1、创建一个对象(有constructor属性及[[Prototype]]属性),根据ECMA,其中[[Prototype]]属性不可见、不可枚举
2、创建一个函数(有name、prototype属性),再通过prototype属性 引用 刚才创建的对象
3、创建变量A,同时把函数的 引用 赋值给变量A
如下图所示:


(注意图中都是“ 引用 ”类型)
每个函数的创建都经历上述过程。

二、构造函数

那么什么是构造函数呢?

按照ECMA的定义

Constructor is a function that creates and initializes the newly created object.
构造函数是用来新建同时初始化一个新对象的函数。
什么样的函数可以用来创建同时初始化新对象呢?答案是:任何一个函数,包括空函数。
所以,结论是:任何一个函数都可以是构造函数。

三、原型

根据前面空函数的创建图示,我们知道每个函数在创建的时候都自动添加了prototype属性,这就是函数的原型,从图中可知其实质就是对一个对象的引用(这个对象暂且取名原型对象)。
我们可以对函数的原型对象进行操作,和普通的对象无异!一起来证实一下。
围绕刚才创建的空函数,这次给空函数增加一些代码:
function A() {
    this.width = 10;
    this.data = [1,2,3];
    this.key = "this is A";
}
A._objectNum = 0;//定义A的属性
A.prototype.say = function(){//给A的原型对象添加属性
    alert("hello world");
}

第7~9行代码就是给函数的原型对象增加一个say属性并引用一个匿名函数,根据“函数创建”过程,图解如下:

(灰色背景就是在空函数基础上增加的属性)
简单说原型就是函数的一个属性,在函数的创建过程中由js编译器自动添加。
那么原型有什么用呢?

先了解下new运算符,如下:

var a1 = new A;
var a2 = new A;

这是通过构造函数来创建对象的方式,那么创建对象为什么要这样创建而不是直接var a1 = {};呢?这就涉及new的具体步骤了,这里的new操作可以分成三步(以a1的创建为例):
1、新建一个对象并赋值给变量a1:var a1 = {};
2、把这个对象的[[Prototype]]属性指向函数A的原型对象:a1.[[Prototype]] = A.prototype
3、调用函数A,同时把this指向1中创建的对象a1,对对象进行初始化:A.apply(a1,arguments)
其结构图示如下:

从图中看到,无论是对象a1还是a2,都有一个属性保存了对函数A的原型对象的引用,对于这些对象来说,一些公用的方法可以在函数的原型中找到,节省了内存空间。
 
四、原型链

了解了new运算符以及原型的作用之后,一起来看看什么是[[Prototype]]?以及对象如何沿着这个引用来进行属性的查找?
在js的世界里,每个对象默认都有一个[[Prototype]]属性,其保存着的地址就构成了对象的原型链,它是由js编译器在对象 被创建 的时候自动添加的,其取值由new运算符的右侧参数决定:当我们var object1 = {};的时候,object1的[[Prototype]]就指向Object构造函数的原型对象,因为var object1 = {};实质上等于var object = new Object();(原因可参照上述对new A的分析过程)。
对象在查找某个属性的时候,会首先遍历自身的属性,如果没有则会继续查找[[Prototype]]引用的对象,如果再没有则继续查找[[Prototype]].[[Prototype]]引用的对象,依次类推,直到[[Prototype]].….[[Prototype]]为undefined(Object的[[Prototype]]就是undefined)
如上图所示:
//我们想要获取a1.fGetName
alert(a1.fGetName);//输出undefined
//1、遍历a1对象本身
//结果a1对象本身没有fGetName属性
//2、找到a1的[[Prototype]],也就是其对应的对象A.prototype,同时进行遍历
//结果A.prototype也没有这个属性
//3、找到A.prototype对象的[[Prototype]],指向其对应的对象Object.prototype
//结果Object.prototype也没有fGetName
//4、试图寻找Object.prototype的[[Prototype]]属性,结果返回undefined,这就是a1.fGetName的值
简单说就是通过对象的[[Prototype]]保存对另一个对象的引用,通过这个引用往上进行属性的查找,这就是原型链。
 
五、继承

有了原型链的概念,就可以进行继承。
function B() {};
这个时候产生了B的原型B.prototype
原型本身就是一个Object对象,我们可以看看里面放着哪些数据
B.prototype 实际上就是 {constructor : B , [[Prototype]] : Object.prototype}
因为prototype本身是一个Object对象的实例,所以其原型链指向的是Object的原型
B.prototype = A.prototype;//相当于把B的prototype指向了A的prototype;这样只是继承了A的prototype方法,A中的自定义方法则不继承
B.prototype.thisisb = "this is constructor B";//这样也会改变a的prototype

但是我们只想把B的原型链指向A,如何实现?
第一种是通过改变原型链引用地址
B.prototype.__proto__ = A.prototype;
ECMA中并没有__proto__这个方法,这个是ff、chrome等js解释器添加的,等同于EMCA的[[Prototype]],这不是标准方法,那么如何运用标准方法呢?
我们知道new操作的时候,实际上只是把实例对象的原型链指向了构造函数的prototype地址块,那么我们可以这样操作
B.prototype = new A();
这样产生的结果是:
产生一个A的实例,同时赋值给B的原型,也即B.prototype 相当于对象 {width :10 , data : [1,2,3] , key : "this is A" , [[Prototype]] : A.prototype}
这样就把A的原型通过B.prototype.[[Prototype]]这个对象属性保存起来,构成了原型的链接
但是注意,这样B产生的对象的构造函数发生了改变,因为在B中没有constructor属性,只能从原型链找到A.prototype,读出constructor:A
var b = new B;
console.log(b.constructor);//output A
所以我们还要人为设回B本身
B.prototype.constructor = B;
//现在B的原型就变成了{width :10 , data : [1,2,3] , key : "this is A" , [[Prototype]] : A.prototype , constructor : B}
console.log(b.constructor);//output B
//同时B直接通过原型继承了A的自定义属性width和name
console.log(b.data);//output [1,2,3]
//这样的坏处就是
b.data.push(4);//直接改变了prototype的data数组(引用)
var c = new B;
alert(c.data);//output [1,2,3,4]
//其实我们想要的只是原型链,A的自定义属性我们想在B中进行定义(而不是在prototype)
//该如何进行继承?
//既然我们不想要A中自定义的属性,那么可以想办法把其过滤掉
//可以新建一个空函数
function F(){}
//把空函数的原型指向构造函数A的原型
F.prototype = A.prototype;
//这个时候再通过new操作把B.prototype的原型链指向F的原型
B.prototype = new F;
//这个时候B的原型变成了{[[Prototype]] : F.prototype}
//这里F.prototype其实只是一个地址的引用
//但是由B创建的实例其constructor指向了A,所以这里要显示设置一下B.prototype的constructor属性
B.prototype.constructor = B;
//这个时候B的原型变成了{constructor : B , [[Prototype]] : F.prototype}
//这样就实现了B对A的原型继承

图示如下,其中红色部分代表原型链:

作为初学者浅陋的理解,本文目的在于更具象地去理解js的面向对象,疏漏之处请指正。

时间: 2024-10-17 09:40:00

JavaScript原型和继承用法详解的相关文章

JavaScript中的继承方式详解

 这篇文章主要介绍了JavaScript中的继承方式详解,本文讲解了js继承的概念.原型式继承与类式继承.原型链继承.类式继承.组合继承.原型式继承等内容,需要的朋友可以参考下     js继承的概念 js里常用的如下两种继承方式: 原型链继承(对象间的继承) 类式继承(构造函数间的继承) 由于js不像java那样是真正面向对象的语言,js是基于对象的,它没有类的概念.所以,要想实现继承,可以用js的原型prototype机制或者用apply和call方法去实现 在面向对象的语言中,我们使用类来

JavaScript中eval()函数用法详解_javascript技巧

eval() 函数计算 JavaScript 字符串,并把它作为脚本代码来执行. 如果参数是一个表达式,eval() 函数将执行表达式.如果参数是Javascript语句,eval()将执行 Javascript 语句. 语法 复制代码 代码如下: eval(string) 参数 描述 string 必需.要计算的字符串,其中含有要计算的 JavaScript 表达式或要执行的语句. eval()函数用法详解: 此函数可能使用的频率并不是太高,但是在某些情况下具有很大的作用,下面就介绍一下eva

javascript 继承与原型继承用法详解

我们先来说说原型prototype. 每个对象都有原型,原型是一种对象,修改对象的原型,可以影响所有由本对象派生的对象,但是如果仅仅修改本对象,并不会影响已经创建的实例的原型.因为每个对象和原型都有一个原型,对象的原型指向对象的父原型,而父原型又指向父原型的父原型,这种通过原型层层连接起来的关系称为原型链,这条链的末端一般总是默认的对象原型. JavaScript 是基于原型的面向对象语言.也就是说,每个实例对象都具有一个原型.对象从该原型中继承属性和方法. JavaScript 是基于原型的面

Javascript原型链的原理详解_javascript技巧

本文实例分析了Javascript原型链的原理.分享给大家供大家参考,具体如下: 一.JavaScript原型链 ECMAScript中描述了原型链的概念,并将原型链作为实现继承的主要方法.其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法.在JavaScript中,用 __proto__ 属性来表示一个对象的原型链.当查找一个对象的属性时,JavaScript 会向上遍历原型链,直到找到给定名称的属性为止! 比如现在有如下的代码: 扩展Object类,添加Clone和Extend

Javascript中this的用法详解_基础知识

前些日子面试的时候用到了this,面试官说我的理解有点偏差,回来看了下书和一些博客,做了点测试,发现自己的理解的确有误 1.全局变量 应该是最常用的吧,函数中调用一个this,这里其实就是全局变量 var value="0"; function mei(){ var value="1"; console.log(this.value); //0 console.log(value); //1 } mei(); 输出0就是因为this指向的是全局 2.构造函数 这是我

PHP面向对象继承用法详解(优化与减少代码重复)_php技巧

本文实例讲述了PHP面向对象继承用法.分享给大家供大家参考,具体如下: 继承 先看两个类 <?php class CdProduct { public $playLength; // 播放时间 public $title; public $producerMainName; public $producerFirstName; public $price; function __construct( $title, $firstName, $mainName, $price, $playLeng

JavaScript中的继承方式详解_javascript技巧

js继承的概念 js里常用的如下两种继承方式: 原型链继承(对象间的继承) 类式继承(构造函数间的继承) 由于js不像java那样是真正面向对象的语言,js是基于对象的,它没有类的概念.所以,要想实现继承,可以用js的原型prototype机制或者用apply和call方法去实现 在面向对象的语言中,我们使用类来创建一个自定义对象.然而js中所有事物都是对象,那么用什么办法来创建自定义对象呢?这就需要用到js的原型: 我们可以简单的把prototype看做是一个模版,新创建的自定义对象都是这个模

javascript中clipboardData对象用法详解_javascript技巧

本文实例讲述了javascript中clipboardData对象用法.分享给大家供大家参考.具体分析如下: clipboardData对象  ,注意网页里剪贴板到现在只能设置Text类型,即只能复制文本 clearData("Text")清空粘贴板 getData("Text")读取粘贴板的值 setData("Text",val)设置粘贴板的值 当复制的时候body的oncopy事件被触发,直接return false就是禁止复制,注意是不能

JavaScript中eval() 函数用法详解

定义和用法 eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码. 语法 eval(string) 参数 描述 string 必需.要计算的字符串,其中含有要计算的 JavaScript 表达式或要执行的语句. 返回值 通过计算 string 得到的值(如果有的话). 说明 该方法只接受原始字符串作为参数,如果 string 参数不是原始字符串,那么该方法将不作任何改变地返回.因此请不要为 eval() 函数传递 String 对象来作为参数. 如果试图覆盖 eval