《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 前端开发 Java
深入JS面向对象(原型-继承)(一)
深入JS面向对象(原型-继承)
31 0
|
1月前
|
JavaScript 前端开发
js开发:请解释原型继承和类继承的区别。
JavaScript中的原型继承和类继承用于共享对象属性和方法。原型继承利用原型链查找属性,节省内存但不支持私有成员。类继承通过ES6的class和extends实现,支持私有成员但占用更多内存。两者各有优势,适用于不同场景。
19 0
|
1月前
|
自然语言处理 JavaScript 前端开发
探索JavaScript中的闭包:理解其原理与实际应用
探索JavaScript中的闭包:理解其原理与实际应用
19 0
|
1月前
|
JavaScript
JS数组增删方法的原理,使用原型定义
JS数组增删方法的原理,使用原型定义
|
3天前
|
JavaScript 前端开发 测试技术
学习JavaScript
【4月更文挑战第23天】学习JavaScript
11 1
|
4天前
|
前端开发 JavaScript 编译器
深入解析JavaScript中的异步编程:Promises与async/await的使用与原理
【4月更文挑战第22天】本文深入解析JavaScript异步编程,重点讨论Promises和async/await。Promises用于管理异步操作,有pending、fulfilled和rejected三种状态。通过.then()和.catch()处理结果,但可能导致回调地狱。async/await是ES2017的语法糖,使异步编程更直观,类似同步代码,通过事件循环和微任务队列实现。两者各有优势,适用于不同场景,能有效提升代码可读性和维护性。
|
7天前
|
JavaScript
什么是js的原型链
什么是js的原型链
|
11天前
|
JavaScript 前端开发 应用服务中间件
node.js之第一天学习
node.js之第一天学习
|
1月前
|
运维 JavaScript 前端开发
发现了一款宝藏学习项目,包含了Web全栈的知识体系,JS、Vue、React知识就靠它了!
发现了一款宝藏学习项目,包含了Web全栈的知识体系,JS、Vue、React知识就靠它了!
|
1月前
|
JavaScript
Vue.js学习详细课程系列--共32节(4 / 6)
Vue.js学习详细课程系列--共32节(4 / 6)
35 0