阅读 77

【第24题】三条路径理清原型链(定格记忆篇)

面试题(头条)

利用原型和原型链相关知识,画出 Object、 Function、Object.prototype、Function.prototype 四个对象间的关联关系

答案解析:

原型和原型链,是javascript这门语言中的重要概念,同时也是面试过程中的必考知识点。小编对它也是日常迷惑,每次都需要查资料、重新理逻辑,近日重新换了个角度思考,对原型和原型链有了更加深刻的认识和理解。

被忽视的点

有一些比较重要的知识点,往往容易被忽略,清醒的认识下面的几个点,对于后面理解原型和原型链有很大帮助。

1. prototype是函数对象的独有属性,同时prototype对象是一个普通对象object(Function.prototype除外,它是一个function)

在JS的世界中,对象被分为两种:普通对象(object)、函数对象(function)。

只有函数对象才有prototype属性,普通对象是没有prototype属性的。

可以通过下面代码进行验证:

let obj = {
    name: '前端名狮'
};

function foo () {
    console.log('my name is 前端名狮');
}

console.log(obj.prototype); // undefined
console.log(foo.prototype); // {constructor: foo}

console.log(typeof(foo.prototype)) // object
console.log(foo.prototype.hasOwnProperty('constructor')) // true
复制代码

由上我们能看出:

obj.prototype 输出undefined,表明普通对象没有prototype属性。

foo.prototype的输出结果是一个普通的object,同时里面包含了constructor属性,而constructor指向了foo函数本身。所以我们能得出下图结论:

2. Object,Function 都是函数

由于原型和原型链之间的关系复杂,梳理过程中,很容易忽视Object、Function是函数,可以通过下面代码加深理解

console.log(typeof(Object)); //打印出 Function
let obj = new Object(); // 可以使用new创建,表明是一个函数

console.log(typeof(Function));
let fn = new Function('console.log(88)'); // 通过new的方式创建函数,表明Function本身是一个函数
复制代码

三条路径覆盖原型链

所谓的原型链,就是查找一个对象的属性时,先查找它本身是否存在该属性,如果本身不存在的话,会通过隐式原型属性__proto__ 找到它的上一级对象,,然后再通过上一级对象的__proto__查找上一级的上一级,一直到最顶层对象null,这样的一种查找途径,通过__proto__形成了一条链路。

上面提到,prototype是函数对象的特有属性,而__proto__属性任何对象有。所以原型链实际是靠__proto__属性实现的。

我们一定要牢记下面这句话,我们下面所有的内容都是基于这句话展开的,没有为什么,JS这门语言实现时就是这么指定的,记住就好了: 对象的__proto__指向的是创建它的函数的prototype

__proto__相当于C++中的指针,prototype相当于指针指向的对象,该prototye对象中包含了__proto__指针,指向上一级对象。

下面我们来分析一下原型链存在的三条路径:

一. 普通对象

常见普通对象的生成其实有两种形式,如下代码:

// 第一种
let obj = {
    name: '前端名狮'
}

// 第二种,通过函数对象新建

function Foo () {
    console.log('my name is 前端名狮');
}

let foo = new Foo();
复制代码

正常情况下,两种情况实际属于同一种情况,为什么呢?

new 本身经历了一个复杂的过程,正常情况返回的实际是内部创建的一个object对象,具体可以阅读【第15题】- new 操作符内部实现原理

下面我们分析一下obj,foo的原型链路径

  1. obj 本身是一个普通对象,它的构造函数实际是Object,所以我们找到了创建obj的函数Object。具体推导流程如下面代码所示,一直到顶层null
obj.constructor === Object

obj.__proto__ === Object.prototype

Object.prototype.__proto__ === null

复制代码

  1. foo的构造函数是Foo,所以foo.__proto__ === Foo.prototype

由上方的分析可知,prototype是一个普通对象,所以Foo.prototype是一个普通对象object,再往上查找就回到了第一种情况Foo.prototype.__proto__ === Object.prototype,再往上就到达原型链的顶层了。

foo.constructor === Foo

foo.__proto__ === Foo.prototype

Foo.prototype.__proto__ === Object.prototype

Object.prototype.__proto__ === null
复制代码

二. 函数对象

从函数Foo开始,分析下原型链的路径

function Foo () {
    console.log('my name is 前端名狮');
}
复制代码

Foo 是一个函数,所以它的构造函数是Function,即Foo.constructor === Function,可以得知Foo.__proto__ === Function.prototype。再往上就是Function.prototype.__proto__ === Object.prototype,剩下的就跟上面的情况一样了。

Foo.constructor === Function

Foo.__proto__ === Function.prototype

Function.prototype.__proto__ === Object.prototype

Object.prototype.__proto__ === null
复制代码

三. Object和Function

正如前面分析的那样,Object、Function都是函数对象,这两个对象是JS对象的源头,所以单独拎出来分析。

Object本身是一个函数对象,它的构造函数是Function,即 Object.constructor === Function,所以Object.__proto__ === Function.prototype,再往上查找就是Function.prototype.__proto__ === Object.prototype,再往上就是null。

Object.constructor === Function

Object.__proto__ === Function.prototype

Function.prototype.__proto__ === Object.prototype

Object.prototype.__proto__ === null
复制代码

Function本身是一个函数,它的构造函数还是一个函数,所以Function.constructor === Function,所以Function.__proto__ === Function.prototype,后面的就和上面一样了。

Function.constructor === Function

Function.__proto__ === Function.prototype

Function.prototype.__proto__ === Object.prototype

Object.prototype.__proto__ === null

复制代码

总结

按照上面的三个路径,很容易记忆理解原型链,上面内容也基本上涵盖了原型链的大部分场景,应付面试题绰绰有余。记忆理解后,再也不用每次面试都要重新复习这个难点了。

推荐阅读

  1. 2019年前端大事件回顾:流年笑掷,未来可期
  2. Single-Spa + Vue Cli 微前端落地指南
  3. BAT开源项目汇总
  4. 【第22题】理解 JS 模块化
  5. 【深入vue】为什么Vue3.0不再使用defineProperty实现数据监听?
  6. 抛弃jenkins,如何用node从零搭建自动化部署管理平台
  7. 前端部署演化史
  8. 深入理解 ES6 Iterator
  9. 解读HTTP/2与HTTP/3 的新特性(推荐)

关注我

扫一扫 关注我的公众号【前端名狮】,更多精彩内容陪伴你!