前言

不得不说,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

总结

“万物皆对象”
虽然只是摸到了一点皮毛,但是不得不感叹,妙哉!