最近在学习node.js,学习过程中遇到很多坑,总结下来是对核心javascript(新手童鞋别误解,这里说的不是客户端javascript哦。两者有相似性,侧重点不同)的相关知识认识相对浅薄导致,所以再次捡起犀牛书(javascript权威指南)啃相关原理。犀牛书确实是一部经典,每次翻阅,对javascript的认识都能上升一个层次,对于有志于从事javascript工作的童鞋,建议多翻翻,如果想成为这方面的权威专家,我想至少应该精读10遍以上:
我们知道,javascript是一种面向对象的程序设计语言,在面向对象的编程世界里,有一句经典的话:一切皆对象(Everything is an object)。所有我们常见的数字、字符串、数组、函数、流、套接字等等都是一种对象,不过是表现形式不同而已。虽都是对象,但对象与对象之间也是有差异的,javascript中对象分为两种对象,一种是普通对象,一种是函数对象。废话不多讲,直接上代码。
创建实例对象:
var obj1 = new Object();
var obj2 = {};
obj1与obj2两句代码达到的效果是一样的(但创建过程略有区别)。我认为两者最大的不同的是表达方式,new Object()是对象表达式(expression),{}是对象字面量(literal),两种方式表达的侧重点不同,new Object()侧重这是个对象的类型为Object,而{}侧重这个对象的内容为空。这就像我们日常生活中说“北京是中国的首都”(类似new Object()方式),表达的重点是对象的类型”首都”;而“中国的首都是北京”(类似{}的方式),表达的重点是对象的内容”北京”,仅此而已。
创建函数对象:
function fn1(){}
var fn2 = function(){}
var fn3 = new Function("name","alert(name)");
打印实体对象:
console.log(obj1);
console.log(obj2);
console.log(typeof obj1);
console.log(typeof obj2);
console.log(typeof Object);
效果如下,obj1,obj2结果都是用一个大括号{ }表示的空对象如下:
打印函数对象:
console.log(fn1);
console.log(fn2);
console.log(fn3);
console.log(typeof fn1);
console.log(typeof fn2);
console.log(typeof fn3);
console.log(typeof Function);
效果如下,fn1,fn2,fn3结果都是一个函数对象对象,它们的typeof类型都是function如下:
不管是普通对象还是函数对象,它们都有一个隐含属性”__proto__ “,而这属性就是我们通常说的原型(属性),它其实就是一个Object类型的对象。
对于函数对象,它们还会多一个prototype的属性,它和以它为构造函数创建的普通对象的”__proto__ “属性等同,即new Object().__proto__ ===Object.prototype为true。
在普通对象的属性里,我们可以看到很多我们经常用到的方法,如toString(),valueOf(),hasOwnProperty()等等,那么这些属性有是从哪里来的呢?最后我们会得到一个更大的疑问,obj1、obj2对象的隐藏属性__proto__又是从哪里来的?或者说这个__proto__属性引用的是什么?
带着这个疑问我们再回头分析下var obj1 = new Object();这句代码。我们知道javascript的函数体内的this指向的是调用这个函数的对象。
new Object()这句代码结构是new关键字 + Object构造函数名称 + ()。当javascript解析器遇到new关键字时:
1、首先,创建一个空对象{},这一步时这个对象确确实实是个空对象,因为什么都没有;
2、接着,Object()语句,javascript解析器会找到名字为Object的函数,然后立即执行(如果是其他类型就是其他名称的函数)。一般情况下,其实就是把Object(){}函数体(或者其他名称函数体)内的this指向这个空对象,让这个原本为空的对象拥有相关的属性或者方法;
执行完以上两步,这个对象就已经是一个独立的对象了。如果就仅仅执行以上两步,该对象并不能实现面向对象的继承。那么javascript是怎么实现对象的继承的呢?答案就是第三步,也是最重要的一步:
3、将该对象的原型(即__proto__属性)指向名字叫做Object函数(或者其他)的prototype属性(具体怎么实现,后面会讲到);
这里有个小问题,Object是什么(注意,没有小括号)?
我们看到Object是一个javascript原生实现的函数,一切皆对象,所以函数也是对象,严格意义上说Object函数是一个对象,那么问题来了,Object函数这个对象是怎么来的呢?
既然是对象,当然,答案肯定是被new出来的或者是对象直接量。对,没错,new出来的(为了便于理解,你可以以new的方式去帮助理解)。
比如,我们定义一个函数,也可以说成创建一个函数定义:
function Person(){
//TODO
}
javascript解析器相当于执行以下代码:
var Person = new Function(...);
所以Person函数对象可以认为是javascript解析器遇到函数定义时通过Function函数new出来的,因为Function原生内置函数中包含了prototype属性,所以所有函数对象都有了prototype的属性。
如果没有显式调用Person.prototype = ….,
那么javascript会把Person的父类指向Object类(这里借用一下java的说法),即继承自Object类,
即new Person().__proto__.__proto__===Object.prototype,
即Person.prototype.__proto__===Object.prototype,
因为new Person().__proto__===Person.prototype。
具体的原型链图如下(图片摘自慕课网bosn老师深入浅出javascript课程,课程相当不错,新手建议多看几遍,最好结合权威指南看):
根据上图我们知道,对象bosn在调用对象属性或方法时的调用顺序应该是
1、查找对象bosn自身的属性,如果有,执行,如无,下一步;
2、查找对象原型bosn.__proto__(即Student.prototype)属性,如果有,执行,如无,下一步;
……….
n、查找对象原型的原型的原型的圆形的………..代码为bosn.__proto__.__proto__.__proto__.__proto__.__proto__………,直到查找到最顶层Object.prototype,如果还没有找到,则返回undefined,不再去查找Object.prototype.__proto__,因为Object.prototype的原型为null;
*注意:null作为一个特殊的对象,它并没有.__proto__属性。另一个没有.__proto__属性的是undefined,整数(如1、20、-30等)需要将数字用小括号括起来再调用.__proto__属性,不括起来将报语法错误。
到这里,我们就能弄清楚javascript的对象、__proto__、prototype的关系了。
javascript是面向对象的编程语言,对象是基于原型链的继承,而传统java,c++是基于类的继承。
实例展示:
创建一个矩形类,并实例化:
function Rect(w,h){
this.witdh = w;
this.height = h;
this.area = function(){
return this.width * this.height
};
}
var r = new Rect(2,2);
var a = r.area();
这是我们通常的做法,也很有效,但不是最优的做法。我们看,每个矩形有3个自定义属性,width属性和hegiht属性可能都不同,但area()方法属性每个都一样所以这里就存在冗余,我们应该把area()属性放到原型上,更改如下:
function Rect(w,h){
this.witdh = w;
this.height = h;
}
Rect.prototype.area = function(){
return this.width * this.height
};
var r = new Rect(2,2);
var a = r.area();
放在原型上这种方式比前一种方式更进一步,原型对象是放置方法和其他不变属性的理想地方。但如果这种方法不想别别人继承,应该考虑放在对象本身内。
那怎么判断属性是对象自身的还是原型链上的,可以通过下面的方法验证
r.hasOwnProperty("width");//true;
r.hasOwnProperty("area"); //false;
"area" in r ;//true;