menu 光风霁月。
原型关系
327 浏览 | 2020-05-27 | 阅读时间: 约 4 分钟 | 分类: Javascript 原理 | 标签: Javascript
请注意,本文编写于 148 天前,最后修改于 148 天前,其中某些信息可能已经过时。

前言

学习 原型关系 是学习 Javascirpt 原理的必经之路,想要看懂 Vue、React 等前端框架,也必须有扎实的底层知识才行。因此,我将自己学习原型关系得出的结论一一列出,与大家共勉。此处的知识比较抽象,也比较绕,但是一旦明白了就会不由地赞叹 Javascript 语言的发明者是多么聪明,也会有一种豁然开朗的感觉。

原型关系图

我把这张图片放在了第一位,足以说明其重要性。这张图片是我总结前人经验后自己绘制的,理清了很多关系。后面的内容也都离不开这张图片,所以大家在看后面知识时,不妨回过头来看一看,也许会有意外收获。

所有对象都拥有同一个属性

先从声明一个名称为 Foo 的函数开始,函数里面的内容是什么我们并不关心。我们知道 JS 里面有这样一句话: " 一切都是对象 ! ",实际上这样说是不严谨的,除了基本类型 (包含 数字字符串布尔nullundefined符号 共六种) 之外,其他的都可以称之为对象。

函数 (function)、类 (class) ... 都是对象, 所有对象都有一个 __proto__ (注意这里是前后均有两个下划线 _ ) 属性, 如何验证呢 ?

const obj1 = 10; console.log(obj1.__proto__);
const obj2 = new Object(); console.log(obj2.__proto__);
const obj3 = [1,2]; console.log(obj3.__proto__);
... more validation

无论你用什么样的方法去验证, 它的 __proto__ 都会存在,尽管有时__proto__的值是 null , 也是存在的。

新创建的对象可以访问其构造器的原型对象, 对象实例会将它储存在自己的 __proto__ 属性中, 这也就是继承了。因此, 在一开始的图中, 所有对象的 __proto__都是指向了其构造函数的原型, 也就是 prototype


所有函数都有一个 prototype

构造函数、自定义函数 ... 是个函数都会有一个 prototype 属性, 当使用 new 关键字创建一个对象实例时, 都会把原构造函数的原型给继承下来, 原型里面可以放任何你想传递给对象实例的东西。

function Foo() {
    Foo.prototype.sing = function(){
        console.log("I like singing!");
    };
    Foo.prototype.name = "赵刚";
    Foo.prototype.work = "政委";
}
let foo1 = new Foo();
console.log(Foo.prototype);
console.log(foo1.__proto__);
console.log(Foo.prototype === foo1.__proto__);
# 结果如下
Object {sing: , name: "赵刚", work: "政委", constructor: }
Object {sing: , name: "赵刚", work: "政委", constructor: }
true

在这里我们使用了 === 符号, 如果 __proto__prototype 是两个内容相同的不同对象 (开辟了两块内存), === 符号就会返回 false, 因此每个对象的 __proto__ 其实是一个指针, 它指向了其构造函数的原型 => prototype


函数自指现象

所谓函数自指现象,指的是每个函数的 prototype 的 constructor 又指向了它自身。

function Foo(){};
console.log(Foo.prototype.constructor === Foo);
# 结果如下
true

如果到这里你开始感觉有点绕, 你可以返回来再看看图, 里面的箭头都是指针, 比如说 Foo.prototype 这句话里的 prototype 就是个指针, 而这个指针指向了 prototype这个对象。

这就好比两个人拿着可以找到对方的地址, 从而形成了一个闭环, 并不绕。


prototype 也是对象

我们在第三个模块的实验里打印了 Foo.prototype , 其结果为

Object {sing: , name: "赵刚", work: "政委", constructor: }

可以看到 prototype 也是对象, 是对象就会有 __proto__ 这个属性 (指针), 那么这个指针指向的是哪里 ? => Object.prototype

function Foo(){};
console.log(Foo.prototype.__proto__);
console.log(Foo.prototype.__proto__ === Object.prototype);
# 结果如下
Object {constructor: , __defineGetter__: , __defineSetter__: , hasOwnProperty: , __lookupGetter__: , …}
true

为了更多的看到 Object.prototype 可以到浏览器的控制台 ( VS code 输出不全 )

正是因为 Object 是所有对象的构造器, 这个输出结果也不足为奇。

Object.prototype

既然 Object 是个函数(构造器), 那他就会有 prototype 属性, 这个 prototype 又是个对象, 它势必就会有 __proto__ , 如果 __proto__再指向了一个 prototype 这样重复下去便会无穷无尽。

事实并不如此, 从图中可以看到它指向的是 null。以 null 为原料种子, 进而构造了那么多的东西。

看到这里我瞬间想到了一个成语:

无中生有:《三十六计》中的第七计,原文为:“诳也,非诳也,实其所诳也。少阴,太阴,太阳。”本指本来没有却硬说有。现形容凭空捏造。本计计语出自中国古代哲学家(也有的称为兵家)老子《道德经》第40章:“天下万物生于有,有生于无”。老子揭示了万物第有与无相互依存、相互变化的规律。中国古代军事家尉缭子把老子的辩证思想运用到军事上,进一步分析虚无与实有的关系。

​ ——百度百科

Object.__proto__

刚刚只说到了 Object 作为函数的一面, 那么它作为一个对象也势必会有 __proto__, 按照惯例, 它指向的是构造它的函数的原型。

我们知道所有的函数都是 Function 制造出来的, Object 也不例外, 它的 __proto__正是指向了 Function.prototype, 进而 Function.prototype.__proto__指向的是构造它的 Object 的原型。

console.log(Object.__proto__ === Function.prototype);
console.log(Function.prototype.__proto__ === Object.prototype);
# 结果如下
true
true

Function.__proto__

最后一个关键的问题来了, Function 作为一个函数 它有 prototype, 在第七模块我们已经讨论并实验了, 那么它作为一个对象 (没错, 它也是个对象), 它势必会有 __proto__, 那么这个指针也会按照以往一样, 指向的是构造它的函数的原型。

什么?

cosole.log(Function.__proto__ === Function.prototype);

它的结果竟然为 true ! ! !

按照结果的意思解释下来就是 Function 作为一个对象, 是它自己构造了它自己。

这样看起来十分荒唐, 但是如果你仔细的看 FunctionFunction.prototype 这是两个不同的对象, 根本不是自己构造自己, 所以这样的结果便可以接受了吧。

本文小结

从一开始的图说起, 从图中找出只有进没有出的东西 (箭头的指向), 整个图中, 只有 null 可以做到, 是 null 构成了 Object.prototype, 又是 Object.prototype 构成了 Function.prototype

再从图中看, 所有的函数对象都是继承自 Function.prototype , 与其说是函数构造了对象, 倒不如说是对象继承构建了新对象,而函数只是一层外衣。

知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议

发表评论

email
web

全部评论 (暂无评论)

info 还没有任何评论,你来说两句呐!