JavaScript_原型和继承(2017-03-15)
一、函数创建过程
在了解原型链之前我们先来看看一个函数在创建过程中做了哪些事情,举一个空函数的例子:
function A() {};
当我们在代码里面声明这么一个空函数,js解析的本质是(肤浅理解有待深入):
1、创建一个原型对象(有constructor属性及[[Prototype]]属性,根据ECMA,其中[[Prototype]]属性不可见、不可枚举 )。文中[[Prototype]]属性等同于__proto__属性
2、创建一个函数对象(有name、prototype属性),再通过prototype属性 引用 刚才创建的对象
3、创建变量A,同时把函数的 引用 赋值给变量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运算符,如下:
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")
} 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的原型继承
最终:
function A() {
this.width = 10;
this.data = [1,2,3];
this.key = "this is A";
}
A._objectNum = 0;//定义A的属性
A.prototype.say = function(){//给A的原型对象添加属性
console.log("hello world")
} var a1 = new A;
var a2 = new A; //我们想要获取a1.fGetName
console.log("想要获取a1.fGetName " + 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的值 function B() { }; 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; //第二种
B.prototype = new A(); var b = new B;
//B产生的对象的构造函数发生了改变,因为在B中没有constructor属性,只能从原型链找到A.prototype,读出constructor:A
/*
实例b的构造函数:function A() {
this.width = 10;
this.data = [1,2,3];
this.key = "this is A";
}
*/
console.log("实例b的构造函数:" + 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}
/*
实例b的构造函数2:function B() { }
*/
console.log("实例b的构造函数2:" +b.constructor);//output B
//同时B直接通过原型继承了A的自定义属性width和name
/*
实例b的data:1,2,3
*/
console.log("实例b的data:" +b.data);//output [1,2,3]
//这样的坏处就是
b.data.push(4);//直接改变了prototype的data数组(引用) var c = new B;
/*
实例c的data:1,2,3,4
*/
console.log("实例c的data:" +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的原型继承
结构图:
附录 :之前分析的一个例子
代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>js</title>
<script>
function Parent(){
this.superName='Parent';
}; function Child(){
this.subName='Child';
}; //隐藏属性__proto__,相当于c中的指针,存储的是对象的地址。
//增加constructor属性
Parent.prototype.otherName = 'Other'; //指向新的对象
//导致Parent.prototype下constructor等属性丢失
/*
console.log(Person.prototype.hasOwnProperty("constructor")); //true
Parent.prototype={
otherName2:'Other2',
};
console.log(Person.prototype.hasOwnProperty("constructor")); //false
*/ var parentA = new Parent(); //此时
//Child.prototype.__proto__=Parent.prototype
//Child.prototype下没有constructor属性,将去父层次寻找该属性
Child.prototype = new Parent();
//Child.prototype = Parent.prototype;//此种方式只继承父类的原型方法 var childA = new Child();
var childB = new Child(); var objectA = new Object();
var functionA = new Function(); //搜索顺序:childA对象 -> childA的原型对象(Child.prototype=new Parent()) ->childA的原型对象的原型对象(Parent.prototype) -> ....
//1.凡是通过new Function()创建的对象都是函数对象,其他的是普通对象。
//2.所有对象都有__proto__属性,函数对象有属性是原型对象 prototype。
//3.原型对象prototype是普通对象(Function.prototype是函数对象,但没有prototype属性)。
console.log("输出Child");
console.log(childA.subName);//输出 Child
console.log(childA.superName);//输出 Parent
console.log(childA.otherName);//输出 other
console.log(childA.unName);//输出 undefined console.log("输出Child原型");
console.log(Child.prototype) //Parent {superName: "Parent", otherName: "Other"}
console.log(Child.prototype.constructor === Object); //false
console.log(Child.prototype.constructor === Parent); //true console.log("输出Parent原型");
console.log(Parent.prototype) //Parent {otherName: "Other"}
console.log(Parent.prototype.constructor === Object); //false
console.log(Parent.prototype.constructor === Parent); //true console.log(childA.__proto__ === Child.prototype) //true console.log(typeof Child. prototype) //Object
console.log(typeof Function.prototype) //function 这个特殊 </script>
</head>
<body>
</body>
</html>
copy来源:http://blog.jobbole.com/19795/
JavaScript_原型和继承(2017-03-15)的更多相关文章
- JavaScript原型与继承
JavaScript原型与继承 原型 在JavaScript中,每个函数都有一个prototype属性,这个属性是一个指针,指向该函数的原型对象.这个原型对象为所有该实例所共享.在默认情况下,原型对象 ...
- 深入浅出JavaScript之原型链&继承
Javascript语言的继承机制,它没有"子类"和"父类"的概念,也没有"类"(class)和"实例"(instanc ...
- JS中原型链继承
当我们通过构造函数A来实现一项功能的时候,而构造函数B中需要用到构造函数A中的属性或者方法,如果我们对B中的属性或者方法进行重写就会出现冗杂的代码,同时写出来也很是麻烦.而在js中每个函数都有个原型, ...
- 一步步学习javascript基础篇(5):面向对象设计之对象继承(原型链继承)
上一篇介绍了对象创建的几种基本方式,今天我们看分析下对象的继承. 一.原型链继承 1.通过设置prototype指向“父类”的实例来实现继承. function Obj1() { this.name1 ...
- 【转】JavaScript中的原型和继承
请在此暂时忘记之前学到的面向对象的一切知识.这里只需要考虑赛车的情况.是的,就是赛车. 最近我正在观看 24 Hours of Le Mans ,这是法国流行的一项赛事.最快的车被称为 Le Mans ...
- PSP(16/03/14-16/03/15)
//整理了自己过去的时间记录文件,最早用电子版记录是15/04/21,依旧断断续续记录到15/11/21,每月至少三次记录,然而自己并没有对数据进行整理,只是纯粹记录,真是浪费了花在上面的时间.期间八 ...
- JavaScript 随笔2 面向对象 原型链 继承
第六章 面向对象的程序设计 1.创建对象的几种方式 A)工厂模式 function CreatObj(name,sex,age){ this.name=name; this.sex=sex; this ...
- 【面试必备】javascript的原型和继承
原型.闭包.作用域等知识可以说是js中面试必考的东西,通过你理解的深度也就能衡量出你基本功是否扎实.今天来复习一下javascript的原型和继承,虽说是老生常谈的话题,但对于这些知识,自己亲手写一遍 ...
- js中的原型、继承的一些想法
最近看到一个别人写的js类库,突然对js中的原型及继承产生了一些想法,之前也看过其中的一些内容,但是总不是很清晰,这几天利用空闲时间,对这块理解了一下,感觉还是有不通之处,思路上没那么条理,仅作为分享 ...
随机推荐
- 用nodejs做一下发送邮件例子
var nodemailer = require("nodemailer"); var transport = nodemailer.createTransport("S ...
- sysbench安装、对Mysql压力测试、结果解读及mysql数据库跟踪优化
sysbench是一款开源的多线程性能测试工具,可以执行CPU/内存/线程/IO/数据库等方面的性能测试. sysbench支持以下几种测试模式: 1.CPU运算性能 2.磁盘IO性能 3.调度程序性 ...
- S5PV210串口
串口设置之输入输出字符 S5PV210 UART相关说明 通用异步收发器简称UART,即UNIVERSAL ASYNCHRONOUS RECEIVER AND TRANSMITTER,它 ...
- HDU 5288 OO’s Sequence
题意: 给你一个序列, 有一个函数 F(L,R) 其中 ai 均不能 被 aL - aR整除的 函数值是这个ai个数 思路 : 反过来求 满足这样的条件的 ai 的区间,然后求和 #include& ...
- 擅于使用JS的eval方法
样例如下: var appsDetails = {“app1”:"", “app2”:"", “app3”:"", “app4”:" ...
- VS2017项目程序打包成.msi或者.exe
VS2017项目程序打包成.msi或者.exe 1.安装打包插件:Microsoft Visual Studio 2017 Installer Projects 打开vs2017 ,选择 工具 --& ...
- Eclipse开发环境配置
1. java环境 安装 本系统使用java6开发,老师使用1.6.0 _45版本开发,如下图所示: "开发工具"目录提供了1.6.0 _45版本32位和64位两个安装程序,大家根 ...
- 转换类型 totypeString
type.totypeString(variable) 其中front type is want to turn after type是你要转换成的类型 //: dsfsf/Literals. ...
- poj3579 二分套二分
和poj3685类似,都是二分答案然后在判断时再二分 这题的内层二分可以用stl代替 /* 二分套二分,思路:升序排序数据,先二分答案x进行判断,判断时枚举每个元素,二分找到和其之差小于等于x的所有值 ...
- Codeforces Round #429 (Div. 2) - D Leha and another game about graph
Leha and another game about graph 题目大意:给你一个图,每个节点都有一个v( -1 , 0 ,1)值,要求你选一些边,使v值为1 的点度数为奇数,v值为0的度数为偶数 ...