理解JavaScript中的原型与继承,作用域与闭包是写出高质量和有设计性代码的重要一环。虽然我没接触过Java,C#之类的语言,不清楚它们具体类的继承方式,但经常看到别人说起,便也知道JavaScript中基于原型的继承是非常具有特色的。想着深入了解一下原型与继承机制,就看了很多关于这方面的博客文章,自己也算是和之前懵懵懂懂相比,基本摸清了一些东西。这里只记录关于原型和继承方面的,作用域和闭包下次再记录。
一切皆对象
在刚刚看高程的时候就知道ECMAScript中包含两种不同数据类型的值,一个是基本类型值,还有一个就是引用类型值。像undefined,string,number,boolean就是基本类型的值,数组,对象,函数,null等就是引用类型值。然后又为了方便地操作基本类型值又相应创建了三个特殊的引用类型:Boolean
,String
和Number
。所以从这一点来看说JavaScript中的“一切”都是对象应该能解释得通了。对象是属性的集合,面向对象的知识所涉及的也都是对象,所以意识到这一点应该很重要。
对象由函数创建
起初我在别人的博文中看到对象由函数创建这句话其实是懵懂的,因为我知道通过构造函数能够new出一个实例对象,但是其中的关联却又不清楚,要想理清楚好像并不是一句两句话能够做到的。幸好我看到了一个介绍这方面的一系列文章,渐渐帮我理清了思绪,博客链接放在文章最后。
首先对象都是由函数创建的。除了自定义的构造函数new实例对象外,其实还有印证这个观点的证据。以下是几条我们经常会写的代码:
var obj = {}; //对象字面量形式声明一个对象
var arr = []; //数组字面量声明一个数组
var fn = function(){}; //function关键字声明一个函数
这三句代码有另一种书写方式,从下面可以看出对象,数组还有函数都是对象,而实际上它们也是通过相应构造函数new出来的。
var obj = new Object();
var arr = new Array();
var fn = new Function();
所以我看的时候就有疑问了,对象是有函数创建出来的,而函数又是对象,对象创造了对象?呸呸呸,我还是摆脱自己的思维睁大眼睛看下去吧。由于JavaScript中函数和构造函数并没有什么区别,除了人为的将构造函数首字母大写,它们都是对象,是属性的集合。
prototype和proto
函数有个默认的属性——prototype属性,这个prototype属性是个对象,又有个constructor属性指向这个函数。同时每个对象都有一个隐式的__proto__
属性,这个属性引用了创建这个对象的函数的prototype。
这两句话的意思用代码体现就是:
var fn = new Function();
fn.__proto__ === Function.prototype; //true
var obj = new Object();
obj.__proto__ === Object.prototype; //true
不知道刚开始看到这个是否很容易理解,反正我是花了点时间绕来绕去,不过这里应该好理解,比如fn是Function构造函数new出来的一个实例,fn是个对象,有个隐式的__proto__
属性引用Function的prototype属性来得到继承。obj也是一样,任何对象都是一样。然后就出现了两个问题:
1、Function.prototype也是对象,也应该有一个__proto__
属性,这个属性引用创造出它这个对象的函数的prototype,也就是Object函数了,然后Object.prototype也有__proto__
属性啊,它指向的就是null了,到顶了。以下是相关代码:
var fn = new Function();
fn.__proto__ === Function.prototype; //true
Function.prototype.__proto__ === Object.prototype; //true
var obj = new Object();
obj.__proto__ === Object.prototype; //true
Object.prototype.__proto__ === null; //true
2、fn是Function函数的实例,它的__proto__
指向Function.prototype
,那Function也是个对象啊,它也应该有__proto__
,所以会发现:
Function.__proto__ === Function.prototype; //true
可见Function被Function自己创建了,环形结构的循环引用,真是奇妙!
通过原型链实现继承
到上面的部分会发现提到很多次对象的__proto__
属性引用创造出它这个对象的函数的prototype。知道这些有啥用?好像又回到了构造函数上,或者说讨论的其实一直都是构造函数和对象相关。
function Foo(){
this.a = 7;
}
Foo.prototype.b = 77;
var fn = new Foo();
fn.a; //7
fn.b; //77
fn.c; //undefined
从上面可以知道fn是Foo的一个实例,它自身有一个属性a的值为7,能够轻松访问到。而在访问fn的b属性的时候由于找不到,就会顺着__proto__
属性往上在Foo.prototype
找,找到了就停止,没找到继续往上到Object.prototype
找,再没找到就到null
了,自然也找不到就只好返回undefined。这种链条式的查找形式就是原型链,而通过这条链子来从上往下继承相关属性和方法就是通过原型链来实现继承,这种方式实现继承还是很方便的.
不知道我所认为的懂是否是真的懂,也不确定是否有错误。只是看了参考文章后自己的一些理解,而且我现在还没太在实践中运用这种链式原型继承,不过我想马上就会用到了。