略解《JavaScript语言精粹》- 原型

大熙哥
大熙哥 2022年02月14日 阅读:301

前言

  1. 本篇博客因篇幅有限,仅提取出个人认为十分重要且有用的精华,由于本人能力有限,文章可能未能一一提及,仅供各位同学参考学习。

  2. 本文章所有代码已在WebMaker上有代码测试用例。请点击略解《JavaScript语言精粹》代码片段访问配合学习。

原型 Prototype

  1. 每个对象都连接到一个原型,并且从中继承属性,所有通过对象字面量(一个对象字面量就是包围在一对花括号中的键值对。)创建的对象都连接到Object.prototype。

  2. 当创建一个新对象,可以选择某个对象作为它的原型。

    Object.create = function (o){
        var F = function (){};
        F.prototype = o;
        return new F()
    }
  1. 当我们对某个对象做出改变时,不回触及该对象原型,原型连接只有在检索值的时候才会被用到,如果我们尝试去获取对象的某个属性值,但该对象没有此属性名,那么JS会试着从原型对象中获取属性值,如果那个原型对象也没有该属性,那么再从它的原型中找,以此类推,直到该过程最后到达终点Object.prototype。如果想要的属性完全不存在于原型链中,那么结果就是undefined值。这个过程成为“委托”。原型关系是一种动态的关系。如果我们添加一个新的属性到原型中,该属性会立即对所有基于该原型创建的对象可见。

剖析原型及原型链

  1. 假设我们new一个数组,通过__proto__可以得到该对象的原型对象,原型对象通过constructor可以得到实例化对象本身的构造函数,而构造函数本身又可以通过prototype拿到原型对象。
let arr = new Array()
// 通过__proto__可以拿到Array的原型对象
console.log(arr.__proto__ === Array.prototype) // true
// 通过原型对象的constructor可以拿到构造函数本身
console.log(arr.__proto__.constructor === Array) // true
  • Array构造函数通过new实例化出array对象
  • array对象的__proto__指向Array的原型对象
  • Array原型对象的constructor属性可以得到实例化对象本身的构造函数Array
  • Array构造函数的prototype又指向该构造函数的原型对象

原型链数组示例.drawio.png

Array的构造函数的原型

Array的构造函数的原型又是谁呢?函数本身也是一种对象,指向Function原型对象Function原型对象的constructor指向Function构造函数,所以Array构造函数是由Function构造函数实例化来的。

原型链数组示例.drawio.png

Function构造函数的原型

Function构造函数的原型又是谁呢?我们可以打印一下Function的原型对象__proto__。发现Function的原型对象还是Function并且Function构造函数是由Function构造函数本身实例化而来的。

let a = new Function()
console.log(a.__proto__)
console.log(a.__proto__.constructor)

原型链数组示例.drawio.png

Function构造函数已经到达顶端了,用大白话讲,所有构造函数的顶头上司都是Function构造函数,其他构造函数都是老大Function实例化出来的。

什么产生的Array原型对象

什么产生的Array原型对象?

我们可以打印一下Array原型对象的原型对象。发现是Object!Array构造函数的原型对象是通过Object构造函数实例化出来的。

let a = new Array()
console.log(typeof a.__proto__.__proto__)

原型链数组示例.drawio.png

Object构造函数是谁创造的呢?

Object构造函数是谁创造的呢?

所有构造函数的顶头上司都是Function构造函数,那么Object构造函数就是Function构造函数实例化出来的。不信我们可以打印一下。

console.log(typeof Object.prototype)
console.log(typeof Object.constructor.prototype)

原型链数组示例.drawio.png

此时我冒出了一个疑问,是先有Object原型对象还是先有Function原型对象?

JS 究竟是先有鸡还是有蛋

我们常说JavaScript中函数是一等公民,这是因为函数扮演了创造万物的角色,原始构造函数Function创造了function fn(){}(ES5中函数与构造函数并无区别)、Object()、Array()、Number()、String()等诸多构造函数,而构造函数也拥有创造对应实例对象的能力,比如Array()生产数组,String()生产字符串,你会发现JavaScript中绝大多数的数据类型,都能找到创造自己的构造函数,所以说函数是一等公民不无道理。

几个疑问

  1. 如果说Function()扮演着创世主的角色,那Function.prototype不应该是仅次于原型链顶端null的存在吗?
  2. 在我们绘画原型链的图过程中,我们知道紧接在null之下的是Object.prototype,Object.prototype的起源地位似乎比Function.prototype更早,那Object.proto === Function.prototype又是怎么回事?Object与Function到底谁的起源更早,谁才是真正的创世主?

起源

盘古开天辟地,js 中并不是就有了 Object构造函数,而是 Object.prototypeObject原型对象。

所以,Object.prototype原型对象 先于 Object构造函数 出现,然后用这个 prototype原型对象 构造出 Function.prototype原型对象,有了 Function.prototype原型对象 再构造出 Function构造函数 , Object构造函数 这几个构造器。然后把 Object.prototype原型对象 挂到 Object构造函数 上,Function.prototype原型对象 挂到Function构造函数 上。

所以,是先有的 Object.prototype,再有的 Function.prototype ,再有的 FunctionObject

总结

通过上面我们深刻认识到原型及原型链的知识,以前刷面试题的时候总是很粗浅的了解很少深入了解到其中的奥秘,比如像原型链我们最多就知道原型链的顶端是Object,Object的顶端是NULL,function是一等公民等,可是我们仿佛是背诵或者强行自己理解一样并没有领悟原型和原型链的真正的构造,当我把上面的图画出来的时候,原型链的迷雾就已经被拨开,本章总结如下:

  1. 先有了Object原型对象,
  2. 然后有Function原型对象,
  3. 然后有了Function构造函数,
  4. 然后创建了Object构造函数,
  5. Object构造函数.prototype又指向了Object的原型,构成了循环指向;

可以说先有Function构造函数再有Object构造函数。也可以说先有的Object原型,再有的Function原型。Object.prototype.proto === null,说明原型链到Object.prototype终止。原型链的顶端可以说是Object也可以说是null,看你个人定义。

分类:
标签:
目录
目录