JavaScript之彻底理解原型与原型链

简介: JavaScript之彻底理解原型与原型链

前言


原型与原型链知识历来都是面试中考察的重点,说难不算太难,但要完全理解还是得下一定的功夫。先来看一道面试题开开胃口吧:


function User() {}
User.prototype.sayHello = function() {}
var u1 = new User();
var u2 = new User();
console.log(u1.sayHello === u2.sayHello); 
console.log(User.prototype.constructor); 
console.log(User.prototype === Function.prototype); 
console.log(User.__proto__ === Function.prototype); 
console.log(User.__proto__ === Function.__proto__); 
console.log(u1.__proto__ === u2.__proto__); 
console.log(u1.__proto__ === User.__proto__); 
console.log(Function.__proto__ === Object.__proto__); 
console.log(Function.prototype.__proto__ === Object.prototype.__proto__); 
console.log(Function.prototype.__proto__ === Object.prototype); 
复制代码


基础铺垫


  1. JavaScript所有的对象本质上都是通过new 函数创建的,包括对象字面量的形式定义对象(相当于new Object()的语法糖)。

image.png

2.所有的函数本质上都是通过new Function创建的,包括ObjectArray


image.png


  1. 所有的函数都是对象。

prototype


每个函数都有一个属性prototype,它就是原型,默认情况下它是一个普通Object对象,这个对象是调用该构造函数所创建的实例的原型。


image.png


contructor属性


JavaScript同样存在由原型指向构造函数的属性:constructor,即Func.prototype.constructor --> Func

image.png


__proto__


JavaScript中所有对象(除了null)都具有一个__proto__属性,该属性指向该对象的原型。


function User() {}
var u1 = new User();
// u1.__proto__ -> User.prototype
console.log(u1.__proto__ === User.prototype) // true
复制代码


显而易见,实例的__proto__属性指向了构造函数的原型,那么多个实例的__proto__会指向同一个原型吗?


var u2 = new User();
console.log(u1.__proto__ === u2.__proto__) // true
复制代码


如果多个实例的__proto__都指向构造函数的原型,那么实例如果能通过一种方式,访问原型上的方法,属性等,就可以在原型进行编程,实现继承的效果。

我们继续更新一下原型与原型链的关系图:


image.png


原型链


实例对象在查找属性时,如果查找不到,就会沿着__proto__去与对象关联的原型上查找,如果还查找不到,就去找原型的原型,直至查到最顶层,这也就是原型链的概念。

就借助面试题,举几个原型链的例子:


举例


  1. u1.sayHello()u1上是没有sayHello方法的,因此访问u1.__proto__(User.prototype),成功访问到sayHello方法
  2. u2.toString()u2,User.prototype都没有toString方法,User.prototype也是一个普通对象,因此继续寻找User.prototype.__proto__(Object.prototype),成功调用到toString方法


提高


学完上面那些,大多数面试题都可以做出来了,例如下面这种


function A() {}
function B(a) {
    this.a = a;
}
function C(a) {
    if (a) {
        this.a = a;
    }
}
A.prototype.a = 1;
B.prototype.a = 1;
C.prototype.a = 1;
console.log(new A().a); //1
console.log(new B().a); //undefined
console.log(new C(2).a); //2
复制代码


但距离解决文章的最初的面试题还欠缺些什么,比如Function.__proto__ === Object.__proto__、Function.prototype.__proto__ === Object.prototype.__proto__等,接着我们来一一攻克它。


Objcet.__proto__Object.prototypeObject.prototype.__proto__


  • Object是构造函数,在第二部分我们讲过所有的函数都是通过new Function创建了,因此Object相当于Function的实例,即Object.__proto__ --> Function.prototype
  • Object.prototypeObject构造函数的原型,处于原型链的顶端,Object.prototype.__proto__已经没有可以指向的上层原型,因此其值为null


// 总结:
Object.__proto__ --> Function.prototype
Object.prototype.__proto__ --> null
复制代码


Function.__proto__Function.prototypeFunction.prototype.__proto__


  • Function.prototypeFunction的原型,是所有函数实例的原型,例如上面讲的Object.__proto__
  • Function.prototype是一个普通对象,因此Function.prototype.__proto__ --> Object.prototype
  • Function.__proto__: __proto__指向创造它的构造函数的原型,那谁创造了Function那?


  • 猜想:函数对象也是对象,那Function.__proto__会指向Object.prototype吗?上文提到,Object.__proto__ --> Function.prototype。如果Function.__proto__ -> Object.prototype,感觉总是怪怪的,到底谁创造了谁,于是我去做了一下测试:

image.png


  • 实践证明只存在Object.__proto__ --> Function.prototype
  • 苦思冥想没得出结果,难道Function函数是猴子不成,从石头缝里面蹦出来的?于是我进行了一同乱七八糟的测试,没想到找出了端倪。

image.png


  • 通过上面我们可以得出:Function.__proto__ --> Function.prototype


Function函数不通过任何东西创建,JS引擎启动时,添加到内存中


总结


最后将原型与原型链方面的知识凝结成一张图:

image.png


  1. 所有函数(包括Function)的__proto__指向Function.prototype
  2. 自定义对象实例的__proto__指向构造函数的原型
  3. 函数的prototype__proto__指向Object.prototype
  4. Object.prototype.__proto__ --> null


后语


知识的海洋往往比想象中还要辽阔,原型与原型链这边也反复的学过多次,我认为应该学的比较全面,比较完善了。但遇到这个面试题后,我才发现我所学的只不过是一根枝干,JS里面真的有很多深层次的宝藏等待挖掘。学海无涯,与君共勉。


最后再附赠个简单的面试题,提高一下自信:


var F = function () {}
Object.prototype.a = function () {}
Function.prototype.b = function () {}
var f = new F();
console.log(f.a, f.b, F.a, F.b);
// 原型链
// f.__proto__ --> F.prototype --> Object.prototype
// F.__proto__ --> Function.prototype --> Object.prototype
复制代码



相关文章
|
18天前
|
JavaScript 前端开发
js开发:请解释原型继承和类继承的区别。
JavaScript中的原型继承和类继承用于共享对象属性和方法。原型继承通过原型链实现共享,节省内存,但不支持私有属性。
19 0
|
JavaScript 前端开发 Java
深入JS面向对象(原型-继承)(三)
深入JS面向对象(原型-继承)
31 0
|
JavaScript 前端开发 Java
深入JS面向对象(原型-继承)(一)
深入JS面向对象(原型-继承)
32 0
|
2月前
|
JavaScript
JS数组增删方法的原理,使用原型定义
JS数组增删方法的原理,使用原型定义
|
21天前
|
JavaScript
什么是js的原型链
什么是js的原型链
|
1月前
|
JavaScript
未能正确利用原型继承(js的问题)
未能正确利用原型继承(js的问题)
|
2月前
|
JSON JavaScript 前端开发
深入JS面向对象(原型-继承)(四)
深入JS面向对象(原型-继承)
28 0
|
设计模式 JavaScript 前端开发
深入JS面向对象(原型-继承)(二)
深入JS面向对象(原型-继承)
45 0
|
2月前
|
JavaScript
JS原型对象prototype
JS原型对象prototype
|
2月前
|
JavaScript 前端开发
在JavaScript中,如何优化原型链的性能?
在JavaScript中,如何优化原型链的性能?
18 2