最近在研究iOS第三方框架JSPath的时候,原本以为该热修复功能的实现是基于react-native的方式,但是根据很多博客和对该第三方源码的分析发现,JSPath就像这个名字一样,其实是基于库<JavaScriptCore.framework>与JS代码进行动态调用,由于OC语言的动态特性,在程序运行时,可以通过解析JS代码动态添加类、对象、执行方法等。于是就翻看了一下JS,就搜到了这么一句话:一切皆对象...对象原型...继承,众说纷纭,好吧我承认,懵逼了,那么到底什么是原型,网上说的prototype 和 __proto__有什么联系和不同,接下来就会谈谈如下几点:
1. 创建一个对象
2. 函数与函数对象
3. 什么是prototype
4. 什么是__proto__
5. 结论
1. 创建一个对象
js对象有普通对象和函数对象,普通对象类似json或者说其他语言的字典,先来欣赏下:
var people = {
name: "Tom",
age: 18,
init: function(name , age){
this.name = name;
this.age = age;
}
}
people // ①
以上代码创建了一个people对象,① 处在chrome打印为:
调用一下init方法吧,重新设置一下name和age属性:
people.init("Jerry" , 23) ;
people // ②
② 处打印如下所示:
以上的例子就是js创建对象最简单的方法,但是有一个弊端,了解其它面向对象程序语言(例如Java和OC)的猿应该知道,通常我们获得一个对象都是由一个模板实例化来的,我们把这个模板叫做类,由类可以得到任意多的对象(这就有点像设计图和产品一样),但是普通对象的创建并不能满足这个条件,那么来看看函数对象。
其实一开始我是比较好奇,为啥js要基于函数创建对象,并通过原型搞继承呢?这个原因要追溯到javascript的诞生史,“与其说我爱Javascript,不如说我恨它。它是C语言和Self语言一夜情的产物”,感兴趣的读者可以读一下这篇文章:<a href="http://www.ruanyifeng.com/blog/2011/06/birth_of_javascript.html">Javascript诞生记</a>。
那么首先来看一个简单的js函数:
function sum(a , b) {
return a+b;
}
sum(10,100); //110
现在有了函数,马上new一个对象出来:
var sumObject = new sum(10 , 100);
sumObject // 此处打印的是sumObject对象
如下图所示:
我滴天哪,这是什么,不应该是110么,确实不是110,现在的sumObject就是一个对象,也就是说通过new 一个函数,就可以得到函数对象了,说到对象,那得有属性和方法啊,这个可以有,重新定义一下sum函数,让他看起来更类一点:
function Sum(a , b) {
this.a = a;
this.b = b;
this.execute = function(){
return this.a + this.b;
}
}
var s = new Sum(10,110);
s;
s.execute();
对象s的打印结果为:
s.execute()执行的结果:
面包有了,牛奶也有了,这样我们可以通过这个Sum函数new出来很多的对象,噢耶!那么函数与函数对象有什么区别呢?继续分析。
2. 函数与函数对象
首先来研究一下,为什么加了一个new,就能有对象呢?真理解不了咱们这些猿啊。
还是上面的Sum函数吧,我们执行一下这个代码:
var sum = Sum(); // undefined
是的返回的是undefined,Sum()这个函数并没有返回值,所以sum的值为undefined并没有毛病,如果我们使用sum函数呢:
var sumOrigin = sum(10,100); // 110
是不是又绕回来了,这个结果我们之前得到过的,此时sumOrigin其实就是函数的返回值,也就是说,我们call了一个函数。
我们首先能想到的是,通过在函数前加了一个new就得到了一个对象,js引擎要根据这个new返回给我们一个对象。猜的没有错,js引擎在遇到new一个函数的时候,会执行两步隐式的操作:
this = Object.create(Sum.prototype); // 1
.
.
.
.
return this; // 2
js引擎通过Sum的原型隐式创建了this对象,之后又将这个this对象返回给调用处。我们试一下,让sum(a,b)函数返回sum对象:
function sum(a,b){
that = Object.create(sum.prototype);
return that; //此处返回一个对象,不能返回a+b,否则得不到that对象
}
结果:
sum函数内我们用that来接受对象,并返回给调用处。(为啥不用this,我在浏览器中进行实例的编写和调试,通过上述方式调用的话this代表的是window)。下面问题来了,prototype是什么?
3. 什么是prototype
上面说到,js引擎通过一个函数的prototype来构建函数对象,那么就打印一下这个prototype吧:
Sum.prototype;
得到结果:
可以发现prototype也是一个对象,该对象内有两个引用类型的属性变量:constructor和__proto__;这个constructor是构建Sum对象的构造函数么?试试就知道了。
Sum.prototype = null;
Sum.prototype ; //此处为null
var snut = new Sum(10,100);
snut;
结果如下:
再来执行一下execute函数:
snut.execute(); // 110
函数执行结果为110;也就是说即使将Sum的prototype赋值null,同样可以得到对象,那我要它有何用,看一下下面的例子:
Sum.prototype = {
title : "Sum",
age: 18,
}
var snut = new Sum(10,100);
snut;
如下图所示:
可以看到当我们为Sum的prototype指定了一个对象的时候,由Sum产生的对象snut中的__proto__属性变成了prototype所指的对象,我们其实是将普通对象赋值给了Sum的prototype,那换一个函数对象吧:
function Minus(a , b){
this.a = a;
this.b = b;
this.execute = function() {
return a - b
}
}
Sum.prototype = new Minus(10 , 110);
var snut = new Sum(100 , 10);
snut;
如果没猜错的话,snut中的__proto__应该指向Minus的对象(是对象啊,就是代码里new Minus(10,110) 这个匿名对象),打印如下:
果然没猜错,这个__proto__就是赋值给Sum.prototype的Minus对象,试一试调用__proto__中的execute()方法:
snut.__proto__.execute() // 输出 -100
输出结果为-100。
好了分析完prototype了再来看看Object.create()这个方法吧,上面sum函数的例子,通过Object.create(sum.prototype)返回了一个that,再试试下面的代码:
function muliple(x , y) {
this.x = x;
this.y = y;
this.execute = function(){
return x * y;
}
}
var mul = Object.create(muliple.prototype);
mul;
打印结果如下
我们发现muliple的属性都没有了,但是mul对象中的__proto__却有一个构造函数:muliple(x, y),那么可以理解为,通过Object.create(muliple.prototype)可以创建一个对象,该对象的原型就是muliple.prototype(Object.create()方法的第一个参数)。清晰了么?还是糊涂了?可以这么理解,通过Object.create()方法js回返给你一个没有属性的对象,只有一个原型指向方法的第一个参数(指定的对象)。我们可以将那个小写的sum改进一下啦:
function sum(a,b){
that = Object.create(sum.prototype);
that.a = a ;
that.b = b ;
that.execute = function(){
return that.a + that.b;
}
return that;
}
var s = sum(10 , 8);
s ;
执行结果如下:
这样就返回了一个完整的sum对象。
4. 什么是__proto__
有了上面对prototype的分析,相信应该有答案了吧,__proto__这个玩意就是一个对象啊,这个对象是由函数对象的prototype来的:
Sum.prototype = new Minus(10,5);
var sm = new Sum(10,5);
sm.__proto__ === Sum.prototype; // true
严格相等,没错就是一个东西,原型只不过也是一个对象,prototype,是在对象创建时传递给Object.create()函数的,而__proto__,是对象的属性,只是该属性有点特别,会构成原型链,先来一睹原型链的风采:
Sum->Minus->Object 这就是sm对象的原型链,身为对象的它原本可以平平凡凡的做一个安静的对象,可偏偏不安定。当我们试图调用sm中的属性时,如果该属性能在Sum这层找到,就返回并结束查找,否则会去Minus中去查找,还找不到就去Object这层去找,如果还找不到就undefined吧,因为Object的__proto__属性为null,是不是有点类似于Java的继承机制,子类找不到的东西就去父类找,只是在JS中我觉得用继承这个词并不是那么贴切。网上在讲解这个原型的时候很多帖子画了很多图,绕来绕去的就被绕进去了,你只需要把原型理解为一个比较特殊的对象,这个对象是被穿在钎子上的,一个原型挨着另一个,组成一条链,原型并不是非得指向自身或者某一个对象,而是任意的对象,甚至可以将其设置成null,所以有些人说js的对象都继承自Object也不贴切。
5. 结论
prototype和__proto__本身就是对象,只不过是特殊的对象,创建后的对象有了__proto__属性,可以用来实现继承,通常原型链的顶层为Object。prototype中的constructor属性并不是用来创建对象的构造函数(我看好多博客都说是构造了该对象的函数虽然通常情况下该函数与对象的构造函数相同),这个constructor只是一个函数(注意是函数),通过constructor可以用一个对象构建出其父类的对象,例如var mi = new sm.__proto__.__proto__.constructor()就构建出了Minus的对象。----一切皆对象!