一、基本性质

function obj(){
this.name1 = '可以被delete删除';
} obj.prototype.name2 = '不能被delete删除';
obj.prototype.name = '成功调用getName()方法'; obj.getName = function(){
console.log(this.prototype.name);
} var o = new obj(); delete o.name1
//输出:'undefined', 成功删除属性name1
console.log(o.name1); delete o.name2
//输出:'不能被delete删除',因为对象没有原型,访问不到属性name2
console.log(o.name2); //报错:‘Uncaught TypeError: undefined is not a function’,因为,当obj被实例化为o时,obj 就把它的 prototype 赋给了 o 的 __proto__,并且 o 的 constructor 指向了 obj
o.getName(); //输出:'成功调用getName()方法',因为getName方法是挂载在o.constructor上的
o.constructor.getName();

结论:对象实例(Object Instance)没有原型,而构造器(Constructor)有(<constructor>.prototype指向原型),对象没有“持有某个原型”的问题,只有“构造自某个原型”的问题。

//取原型对象
proto = Object.prototype;
//列举对象成员并计数
var num = 0; for (var n in proto) {
num++;
} //显示计数:0
alert(num);

结论:object()构造器的原型是一个空的对象。

obj1 = new Object();
obj2 = {};

那么,obj1和obj2也是“空的对象”,因为它们都是从Object.prototype复制出了一个“对象”的映像来。

结论:空的对象是所有对象的基础

//弹出 Object
alert(typeof null); //false
alert(null instanceof Object); //true
alert({} instanceof Object); //true
alert(Object instanceof Object);

注意:空对象(null) != 空的对象

空对象(null):是一个保留字,它属于对象类型,但这个对象是空值的,它没有方法和属性。

空的对象:是一个标准的,通过Object()构造的对象实例。此外,对象直接量也会隐性的调用Object()构造实例。空的对象具有对象的一切特性,它可以存取预定义属性和方法(如toString、valueof等),instanceof运算也会返回true

小结

原型的含义指:如果构造器有一个原型对象A,那么由这个构造器所创建的实例(Instance)都必然复制自A。

“原型也是对象实例”是一个关键的性质,这是他与“类继承体系”最根本的区别。举例说明:“类”可以是一个内存块或者一段描述文本,而不必是一个有对象特性(例如可以调用方法或存取属性)的结构。

二、 写复制

上图说明每构造一个实例,都从原型中复制出一个实例,新的实例和原型占用了相同的内存空间,造成了内存空间的浪费,有没有策略让新实例和原型共用一个内存空间呢,这就是——写复制(一种欺骗系统的技术:操作系统中的动态链接库(DLL),它的内存区总是写时复制的),下面是机制图:

obj1和obj2都是指向原型的引用,这时它们并不占用内存,当需要写对象obj2的属性时,就会复制一个原型的映像出来,并使以后的操作指向该原型就行了。这种方法的优点就是只有第一次写操作的时候才会分配内存,以后就不会了,因为访问映像与访问原型的效率是一致的;缺点就是如果需要大量写操作的时候,就不太经济了。

接下来,重点来了,JavaScript的实现机制是:仅当写某个实例的成员时,将成员的信息复制到实例映像中(需进一步论证),但当写对象属性时(如obj2.value=10),会产生一个包含属性的成员列表,此时,obj2仍然是一个指向原型的引用,并没有创建对象实例,所以不会占用内存,但obj2需要维护一张成员列表。机制图如下:

三、构造过程——从函数到构造器

函数只有在被引用到原型时,才具有构造器特性。而且函数的原型永远都是一个标准的、系统内置的Object()构造器的实例(这也证明了‘js中一切皆对象’的说法),并且函数的原型的constructor属性值总是先被设置为当前函数。

function myObj(){

}

//true,表明原型的构造器总是指向函数本身
console.log(myObj.prototype.constructor == myObj); //删除该成员的值
delete myObj.prototype.constructor; //false
console.log(myObj.prototype.constructor == myObj);
//true,删除操作使该成员指向父类原型中的值
console.log(myObj.prototype.constructor == Object);
console.log(myObj.prototype.constructor == new Object().constructor);

