前言
不得不说,js真的很灵活,花了近半天的时间才算摸清了个大概
“万物皆对象”,这话说的一点也没错。
这篇文章的假设前提是,你已经接触过了类似于Java的其他面向对象语言,了解过什么是对象
溯源
一点点铺垫
js的函数是什么?
首先,理解一个概念,“js的函数是对象实例”
function aaa(){
// do sth
}
let aaa = new Function("// do sth");
上面的两种表述作用完全相同,第二种表述的执行效率会略低一些但是无伤大雅(帮助理解函数名到底是什么)
现在一切已经很明显了,函数名本身就是一个Function对象。
typeof function(){} //function
怎么样?是不是很离谱?
js的对象哪里来?
从上面我们已经知道了,js的函数名是一个Function对象
那么,js中的对象又是怎么产生的呢?
半吊子的我常会无脑的写出
let a = new Array();
但是却从来没有考虑过Array
是个什么
实际上
typeof Array; //function
它是个Function对象
标准的创造一个对象的方法是
function a(){
}
let b = new a();
b就是创造出的新对象
也就是说,js的对象来自new一个函数名()
(大概可以理解为new的过程开辟了一块全新的内存空间,用于存储这个new出来的东西的内容(也就是函数中this所指向的对象(没错,this指的就是new出来的那个东西)))
既然对象一定由函数来创建,那么就一定可以找到那个用来创造对象的函数名
因此,js提供了constructor属性,指向那个创造这个对象的函数名
比如
function a(){}
let b = new a();
b.constructor === a;//true
上文也说了,函数本身是一个Function对象,那么,每个函数名也都有一个constructor
function a(){}
a.constructor === Function;//true
而函数名的constructor就是Function,原因是上文所说的每个函数都可以改写为new Function(xxx)
的形式
有趣的事情就来了,既然对象是被new出来的
那么,Function是个什么东西?
函数之源
接下来就来揭开Function的面纱
function Function(){
[native code]
}
没错,它就是长这样,不信你可以打印一下function(){}.constructor
至于native code,那是一些可执行的binary(elf)代码,这就牵扯到底层的东西了,这里只看上层建筑。
所以
js中每一个函数都是一个Function对象的实例
它们由function Function(){xx}
函数构造
完全自洽,符合上面js对象的通用定义
可能有人会好奇这个function Function(){xx}
函数会是由谁构造的
这还不明显吗?它是一个函数,是一个Function对象,那就还是由function Function(){xx}
new出来的
(是无限套娃)
对象之源
所有的对象总要有个源来说明它是个对象吧
在Java中,有个Object类,所有的类都继承自它,而Java中的对象也是从这里延伸而来
那么,js呢?
当然有,无论是Function所生成的对象,还是Array所生成的对象,抑或是XMLHttpRequest所生成的对象,都有一个共同的根基
function Object(){
[native code]
}
它是一个函数,因为对象一定是由函数产生的
我们常用以下的方法产生一个空对象
let a = new Object();
//或者
let b = {};
可能你会一直疑惑{}和new Object()到底有啥区别
可以说,没有区别
因为{}.constructor === Object //true
也就是说,和new Object()
完全等价
(拓展:new Array()
和[]
完全等价)
那么
function a() {}
let b = new a();
请问,b是一个Object吗?
这还用问?肯定是啊,任何对象都是Object的延申
但是,b不等于一个Object
为何?
一把附魔钻石剑是钻石剑
但是这把附魔钻石剑等于钻石剑吗?
(即使附的魔是空的,但是剑的光泽也改变了(?))
是什么把它们联系在了一起,又是什么把它们区分开了呢?
原型链
js中的每一个对象,都有一个__proto__
属性(这个属性在不同浏览器中名字可能不同,但都是有的)
这个属性指向了另一个对象
当一个对象被访问时,首先会去访问这个对象本身,如果找不到所要的东西,再会去访问这个对象的__proto__
属性所指向的另一个对象,如此层层套娃,直到终点——一个Object对象
这套机制便被称为原型链
而原型链,确定了各对象之间的继承关系
(你没有看错,就是一个Object对象,默认情况下所有对象在原型链上溯源的终点都是一致的)
(正是如此,所有对象才会拥有类似的toString()方法,这个方法来自于原型链上的溯源)
(当然,也有不默认的情况,那就是使用Object.create(__proto__)
来指定proto对象,指定为null时就是一条根基与原来不同的原型链了)
(原型链上的东西就是一个个对象,不像Java拥有class的继承方式)
眼尖的人可能已经注意到__proto__
是由两条下划线作为开头结尾的,这也就说明了,它被作为内部属性,不被推荐使用
那么,标准的原型链写法是啥?
是这样
function a(){
this.name = "111";
}
function b(){}
b.prototype = new a();
let c = new b();
alert(c.name);//111
为一个函数指定prototype之后,由这个函数new出来的新对象的__proto__全部都会指向那个prototype
也就是说
对象的__proto__和其构造函数的prototype完全等价
即new Object().__proto__ === new Object().constructor.prototype //true
注意,上面的两个new Object()
不是一个东西,它们的内存地址(引用)是不同的
也就是说可以拓展写为
let a = new Object();
let b = new Object();
a.__proto__ === b.__proto__; //true
a.constructor.prototype === b.__proto___;//true
Object.prototype === b.__proto__;//true
注意,函数的prototype和其造出的对象的__proto__完全等价(内存地址相同),并不存在“复制内容”
function a(){}
let b = new a();
b.__proto__ === a.prototype;//true
也就是说,修改new Object().__proto__
的属性相当于修改Object.prototype
的属性,可以直接影响到默认情况下js里的所有对象
new Object().__proto__.aaa = function (){alert("123")}
new Array().aaa(); //会直接弹窗显示123
原因是所有对象都默认继承于同一个Object对象
即
每个函数的默认prototype都是一个new Object()
而这些Object对象的溯源是相同的,也就是它们的__proto__属性指向了相同的东西,而这个东西就是Object.prototype (!!注意Object是个函数对象,看上文!!)
function a(){}
function b(){}
a.prototype === b.prototype// false。因为分别是两个new Object(),地址不同
a.prototype.__proto__ === b.prototype.__proto__ //true。相当于两个地址不同的new Object()的__proto__属性相等,都是Object函数的prototype
总结
“万物皆对象”
虽然只是摸到了一点皮毛,但是不得不感叹,妙哉!