1. 首页

JS中的经典继承实现方法与原型的深入探讨

原文链接

前言 🎤

JS在很久很久之前,其实是并没有类这种概念的,也没有明确的继承这个概念,所以聪明的人们就创造出了很多种办法,来模拟出和继承类似的效果,本文就来讲一下继承的原理和常见的继承方法(不包含Class语法糖)

简单的定义一下主角

 js
function Tank(){
    this.speed = 10
}

Task.prototype.fire = function(){
    console.log('fire')
}

function Tiger(){
    this.armar = 10
}

Tiger.prototype.SpeedUp = function(){
    console.log('up!')
}


寄生组合式继承

 js
function Tank(){
    this.speed = 10
}

Tank.prototype.fire = function(){
    console.log('fire')
}

function Tiger(){
    Tank.call(this) // 调用Tank构造函数中的内容
    this.armar = 10
}

(function(){
    let empty = function(){}
    empty.prototype = Tank.prototype
    //复制原型 并且传递给一个新的临时函数对象
    Tiger.prototype = new empty()
})()

//子元素的prototype内容需要在上面的IIFE之后调用
Tiger.prototype.speedUp = function(){
    console.log('up!')
}

let tank = new Tiger()
console.log(tank)
tank.fire()
tank.speedUp()
/*
Tank { speed: 10, armar: 10 }
fire
up!
*/


这个方法非常好,没有过多的空间浪费,没有多余的调用,一步到位堪称完美。
但是我们可以稍微升级一下

升级后的方法

 js
function Tank(){
    this.speed = 10
}

Tank.prototype.fire = function(){
    console.log('fire')
}

// function Tiger(){
//     this.armar = 10
// }

function Tiger(){
    Tank.call(this)
    this.armar = 10
}

// modify
Tiger.prototype = Object.create(Tank.prototype)
Tiger.prototype.constructor = Tiger

Tiger.prototype.speedUp = function(){
    console.log('up!')
}

let tank = new Tiger()
console.log(tank)
tank.fire()
tank.speedUp()


改动非常少,但是更加简洁了,而且更加的清晰易懂。其实原理是一样的,通过原型函数创建新的对象,然后将其作为基础进行修改。同时修改构造函数为子类。

Object.create

什么是Object.create?它是一个处于ES5规范中的,用于通过原型创建对象的函数。也就是说,新对象的__proto__将会指向传入的参数原型。
但是,为了更加升入的理解,所以我们需要实现一个Object.create来加深理解程度。

 js
Object.breate = function(p){
    if(typeof p != 'object' && typeof p != 'function'){
        throw new Error("....!%$") //加密语言
    }
    function C(){}
    C.prototype = p
    return new C()
}


哈哈,是不是似成相识?效果其实是完全一样的。只不过要注意一点⚠️Object.create的参数可以传递两个,而并非只能一个,你可以去MDN上详细阅读它的使用方法。这里不进行展开。
有人可能认为xx.prototype = new YY()是错误的方法,而只有Object.create才是唯一的正确答案。其实这种想法非常过激,我们对错误的定义有时候太过于狭隘。如果它能得到正确的答案,它的运行状态正常而且稳定,它的运行结果并无任何偏差,那么我们怎么能说他是一种错误的方式?

Prototype

其实在阅读上面的代码的时候,你一定会产生很多疑问🤔️,什么是prototype?,为什么可以传入一个对象作为prototype?为什么函数可以直接new?等等之类的问题。接下来,我将讲述我的见解。

在我研究JS的时候,我就一直在思考这些问题,随着我的不断深入,我感觉我对prototype的形象越发的了解,虽然我现在并不能100%的肯定,我理解的prototype就一定是正确的,但是我觉得八九不离十。

Prototype是一个实例对象

  • prototype可以被对象随意覆盖
  • 可以随时修改prototype的属性
  • prototype中的修改会影响到所有继承当前prototype的对象。
  • prototype可以当成一个对象来进行操作

但是!!请注意,你在对原型赋值的时候,并不是在你想的prototype进行赋值。

这里其实有个非常非常神奇的特性。

Prototype ?= Prototype

请注意⚠️,构造对象,也就是Function,在搞乱我们对原型链对理解这件事上面,占有很大一笔责任。
一个普通对构造对象,它同时有着两个属性,__proto__表示继承的上一个原型,prototype表示当前构造函数中的原型。 而当他被构造后,会产生新的情况。最重要的是构造函数中的prototype会转移到实例对象的__proto__之中
因此,如果你直接赋值一个构造好的实例到子类的prototype时,一切都恰恰刚好。
而如果你在父类对象的构造函数中赋值了实例属性,那么这些属性也会成为子类prototype的一部分。

 js