结论:构造器的原型 prototype 总是来自于 new Object() 产生的实例

由上可知:对象实例本质上就是一个指向构造器原型,并拥有一个属性列表(properties,或成员列表)的结构。

进一步推论:“实例”之所以具有对象的某些性质,是因为他们拥有共同的最顶层原型Object.prototype(空的对象是一切对象的基础)。那么,js系统中都有什么预定义的属性与方法呢,见下表:

表一:对象原型所具有的

成员名 类型 分类
toString function 动态语言
toLocaleString function 动态语言
valueOf function 动态语言
constructor function 对象系统:构造
propertyIsEnumerable function 对象系统:属性
hasownProperty function 对象系统:属性
isPrototypeOf function 对象系统:原型

表二:构造器Object()所具有的

成员名 类型 分类
call function 函数式语言
apply function 函数式语言
caller function 函数式语言
arguments object 动态语言
length number 动态语言
prototype object 对象系统:原型

三、原型链的维护

function myObj(){

}
function myObjEx(){ } myObjEx.prototype = new myObj(); obj1 = new myObjEx();
obj2 = new myObjEx();

结合以上结论(原型也是对象实例),画出上面代码的原型链:

该图说明,构造器通过prototype属性构建了一个原型链,而实例则通过constructor属性维护了一个原型链(其根源是构造器的原型的constructor属性指向了构造器本身)。

然后,运行如下代码:

object1 = new myObj();
object2 = new myObjEx(); //true
console.log(object1.constructor == object2.constructor);

没错,输出的结果是 true !

object1和object2是两个不同的构造器的实例,可它们的constructor属性却指向了相同的构造器myObj。这是为什么呢?因为下面这行代码:

/*
* 等价于
* proto = new myObj(); //proto.constructor = myObj
* myObjEx.prototype = proto; //myObjEx.prototype.constructor = proto... = myObj
*/
myObjEx.prototype = new myObj();

既然知道了原因,那么我们就解决它

很多人会这样做:

//不完善的维护constructor
myObjEx.prototype.constructor = myObjEx;

这样就完美了吗?答案是否定滴,因为这样一来 constructor 就切断了原来的原型链,见下图:

当然,如果你不不想回溯原型链,就用上面的方法,否则,为了严谨一点,用下面的方法:

//正确维护constructor,以便回溯原型链
function myObjEx(){
this.constructor = myObjEx;
//or this.constructor = arguments.callee;
}

最后,展示一个经典的模型:动物王国

//构造器
function Animal() {} //动物类
function Mammal() {} //哺乳类
function Canine() {} //犬类
function Dog() {}
function Cat() {} Animal.prototype.eat = function() {} //原型链
Mammal.prototype = new Animal();
Canine.prototype = new Mammal();
Dog.prototype = new Canine();
Cat.prototype = new Mammal(); //示例函数
function isAnimal(obj) {
return obj instanceof Animal;
}
//示例代码
var dog = new Dog(),
cat = new Cat();
//true
console.log(isAnimal(dog));
//true
console.log('eat' in cat);
//true
console.log('eat' in dog);

