Javascript语言是符合面向对象思想的。通常来讲,面向对象思想须要知足如下三个基本要求:javascript
这里的关键问题就是Javascript的原型继承机制究竟是个啥玩意?java
有很大的可能性,Javascript在设计之初根本就没有考虑那么复杂。啥玩意面向对象思想,跟我有半毛钱关系啊。公司就给我两周时间,连设计带编码,我固然是怎么简单怎么来了。若是说一门计算机语言必定要知足惟一的一个最最基本的设计思想,那必定不是面向对象,而应该是更简单的:语言应支持功能的复用。若是一个对象功能不够用了,那就再叫个帮手呗,因而Javascript硬性规定:任何对象都必须有一个原型对象。这下好了,全部的Javascript对象都是自带秘书的,本身搞不定的就交给秘书去搞,秘书再搞不定的,就交给秘书的秘书去搞,这样一路交接过去,直到完全搞不定了,那就只好报错。这就是Javascript中的原型链检索机制,是否是超简单?浏览器
由于对象的原型是Javascript对象的基本构件,那么首要的问题就是如何获得对象的原型对象?大致来讲有如下两种方法:app
说了这么多,来一段测试代码吧:函数
test("获得对象的原型", function() { var empty = {}; assert(empty.__proto__ === Object.getPrototypeOf(empty), "__proto__和Object.getPrototypeOf的等效性"); });
这个测试是说那两种获得对象原型对象的方法是一致的,为了简单,如下采起第一种方式。学习
再测试一下对象的原型链:测试
test("对象的原型链", function() { var p = {}; var pp = p.__proto__; var ppp = pp.__proto__; var q = new Object(); var qp = q.__proto__; var qpp = qp.__proto__; assert(pp === qp, "Object.__proto__"); assert(ppp === qpp && ppp === null, "Object.__proto__.__proto__"); });
这段测试须要咱们注意如下几点:this
既然函数也是对象,那么咱们来测试一下函数的原型链:编码
test("函数的原型链", function() { function p() {}; var pp = p.__proto__; var ppp = pp.__proto__; function q() {}; var qp = q.__proto__; var qpp = qp.__proto__; assert(pp === qp, "Function.__proto__"); assert(ppp === qpp, "Function.__proto__.__proto__"); assert(ppp === {}.__proto__, "Function.__proto__.__proto__ === Object.__proto__"); });
这段测试须要咱们注意如下几点:spa
总有一些对象是相似的,例如每一个person对象中都有name和age属性,咱们能够建立不少的相似的person1,person2等等,咱们但愿可以把这种相似的对象的建立过程复用起来,直接封装为一个函数无疑是最简单直接的:
test("构造函数的引入", function() { function Person(name, age) { this.name = name; this.age = age; this.say = function() { return this.name + " " + this.age; }; } var person1 = {}; Person.call(person1, "wgs", 18); assert(person1.name === "wgs" && person1.age === 18 && person1.say() === "wgs 18", "person1"); var person2 = {}; Person.call(person2, "www", 28); assert(person2.name === "www" && person2.age === 28 && person2.say() === "www 28", "person2"); });
利用函数的知识能够很容易的解决这个问题。相似的咱们还能够搞出Animal函数,Student函数等等。可是这里的问题是这样建立的任何对象都是Object类型的,咱们的需求是最好Person函数建立的对象是Person类型的,Animal函数建立的对象是Animal类型的,Student函数建立的对象是Student类型的等等。这就要求每一个函数对象必须有个函数描述对象。因而Javascript硬性规定:任何函数都必须有一个描述对象。这下就热闹了,当你你定义一个函数的时候,你觉得你只搞出了一个函数对象,实际上倒是两个对象,一个函数对象,一个函数描述对象。后者是强制附送的,这情形就比如是说原本公司只打算招收了10位员工的,没想到国家法律规定,必须夫妻俩一块儿招,因而因而一会儿来了20位员工。这里的好消息是咱们不用本身手动建立函数的描述对象,每当Javascript引擎解析完一个函数后,它会在建立函数对象的同时,自动建立好函数的描述对象。
那么最直接的问题来了,怎么获得函数的描述对象?Javascript是这样规定的:
不少翻译把函数的描述对象翻译为原型对象,对象的原型对象,函数的原型对象,又一个函数的原型对象,这要把人绕晕吗?我建议就直接用__proto__对象和prototype对象区分一下吧,二者根本不是一回事,尽管有时候,二者能够是同一个对象。
全部的prototype对象也是有__proto__对象的,很简单,那就是前面反复提到过的Object.__proto__对象。
有人注意到一个有趣的现象没有?Javascript这么搞其实是把函数和类型合二为一了,有一个函数就有一个类型,有一个类型就有一个函数。这下子玩得有点大啊,想一想在通常面向对象语言中,咱们定义一个类(在Javascript中其实它也是一个函数),咱们为这个类定义几个方法(在Javascript中其实它们也是几个类型)。Javascript的内存使用率和运行效率确定不如C++,C#,Java这些语言,可是毫无疑问,Javascript的灵活性,那是至关的高。
因而咱们熟知的Javascript中的那几个内置对象类型:Object、Array,Function,Regexp,Date,Error其实都是函数。那几个包装函数Number,String,Boolean其实也能够看作是内置对象类型。这样咱们就很容易理解Object.__proto__其实和Object函数是一对,Function.__proto__其实和Function函数是一对。
到如今为止,咱们就没有必要再纠结Object.__proto__,Function.__proto__这种奇怪的命名了,其实它们就是Object.prototype和Function.prototype
综上所述,咱们获得了下面的图:
这就是那张号称把无数Javascript初学者绕晕的经典图。如今看起来是否是很清晰了呢?
再补点简单的测试吧:
test("函数的prototype", function() { function Person() {}; assert(Person.prototype.constructor === Person, "Person.prototype.constructor === Person"); assert(Person.prototype.__proto__ === {}.__proto__, "Person.prototype.__proto__ === Object.__proto__"); });
还回到引入函数prototype对象的最初问题上来,如何实现Person函数建立的对象是Person类型的,Animal函数建立的对象是Animal类型的,Student函数建立的对象是Student类型的等等?道理上基本都懂了,如今欠缺只是一点编码实践,因而咱们来一个new关键字的模拟:
test("模拟构造函数", function() { function _new(Fun) { // 建立一个新对象 var this_ = {}; // 除了类型参数之外,咱们须要打包构造函数的参数 var args = Array.prototype.slice.call(arguments, 1); // 调用构造函数 Fun.apply(this_, args); // 将函数的prototype赋值给对象对的__proto__ this_.__proto__ = Fun.prototype; // 返回建立的新对象 return this_; } function Person(name, age) { this.name = name; this.age = age; } var person1 = new Person("wgs", 18); var person2 = _new(Person, "wgs", 18); assert(JSON.stringify(person1.__proto__) === "{}", "person1.__proto__ is {}"); assert(person1.__proto__.constructor === Person, "person1.__proto__.constructor is Person"); assert(person1.constructor === Person, "person1.constructor is Person"); assert(person1.name === "wgs" && person1.age === 18, "person1 name and age"); assert(JSON.stringify(person2.__proto__) === "{}", "person2.__proto__ is {}"); assert(person2.__proto__.constructor === Person, "person2.__proto__.constructor is Person"); assert(person2.constructor === Person, "person2.constructor is Person"); assert(person2.name === "wgs" && person2.age === 18, "person2 name and age"); });
new和_new只是调用语法上稍微有些不一样,但结果实际上是如出一辙的。这下对构造函数的调用原理也不用纠结了吧,源码在那里,一共也没有几行,还不清楚的回去再多看几遍。
再回头看看那张经典的图,在不考虑继承的状况下,咱们对类型的扩充其实就在prototype对象上
这里咱们须要把全部内置对象类型的prototype显示出来,以验证咱们的想法:
<script type="text/javascript"> asserts(); function testCase(desc, proto) { // 获得prototype的全部属性名称 var names = Object.getOwnPropertyNames(proto); for (var i = 0; i < names.length; i++) { var name = names[i]; // 由于Function的arguments和caller不可访问, // 因此须要过滤掉这两个属性 if (name == "arguments" || name == "caller") { continue; } // 根据名称获得属性的值 var value = proto[name]; // 显示属性及其内容 assert(true, "{0} : {1}".fmt(name, _varDesc(value))); } log(desc, proto); } test("Object.prototype", function() { testCase("Object.prototype:\n", Object.prototype); }); test("Array.prototype", function() { testCase("Array.prototype:\n", Array.prototype); }); test("Function.prototype", function() { testCase("Function.prototype:\n", Function.prototype); }); test("RegExp.prototype", function() { testCase("RegExp.prototype:\n", RegExp.prototype); }); test("Date.prototype", function() { testCase("Date.prototype:\n", Date.prototype); }); test("Error.prototype", function() { testCase("Error.prototype:\n", Error.prototype); }); test("Number.prototype", function() { testCase("Number.prototype:\n", Number.prototype); }); test("String.prototype", function() { testCase("String.prototype:\n", String.prototype); }); test("Boolean.prototype", function() { testCase("Boolean.prototype:\n", Boolean.prototype); }); </script>
代码没有多复杂,结果以下:
。。。
结果太多,就截一部分图吧,看看这些熟悉的方法,是否是有种点进去看看每一个函数源码的冲动?
关于Javascript的面向对象,这里对继承和多态没有涉及,那将是另外一篇文档的事情。不过若是看懂了本篇文章的内容,我保证,那些继承和多态的东西还真没什么难度。