//伪代码
//构造对象结构
let A = {
    prototype:{
        __proto__:{x:1},
        y:2,
        constructor:function(){}
    }
}
//实例对象结构
let newA = new A()
newA == {
    __proto__:{
        __proto__:{x:1},
        y:2,
        constructor:function(){}
    }
}
//继承了A的B
let B = {
    prototype:{
        __proto__:{
            __proto__:{x:1},
            y:2,
            constructor:function(){}
        }
    },
}
//实例化B
let newB = new B()
newB == {
    __proto__:{
        __proto__:{
            __proto__:{x:1},
            y:2,
            constructor:function(){}
        }
    }
}


prototype的谜题解开了。

Prototype结论

经过多次研究发现,实际上的prototype机制异常的简单。重点就两个。

  • 实例对象中不存在prototype
  • 在构造对象中,如果这个对象被构造,那么构造对象的prototype将会变成实例对象的__proto__
 js
function A(){}
let a = new A()
console.log(a.__proto__ === A.prototype)


还记得我们一开始是怎么实现继承的吗?

 js
    let empty = function(){}
    empty.prototype = Tank.prototype
    //复制原型 并且传递给一个新的临时函数对象
    Tiger.prototype = new empty()


结合一张图片看看 {% img /images/JS中的经典继承实现方法-2020-11-12-18-17-11.png %}
现在明白为什么需要创建一个新函数了吗?

不过,其实这种继承有着少量的缺点,下面看一看Babel是如何实现继承的。

Babel

 js
function _inherits(subClass, superClass) {
    // 喜闻乐见的Object.create
    subClass.prototype = Object.create(superClass.prototype, {
        //第二个参数的说明详细见MDN,这里的作用类似于 subClass.prototype.constructor = subClass
        constructor: {
            value: subClass,
            enumerable: false,
            writable: true,
            configurable: true;
        }
    })
    if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
    // 设置subClass中__proto__属性,将其设置为superClass的构造函数
    // 这个举动的作用是将赋值于构造函数上的方法进行继承,在es6中是class{static xx(){}}中的static
}

function A(){}
function B(){}
A.StaticMethod = ()=>{} // 可以认为是静态方法
A.prototype.hello = ()=>{} // 可以认为是对象实例化后的方法


一个小例子

 js
function A(){}
function B(){}
B.C = function(){
    console.log('static')
}
console.log(A instanceof B)
_inherits(A,B)
let a = new A()
console.log(a instanceof B)
A.C()
/*
false
true
static
*/


这里其实也反应了JS原型链的机制,当你在一个对象上面调用方法时,无论是实例对象还是构造对象,JS都会从其__proto__中查找方法,如果找不到就在__proto__.__proto__中继续查找,直到为null

结语 👨‍🏫

prototype真的是js中非常重要的一个部分,我觉得每个前端开发者都需要好好理解掌握prototype中的每一个细节。

.markdown-body pre,.markdown-body pre>code.hljs{color:#abb2bf;background:#282c34}.hljs-comment,.hljs-quote{color:#5c6370;font-style:italic}.hljs-doctag,.hljs-formula,.hljs-keyword{color:#c678dd}.hljs-deletion,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-subst{color:#e06c75}.hljs-literal{color:#56b6c2}.hljs-addition,.hljs-attribute,.hljs-meta-string,.hljs-regexp,.hljs-string{color:#98c379}.hljs-built_in,.hljs-class .hljs-title{color:#e6c07b}.hljs-attr,.hljs-number,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-pseudo,.hljs-template-variable,.hljs-type,.hljs-variable{color:#d19a66}.hljs-bullet,.hljs-link,.hljs-meta,.hljs-selector-id,.hljs-symbol,.hljs-title{color:#61aeee}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-link{text-decoration:underline}

作者:陈小盒
链接:https://juejin.im/post/6894185806936670216

看完两件小事

如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:

  1. 关注我们的 GitHub 博客,让我们成为长期关系
  2. 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
  3. 关注公众号 「画漫画的程序员」,公众号后台回复「资源」 免费领取我精心整理的前端进阶资源教程

JS中文网是中国领先的新一代开发者社区和专业的技术媒体,一个帮助开发者成长的社区,目前已经覆盖和服务了超过 300 万开发者,你每天都可以在这里找到技术世界的头条内容。欢迎热爱技术的你一起加入交流与学习,JS中文网的使命是帮助开发者用代码改变世界

本文著作权归作者所有,如若转载,请注明出处

转载请注明:文章转载自「 Js中文网 · 前端进阶资源教程 」https://www.javascriptc.com

标题:JS中的经典继承实现方法与原型的深入探讨

链接:https://www.javascriptc.com/4621.html

« JavaScript的原型对象的彻底理解
HTTP系列之HTTP缓存»
Flutter 中文教程资源

相关推荐

QR code