S1:原型继承的更多相关文章

  1. javascript实现继承3种方式: 原型继承、借用构造函数继承、组合继承,模拟extends方法继承

    javascript中实现继承的三种方式:原型继承.借用构造函数继承.混合继承: /* js当中的继承 js中 构造函数 原型对象 实力对象的关系: 1 构造函数.prototype = 原型对象 2 ...

  2. javaScript的原型继承与多态性

    1.prototype 我们可以简单的把prototype看做是一个模版,新创建的自定义对象都是这个模版(prototype)的一个拷贝 (实际上不是拷贝而是链接,只不过这种链接是不可见,给人们的感觉 ...

  3. JS继承之原型继承

     许多OO语言都支持两种继承方式:接口继承和实现继承.接口继承只继承方法签名,而实现继承则继承实际的方法.如前所述,由于函数没有签名,在ECMAScript中无法实现接口继承.ECMAScript只支 ...

  4. JS原型继承和类式继承

    前言 一个多月前,卤煮读了一篇翻译过来的外国人写的技术博客.此君在博客中将js中的类(构造)继承和原型继承做了一些比较,并且得出了结论:建议诸位在开发是用原型继承.文中提到了各种原型继承的优点,详细的 ...

  5. JS面向对象(2) -- this的使用,对象之间的赋值,for...in语句,delete使用,成员方法,json对象的使用,prototype的使用,原型继承与原型链

    相关链接: JS面向对象(1) -- 简介,入门,系统常用类,自定义类,constructor,typeof,instanceof,对象在内存中的表现形式 JS面向对象(2) -- this的使用,对 ...

  6. 高级javascript---原型和原型继承

    高级javascript---原型和原型继承 在 JavaScript 中,prototype 是函数的一个属性,同时也是由构造函数创建的对象的一个属性. 函数的原型为对象. 它主要在函数用作构造函数 ...

  7. 【09-23】js原型继承学习笔记

    js原型继承学习笔记 function funcA(){ this.a="prototype a"; } var b=new funcA(); b.a="object a ...

  8. web前端学习(二) javascript对象和原型继承

    目录 1. JavaScrpt对象 2. 原型对象和继承 3. 对象的克隆 (1)javascript对象 在JS中,对象是属性的容器.对于单个对象来说,都由属性名和属性值构成:其中属性名需要是标识符 ...

  9. call()和原型继承的方法

    1.call() call()方法接受两个参数,obj和arg 比如functionA.call(obj,arg)   就是说现在运行(执行)functionA这个方法,但是functionA里面的方 ...

随机推荐

  1. html5 drap & drop

    小知识点记录一下:onselectstart,onselect 1.onselectstart 该js方法是用来控制盒中内容是否被允许选中 <head> <style> #tm ...

  2. Linux 下操作GPIO(两种方法,驱动和mmap)(转载)

    目前我所知道的在Linux下操作GPIO有两种方法: 1.编写驱动,这当然要熟悉Linux下驱动的编写方法和技巧,在驱动里可以使用ioremap函数获得GPIO物理基地址指针,然后使用这个指针根据io ...

  3. Linux输入子系统(转)

    Linux输入子系统(Input Subsystem) 1.1.input子系统概述 输入设备(如按键,键盘,触摸屏,鼠标等)是典型的字符设备,其一般的工作机制是低层在按键,触摸等动作发生时产生一个中 ...

  4. ajax请求成功或失败的参数

    success:function(response, status, xhr){ }, error:function(xhr, errorText, errorType){ alert(errorTe ...

  5. JS获取用户控件中的子控件Id

    用户控件 <asp:HiddenField ID="hfGradeId" runat="server" /> <asp:HiddenField ...

  6. Android星星评分控件RatingBar的使用

    在Android的开发中,有一个叫做评分控件RatingBar,我们可以使用该控件做等级划分.评分等作用,星星形状显示,也可以半星级别,我们来看一下评分控件如何使用. 布局文件中定义控件以及属性,这里 ...

  7. openfire升级指南

    原文:http://www.liuhaihua.cn/archives/355.html 升级Openfire是和从头开始安装Openfire几乎一样简单.作为升级过程的一部分,它强烈建议您先备份当前 ...

  8. Linux上安装Mysql后除了本机其他机器不能访问的问题(zhuan)

    http://blog.sina.com.cn/s/blog_a338027c0101esbs.html http://niutuku.com/tech/Mysql/237638.shtml http ...

  9. 转!!为什么要java环境变量配置?

    1. PATH环境变量.作用是指定命令搜索路径,在shell下面执行命令时,它会到PATH变量所指定的路径中查找看是否能找到相应的命令程序.我们需要把 jdk安装目录下的bin目录增加到现有的PATH ...

  10. Request 接收参数乱码原理解析

    起因: 今天早上被同事问了一个问题:说接收到的参数是乱码,让我帮着解决一下. 实际情景: 同事负责的平台是Ext.js框架搭建的,web.config配置文件里配置了全局为“GB2312”编码: &l ...