《JS原理学习 (2) 》深入理解原型链与继承(上)

简介: 《JS原理学习 (2) 》深入理解原型链与继承(上)

前言


JavaScript是一门面向对象的语言,所有的对象都从原型继承属性和方法,那么什么是原型?对象与对象之间如何实现继承?


本文就带大家来深入理解下JavaScript中的原型,欢迎各位感兴趣的开发者阅读本文。


原理解析


接下来我们来逐步分析下原型与对象之间的关系。


原型对象


我们使用function关键字来创建函数时,内存中会创建一个包含prototype属性的对象,这个属性指向函数的原型对象,如下所示:


function Person() {
}
Person.prototype // {constructor: Person(), __proto__}


上述代码中我们创建了一个名为Person的函数:


  • prototype属性指向的就是Person的原型(每个除null外的JS对象在创建的时候都会关联到另一个对象,这个关联的对象就是原型)
  • 每个对象都会从原型“继承”属性
  • 原型对象里包含constructor__proto__属性。


640.png

                            image-20210310173710555


我们画个图来描述下Personprototype之间的关系


640.png

                         image-20210310195733848


用new运算符调用的函数便为构造函数,建议构造函数命名时将首字母大写。


函数实例与原型对象的关系


上个章节我们捋清了构造函数原型对象的关系,接下我们来看下函数实例原型对象之间的关系。


我们用运算符new将上个章节创建的Person函数进行实例化,得到person实例,代码如下:


// 实例化对象
const person = new Persion();


在上个章节中,我们知道原型对象有2个属性,其中__proto__是每一个除null外的JavaScript对象都具有的一个属性,它指向该对象的原型对象。


接下来,我们来证明下person.__proto__是否和Persion.prototype相等,代码如下:


function Person() {
}
const person = new Persion();
console.log("函数实例的__proto__指针指向构造函数的原型对象: ", person.__proto__ === Person.prototype);


执行结果如下:


640.png

                                    image-20210312172907318


除了使用__proto__来访问原型对象,我们还可以使用Object.getPrototypeOf()来获取。


证明出他们相等后,结合构造函数与原型对象可知他们三个之间的关系,如下所示:


640.png

                             image-20210310202939966


当我们实例化一个构造函数时,也会为这个实例创建一个__proto__属性,这个属性是一个指针,它指向构造函数的原型对象。


由于同一个构造函数创建的所有实例对象的__proto__属性都是指向其构造函数的原型对象,因此所有的实例对象都会共享构造函数原型对象上的属性和方法,因此,一旦原型对象上的属性或方法发生改变,所有的实例对象都会受到影响。


原型对象与构造函数的关系


上个章节我们分析了原型对象中__proto__的指向,接下来我们来分析下constructor的指向。每个原型对象都有一个constructor属性,它指向该对象的构造函数。


接下来,我们来证明下Person.prototype.constructor是否和Person相等,代码如下:


function Person() {
}
const person = new Person();
console.log("原型对象与构造函数相等: ", Person.prototype.constructor === Person);


执行结果如下:


640.png

                                image-20210310211445102


证明出他们相等后,我们结合构造函数、函数实例、原型对象可知他们四个之间的关系,如下所示:


640.png

                               image-20210310212117231


获取对象原型,除了访问它的prototype外,我们还可以使用Object.getPrototypeOf()来获取。


实例属性的读取顺序


读取实例中的属性时,如果找不到,就会查找该对象原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止。


接下来,我们举个例子来证实下上述话语:


function Person() {
}
Person.prototype.name = "原型上的name属性";
const person = new Person();
person.name = "实例上的name属性";
console.log(person.name) // 实例上的name属性
delete person.name;
console.log(person.name); // 原型上的name属性
delete Person.prototype.name;
console.log(person.name); // undefined


