资讯专栏INFORMATION COLUMN

汇总有关JS对象的创建与继承

3403771864 / 276人阅读

  之前也有和大家讲过有关JS的对象创建和对象继承,本篇文章主要为大家做个汇总和梳理。

  JS中其实就是原型链继承和构造函数继承的“毛病”,还有就是“工厂、构造、原型”设计模式与JS继承。

       JS高级程序设计4: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")}
  }

  这样写:

  其实上面写的不仅麻烦,而且代码量也比较大;

  也不必方便修改,比如当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//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

  再到后来,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中,万物皆对象,对象都是有函数构造而来,函数本身也是对象。

  对应代码中的意思:

  所有的构造函数的隐式原型都等于Function的显示原型,函数都是由Function构造而来,Object构造函数也不例外;

  所有构造函数的显示原型的隐式原型,都等于Object的显示原型,Function也不例外;

  //1.
  Object.__proto__===Function.prototype//true
  //2.
  Function.prototype.__proto__===Object.prototype//true

  对于这个设计我只可以给一个大大的无语了。

  先这样说吧:Function就是上帝,上帝创造了万物;Object就是万物。万物由上帝创造(对象由函数构造而来),上帝本身也属于一种物质(函数本身却也是对象);

  对于本篇来说,继承,其实都是父子构造函数在继承,然后再由构造函数实例化对象,以此来实现对象的继承。

  到底是谁在继承?函数?对象?都是吧~~

  小结

  本篇由创建对象说起,讲了工厂函数,它可以做一层最基本的封装;

  再到,对工厂的拓展,演进为构造函数;

  再基于原型特点,构造+原型,得出组合继承;

  再追求极致,讲到寄生组合;

  再讲到简化书写的Es6 class;

  以及最后对对象与函数的思考。

     本篇文章到此结束了,欢迎大家关注后续更多精彩内容。



文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/130356.html

相关文章

  • 前端工程师面试必备(持续更新中)

    摘要:最近的一次更新的变量有效,并且会作用于全部的引用的处理方式和相同,变量值输出时根据之前最近的一次定义计算,每次引用最近的定义有效嵌套三种预编译器的选择器嵌套在使用上来说没有任何区别,甚至连引用父级选择器的标记也相同。 面试汇总一:2018大厂高级前端面试题汇总 高级面试:【半月刊】前端高频面试题及答案汇总 css内容 响应式布局 当前主流的三种预编译器比较 CSS预处理器用一种专门的...

    jubincn 评论0 收藏0
  • js对象创建方法汇总及对比

    摘要:中创建对象的方式有很多,尤其是基于原型的方式创建对象,是理解基于原型继承的基础。该函数中的属性指向该源性对象当通过该函数的构造函数创建一个具体对象时,在这个对象中,就会有一个属性指向原型。 js中创建对象的方式有很多,尤其是基于原型的方式创建对象,是理解基于原型继承的基础。因此在这里汇总一下,并对各种方法的利弊进行总结和对比,不至于以后对这些概念有模糊。 简单方式创建 var o = ...

    piapia 评论0 收藏0
  • 属性设置百分比时计算参考汇总

    摘要:对象脱离常规流,偏移定位是以窗口为参考绝对定位的元素,在,,,属性未设置时,会紧随在其前面的兄弟元素之后,但在位置上不影响常规流中的任何元素。例如设置百分比时,子元素继承是父元素乘以百分百之后的具体数值,所以可能会出现重叠现象。元素宽高 width,min-width,max-width等元素宽度设置百分比,以包含块的宽度为标准进行计算; height,min-height,max-hei...

    Moxmi 评论0 收藏0
  • 前端面试经典题目汇总(持续更新中)

    摘要:只要没有被覆盖的话对象原型的属性就能在所有的实例中找到,若整个原型链未找到则返回如何实现继承构造继承原型继承实例继承拷贝继承原型机制或和方法去实现较简单,建议使用构造函数与原型混合方式。 HTML相关问题 1.XHTML和HTML有什么区别 HTML是一种基本的WEB网页设计语言,XHTML是一个基于XML的标记语言最主要的不同:XHTML 元素必须被正确地嵌套。XHTML 元素必须被...

    BigNerdCoding 评论0 收藏0
  • 前端面试经典题目汇总(持续更新中)

    摘要:只要没有被覆盖的话对象原型的属性就能在所有的实例中找到,若整个原型链未找到则返回如何实现继承构造继承原型继承实例继承拷贝继承原型机制或和方法去实现较简单,建议使用构造函数与原型混合方式。 HTML相关问题 1.XHTML和HTML有什么区别 HTML是一种基本的WEB网页设计语言,XHTML是一个基于XML的标记语言最主要的不同:XHTML 元素必须被正确地嵌套。XHTML 元素必须被...

    Warren 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<