前几天看了《再谈js面向对象编程》,当时就请教哈大神,发现文章有的地方可能会造成误导(或者说和ECMA有出入),后来自己翻一翻ECMA,总算找到“标准”的理解……
本文适合初学者,特别是对构造函数、原型和原型链概念比较模糊的,大牛请路过,好了,让我们一步步来看看 Javascript原型(链)到底有多神秘……
一、函数创建过程
在了解原型链之前我们先来看看一个函数在创建过程中做了哪些事情,举一个空函数的例子:
1
|
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属性,这就是函数的原型,从图中可知其实质就是对一个对象的引用(这个对象暂且取名原型对象)。
我们可以对函数的原型对象进行操作,和普通的对象无异!一起来证实一下。
围绕刚才创建的空函数,这次给空函数增加一些代码:
1
2
3
4
5
6
7
8
9
|
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运算符,如下:
1
2
|
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
)
如上图所示:
1
2
3
4
5
6
7
8
9
|
//我们想要获取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]]保存对另一个对象的引用,通过这个引用往上进行属性的查找,这就是原型链。
五、继承
有了原型链的概念,就可以进行继承。
1
|
function B() {};
|
这个时候产生了B的原型B.prototype
原型本身就是一个Object对象,我们可以看看里面放着哪些数据
B.prototype 实际上就是 {constructor : B , [[Prototype]] : Object.prototype}
因为B.prototype本身是一个Object对象的实例,所以其原型链指向的是Object的原型
1
2
|
B.prototype = A.prototype; //相当于把B的prototype指向了A的prototype;这样只是继承了A的prototype方法,A中的自定义方法则不继承
B.prototype.thisisb = "this is constructor B" ; //这样也会改变a的prototype
|
但是我们只想把B的原型链指向A,如何实现?
第一种是通过改变原型链引用地址
1
|
B.prototype.__proto__ = A.prototype; |
ECMA中并没有__proto__这个方法,这个是ff、chrome等js解释器添加的,等同于EMCA的[[Prototype]],这不是标准方法,那么如何运用标准方法呢?
我们知道new操作的时候,实际上只是把实例对象的原型链指向了构造函数的prototype地址块,那么我们可以这样操作
1
|
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
1
2
|
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的面向对象,疏漏之处请指正。
相关推荐
javascript原型继承,prototype的使用,可以像java一样继承
### 理解Javascript原型继承原理 #### 一、引言 在JavaScript中,原型继承是一种非常核心且独特的机制,它使得对象能够继承其他对象的属性和方法。本文旨在深入探讨这一机制,并通过具体的示例代码帮助读者更好地...
本文将深入探讨JavaScript原型继承的工作原理、实现方式以及在现代Web开发中的应用。 JavaScript的原型继承是一种强大且灵活的机制,它允许对象之间共享功能和行为。通过深入理解原型继承的工作原理和实现方式,...
继承和原型链让 JavaScript 对象能够继承和扩展功能,体现了 JavaScript 强大的对象导向特性。在实际开发中,熟练掌握原型和继承的使用,可以显著提高编程效率和代码质量。 以上便是关于 JavaScript 中原型和继承...
Javascript原型继承是一个被说烂掉了的话题,但是自己对于这个问题一直没有彻底理解,今天花了点时间又看了一遍《Javascript模式》中关于原型实现继承的几种方法,下面来一一说明下,在最后我根据自己的理解提出了一...
在深入探讨JavaScript的原型继承之前,首先要明确的是JavaScript中并没有类似其他编程语言中的类继承的概念。虽然有传言JavaScript 2.0将加入类继承...掌握原型继承对于深入理解JavaScript对象模型和设计模式至关重要。
### JavaScript原型继承工作原理及实例详解 #### 一、引言 JavaScript作为一种广泛使用的脚本语言,在Web开发中扮演着重要角色。其独特的面向对象机制是通过原型继承来实现的,这种机制使得JavaScript能够灵活地...
### 浅析JavaScript原型继承机制 #### 一、引言 JavaScript作为一种动态语言,其对象模型与传统的面向对象编程语言有所不同。在JavaScript中,并没有直接提供类的概念,而是通过原型来实现继承。本文将深入探讨...
简单的说就是:所有对象都从他的原型继承属性。 (each object inherits properties from its prototype). 对象的 prototype 通过它的 constructor function 来定义。JavaScript 里所有的 function 都有一个 ...
javascript原型继承工作原理和实例详解.doc
JavaScript原型继承是面向对象编程在JavaScript中的实现方式之一,它基于原型(Prototype)和对象的特性,使得一个对象可以继承另一个对象的属性和方法。在JavaScript中,每个对象都有一个特殊的内部属性`[...
javascript的继承在很多框架中都有运用,尤其是原型式继承。首先要理解一个概念,什么是原型式继承?所谓的原型式继承,就是在函数内部先创建一个临时性的构造函数,然后将传入的对象做这个构造函数的原型,最后返回...
javascript原型继承机制参考.pdf
javascript原型继承机制借鉴.pdf
javascript原型继承机制归类.pdf
了解并熟练运用JavaScript原型链,可以有效地实现对象间的继承、复用和组合,这对于编写可维护和扩展的代码至关重要。同时,原型链也是许多高级概念如鸭子类型、模拟类继承等的基础。因此,深入学习和掌握JavaScript...