Sanakey

彻底弄清楚JS原型与原型链
前言原型与原型链,在JS中是个重难点知识,不少人被它们之间绕来绕去的关系弄得云里雾里,现在我就试着写一篇文章,讲讲...
扫描右侧二维码阅读全文
06
2019/05

彻底弄清楚JS原型与原型链

前言

原型与原型链,在JS中是个重难点知识,不少人被它们之间绕来绕去的关系弄得云里雾里,现在我就试着写一篇文章,讲讲我对原型与原型链的理解,希望对你有帮助。

知识点

在进入主题前,我们先要记住以下知识点。可能有些绕,一遍看不懂没关系,多看几遍,一定要记住这些概念。

  • 只有对象__proto__constructor属性。
  • 只有函数具有prototype属性,因为函数也是一种对象,所以函数也拥有__proto__constructor属性。
  • 无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个 prototype
    属性,这个属性指向函数的原型对象,可以理解为函数.prototype就是函数的原型对象
  • 在默认情况下,所有原型对象都会自动获得一个 constructor(构造函数)属性,这个属性包含一个指向 prototype 属性所在函数的指针。

即:函数.prototype.constructor === 函数 ==> 构造函数.prototype.constructor === 构造函数

  • 当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性__proto__),指向构造函数的原型对象。即:实例(对象)的__proto__ === 其构造函数.prototype__proto__属性在ES5标准里管这个指针叫[[Prototype]],在ES5中没有标准的方式访问 [[Prototype]] ,但现代浏览器在每个对象上都支持一个属性__proto____proto__===[[Prototype]]

构造函数

记住了上面的概念,我们从两行代码说起:

function Foo(){};
var f1 = new Foo;

其中,Foo是构造函数,f1是Foo构造出的实例对象。

按照上面的结论,应该得出以下结果

// 只有函数具有prototype属性
f1.prototype === undefined // true

// 实例(对象)的__proto__ === 其构造函数.prototype
f1.__proto__ === Foo.prototype // true
Foo.__proto__ === Function.prototype // true

// 构造函数.prototype.constructor === 构造函数
Foo.prototype.constructor === Foo // true

// f1.__proto__ === Foo.prototype,Foo.prototype.constructor === Foo
f1.__proto__.constructor === Foo // true
f1.constructor === Foo // true

经实际验证,结论都正确

img

函数对象与普通对象

JavaScript 中,有两种对象类型。分为普通对象和函数对象,凡是通过 new Function() 创建的对象都是函数对象,其他的都是普通对象。通过typeof也能方便区分,结果为'function'的,都是函数对象。

// 所有的函数对象都来自于Function.prototype,甚至包括Object及Function自身
Number.__proto__ === Function.prototype  // true
Number.constructor == Function //true

Boolean.__proto__ === Function.prototype // true
Boolean.constructor == Function //true

String.__proto__ === Function.prototype  // true
String.constructor == Function //true

Object.__proto__ === Function.prototype  // true
Object.constructor == Function // true

Array.__proto__ === Function.prototype   // true
Array.constructor == Function //true

Function.__proto__ === Function.prototype // true
Function.constructor == Function //true

RegExp.__proto__ === Function.prototype  // true
RegExp.constructor == Function //true

Error.__proto__ === Function.prototype   // true
Error.constructor == Function //true

Date.__proto__ === Function.prototype    // true
Date.constructor == Function //true

Foo.prototype是原型对象,原型对象也是一个普通对象,由于一个普通对象的构造函数 === Object,因此

Foo.prototype.__proto__ === Object.prototype // true
Function.prototype.__proto__ === Object.prototype // true

延伸一下,Object.__proto__ === Function.prototypeObject.__proto__.__proto__ === Function.prototype.__proto__,由于Function.prototype.__proto__ === Object.prototype,所以Object.__proto__.__proto__ === Object.prototype

经过以上的分析,思考一下下面的问题

Object.prototype.__proto__ === Object.prototype // false

Object.prototype是原型对象,按理说上面值应该相等才对,然而结果是不相等,这里引入新的知识点:

__proto__属性的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__属性所指向的那个对象(父对象)里找,一直找,直到__proto__属性的终点null,再往上找就相当于在null上取值,会报错。通过__proto__属性将对象连接起来的这条链路就是所谓的原型链

因为null处于原型链的顶端。所以Object.prototype.__proto__的值为null。

Object.prototype.__proto__ === null // true

总结

最后总结一下文章中出现的所有重要知识点:

  • 只有对象__proto__constructor属性。
  • 只有函数具有prototype属性,因为函数也是一种对象,所以函数也拥有__proto__constructor属性。prototype属性的作用就是让该函数所实例化的对象们都可以找到公用的属性和方法。constructor属性的作用就是指向该对象的构造函数。
  • 函数.prototype就是函数的原型对象
  • 构造函数.prototype.constructor === 构造函数,实例.constructor === 构造函数。
  • 实例(对象).__proto__ === 其构造函数.prototype
  • JavaScript 中 凡是通过 new Function() 创建的对象都是函数对象,其他的都是普通对象。 即:typeof结果为'function'的,都是函数对象。
  • 所有的函数对象都可以看成是构造函数Function()的new操作的实例化对象。函数对象(Function也是函数)是new Function的结果,所以函数可以作为实例对象,其构造函数是Function(),原型对象是Function.prototype,即:所有函数对象的__proto__ === Function.prototype,包括Object及Function自身
  • 所有的普通对象都可以看成是Object()构造函数的new操作的实例化结果。普通对象(函数也是对象)是new Object的结果,所以对象可以作为实例对象,其构造函数是Object(),原型对象是Object.prototype,即:所有普通对象的__proto__ === Object.prototype
  • 以上两点结论是基于原型链查找规则的,当实例对象有自定义的构造函数时,首先指向该构造函数,否则会向上查找,直到__proto__指向Object.prototype。再往上就是null了,null位于原型链顶端。
  • Object.__proto__.__proto__ === Object.prototype
  • Object.prototype.__proto__ === null

这些知识点里,弄明白原型链指向的核心就是实例(对象).__proto__ === 其构造函数.prototype ,只要分清楚实例对象究竟是由哪个构造函数实例化的结果,就可以轻易弄清原型链的指向了。

function Foo(){};
var f1 = new Foo;

f1.__proto__ === Foo.prototype 
//f1是普通对象,Foo是f1的构造函数
Foo.__proto__ === Function.prototype // Function是Foo的构造函数
Function.__proto__ === Function.prototype // Function是Function的构造函数

f1.constructor为:  ƒ Foo() {}
Foo.constructor:  ƒ Function() { [native code] }
f1.prototype:  undefined
Foo.prototype:  {constructor: ƒ}
f1.__proto__:  {constructor: ƒ}
Foo.prototype.__proto__:  {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
Foo.__proto__:  ƒ () { [native code] }
console.log('f1.constructor: ',f1.constructor);
console.log('Foo.constructor: ',Foo.constructor);
console.log('f1.prototype: ',f1.prototype);
console.log('Foo.prototype: ',Foo.prototype);
console.log('f1.__proto__: ',f1.__proto__);
console.log('Foo.prototype.__proto__: ',Foo.prototype.__proto__);
console.log('Foo.__proto__: ',Foo.__proto__);
console.log('Foo.prototype.constructor: ',Foo.prototype.constructor);

img

Last modification:November 26th, 2019 at 12:45 am
如果觉得我的文章对你有用,请随意赞赏

Leave a Comment

kotori.png