《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


相关文章
|
17天前
|
JavaScript 前端开发
如何在 JavaScript 中使用 __proto__ 实现对象的继承?
使用`__proto__`实现对象继承时需要注意原型链的完整性和属性方法的正确继承,避免出现意外的行为和错误。同时,在现代JavaScript中,也可以使用`class`和`extends`关键字来实现更简洁和直观的继承语法,但理解基于`__proto__`的继承方式对于深入理解JavaScript的面向对象编程和原型链机制仍然具有重要意义。
|
2月前
|
自然语言处理 JavaScript 前端开发
深入理解JavaScript中的闭包:原理与实战
【10月更文挑战第12天】深入理解JavaScript中的闭包:原理与实战
|
2月前
|
JavaScript 前端开发 开发者
理解JavaScript中的原型链:基础与实践
【10月更文挑战第8天】理解JavaScript中的原型链:基础与实践
|
9天前
|
存储 JavaScript 前端开发
JavaScript学习第一章
本文档介绍了JavaScript的基础知识,包括其在网页中的作用、如何通过JavaScript动态设置HTML元素的CSS属性,以及JavaScript中的变量类型(`var`、`let`、`const`)和数据类型(基本数据类型与引用数据类型)。通过实例代码详细解释了JavaScript的核心概念,适合初学者入门学习。
|
18天前
|
JavaScript 前端开发
JavaScript 原型链的实现原理是什么?
JavaScript 原型链的实现原理是通过构造函数的`prototype`属性、对象的`__proto__`属性以及属性查找机制等相互配合,构建了一个从对象到`Object.prototype`的链式结构,实现了对象之间的继承、属性共享和动态扩展等功能,为 JavaScript 的面向对象编程提供了强大的支持。
|
26天前
|
JavaScript 前端开发
Javascript如何实现继承?
【10月更文挑战第24天】JavaScript 中实现继承的方式有很多种,每种方式都有其优缺点和适用场景。在实际开发中,我们需要根据具体的需求和情况选择合适的继承方式,以实现代码的复用和扩展。
|
18天前
|
JavaScript 前端开发
原型链在 JavaScript 中的作用是什么?
原型链是 JavaScript 中实现面向对象编程的重要机制之一,它为代码的组织、复用、扩展和多态性提供了强大的支持,使得 JavaScript 能够以简洁而灵活的方式构建复杂的应用程序。深入理解和熟练运用原型链,对于提升 JavaScript 编程能力和开发高质量的应用具有重要意义。
|
20天前
|
JavaScript 前端开发
如何使用原型链继承实现 JavaScript 继承?
【10月更文挑战第22天】使用原型链继承可以实现JavaScript中的继承关系,但需要注意其共享性、查找效率以及参数传递等问题,根据具体的应用场景合理地选择和使用继承方式,以满足代码的复用性和可维护性要求。
|
20天前
|
JavaScript 前端开发 开发者
js实现继承怎么实现
【10月更文挑战第26天】每种方式都有其优缺点和适用场景,开发者可以根据具体的需求和项目情况选择合适的继承方式来实现代码的复用和扩展。
31 1
|
2月前
|
JavaScript 前端开发 开发者
探索JavaScript原型链:深入理解与实战应用
【10月更文挑战第21天】探索JavaScript原型链:深入理解与实战应用
31 1
下一篇
无影云桌面