如何深入理解JS对象创建与继承,实际应用是怎样
Admin 2022-08-12 群英技术资讯 270 次浏览
在 6 月更文中零零散散讲了 JS 的对象创建和对象继承,有工友对此还是表示疑惑,要注意:这是两个不同但又相关的东西,千万别搞混了!
这些文章是:
蓦然回首,“工厂、构造、原型”设计模式,正在灯火阑珊处
JS精粹,原型链继承和构造函数继承的 “毛病”
“工厂、构造、原型” 设计模式与 JS 继承
JS 高级程序设计 4:class 继承的重点
JS class 并不只是简单的语法糖!
本篇作为汇总篇,来一探究竟!!冲冲冲
不难发现,每一篇都离不开工厂、构造、原型这 3 种设计模式中的至少其一!
让人不禁想问:JS 为什么非要用到这种 3 种设计模式了呢??
正本溯源,先从对象创建讲起:
我们本来习惯这样声明对象(不用任何设计模式)
let car= { price:100, color:"white", run:()=>{console.log("run fast")} }
当有两个或多个这样的对象需要声明时,是不可能一直复制写下去的:
let car1 = { price:100, color:"white", run:()=>{console.log("run fast")} } let car2 = { price:200, color:"balck", run:()=>{console.log("run slow")} } let car3 = { price:300, color:"red", run:()=>{console.log("broken")} }
这样写:
肯定是要封装啦,第一个反应,可以 借助函数 来帮助我们批量创建对象~
于是乎:
function makeCar(price,color,performance){ let obj = {} obj.price = price obj.color= color obj.run = ()=>{console.log(performance)} return obj } let car1= makeCar("100","white","run fast") let car2= makeCar("200","black","run slow") let car3= makeCar("300","red","broken")
这就是工厂设计模式在 JS 创建对象时应用的由来~
到这里,对于【对象创建】来说,应该够用了吧?是,在不考虑扩展的情况下,基本够用了。
但这个时候来个新需求,需要创建 car4、car5、car6 对象,它们要在原有基础上再新增一个 brand
属性,会怎么写?
第一反应,直接修改 makeCar
function makeCar(price,color,performance,brand){ let obj = {} obj.price = price obj.color= color obj.run = ()=>{console.log(performance)} obj.brand = brand return obj } let car4= makeCar("400","white","run fast","benz") let car5= makeCar("500","black","run slow","audi") let car6= makeCar("600","red","broken","tsl")
这样写,不行,会影响原有的 car1、car2、car3 对象;
那再重新写一个 makeCarChild
工厂函数行不行?
function makeCarChild (price,color,performance,brand){ let obj = {} obj.price = price obj.color= color obj.run = ()=>{console.log(performance)} obj.brand = brand return obj } let car4= makeCarChild("400","white","run fast","benz") let car5= makeCarChild("500","black","run slow","audi") let car6= makeCarChild("600","red","broken","tsl")
行是行,就是太麻烦,全量复制之前的属性,建立 N 个相像的工厂,显得太蠢了。。。
于是乎,在工厂设计模式上,发展出了:构造函数设计模式,来解决以上复用(也就是继承)的问题。
function MakeCar(price,color,performance){ this.price = price this.color= color this.run = ()=>{console.log(performance)} } function MakeCarChild(brand,...args){ MakeCar.call(this,...args) this.brand = brand } let car4= new MakeCarChild("benz","400","white","run fast") let car5= new MakeCarChild("audi","500","black","run slow") let car6= new MakeCarChild("tsl","600","red","broken")
构造函数区别于工厂函数:
到此为止,工厂函数的复用也解决了。
新的问题在于,我们不能通过查找原型链从 MakeCarChild 找到 MakeCar
car4.__proto__===MakeCarChild.prototype // true MakeCarChild.prototype.__proto__ === MakeCar.prototype // false MakeCarChild.__proto__ === MakeCar.prototype // false
无论在原型链上怎么找,都无法从 MakeCarChild
找到 MakeCar
这就意味着:子类不能继承父类原型上的属性
这里提个思考问题:为什么“要从原型链查找到”很重要?为什么“子类要继承父类原型上的属性”?就靠 this 绑定来找不行吗?
于是乎,构造函数设计模式 + 原型设计模式 的 【组合继承】应运而生
function MakeCar(price,color,performance){ this.price = price this.color= color this.run = ()=>{console.log(performance)} } function MakeCarChild(brand,...args){ MakeCar.call(this,...args) this.brand = brand } MakeCarChild.prototype = new MakeCar() // 原型继承父类的构造器 MakeCarChild.prototype.constructor = MakeCarChild // 重置 constructor let car4= new MakeCarChild("benz","400","white","run fast")
现在再找原型,就找的到啦:
car4.__proto__ === MakeCarChild.prototype // true MakeCarChild.prototype.__proto__ === MakeCar.prototype // true
其实,能到这里,就已经很很优秀了,该有的都有了,写法也不算是很复杂。
但,总有人在追求极致。
上述的组合继承,父类构造函数被调用了两次,一次是 call 的过程,一次是原型继承 new 的过程,如果每次实例化,都重复调用,肯定是不可取的,怎样避免?
工厂 + 构造 + 原型 = 寄生组合继承 应运而生
核心是,通过工厂函数新建一个中间商 F( ),复制了一份父类的原型对象,再赋给子类的原型;
function object(o) { // 工厂函数 function F() {} F.prototype = o; return new F(); // new 一个空的函数,所占内存很小 } function inherit(child, parent) { // 原型继承 var prototype = object(parent.prototype) prototype.constructor = child child.prototype = prototype } function MakeCar(price,color,performance){ this.price = price this.color= color this.run = ()=>{console.log(performance)} } function MakeCarChild(brand,...args){ // 构造函数 MakeCar.call(this,...args) this.brand = brand } inherit(MakeCarChild,MakeCar) let car4= new MakeCarChild("benz","400","white","run fast")
car4.__proto__ === MakeCarChild.prototype // true MakeCarChild.prototype.__proto__ === MakeCar.prototype // true
再到后来,ES6 的 class 作为寄生组合继承的语法糖:
class MakeCar { constructor(price,color,performance){ this.price = price this.color= color this.performance=performance } run(){ console.log(console.log(this.performance)) } } class MakeCarChild extends MakeCar{ constructor(brand,...args){ super(brand,...args); this.brand= brand; } } let car4= new MakeCarChild("benz","400","white","run fast")
car4.__proto__ === MakeCarChild.prototype // true MakeCarChild.prototype.__proto__ === MakeCar.prototype // true
有兴趣的工友,可以看下 ES6 解析成 ES5 的代码:原型与原型链 - ES6 Class的底层实现原理 #22
最后本瓜想再谈谈关于 JS 对象和函数的关系:
即使是这样声明一个对象,let obj = {}
,它一样是由构造函数 Object
构造而来的:
let obj = {} obj.__proto__ === Object.prototype // true
在 JS 中,万物皆对象,对象都是有函数构造而来,函数本身也是对象。
对应代码中的意思:
// 1. Object.__proto__ === Function.prototype // true // 2. Function.prototype.__proto__ === Object.prototype // true
这个设计真的就一个大无语,大纠结,大麻烦。。。
只能先按之前提过的歪理解记着先:Function 就是上帝,上帝创造了万物;Object 就是万物。万物由上帝创造(对象由函数构造而来),上帝本身也属于一种物质(函数本身却也是对象);
对于本篇来说,继承,其实都是父子构造函数在继承,然后再由构造函数实例化对象,以此来实现对象的继承。
到底是谁在继承?函数?对象?都是吧~~
本篇由创建对象说起,讲了工厂函数,它可以做一层最基本的封装;
再到,对工厂的拓展,演进为构造函数;
再基于原型特点,构造+原型,得出组合继承;
再追求极致,讲到寄生组合;
再讲到简化书写的 Es6 class ;
以及最后对对象与函数的思考。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。
猜你喜欢
这篇文章主要介绍了多种类型jQuery网页验证码插件代码实例,有正好需要的同学可以测试研究下具体使用效果
node后台框架有:1、Koa,一个开源的Node web框架,用Generator来实现中间件的流程控制,用try/catch来增强异常处理;2、Nest,一个用于构建高效、可扩展的Node服务器端应用程序的框架;3、Socket,是用来在客户端和服务器端之间创建实时双向通信的框架;4、Sails,是一个非常稳固的Node框架,提供建立任何规模的Web应用所需要的所有功能。
最近在造轮子的时候遇到了这么一个问题,那就是数组在调用内部方法的时候怎么才可以监听到数组发生了变化,这篇文章主要给大家介绍了关于JavaScript如何监测数组变化的相关资料,需要的朋友可以参考下
这篇文章主要介绍了JavaScript 箭头函数的特点、与普通函数的区别,很多情况下,箭头函数和函数表达式创建的函数并无区别,只有写法上的不同,本文第二块内容将介绍箭头函数和普通函数功能上的区别,感兴趣的朋友跟随小编一起看看吧
本篇文章给大家带来了关于javascript的相关知识,主要介绍了关于JavaScript的运行原理,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下。
成为群英会员,开启智能安全云计算之旅
立即注册Copyright © QY Network Company Ltd. All Rights Reserved. 2003-2020 群英 版权所有
增值电信经营许可证 : B1.B2-20140078 粤ICP备09006778号 域名注册商资质 粤 D3.1-20240008