我们来分析下上述例子:


  • 向原型上添加了name属性
  • 向实例上添加了name属性
  • 此时,name的属性值为实例上的name属性
  • 删除了实例上的name属性
  • 此时,它就会找原型上的name属性,因此值为原型上的name属性
  • 删除了原型上的name属性
  • 此时,他就会找原型的原型的name值,原型的原型不存在name属性,因此返回undefined


在上面的分析中,我们在Person的原型的原型中没找到name属性,那么Person的原型的原型是什么呢?我们在谷歌浏览器的控制台来打印下看看,如下所示:


640.png

                         image-20210310222010988


正如上图结果所示,Person的原型的原型是一个对象,证明出了原型也是一个对象,那么我们就可以用最原始的方式创建它,代码如下所示:


const object = new Object();
object.name = "对象中的属性";
console.log(object.name); // 对象中的属性
console.log("object实例与Object的原型对象相等", object.__proto__ === Object.prototype);
console.log("Object的原型对象与构造函数相等", Object.prototype.constructor === Object);


执行结果如下:


640.png

                             image-20210312175303383


知道原型也是对象后,结合我们上面所证明出来的内容,他们之间的关系如下所示:


640.png

                               image-20210310224603680


原型链


通过前面的分析我们知道了万物基于Object,那么Object的原型是什么呢?答案是null

我们在谷歌浏览器的控制台上验证下,结果如下所示:


640.png

                               image-20210310231037617


综合上述,他们最终的关系如下所示:


640.png

                            image-20210310232450892


图中橙色线条组成的链状结构就是原型链


重写原型对象


我们在实现实现一些功能时,经常会用一个包含所有属性和方法的对象字面量来重写整个原型对象。


如下所示:


Person.prototype = {
    name: "神奇的程序员",
    age: "20",
    job: "web前端开发",
    sayName: function () {
        console.log(this.name);
    }
}


  • 将Person的原型指向了一个新的对象
  • 原型上拥有三个属性和一个方法
  • 对象中不存在constructor属性


由于重写的对象中不存在constructor属性,那么它的constructor属性将会指向Object。


我们来验证下,代码如下所示:


console.log("Person的原型对象的构造函数与Person构造函数相等", Person.prototype.constructor === Person)


执行结果如下:


640.png

                                  image-20210312211047400


如果constructor的值很重要,那么我们就需要特意将constructor的指向改为构造函数了,代码如下所示:


Person.prototype = {
    name: "神奇的程序员",
    age: "20",
    job: "web前端开发",
    sayName: function () {
        console.log(this.name);
    },
    constructor: Person
}
console.log("Person的原型对象与Person构造函数相等", Person.prototype.constructor === Person)


执行结果如下:


640.png

                              image-20210311000952986


