【第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函数本身。所以我们能得出下图结论:
image
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的原型链路径
obj 本身是一个普通对象,它的构造函数实际是Object,所以我们找到了创建obj的函数Object。具体推导流程如下面代码所示,一直到顶层null
obj.constructor === Object
obj.__proto__ === Object.prototype
Object.prototype.__proto__ === null
image
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
image
二. 函数对象
从函数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
image
三. 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
image
总结
按照上面的三个路径,很容易记忆理解原型链,上面内容也基本上涵盖了原型链的大部分场景,应付面试题绰绰有余。记忆理解后,再也不用每次面试都要重新复习这个难点了。
推荐阅读
2019年前端大事件回顾:流年笑掷,未来可期
Single-Spa + Vue Cli 微前端落地指南
BAT开源项目汇总
【第22题】理解 JS 模块化
【深入vue】为什么Vue3.0不再使用defineProperty实现数据监听?
抛弃jenkins,如何用node从零搭建自动化部署管理平台
前端部署演化史
深入理解 ES6 Iterator
解读HTTP/2与HTTP/3 的新特性(推荐)
关注我
扫一扫 关注我的公众号【前端名狮】,更多精彩内容陪伴你!
【前端名狮】
发表评论 (审核通过后显示评论):