当前位置:网站首页>知其然,知其所以然,JS 对象创建与继承
知其然,知其所以然,JS 对象创建与继承
2022-08-04 10:15:00 【华为云】
对象创建
不难发现,JS对象创建和继承都离不开工厂、构造、原型这 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")}}这样写:
- 写起来麻烦,重复的代码量大;
- 不利于修改,比如当 car 对象要增删改一个属性,需要多处进行增删改;
工厂函数
肯定是要封装啦,第一个反应,可以 借助函数 来帮助我们批量创建对象~
于是乎:
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")构造函数区别于工厂函数:
- 函数名首字母通常大写;
- 创建对象的时候要用到 new 关键字(new 的过程这里不再赘述了,之前文章有);
- 函数没有 return,而是通过 this 绑定来实现寻找属性的;
到此为止,工厂函数的复用也解决了。
构造+原型
新的问题在于,我们不能通过查找原型链从 MakeCarChild 找到 MakeCar
car4.__proto__===MakeCarChild.prototype // trueMakeCarChild.prototype.__proto__ === MakeCar.prototype // falseMakeCarChild.__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 // trueMakeCarChild.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 // trueMakeCarChild.prototype.__proto__ === MakeCar.prototype // trueES6 class
再到后来,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 // trueMakeCarChild.prototype.__proto__ === MakeCar.prototype // true有兴趣的工友,可以看下 ES6 解析成 ES5 的代码:原型与原型链 - ES6 Class的底层实现原理 #22
对象与函数
最后本瓜想再谈谈关于 JS 对象和函数的关系:

即使是这样声明一个对象,let obj = {} ,它一样是由构造函数 Object 构造而来的:
let obj = {} obj.__proto__ === Object.prototype // true在 JS 中,万物皆对象,对象都是有函数构造而来,函数本身也是对象。
对应代码中的意思:
- 所有的构造函数的隐式原型都等于 Function 的显示原型,函数都是由 Function 构造而来,Object 构造函数也不例外;
- 所有构造函数的显示原型的隐式原型,都等于 Object 的显示原型,Function 也不例外;
// 1.Object.__proto__ === Function.prototype // true// 2. Function.prototype.__proto__ === Object.prototype // true这个设计真的就一个大无语,大纠结,大麻烦。。。

只能先按之前提过的歪理解记着先:Function 就是上帝,上帝创造了万物;Object 就是万物。万物由上帝创造(对象由函数构造而来),上帝本身也属于一种物质(函数本身却也是对象);
对于本篇来说,继承,其实都是父子构造函数在继承,然后再由构造函数实例化对象,以此来实现对象的继承。
到底是谁在继承?函数?对象?都是吧~~
小结
本篇由创建对象说起,讲了工厂函数,它可以做一层最基本的封装;
再到,对工厂的拓展,演进为构造函数;
再基于原型特点,构造+原型,得出组合继承;
再追求极致,讲到寄生组合;
再讲到简化书写的 Es6 class ;
以及最后对对象与函数的思考。
就先到这吧~~
OK,以上便是本篇分享。点赞关注评论,为好文助力
我是掘金安东尼 100 万人气前端技术博主 INFP 写作人格坚持 1000 日更文 关注我,安东尼陪你一起度过漫长编程岁月
边栏推荐
猜你喜欢

ps如何换背景颜色,自学ps软件photoshop2022,3种不同的方式笔记记录

HTB-Sense

低代码是开发的未来吗?浅谈低代码开发平台的发展现状及未来趋势

2022 Cloud Native Computing代表厂商 | 灵雀云第三次入选Gartner中国ICT技术成熟度曲线报告

高级转录组分析和R数据可视化火热报名中(2022.10)

iMeta | Baidu certification is completed, search "iMeta" directly to the publisher's homepage and submission link

陈春花发布声明,这场流量狂欢该到了收尾的时候

有了这篇 Kubernetes 的介绍,它的原理秒懂!

RAID介绍及RAID5配置实例

Win11怎么进行左右键对调?
随机推荐
学习在php中将特大数字转成带有千/万/亿为单位的字符串
如何直击固定资产管理的难题?
用匿名函数定义函数_c语言最先执行的函数是
Win11不识别蓝牙适配器的解决方法
Producer and Consumer Problems in Concurrent Programming
Introduction to the core methods of the CompletableFuture interface
matlab练习程序(多线段交点)
双重for循环案例以及while循环和do while循环案例
语音社交app源码——具备哪些开发优势?
KubeDNS 和 CoreDNS
冰蝎逆向初探
物体颜色的来源
Win11文件资源管理器找不到选项卡怎么办?
leetcode经典例题——56.合并区间
LVS+Keepalived群集部署
【COS 加码福利】COS 用户实践有奖征文,等你来投稿!
There are 12 balls, including 11 weight, only one, don't know is light or heavy. Three times in balance scales, find out the ball.
Mysql应用日志时间与系统时间相差八小时
Ansible deployment scripts - pro available without pit
浅聊偏函数