相关文章
|
JavaScript 前端开发
如何在 JavaScript 中使用 __proto__ 实现对象的继承?
使用`__proto__`实现对象继承时需要注意原型链的完整性和属性方法的正确继承,避免出现意外的行为和错误。同时,在现代JavaScript中,也可以使用`class`和`extends`关键字来实现更简洁和直观的继承语法,但理解基于`__proto__`的继承方式对于深入理解JavaScript的面向对象编程和原型链机制仍然具有重要意义。
|
10月前
|
机器学习/深度学习 JavaScript 前端开发
JS进阶教程:递归函数原理与篇例解析
通过对这些代码示例的学习,我们已经了解了递归的原理以及递归在JS中的应用方法。递归虽然有着理论升华,但弄清它的核心思想并不难。举个随手可见的例子,火影鸣人做的影分身,你看到的都是同一个鸣人,但他们的行为却能在全局产生影响,这不就是递归吗?雾里看花,透过其间你或许已经深入了递归的魅力之中。
388 19
|
11月前
|
JavaScript 数据可视化 前端开发
three.js简单实现一个3D三角函数学习理解
1.Three.js简介 Three.js是一个基于JavaScript编写的开源3D图形库,利用WebGL技术在网页上渲染3D图形。它提供了许多高级功能,如几何体、纹理、光照、阴影等,以便开发者能够快速地创建复杂且逼真的3D场景。同时,Three.js还具有很好的跨平台和跨浏览器兼容性,让用户无需安装任何插件就可以在现代浏览器上观看3D内容。
400 0
|
JavaScript 前端开发 Java
深入理解 JavaScript 中的 Array.find() 方法:原理、性能优势与实用案例详解
Array.find() 是 JavaScript 数组方法中一个非常实用和强大的工具。它不仅提供了简洁的查找操作,还具有性能上的独特优势:返回的引用能够直接影响原数组的数据内容,使得数据更新更加高效。通过各种场景的展示,我们可以看到 Array.find() 在更新、条件查找和嵌套结构查找等场景中的广泛应用。 在实际开发中,掌握 Array.find() 的特性和使用技巧,可以让代码更加简洁高效,特别是在需要直接修改原数据内容的情形。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一
|
监控 JavaScript 前端开发
MutationObserver详解+案例——深入理解 JavaScript 中的 MutationObserver:原理与实战案例
MutationObserver 是一个非常强大的 API,提供了一种高效、灵活的方式来监听和响应 DOM 变化。它解决了传统 DOM 事件监听器的诸多局限性,通过异步、批量的方式处理 DOM 变化,大大提高了性能和效率。在实际开发中,合理使用 MutationObserver 可以帮助我们更好地控制 DOM 操作,提高代码的健壮性和可维护性。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
MutationObserver详解+案例——深入理解 JavaScript 中的 MutationObserver:原理与实战案例
|
JavaScript 前端开发
JavaScript 原型链的实现原理是什么?
JavaScript 原型链的实现原理是通过构造函数的`prototype`属性、对象的`__proto__`属性以及属性查找机制等相互配合,构建了一个从对象到`Object.prototype`的链式结构,实现了对象之间的继承、属性共享和动态扩展等功能,为 JavaScript 的面向对象编程提供了强大的支持。
|
JavaScript 前端开发
原型链在 JavaScript 中的作用是什么?
原型链是 JavaScript 中实现面向对象编程的重要机制之一,它为代码的组织、复用、扩展和多态性提供了强大的支持,使得 JavaScript 能够以简洁而灵活的方式构建复杂的应用程序。深入理解和熟练运用原型链,对于提升 JavaScript 编程能力和开发高质量的应用具有重要意义。
|
数据采集 JavaScript 前端开发
一站搞定原型链:深入理解JavaScript的继承机制
综上所述,可以得出: 1. 原型链是对象通过原型实现属性和方法继承的一种机制。 2. 每个对象都有一个 __proto__ 属性,指向它的原型对象。 3. 每个函数(包括构造函数)都有一个 prototype 属性,指向一个对象,这个对象的属性和方法可以被实例共享。 4. 构造函数创建对象时,新对象的 __proto__ 属性指向构造函数的 prototype 对象。 5. 继承可以通过设置原型对象实现,也可以使用 ES6 的 class 语法糖。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一
|
JavaScript 前端开发 Java
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
柯里化是一种强大的函数式编程技术,它通过将函数分解为单参数形式,实现了灵活性与可复用性的统一。无论是参数复用、延迟执行,还是函数组合,柯里化都为现代编程提供了极大的便利。 从 Redux 的选择器优化到复杂的数据流处理,再到深度嵌套的函数优化,柯里化在实际开发中展现出了非凡的价值。如果你希望编写更简洁、更优雅的代码,柯里化无疑是一个值得深入学习和实践的工具。从简单的实现到复杂的应用,希望这篇博客能为你揭开柯里化的奥秘,助力你的开发之旅! 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一
|
JavaScript 前端开发 API
Vue.js响应式原理深度解析:从Vue 2到Vue 3的演进
Vue.js响应式原理深度解析:从Vue 2到Vue 3的演进
505 17
下一篇
开通oss服务