关于继承:Java进阶-从整体上观察面向对象

一、面向对象面向对象是Java编程中最外围的思维,基本特征:继承、封装、多态。 1、特色之封装将构造、数据、操作封装在对象实体中,应用时能够不关注对象内部结构,只能拜访凋谢权限的性能入口,从而升高程序耦合水平,提供安全性和可继续维护性。 public class Concept01 { public static void main(String[] args) { Student student = new Student("张三","高三",29f); student.conclusion(); }}class Student { private String name ; private String grade ; private Float score ; public Student(String name, String grade, Float score) { this.name = name; this.grade = grade; this.score = score; } public void conclusion (){ System.out.println("姓名:"+this.getName()); System.out.println("年级:"+this.getGrade()); System.out.println("分数:"+this.getGrade()); if (this.getScore() >= 100.0f){ System.out.println("评语:本学期优等生"); } else { System.out.println("评语:本学期潜力股"); } }}案例形容Student的学期总结,通过构造方法构建具体的学生对象,并且只通过conclusion办法获取学生学期评估。 ...

August 10, 2021 · 2 min · jiezi

关于继承:简单通俗的方式认识js继承

前戏:js 继承是在前端面试及js库开发中常常遇到的问题。在面向对象开发时继承常识也是十分重要,自己浏览了一些文章总结了js继承的要点,心愿能对大家有所帮忙。 什么是继承?艰深的说:某一个工程须要蓝图实现来工程,蓝图就是类。但在一个新工程的蓝图设计时须要复制一些老蓝图上的工程教训来晋升效率,这个过程就相当于继承。 js继承有哪些? 一起来看看1.类式继承//父类function SuperClass() { this.superValue = true;}SuperClass.prototype.getSuperValue = function() { return this.superValue;}//子类function SubClass() { this.subValue = false;}SubClass.prototype = new SuperClass();SubClass.prototype.getSubValue = function() { return this.subValue;}var instance = new SubClass();console.log(instance instanceof SuperClass)//trueconsole.log(instance instanceof SubClass)//trueconsole.log(SubClass instanceof SuperClass)//false这种形式是最原始的形式,应用父类的一个实例重写到子类原型对象上,从而实现父类成员的继承,有点继承的样子,然而总感觉怪怪的,你确定把一个实例赋值到原型上真的没有问题吗? 毛病: 援用类型问题,如果父类有个数组成员,只实例化一次赋值到原型上,那就是每次new进去的子实例都是在应用同一个数组,结果可想而知。不能传参重写了prototype,如果要额定给子类原型加货色就要加在SubClass.prototype = new SuperClass();重写语句之后,很不不便instance.constructor 结构器指向父类。子类的实例结构器是父类?那不胡扯吗?2.构造函数继承function SuperClass(id) { this.books = ['a','b']; this.id = id;}SuperClass.prototype.showBooks = function() { console.log(this.books);}function SubClass(id) { //继承父类 SuperClass.call(this,id);}var instance1 = new SubClass(10);var instance2 = new SubClass(11);instance1.books.push('c');console.log(instance1)console.log(instance2)instance1.showBooks();比下面一个略微好了一点,也能传参了,每次进去的数组也是惟一的了。原理是在子类结构的时候应用call的个性借用一下父类的构造函数,把父类的成员都设置在子类中来实现继承。额。。那原型呢?毛病: 继承不了父类原型上的成员每次实例化子类就会call一下父类,多执行了一遍3.组合式继承function SuperClass(name) { this.name = name; this.books = ['A','B'];}SuperClass.prototype.getBooks = function() { console.log(this.books);}function SubClass(name,time) { SuperClass.call(this,name); this.time = time;}SubClass.prototype = new SuperClass();SubClass.prototype.getTime = function() { console.log(this.time);}还能够,能用,原理也就是联合了以上两种(构造函数、类式继承)的继承形式,并修复了重大的毛病。以前应该用这种办法的人也比拟多吧毛病: ...

June 14, 2021 · 2 min · jiezi

关于继承:JS-原生方法原理探究四实现继承的几种方式以及优缺点对比

这是JS 原生办法原理探索系列的第四篇文章。本文会介绍如何实现 JS 中常见的几种继承形式,同时简要介绍它们的优缺点。 实现继承的办法 图源:《JavaScript外围原理精讲》,侵删 实现继承的办法共有 7 种,这 7 种办法并不是相互独立的,它们之间更像是一种互补或者加强的关系。 原型链继承和借用构造函数继承别离解决了继承父类办法和继承父类属性的问题,这两个办法联合就失去了组合继承;原型式继承的外围相似于浅拷贝一个对象,寄生式继承则在这个过程的根底上为对象增加办法,进行加强寄生组合式继承联合了寄生式继承和组合式继承,是绝对比拟完满的计划。Class extends 继承是 ES6 的,实质上是寄生组合式继承的一种使用上面的示例中,SuperType 示意父类,SubType 示意继承父类的子类。 1)原型链继承function SuperType(){ this.names = []}SuperType.prototype.getNames = function(){}function SubType(){ this.ages = []}SubType.prototype = new SuperTye()const obj = new SubType()原型链继承的外围就一句话:用父类实例作为子类原型,这使得子类实例最终能够拜访父类上的属性和其原型上的办法。而它的毛病也很显著: 第一:因为父类构造函数只调用了一次,导致子类的原型都对立指向了这次调用所创立的父类实例,所以子类实例在拜访一些本身没有的援用类型的属性时,实际上拜访的都是那同一个父类实例上的属性。但通常,实例和实例之间应该都有本人的属性正本,不应该共享属性 第二:同样是因为只调用了一次父类构造函数,所以子类无奈向父类传参 2)借用构造函数继承function SupterTye(names){ this.names = names this.getNames = function(){}}function SubType(){ SuperType.call(this,[]) this.ages = []}const obj = new SubType()借用构造函数继承也称为经典继承,这里所谓的借用指的是借用父类构造函数,它的外围就是齐全不应用原型,而是在子类构造函数中通过 call 调用父类构造函数,从而增强子类实例 —— 相当于把父类实例上的属性都搬到子类实例这里来。 这种继承办法的长处就在于,它解决了原型链继承的毛病,咱们当初能够往父类传参了,而且每次 new 子类的时候都会从新调用一次父类,这使得子类的所有实例都有本人的属性正本。 属性是没问题了,办法的继承又有了问题。因为父类构造函数是反复调用的,所以每个实例都有本人的办法正本,但问题是,办法并不需要正本,所有实例齐全应该共享同一个办法,所以这里为每个实例反复创立同一个办法,就存在肯定的性能问题。此外,对于父类原型上的办法,子类是无奈继承的,因为这种继承形式并没有应用到原型。 3)组合继承看起来,原型链继承善于办法继承,而借用构造函数继承善于属性继承,那么能不能取二者之长呢?实际上,联合两者的长处,就是所谓的组合继承了。 function SuperType(names){ this.names = names}SuperType.prototype.getNames = function(){}function SubType(){ SuperType.call(this,[]) this.ages = []}SubType.prototype = new SuperType()const obj = new SubType()组合继承应用原型链继承的形式去继承办法,应用构造函数继承的形式去继承属性。 ...

June 8, 2021 · 2 min · jiezi

关于面向对象的深入理解

关于面向对象的深入理解引言第一次接触面向对象是在大一下学期的Java程序语言设计这门课上,听了老师讲了各种名词:类、对象、接口、继承、多态、封装……。当时也是一知半解,只是知道有这些东西,用的时候也是依葫芦画瓢,并不能理解其真正的设计内涵。随着项目经验的增加,也逐步体会出些许程序设计的精妙所在,再加之读各类书籍有所心得,故准备写这一些列文章,分享交流。 如果把程序员比作剑客的话,那么有两件东西最为重要:宝剑与剑术。宝剑可以理解为各种实打实的硬技能,比如:Java、MySQL、SpringBoot……,这些东西是死的,知识点很明确,及时与别人有所差距,下点功夫总能赶得上;而剑术则是程序设计之道,比如:需求分析、架构设计,这些东西相对来回活泛了许多,因为在这个过程中你所面对的不再是热腾腾的机器,而是各种各样的客户、复杂的业务逻辑和想法各异的同事们。所以,码农与程序员的差别就在于程序设计方面。设计本身就是一门艺术,那些统领千万代码大军的CTO们,是运筹帷幄的将军也是才华横溢的艺术家啊! 从面向机器到面向对象最开始的计算机程序编码就是0101,完全要以机器的二进制思维去思考问题;后来有了汇编只是编码形式上有了变化,但是本质并没有改变,程序员们的关注点还是在于机器本身,程序员要对CPU、寄存器等硬件了如指掌;当C语言、COBOL等面向过程的语言横空出世后,这是一次本质上的革命,把程序员从二进制的世界中解救了出来,终于可以让他们“像人一样”去思考问题啦。 此时程序员可以专注于问题本身,理清解决问题的具体步骤,然后将之用程序语言讲给计算机就可以啦。对于单一、具体的问题无论其复杂与否,采用面向过程的语言都可以完全hold住。这就如同流水线作业一般,环环相扣,问题复杂了无非就是工序多一些而已,整体来看难度不大。但是随着计算机硬件的发展,人们希望用计算机处理的事务也越来越多,业务逻辑也越来越复杂,此时再用面向过程的语言去实现如此纷乱的业务逻辑就有些难以招架了。就如同好多条生产流水线掺杂在了一起,A线依赖B线上的一道工序,B线又依赖于C线的一道工序,而C线还和A线有着关联;这样杂乱的流水线普通主管看了肯定会头大。此时就需要面向对象的程序设计语言来大杀四方了。 面向对象这种设计思想更加适合对于现实世界的模拟,更加适合处理一个系统性问题。它不再是以具体的点为中心,而是着眼于整个面。用类抽象出一群事物的共性,用接口定义好不同事物之间的交流规则,用继承的方式让同一类的事物也可以千姿百态,用多态的方式让千姿百态的事物也可以整齐划一。每个程序员都是代码世界的上帝,以面向对象的思想去设计整个世界的架构,以面向过程的思想去研究世界的每一株土木,方可达到一花一世界,一叶一菩提的境界。 面向对象三剑客类物以类聚,人以群分。——《战国策·齐策三》类是整个面向对象设计理论的基石,只有把类搞清楚搞明白了才能让整个理论大厦有坚实的地基。 首先抛开程序设计,单从现实世界的角度来看:枪保卫祖国的人叫军人,在工地上干活的人叫工人,在高级写字楼里面办公的人叫白领。对于每一类人,他们每个个体都不相同,但是它们都有相似的特征,所以我们把他们归为的一类人。面向对象最根本的思想就是对于现实世界的模拟,用程序语言来表述业务逻辑。所以程序中的类可以这样理解:类是一组相似事物的统称,是对他们共性的一种归纳。 类具有属性和方法,从军人的角度来看他们所属于的部队、所驻守的地区……,这是军人的属性;他们可射击、可擒拿……这是他们的方法。 实际上世间万物皆可归于一类,因为只要有相似点,就有依据把他们归于一类。那应该如何来设计类呢?请记住这样一句话:横看成岭侧成峰,远近高低各不同。角度决定看法,一定要结合具体需求,来考虑类的设计。 类有属性和方法,那么如何划分属性和方法呢?这里应该遵循极简原则。即属性原子化、方法单一化。属性细化到不能再分割,方法只能做一件事。这样能够最大化地解耦,满足系统的可拓展性与易修改性。 对象对于类的认知清晰以后,对象就比较容易理解了。对象就是类的一个具体实例。以军人为例,黄继光,邱少云就是军人这一类人当中一个具体的实例。 其实关于类和对象还可以深入探究一下,为什么有了类还会有对象?类和对象各自的作用是什么?这个时候我们还是要回归于现实世界。中国的人很多,那么党和政府如何来组织管理我们呢?首先不同的人有不同的职业,比如医生、教师、军人、工人等等。政府依据不同类别的职业制定出了不同的政策,比如军人买票优先、教师游览一些景点免费。其实依据不同职业有不同治理策略是一种归类更是一种抽象。 抽象的本质就是隔离干扰、总结共性、降低事物的复杂度,站在更高的角度去看待问题,从而更好地把控全局。所以类对于程序员来说是一种抽象的工具,让我们能够更好地全局把控整个项目,组织管理整个项目。党和国家领导人提出了全面建设小康社会,提出社会主义核心价值观等理论,有些普通民众看起来是空话套话,原因他们的视野只局限于自己的一亩三分地,看到的是具体的事务,而习大大则是心系整个国家,他说的话是立足于整个中国,对于亿万华夏儿女都有用的话。所以习大大是对中国国情高度总结归纳抽象后才能提出这样的理论! 对于程序员来说,类是抽象的,对象是具体的,拥有了类就拥有了上帝视角,来俯瞰整个系统;拥有了对象就拥有的亿万子民,来进行劳动,创造价值。 接口“接口”这个词是被滥用、乱用最多的术语了。抛开其他的接口不谈,这里主要是说一下面向对象中的接口。如果只看教科书上的定义,你很容易明白接口具体是什么: Java接口是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能)。—— 百度百科但是光看这些冷冰冰的定义,却很难让人明白,尤其是对新手来说,为什么要用到接口?接口本质作用是什么?为了了解这个,还是得再次回归到现实世界中。想想有身边哪些接口:Micro usb、Type-C、HDMI……,为什么要用到这些接口?因为手机要连接电源,显示器要连接电脑……,从中我们进行一次抽象总结:接口是两种不同事物交互时的一个关卡,更是一组标准,定义了交互双方所应该遵循的规则。从程序的角度来看:这种规则就是一组相关的功能点的集合。举个栗子:目前有一个鼠标类,当某天我们这个鼠标类的对象需要与主机类的对象进行交互时,此时就应该去实现一个叫USB的接口,方可进行交互。 从设计的角度来看:接口着眼于某种具体的功能,是一种规范与约束;类着眼于某一类事物的共性,是一种总结与抽象。 三大核心特征封装这个概念从字面上很容易理解,就是把一堆东西封闭包装起来。现在我们来深入探究一下其设计内涵。在我看来封装的好处主要体现在两点:对外降低复杂度,对内保护数据。其实对于个人开发者来说,一个人写整个项目,第二点好处并不一定能体会的到。尤其是写属性的get()与set()方法之时,总是感觉多此一举。但是当面对系统性项目需要多人协同工作时,封装的意义就体现出来了。 第一:可以专人干专事,每个人管好自己的一亩三分地就OK了,对外暴露自己的实现的功能就行了,而用别人的模块时也只需要调用相应方法就行了,不用操心其具体实现。 第二:可以防止坏人做坏事,保护核心业务。比如小黑在一个银行系统项目组中做取款流程开发,当客户取过钱后小黑需要调用项目主管所提供的 public Boolean updateBalance(Float money,Int operation)这一方法去修改余额,而不是能够直接访问用户余额这一关键属性。如果不进行封装,当某天主管怒斥了小黑一顿,小黑想搞点破坏,是不是可以把代码修改成用户取钱后,不进行减操作,而是直接访问用户余额这一属性给他加上一些钱哪。 继承继承这一机制估计是大家用得最多的了,通过继承我们可以很方便地对父类进行修改与拓展。从设计者的角度来看继承是从抽象到具体的一个过程,架构阶段我们基于主要业务逻辑构建出代码的主框架,建好各种基类,这是一次归纳抽象的过程,细节设计阶段我们根据具体业务逻辑与基类,来构造出各种子类,这是一次展开具体化的过程。 多态多态从字面上很容易理解,就是多种形态嘛。孙大圣七十二变,无论是变成石头、老头、大树其本质上都还是个孙悟空,紧箍咒都还对他管用。这样的好处就是可以屏蔽掉子类对象的差异,使得程序员可以写出通用性的代码,而无需针对不同的子类写不同的代码,让代码更易于拓展。 唐僧的紧箍咒对于孙悟空本身是一个方法,只要一念咒语猴子就头痛难忍,后来孙悟空变成了一个老头,唐僧不用专门去针对老头形态的孙悟空去研究一套紧箍咒,只用念原来的依然奏效。这样对于唐僧来说紧箍咒就具备的通用性与可拓展性,无论你猴子怎么变,唐僧都能以不变应万变! 总结宝剑都是定型的,而剑术确是灵活的,虽然剑术也有一套规则,但在实战中是可以千变万化,形态万千。所谓好马配好鞍,互联网技术日臻成熟,作为开发者的我们有着数不清的宝剑,但拥有再厉害的宝剑也得配合一套自己了熟于心的剑术才能发挥其威力。程序员也如同剑客一般,剑客之道有三重: 手中有剑,心中有剑,人剑合一,第一境界; 手中无剑,心中有剑,杀人于无形,第二境界; 手中无剑,心中无剑,不武而屈人之兵,此乃第三境界! 不知道作为程序员的我们现在到哪中境界了哪?

October 17, 2019 · 1 min · jiezi

从JS继承实现一个VueJs的render函数

市面上的主流框架,相信作为一个前端搬砖人员,或多或少都会有所接触到。如ReactJs、VueJs、AngularJs。那么对于每个框架的使用来说其实是比较简单的,还记得上大学时候,老师曾经说过:"技术就是窗户纸,捅一捅就破了",也就是说,任何一门技术,只要深入去研究,那么它也不再是很神秘的东西了。我个人在工作中用VueJs是比较多的,当然React也会,那今天就为大家来实现一个Vuejs框架中的render函数首先来看一段代码: <div id="div1"> <span>test</span> <Tab></Tab> <UserLogin></UserLogin> </div>最终在页面上的呈现是怎样的呢? 毫无疑问,只看到了test这一段文本内容。因为html不认识Tab、UserLogin这两个"异类"元素。那么假如现在要实现的是,通过一个render方法: render({ root:'#div1', components:{ Tab,UserLogin } })将Tab、UserLogin这两个组件的内容渲染出来,该去怎样实现呢?这里涉及到的知识点如下: 类型判断DOM操作Js的继承、多态组件化思想首先通过Js的继承及组件化思想来定义两个类Tab、UserLogin,它们都有一个自身的render方法(从父类Component)继承而来并进行了重写。直接上代码: Component类: class Component{ render(){ throw new Error('render is required'); } }Tab类: class Tab extends Component{ render(){ let div1 = document.createElement('div'); div1.innerHTML = '我是Tab组件'; return div1; } }UserLogin类: class UserLogin extends Component{ render(){ let div2 = document.createElement('div'); div2.innerHTML = "我是登录组件" return div2 } }到这里,相信大家学过ES6的,对这样的代码都是感觉很熟悉的,重点是render函数究竟该怎样去实现。再来看一下render函数的基本骨架: render({ root:'#div1', components:{ Tab,UserLogin } })render函数接收了一个参数(对象类型),里面包括两个属性root(挂载的元素),以及components(带渲染的组件类)。对于render的实现,先从root这个属性入手。灵魂拷问,root属性一定是某个元素的id吗?对于一个函数的参数来说,使用者传递什么类型都是可以的,但只要符合规定的参数才能有效,说那么多其实就是需要对render函数对象参数的root属性进行校验。代码如下: ...

September 20, 2019 · 2 min · jiezi

JavaScript-之-原型和原型链

几个概念构造函数: 构造函数实际上是一个普通函数,通过new操作符,可以利用构造函数快速创建对象;prototype:每个构造函数自身上都会有一个prototype属性,该属性值为一个对象,这个对象具有一个constructor属性,constructor指向构造函数自身;实例: 通过构造函数创建的对象,可以看做是该构造函数的实例;__proto__:通过构造函数创建的每个对象上面,都会有一个__proto__属性,该属性指向构造函数的prototype;什么是原型在JavaScript中,每个对象会有一个原型对象。对象会从原型对象继承一些属性和方法。 什么是原型链在JavaScript中,访问一个对象的属性时,JS引擎会首先在该对象自身上线查找该属性。如果找到了,则直接返回该属性的值;如果没有找到,则会去改对象的原型上面继续查找。如果对象的原型上面也没有这个属性,则继续在原型对象的上原型上面查找,如此一级级继续往上,直到原型为null,此时JS引擎返回该对象的属性值为undefined。 继承 /** * two methods to implement inheritance; */ function Base(type){ this.type = type; } Base.prototype.base=function(){ console.log(`${this.type} is in base func`); } // method one function Sub(type){ this.type = type; } Sub.prototype = Object.create(new Base('base')); Sub.prototype.sub=function(){ console.log(`${this.type} is in sub func`); } // method two function Foo(type){ this.type = type; } Object.setPrototypeOf( Foo.prototype, new Sub('sub')); Foo.prototype.foo=function(){ console.log(`${this.type} is in foo func`); } let sub = new Sub('sub1'); sub.base(); sub.sub(); sub instanceof Sub; // true sub instanceof Base; // true let foo = new Foo('foo1'); foo.base(); foo.sub(); foo.foo(); foo instanceof Foo; // true foo instanceof Sub; // true foo instanceof Base; // true一些实例Object.getPrototype预测下面几个表达式的结果 ...

July 1, 2019 · 1 min · jiezi

10JavaScript-面向对象高级继承模式

JavaScript 面向对象高级——继承模式一、原型链继承方式1: 原型链继承 (1)流程: 1、定义父类型构造函数。 2、给父类型的原型添加方法。 3、定义子类型的构造函数。 4、创建父类型的对象赋值给子类型的原型。 5、将子类型原型的构造属性设置为子类型。 6、给子类型原型添加方法。 7、创建子类型的对象: 可以调用父类型的方法。 (2)关键: 子类型的原型为父类型的一个实例对象// 1.定义父类型构造函数function Supper() { this.supProp = 'Supper property'}// 2.给父类型的原型添加方法Supper.prototype.showSupperProp = function () { console.log(this.supProp)}// 3.定义子类型的构造函数function Sub() { this.subProp = 'Sub property'}// 4.子类型的原型为父类型的一个实例对象Sub.prototype = new Supper()// 5.将子类型原型的构造属性constructor指向子类型Sub.prototype.constructor = Sub// 6.给子类型原型添加方法Sub.prototype.showSubProp = function () { console.log(this.subProp)}// 7.创建子类型的对象,可以调用父类型的方法var sub = new Sub()sub.showSupperProp() // Supper propertysub.showSubProp() // Sub propertyconsole.log(sub) // Sub ...

June 27, 2019 · 1 min · jiezi

JavaScript系列浅析原型链与继承

一、前言继承是面向对象(OOP)语言中的一个最为人津津乐道的概念。许多面对对象(OOP)语言都支持两种继承方式::接口继承 和 实现继承 。 接口继承只继承方法签名,而实现继承则继承实际的方法。由于js中方法没有签名,在ECMAScript中无法实现接口继承。ECMAScript只支持实现继承,而且其 实现继承 主要是依靠原型链来实现的。 二、概念2.1简单回顾下构造函数,原型和实例的关系:每个构造函数(constructor)都有一个原型对象(prototype),原型对象都包含一个指向构造函数的指针,而实例(instance)都包含一个指向原型对象的内部指针。 关系如下图所示: 2.2什么是原型链每一个对象拥有一个原型对象,通过__proto__指针指向上一个原型,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层层的,最终指向null。这种关系成为原型链(prototype chain)。 假如有这样一个要求:instance实例通过原型链找到了Father原型中的getFatherValue方法. function Father(){ this.property = true;}Father.prototype.getFatherValue = function(){ return this.property;}function Son(){ this.sonProperty = false;}//继承 FatherSon.prototype = new Father();//Son.prototype被重写,导致Son.prototype.constructor也一同被重写Son.prototype.getSonVaule = function(){ return this.sonProperty;}var instance = new Son();console.log(instance.getFatherValue());//true注意: 此时instance.constructor指向的是Father,这是因为Son.prototype中的constructor被重写的缘故. 2.3确定原型和实例之间的关系使用原型链后, 我们怎么去判断原型和实例的这种继承关系呢? 方法一般有两种. 1、第一种是使用 instanceof 操作符。只要用这个操作符来测试实例(instance)与原型链中出现过的构造函数,结果就会返回true. 以下几行代码就说明了这点。 console.log(instance instanceof Object);//trueconsole.log(instance instanceof Father);//trueconsole.log(instance instanceof Son);//true由于原型链的关系, 我们可以说instance 是 Object, Father 或 Son中任何一个类型的实例. 因此, 这三个构造函数的结果都返回了true. 2、第二种是使用 isPrototypeOf() 方法,。同样只要是原型链中出现过的原型,isPrototypeOf() 方法就会返回true console.log(Object.prototype.isPrototypeOf(instance));//trueconsole.log(Father.prototype.isPrototypeOf(instance));//trueconsole.log(Son.prototype.isPrototypeOf(instance));//true 2.4原型链的问题原型链并非十分完美, 它包含如下两个问题: ...

June 14, 2019 · 2 min · jiezi

JS核心知识点梳理原型继承下

引言正如上篇所提到的,有些人认为JavaScript并不是真正的面向对象语言,在经典的面向对象语言中,您可能倾向于定义类对象,然后您可以简单地定义哪些类继承哪些类,JavaScript使用了另一套实现方式,继承的对象函数并不是通过复制而来,而是通过原型链继承(通常被称为 原型式继承 —— prototypal inheritance)。 继承的方式说到继承,首先得明白继承的是什么东西。为了方便理解,我个人把属性(包括方法)分为共有属性和私有属性。私有属性比如名字或者身份证之类的,是每个人独有的。共有属性是能共用的属性或者方法,比如爱好属性,吃饭方法。 私有属性的继承(构造函数+call)这个比较简单,私有属性用利用构造函数和call或者apply const Person = function (name) { this.name = name }const Students = function (name) { Person.call(this,name) }const xm = new Students('小明')console.log(xm) //Students {name: "小明"}公有属性的继承这里里面坑有点多,大家听我娓娓道来。通过原型链实现公有属性继承肯定没错,但是我们设计的时候有个原则 子类需要有自己的原型,父类也必须要有自己的原型,子实例在自己的原型上找不到属性的时候才会到父原型上去找子类.prototype = 父类.prototype 这样肯定不行,虽然能继承父类原型的方法,但是子类的原型和父类的原型是同一个,给子类原型添加方法的时候,相当于给父类的原型也添加了一个方法。so我们应该有一个缓冲。 子类实例---->子类原型------->中间对象------->父类原型 //沿着箭头能访问,表现上符合我们的设计原则 最常见的是使用父类的实例当这个中间对象。 Children.prototype = new Parent() 但是了这么做有个不好的地方。会实例化一次父类。如果父类特别复杂,比如axios,那么会带来很多额外的开销。 我们看一下中间对象有什么作用,实际上只起了一个隔离和原型重定向的作用。完全可以用一个空对象实现这个功能 //实现缓冲var fn = function() {}fn.prototype = Parent.prototypeChildren.prototype = new fn()实际上,这个就是Oject.create()的实现 //Oject.create()Object.create = function(obj){ var fn = funcion(){} fn.prototype = obj reurturn new fn() }终极继承解决方案现在既要继承私有属性,又要继承公有属性。 ...

May 23, 2019 · 2 min · jiezi

JS核心知识点梳理原型继承上

引言最近又攀登了一下JS三座大山中的第二座。登山过程很酸爽,一路发现了许多之前没曾注意到的美景。本着独乐乐不如众乐乐的原则,这里和大家分享一下。 JS的面试对象有些人认为 JavaScript 不是真正的面向对象的语言,比如它没有像许多面向对象的语言一样有用于创建class类的声明(在 ES2015/ES6 中引入了 class 关键字,但那只是语法糖,JavaScript 仍然是基于原型的)。JavaScript 用一种称为构建函数的特殊函数来定义对象和它们的特征。不像“经典”的面向对象的语言,从构建函数创建的新实例的特征并非全盘复制,而是通过一个叫做原形链的参考链链接过去的。同理,原型链也是实现继承的主要方式(ES6的extends只是语法糖)。 原型、原型链一直在犹豫,到底是先讲创建对象的方法还是先讲原型。为了后面保证讲创建对象方法的连贯性,这里还是先讲讲原型吧,这里为了权威,直接就摘抄MDN的定义了 JavaScript 常被描述为一种基于原型的语言 (prototype-based language)——每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法。准确地说,这些属性和方法定义在Object的构造器函数(constructor functions)之上的prototype属性上,而非对象实例本身。 这个__proto__属性有什么用呢?在传统的 OOP 中,首先定义“类”,此后创建对象实例时,类中定义的所有属性和方法都被复制到实例中。在 JavaScript 中并不如此复制,而是在对象实例和它的构造器之间建立一个链接(它是__proto__属性,是从构造函数的prototype属性派生的),之后通过上溯原型链,在构造器中找到这些属性和方法。 简单的说,就是实例对象能通过自己的__proto__属性去访问“类”原型(prototype)上的方法和属性,类如果也是个实例,就会不断往上层类的原型去访问,直到找到 补充:1.“类”的原型有一个属性叫做constructor指向“类” 2.__proto__已被弃用,提倡使用Object.getPrototypeOf(obj) 举例: var arr = [1,2,3] //arr是一个实例对象(数组类Array的实例)arr.__proto__ === Array.prototype //true 实例上都有一个__proto__属性,指向“类”的原型Array.prototype.__proto__ === Object.prototype //true “类”的原型也是一个Object实例,那么就一定有一个__proto__属性,指向“类”object的原型这里补充一个知识点: 浏览器在在Array.prototype上内置了pop方法,在Object.prototype上内置了toString方法 上图是我画的一个原型链图 [1,2,3].pop() //3[1,2,3].toString() //'1,2,3'[1,2,3].constructor.name //"Array" [1,2,3].hehe() //[1,2,3].hehe is not a function当我们调用pop()的时候,在实例[1,2,3]上面没有找到该方法,则沿着原型链搜索"类"Array的原型,找到了pop方法并执行,同理调用toString方法的时候,在"类"Array没有找到则会继续沿原型链向上搜索"类"Object的原型,找到toString并执行。当执行hehe方法的时候,由于“类”Object的原型上并没有找到,搜索“类”Object的__proto__,由于执行null,停止搜索,报错。 注意,[1,2,3].constructor.name显示‘Array’不是说明实例上有constructor属性,而是正是因为实例上没有,所以搜索到类的原型上了,找到了constructor 类,创建对象的方法怎么创建对象,或者说怎么模拟类。这里我就不学高程一样,给大家介绍7种方法了,只讲我觉得必须掌握的。毕竟都es6 es7了,很多方法基本都用不到,有兴趣自己看高程。 利用构造函数 const Person = function (name) { this.name = name this.sayHi = function () { alert(this.name) } } const xm = new Person('小明') const zs = new Person('张三') zs.sayHi() //'张三' xm.sayHi() //'小明'缺点: 每次实例化都需要复制一遍函数到实例里面。但是不管是哪个实例,实际上sayHi都是相同的方法,没必要每次实例化的时候都复制一遍,增加额外开销。 ...

May 22, 2019 · 2 min · jiezi

关于构造函数、原型、原型链、多种方式继承

构造函数与实例对象又是这个经典的问题,嗯,我先来写个构造函数,然后实例化一个对象看看。function Person(name) { this.name = name}Person.prototype.eat = () => {console.log(’eat’)}Person.prototype.play = () => {console.log(‘play’)}let Han = new Person(‘Han’)通过一系列打印发现了这样的关系:原型链 – 原型(prototype)和隐式原型(proto)可以看出实例对象没有prototype(也就是原型),只有构造器才拥有原型。而所有的js对象都拥有__proto__(也就是隐式原型),这个隐式原型所指向的就是创造这个对象的构造器的原型。如实例Han的隐式原型指向了其构造函数(Person)的原型;Person的隐式原型指向了Function的原型;而原型自身也有隐式原型,指向了Object的原型。有点绕口,其实就是通过隐式原型可以向上找到是谁构造了自己,并且如果自己没有相应的属性或者方法,可以沿着这条原型链向上找到最近的一个属性或方法来调用。如Han.eat(),实际上是调用了Han.proto.eat(),把构造器Person的原型的eat方法给拿来用了;再如Han.hasOwnProperty(’name’),实际上是调用了Han.proto.proto.hasOwnProperty(’name’),因为Han自己没hasOwnProperty这方法,就通过隐式原型向上找到了Person的原型,发现也没这方法,就只能再沿着Person的原型的隐式原型向上找到了Object的原型,嗯然后发现有这方法就拿来调用了。构造器constructor所有构造函数都有自己的原型(prototype),而原型一定有constructor这么个属性,指向构造函数本身。也就是告诉大家这个原型是属于本构造函数的。Function & Object可以看出Person这个构造函数是由Function创建出来的,而我们看下Function的隐式原型,竟然是指向了Function的原型,也就是Function也是由Function创建出来的。很绕是不是,我们先不管,继续溯源下去,再看下Function的原型的隐式原型,指向的是Object的原型,继续往上找Object的原型的隐式原型,嗯终于结束了找到的是null,也就是Object的原型是原型链上的最后一个元素了。接下来看下Object,Object是由Function创建出来的,而Function的隐式原型的隐式原型是Object的原型也就是Function通过原型链可以向上找到Object的原型,两者看起来是你生我我生你的关系,这里也就引用比较好懂的文章来解释下: 从Object和Function说说JS的原型链ObjectJavaScript中的所有对象都来自Object;所有对象从Object.prototype继承方法和属性,尽管它们可能被覆盖。例如,其他构造函数的原型将覆盖constructor属性并提供自己的toString()方法。Object原型对象的更改将传播到所有对象,除非受到这些更改的属性和方法将沿原型链进一步覆盖。FunctionFunction 构造函数 创建一个新的Function对象。 在 JavaScript 中, 每个函数实际上都是一个Function对象。—- 来自mozilla接下来说下构造函数实例化对象到底做了些啥,其实看也能看出来了。let Jan = {}Person.call(Jan, ‘Jan’)Jan.proto = Person.prototype1、创建一个空对象。2、将构造函数的执行对象this赋给这个空对象并且执行。3、把对象的隐式原型指向构造函数的原型。4、返回这个对象是的就是这样,next page!继承原型链继承function Person(name) { this.name = name this.skills = [’eat’, ‘sleep’]}Person.prototype.say = ()=> {console.log(‘hi’)}function Boss() {}Boss.prototype = new Person()let Han = new Boss()原理就是这样????子构造函数的原型指向了父构造函数的实例对象,因此子构造函数的实例对象可以通过原型链找到父构造函数的原型方法和类属性。优点:所有实例对象都可以共享父构造函数的原型方法。缺点:1、父构造函数的引用属性也被共享了,相当于所有的实例对象只要对自身的skills属性进行修改都会引发共振,因为其实修改的是原型链上的skills属性。当然对skills重新赋值可以摆脱这一枷锁,相当于自身新建了skills属性来覆盖了原型链上的。2、实例化时无法对父构造函数传参。3、子构造函数原型中的constructor不再是子类自身,而是通过原型链找到了父类的constructor。构造函数继承function Person(name) { this.name = name this.skills = [’eat’, ‘sleep’]}Person.prototype.say = ()=> {console.log(‘hi’)}function Boss(name) { Person.call(this, name)}let Han = new Boss(‘Han’)原理就是父构造函数把执行对象赋给子构造函数的实例对象后执行自身。优点:1、实例化时可以对父构造函数传参。2、父类的引用属性不会被共享。3、子构造函数原型中的constructor还是自身,原型没有被修改。缺点:每次实例化都执行了一次父构造函数,子类不能继承父类的原型,如果把父类原型上的方法写在父类的构造函数里,虽然子类实例对象可以调用父类的方法,但父类的方法是单独加在每个实例对象上,会造成性能的浪费。组合继承结合了原型链继承和构造函数继承两种方法。function Person(name) { this.name = name this.skills = [’eat’, ‘sleep’]}Person.prototype.say = ()=> {console.log(‘hi’)}function Boss(name, age) { Person.call(this, name) this.age = age}Boss.prototype = new Person()Boss.prototype.constructor = BossBoss.prototype.sleep = ()=> {console.log(‘sleep’)}let Han = new Boss(‘Han’, 18)看起来是完美解决了一切。但就是????实例化的对象实际上是用构造函数继承的方法往自己身上加属性从而覆盖原型链上的相应属性的,既然如此,为什么不直接那父构造器的原型加到子构造器的原型上呢?这样就不会出现那多余的父类实例化对象出来的属性了。function Person(name) { this.name = name this.skills = [’eat’, ‘sleep’]}Person.prototype.say = ()=> {console.log(‘hi’)}function Boss(name, age) { Person.call(this, name) this.age = age}Boss.prototype = Person.prototype //modifiedBoss.prototype.constructor = BossBoss.prototype.sleep = ()=> {console.log(‘sleep’)}let Han = new Boss(‘Han’, 18)看起来很是完美,反正效果是达到了,性能优化也是最佳。但问题就是这样一点继承关系都看不出来啊,父类和子类的原型完全融合在一块了,一点都不严谨。所以最优的继承方式应该是。。。寄生组合继承function Person(name) { this.name = name this.skills = [’eat’, ‘sleep’]}Person.prototype.say = ()=> {console.log(‘hi’)}function Boss(name, age) { Person.call(this, name) this.age = age}Boss.prototype = Object.create(Person.prototype)Boss.prototype.sleep = ()=> {console.log(‘sleep’)}Boss.prototype.constructor = Bosslet Han = new Boss(‘Han’, 18)先看图????其实跟组合继承有点像,构造函数继承部分和组合继承的一样就不说了。原型链那块和原型链继承有所不同的是原型链继承是直接拿了父类的实例对象来作为子类的原型,而这里是用以父类的原型为原型的构造函数实例化出来的对象作为子类的原型(Object.create做的事情),完美避开了不必要的父类构造函数里的东西。Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。相当于这样????function create(proto) { function F() {} F.prototype = proto return new F()}听说ES6的class extend也是这么做的,更多的继承细节可以看看这篇文章,本继承章节也参考了的????一篇文章理解JS继承——原型链/构造函数/组合/原型式/寄生式/寄生组合/Class extends ...

March 16, 2019 · 1 min · jiezi

odoo视图继承

简介继承在odoo里可以通过继承的方式来改写已经存在的view对比view的定义视图定义中写法多了inherit_idinherit_id指向要改写的view<field name=“inherit_id” ref=“id_category_list”/>在arch里用xpath定位改写的元素<!– 改写 ibuilding list 列表视图 –><record id=“building_list_view” model=“ir.ui.view”> <field name=“name”>building.list.view</field> <field name=“model”>ibuilding.list</field> <field name=“inherit_id” ref=“building_list”/> <field name=“arch” type=“xml”> <!– 找到ids字段,在其后添加idea_id字段 –> <xpath expr="//field[@name=‘ids’]" position=“after”> <field name=“idea_id” string=“Number of ideas”/> </xpath> <!– 找到 upload 字段,在其后添加idea_ids字段 –> <xpath expr="//field[@name=‘upload’]" position=“replace”> <field name=“download” string=“下载一个文件”/> </xpath> </field></record>注意找到的元素必须是一个,如果定位出多个元素或者是空,都会报错。expr Xpath表达式用来选择父视图中的某个元素,如果没找到或找到多个元素会抛出一个异常position定位有如下选择inside 在内部结尾插入元素replace 替换元素before 在之前插入元素after 在其后插入元素attributes 修改xml的属性当仅匹配一个field时,erpr表达式可以简化如下<xpath expr="//field[@name=‘upload’]" position=“replace”> <field name=“download” /></xpath><field name=“upload” position=“replace”> <field name=“download” /></field>

March 15, 2019 · 1 min · jiezi

odoo 模型继承

在odoo中有两种模型的继承机制(传统方式和委托继承方式)重点:在__manifest__.py中找到depends,加上要继承的模块’depends’: [‘account’]注意继承的模型所在addon需要在本addon里添加依赖,不然会报一个TypeError: Model ‘xxx’ does not exist in registry 错误。传统方式能够添加字段 改写字段定义 添加约束 添加或改写方法共有两种写法 1 类继承 2 原型继承类继承_name = ’event.registration’_inherit = ’event.registration’_name和_inherit的模型名一致,都为’event.registration’, 此时_name可以省略不写。类继承不会创建新的模型,能够直接修改模型定义,新加的字段会在原表中添加,数据库中没有新的表生成。例子class model_1(models.Model): _name = ‘activity.registration’ class model_2(models.Model): _inherit = ’event.registration’ event_id = fields.Many2one( ‘activity.event’, string=‘Event’, required=True, readonly=True, states={‘draft’: [(‘readonly’, False)]})原型继承_name = ‘activity.registration’_inherit = ’event.registration’_name 和 _inherit 的模型名不同。 相当于把模型 event 的属性(字段 方法等)copy了一份,重新创建一个新的模型 activity,新的表里有模型 event 的字段。例子class ActivityRegistration(models.Model): _name = ’event.registration’ name = fields.Char() def say(self): return self.check(“event”) def check(self, s): return “This is {} record {}".format(s, self.name) class ActivityRegistration(models.Model): _name = ‘activity.registration’ _inherit = ’event.registration’ def say(self): return self.check(“activity”)支持多重继承,用列表表示 _inherit = [‘mail’, ‘resource’]委托继承class NewModel(): _name = “new.model” _inherits = {‘模型1’: ‘关联字段1’,‘模型2’: ‘关联字段2’}支持多重继承,并提供透明的子模型字段访问方法,好像模型有子模型字段例子class Child0(models.Model): _name = ‘delegation.child0’ field_0 = fields.Integer() class Child1(models.Model): _name = ‘delegation.child1’ field_1 = fields.Integer() class Delegating(models.Model): _name = ‘delegation.parent’ _inherits = { ‘delegation.child0’: ‘child0_id’, ‘delegation.child1’: ‘child1_id’, } child0_id = fields.Many2one(‘delegation.child0’, required=True, ondelete=‘cascade’) child1_id = fields.Many2one(‘delegation.child1’, required=True, ondelete=‘cascade’)这种继承只能继承字段,其他方法不继承可以读写子模型的字段record.field_1record.field_2record.write({‘field_1’: 4})如果子模型里的字段重复,只能看到_inherits第一个子模型的字段 ...

March 13, 2019 · 1 min · jiezi

es6声明类实现继承

class声明一个animal类(对象):class Animal{ constructor(){//这个constructor方法内定义的方法和属性是实例化对象自己的,不共享;construstor外定义的方法和属性是所有实例对象(共享)可以调用的 this.type = ‘animal’ //this关键字代表Animal对象的实例对象 } says(say){ console.log(this.type+’ says ’ +say); }}let animal = new Animal();animal.says(‘hello’);//控制台输出‘animal says hello’这里声明一个Cat类,来继承Animal类的属性和方法class Cat extends Animal(){ constructor(){ super();//super关键字,用来指定父类的实例对象 this.type = ‘cat’; }} let cat = new Cat();cat.says(‘hello’);//输出‘cat says hello’

February 21, 2019 · 1 min · jiezi

面试官问:JS的继承

用过React的读者知道,经常用extends继承React.Component。// 部分源码function Component(props, context, updater) { // …}Component.prototype.setState = function(partialState, callback){ // …}const React = { Component, // …}// 使用class index extends React.Component{ // …}点击这里查看 React github源码面试官可以顺着这个问JS继承的相关问题,比如:ES6的class继承用ES5如何实现。据说很多人答得不好。<br/>构造函数、原型对象和实例之间的关系要弄懂extends继承之前,先来复习一下构造函数、原型对象和实例之间的关系。代码表示:function F(){}var f = new F();// 构造器F.prototype.constructor === F; // trueF.proto === Function.prototype; // trueFunction.prototype.proto === Object.prototype; // trueObject.prototype.proto === null; // true// 实例f.proto === F.prototype; // trueF.prototype.proto === Object.prototype; // trueObject.prototype.proto === null; // true笔者画了一张图表示:ES6 extends 继承做了什么操作我们先看看这段包含静态方法的ES6继承代码:// ES6class Parent{ constructor(name){ this.name = name; } static sayHello(){ console.log(‘hello’); } sayName(){ console.log(‘my name is ’ + this.name); return this.name; }}class Child extends Parent{ constructor(name, age){ super(name); this.age = age; } sayAge(){ console.log(‘my age is ’ + this.age); return this.age; }}let parent = new Parent(‘Parent’);let child = new Child(‘Child’, 18);console.log(‘parent: ‘, parent); // parent: Parent {name: “Parent”}Parent.sayHello(); // helloparent.sayName(); // my name is Parentconsole.log(‘child: ‘, child); // child: Child {name: “Child”, age: 18}Child.sayHello(); // hellochild.sayName(); // my name is Childchild.sayAge(); // my age is 18其中这段代码里有两条原型链,不信看具体代码。// 1、构造器原型链Child.proto === Parent; // trueParent.proto === Function.prototype; // trueFunction.prototype.proto === Object.prototype; // trueObject.prototype.proto === null; // true// 2、实例原型链child.proto === Child.prototype; // trueChild.prototype.proto === Parent.prototype; // trueParent.prototype.proto === Object.prototype; // trueObject.prototype.proto === null; // true一图胜千言,笔者也画了一张图表示,如图所示:结合代码和图可以知道。ES6 extends 继承,主要就是:把子类构造函数(Child)的原型(proto)指向了父类构造函数(Parent),把子类实例child的原型对象(Child.prototype) 的原型(proto)指向了父类parent的原型对象(Parent.prototype)。这两点也就是图中用不同颜色标记的两条线。子类构造函数Child继承了父类构造函数Preant的里的属性。使用super调用的(ES5则用call或者apply调用传参)。也就是图中用不同颜色标记的两条线。看过《JavaScript高级程序设计-第3版》 章节6.3继承的读者应该知道,这2和3小点,正是寄生组合式继承,书中例子没有第1小点。1和2小点都是相对于设置了__proto__链接。那问题来了,什么可以设置了__proto__链接呢。new、Object.create和Object.setPrototypeOf可以设置__proto__说明一下,proto__这种写法是浏览器厂商自己的实现。再结合一下图和代码看一下的new,new出来的实例的__proto__指向构造函数的prototype,这就是new做的事情。摘抄一下之前写过文章的一段。面试官问:能否模拟实现JS的new操作符,有兴趣的读者可以点击查看。new做了什么:创建了一个全新的对象。这个对象会被执行[[Prototype]](也就是__proto)链接。生成的新对象会绑定到函数调用的this。通过new创建的每个对象将最终被[[Prototype]]链接到这个函数的prototype对象上。如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调用会自动返回这个新的对象。Object.create ES5提供的Object.create(proto, [propertiesObject])方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。 它接收两个参数,不过第二个可选参数是属性描述符(不常用,默认是undefined)。对于不支持ES5的浏览器,MDN上提供了ployfill方案。MDN Object.create()// 简版:也正是应用了new会设置__proto__链接的原理。if(typeof Object.create !== ‘function’){ Object.create = function(proto){ function F() {} F.prototype = proto; return new F(); }}Object.setPrototypeOf ES6提供的Object.setPrototypeOf MDNObject.setPrototypeOf() 方法设置一个指定的对象的原型 ( 即, 内部[[Prototype]]属性)到另一个对象或 null。Object.setPrototypeOf(obj, prototype)ployfill// 仅适用于Chrome和FireFox,在IE中不工作:Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) { obj.proto = proto; return obj; }nodejs源码就是利用这个实现继承的工具函数的。nodejs utils inheritsfunction inherits(ctor, superCtor) { if (ctor === undefined || ctor === null) throw new ERR_INVALID_ARG_TYPE(‘ctor’, ‘Function’, ctor); if (superCtor === undefined || superCtor === null) throw new ERR_INVALID_ARG_TYPE(‘superCtor’, ‘Function’, superCtor); if (superCtor.prototype === undefined) { throw new ERR_INVALID_ARG_TYPE(‘superCtor.prototype’, ‘Object’, superCtor.prototype); } Object.defineProperty(ctor, ‘super_’, { value: superCtor, writable: true, configurable: true }); Object.setPrototypeOf(ctor.prototype, superCtor.prototype);}ES6的extends的ES5版本实现知道了ES6 extends继承做了什么操作和设置__proto__的知识点后,把上面ES6例子的用ES5就比较容易实现了,也就是说实现寄生组合式继承,简版代码就是:// ES5 实现ES6 extends的例子function Parent(name){ this.name = name;}Parent.sayHello = function(){ console.log(‘hello’);}Parent.prototype.sayName = function(){ console.log(‘my name is ’ + this.name); return this.name;}function Child(name, age){ // 相当于super Parent.call(this, name); this.age = age;}// newfunction object(){ function F() {} F.prototype = proto; return new F();}function _inherits(Child, Parent){ // Object.create Child.prototype = Object.create(Parent.prototype); // proto // Child.prototype.proto = Parent.prototype; Child.prototype.constructor = Child; // ES6 // Object.setPrototypeOf(Child, Parent); // proto Child.proto = Parent;}_inherits(Child, Parent);Child.prototype.sayAge = function(){ console.log(‘my age is ’ + this.age); return this.age;}var parent = new Parent(‘Parent’);var child = new Child(‘Child’, 18);console.log(‘parent: ‘, parent); // parent: Parent {name: “Parent”}Parent.sayHello(); // helloparent.sayName(); // my name is Parentconsole.log(‘child: ‘, child); // child: Child {name: “Child”, age: 18}Child.sayHello(); // hellochild.sayName(); // my name is Childchild.sayAge(); // my age is 18我们完全可以把上述ES6的例子通过babeljs转码成ES5来查看,更严谨的实现。// 对转换后的代码进行了简要的注释"use strict";// 主要是对当前环境支持Symbol和不支持Symbol的typeof处理function _typeof(obj) { if (typeof Symbol === “function” && typeof Symbol.iterator === “symbol”) { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === “function” && obj.constructor === Symbol && obj !== Symbol.prototype ? “symbol” : typeof obj; }; } return _typeof(obj);}// _possibleConstructorReturn 判断Parent。call(this, name)函数返回值 是否为null或者函数或者对象。function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === “object” || typeof call === “function”)) { return call; } return _assertThisInitialized(self);}// 如何 self 是void 0 (undefined) 则报错function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(“this hasn’t been initialised - super() hasn’t been called”); } return self;}// 获取__proto__function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.proto || Object.getPrototypeOf(o); }; return getPrototypeOf(o);}// 寄生组合式继承的核心function inherits(subClass, superClass) { if (typeof superClass !== “function” && superClass !== null) { throw new TypeError(“Super expression must either be null or a function”); } // Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto。 // 也就是说执行后 subClass.prototype.proto === superClass.prototype; 这条语句为true subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass);}// 设置__proto__function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.proto = p; return o; }; return _setPrototypeOf(o, p);}// instanceof操作符包含对Symbol的处理function _instanceof(left, right) { if (right != null && typeof Symbol !== “undefined” && right[Symbol.hasInstance]) { return rightSymbol.hasInstance; } else { return left instanceof right; }}function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError(“Cannot call a class as a function”); }}// 按照它们的属性描述符 把方法和静态属性赋值到构造函数的prototype和构造器函数上function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (“value” in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); }}// 把方法和静态属性赋值到构造函数的prototype和构造器函数上function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor;}// ES6var Parent = function () { function Parent(name) { _classCallCheck(this, Parent); this.name = name; } _createClass(Parent, [{ key: “sayName”, value: function sayName() { console.log(‘my name is ’ + this.name); return this.name; } }], [{ key: “sayHello”, value: function sayHello() { console.log(‘hello’); } }]); return Parent;}();var Child = function (_Parent) { _inherits(Child, _Parent); function Child(name, age) { var _this; _classCallCheck(this, Child); // Child.proto => Parent // 所以也就是相当于Parent.call(this, name); 是super(name)的一种转换 // _possibleConstructorReturn 判断Parent.call(this, name)函数返回值 是否为null或者函数或者对象。 _this = _possibleConstructorReturn(this, _getPrototypeOf(Child).call(this, name)); _this.age = age; return _this; } _createClass(Child, [{ key: “sayAge”, value: function sayAge() { console.log(‘my age is ’ + this.age); return this.age; } }]); return Child;}(Parent);var parent = new Parent(‘Parent’);var child = new Child(‘Child’, 18);console.log(‘parent: ‘, parent); // parent: Parent {name: “Parent”}Parent.sayHello(); // helloparent.sayName(); // my name is Parentconsole.log(‘child: ‘, child); // child: Child {name: “Child”, age: 18}Child.sayHello(); // hellochild.sayName(); // my name is Childchild.sayAge(); // my age is 18如果对JS继承相关还是不太明白的读者,推荐阅读以下书籍的相关章节,可以自行找到相应的pdf版本。推荐阅读JS继承相关的书籍章节《JavaScript高级程序设计第3版》-第6章 面向对象的程序设计,6种继承的方案,分别是原型链继承、借用构造函数继承、组合继承、原型式继承、寄生式继承、寄生组合式继承。图灵社区本书地址,后文放出github链接,里面包含这几种继承的代码demo。《JavaScript面向对象编程第2版》-第6章 继承,12种继承的方案。1.原型链法(仿传统)、2.仅从原型继承法、3.临时构造器法、4.原型属性拷贝法、5.全属性拷贝法(即浅拷贝法)、6.深拷贝法、7.原型继承法、8.扩展与增强模式、9.多重继承法、10.寄生继承法、11.构造器借用法、12、构造器借用与属性拷贝法。ES6标准入门-第21章class的继承《深入理解ES6》-第9章 JavaScript中的类《你不知道的JavaScript-上卷》第6章 行为委托和附录A ES6中的class总结继承对于JS来说就是父类拥有的方法和属性、静态方法等,子类也要拥有。子类中可以利用原型链查找,也可以在子类调用父类,或者从父类拷贝一份到子类等方案。继承方法可以有很多,重点在于必须理解并熟悉这些对象、原型以及构造器的工作方式,剩下的就简单了。寄生组合式继承是开发者使用比较多的。回顾寄生组合式继承。主要就是三点:子类构造函数的__proto__指向父类构造器,继承父类的静态方法子类构造函数的prototype的__proto__指向父类构造器的prototype,继承父类的方法。子类构造器里调用父类构造器,继承父类的属性。行文到此,文章就基本写完了。文章代码和图片等资源放在这里github inhert和demo展示es6-extends,结合console、source面板查看更佳。读者发现有不妥或可改善之处,欢迎评论指出。另外觉得写得不错,可以点赞、评论、转发,也是对笔者的一种支持。关于作者:常以轩辕Rowboat若川为名混迹于江湖。前端路上 | PPT爱好者 | 所知甚少,唯善学。个人博客segmentfault前端视野专栏,开通了前端视野专栏,欢迎关注掘金专栏,欢迎关注知乎前端视野专栏,开通了前端视野专栏,欢迎关注github,欢迎follow~ ...

February 20, 2019 · 5 min · jiezi

JS 继承的实现

JS从诞生之初本就不是面向对象的语言。如何在JS中实现继承,总结而言会有四种写法。构造函数继承function Animal(name) { this.name = name this.sayName = function() { console.log(this.name) }} function Dog(name, hobby) { // 遍历 let ani = new Animal(name) for(let p in ani) { if (ani.hasOwnProperty(p)) { this[p] = ani[p] } } this.hobby = hobby} let dog1 = new Dog(‘xiaohei’, ‘bone’)let dog2 = new Dog(‘fofo’, ‘bone and fish’)console.log(dog1.sayName()) // xiaoheiconsole.log(dog2.sayName()) // fofo通过对象冒充实现继承,实际上是在构造函数中,通过获取父类中的所有属性,并保存到自身对象中,这样则可以调用父类的属性和方法了。这里forin的方式遍历父类属性,因为forin会遍历公开的属性和方法,所以通过hasOwnProperty控制写入当前对象的范围。否则则会将所有属性全部变为私有属性。这样做有一个缺点就是,无法访问父类中的公开方法和属性(prototype中的方法)Animal.prototype.sayHobby = function() { console.log(this.hobby)}dog1.sayHobby() // VM2748:1 Uncaught TypeError: dog1.sayHobby is not a function at <anonymous>:1:6代码优化在子类中,既然是需要获取父类的私有属性,则可以使用call和apply,当调用父类的方法的时候,改变当前上下文为子类对象,则子类对象就可以获取到了父类的所有私有属性。function Animal(name) { this.name = name this.sayName = function() { console.log(this.name) }} function Dog(name, hobby) { // 更改构造函数的上下文 Animal.call(this, name) this.hobby = hobby} let dog1 = new Dog(‘xiaohei’, ‘bone’)let dog2 = new Dog(‘fofo’, ‘bone and fish’)console.log(dog1.sayName()) // xiaoheiconsole.log(dog2.sayName()) // fofo类式继承function Animal(name) { this.name = name || ‘animal’ this.types = [‘cat’, ‘dog’] this.sayTypes = function() { console.log(this.types.join(’-’)) }}Animal.prototype.sayName = function() { console.log(this.name)}function Dog(name) { this.name = name }Dog.prototype = new Animal(‘animal’)let dog1 = new Dog(‘xiaohei’)dog1.sayName() // xiaoheilet dog2 = new Dog(‘feifei’)dog2.sayName() // feifei这种继承方式是通过对子类的prototype.proto__引用父类的prototype,从而可以让子类访问父类中的私有方法和公有方法。详情可以查看关键字new的实现。类式继承会有两方面的缺点引用陷阱-子类对象可以随意修改父类中的方法和变量,并影响其他子类对象 dog1.types.push(‘fish’)console.log(dog1.types) // [“cat”, “dog”, “fish”]console.log(dog2.types) // [“cat”, “dog”, “fish”]无法初始化构造不同的实例属性这个主要是由于类式继承,是通过Dog.prototype = new Animal(‘animal’)实现的,我们只会调用一次父类的构造函数。所以只能在子类中从写父类的属性,如上的name属性,在子类中需要重写一次。组合继承组合继承,即结合以上两种继承方式的优点,抛弃两者的缺点,而实现的一种组合方式function Animal(name) { this.name = name this.types = [‘dog’, ‘cat’]}Animal.prototype.sayName = function() { console.log(this.name)}function Dog(name, hobby) { // 获取私有方法并调用父类的构造函数,并传递构造函数的参数,实现初始化不同的构造函数 Animal.call(this, name) this.hobby = hobby}// 子类实例可以访问父类prototype的方法和属性Dog.prototype = new Animal()Dog.prototype.constructor = DogDog.prototype.sayHobby = function() { console.log(this.hobby)}// test instance of dog1let dog1 = new Dog(‘xiaohei’, ‘bone’)dog1.sayName() // xiaoheidog1.sayHobby() // bonedog1.types.push(‘ant’) // types: [‘dog’, ‘cat’, ‘ant’]// test instance of dog2let dog2 = new Dog(‘feifei’, ‘fish’)dog2.sayName() // feifeidog2.sayHobby() // fishdog2.types // [‘dog’, ‘cat’]组合模式,解决了使用构造函数继承和类式继承带来的问题,算是一种比较理想的解决继承方式,但是这里还有一些瑕疵,调用了两次父类(Animal)的构造函数。所以为了解决这个问题,进行了优化,产生了????这种继承方式组合寄生式继承function Animal(name) { this.name = name this.types = [‘dog’, ‘cat’]}Animal.prototype.sayName = function() { console.log(this.name)}function Dog(name, hobby) { // 获取私有方法并调用父类的构造函数,并传递构造函数的参数,实现初始化不同的构造函数 Animal.call(this, name) this.hobby = hobby}/注意下面这两行代码/Dog.prototype = Object.create(Animal.prototype)// 由于对Animal.prototype进行了浅拷贝,则改变了Dog中的构造函数,所以需要重新赋值Dog为构造函数Dog.prototype.constructor = DogDog.prototype.sayHobby = function() { console.log(this.hobby)}// test instance of dog1let dog1 = new Dog(‘xiaohei’, ‘bone’)dog1.sayName() // xiaoheidog1.sayHobby() // bonedog1.types.push(‘ant’) // types: [‘dog’, ‘cat’, ‘ant’]// test instance of dog2let dog2 = new Dog(‘feifei’, ‘fish’)dog2.sayName() // feifeidog2.sayHobby() // fishdog2.types // [‘dog’, ‘cat’]MDN解释:Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto。可以理解为:使用Object.create()进行一次浅拷贝,将父类原型上的方法拷贝后赋给Dog.prototype,这样子类上就能拥有了父类的共有方法,而且少了一次调用父类的构造函数。重写create方法:function create(target) { function F() {} F.prototype = target return new F()}同时需要注意子类的constructor,由于更改了子类的prototype,所以需要重新设定子类的构造函数。ES6中使用语法糖extends实现如果之前有学习过,或者有面向对象语言基础的,这个则很容易理解,使用extens关键字作为继承。class Animal { constructor(name) { this.name = name } sayName() { console.log(this.name) }}class Dog extends Animal { constructor(name, hobby) { super(name) this.hobby = hobby } sayHobby() { console.log(this.hobby) }}let dog1 = new Dog(‘xiaohei’, ‘bone’)dog1.sayName() // xiaoheidog1.sayHobby() // bonelet dog2 = new Dog(‘feifei’, ‘fish’)dog2.sayName() // feifeidog2.sayHobby() // fish总结综上所述,JS中的继承总共分为构造器继承,类式继承,组合继承,组合寄生继承,ES6中extends的继承五种继承方式,其中第四种是第三种的优化实现。最后,实现new关键字的实现MDN: new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。语法:new constructor[([arguments])]function new(constructor, arguments) { let o = {} if (constructor && typeof constructor === ‘function’) { // 获取构造函数的原形 o.proto = constructor.prototype // 获取构造函数的私有变量和私有方法 constructor.apply(o, arguments) return o }} ...

January 16, 2019 · 2 min · jiezi

es6类和继承的实现原理

在阅读文章之前,您至少需要对JavaScript原型继承有一定了解,如果觉得有所欠缺,可以先了解下我这篇文章:https://segmentfault.com/a/11…1.es6 class 使用javascript使用的是原型式继承,我们可以通过原型的特性实现类的继承,es6为我们提供了像面向对象继承一样的语法糖。class Parent { constructor(a){ this.filed1 = a; } filed2 = 2; func1 = function(){}}class Child extends Parent { constructor(a,b) { super(a); this.filed3 = b; } filed4 = 1; func2 = function(){}}下面我们借助babel来探究es6类和继承的实现原理。1.类的实现转换前:class Parent { constructor(a){ this.filed1 = a; } filed2 = 2; func1 = function(){}}转换后:function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(“Cannot call a class as a function”); }}var Parent = function Parent(a) { classCallCheck(this, Parent); this.filed2 = 2; this.func1 = function () { }; this.filed1 = a;};可见class的底层依然是构造函数:1.调用_classCallCheck方法判断当前函数调用前是否有new关键字。构造函数执行前有new关键字,会在构造函数内部创建一个空对象,将构造函数的proptype指向这个空对象的_proto,并将this指向这个空对象。如上,_classCallCheck中:this instanceof Parent 返回true。若构造函数前面没有new则构造函数的proptype不会不出现在this的原型链上,返回false。2.将class内部的变量和函数赋给this。3.执行constuctor内部的逻辑。4.return this (构造函数默认在最后我们做了)。2.继承实现转换前:class Child extends Parent { constructor(a,b) { super(a); this.filed3 = b; } filed4 = 1; func2 = function(){}}转换后:我们先看Child内部的实现,再看内部调用的函数是怎么实现的:var Child = function (_Parent) { _inherits(Child, _Parent); function Child(a, b) { _classCallCheck(this, Child); var _this = _possibleConstructorReturn(this, (Child.proto || Object.getPrototypeOf(Child)).call(this, a)); _this.filed4 = 1; _this.func2 = function () {}; _this.filed3 = b; return _this; } return Child;}(Parent);1.调用_inherits函数继承父类的proptype。_inherits内部实现:function inherits(subClass, superClass) { if (typeof superClass !== “function” && superClass !== null) { throw new TypeError(“Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.proto = superClass;}(1) 校验父构造函数。(2) 典型的寄生继承:用父类构造函数的proptype创建一个空对象,并将这个对象指向子类构造函数的proptype。(3) 将父构造函数指向子构造函数的_proto(这步是做什么的不太明确,感觉没什么意义。)2.用一个闭包保存父类引用,在闭包内部做子类构造逻辑。3.new检查。4.用当前this调用父类构造函数。var _this = _possibleConstructorReturn(this, (Child.proto || Object.getPrototypeOf(Child)).call(this, a));这里的Child.proto || Object.getPrototypeOf(Child)实际上是父构造函数(_inherits最后的操作),然后通过call将其调用方改为当前this,并传递参数。(这里感觉可以直接用参数传过来的Parent)function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError(“this hasn’t been initialised - super() hasn’t been called”); } return call && (typeof call === “object” || typeof call === “function”) ? call : self;}校验this是否被初始化,super是否调用,并返回父类已经赋值完的this。5.将行子类class内部的变量和函数赋给this。6.执行子类constuctor内部的逻辑。可见,es6实际上是为我们提供了一个“组合寄生继承”的简单写法。3. supersuper代表父类构造函数。super.fun1() 等同于 Parent.fun1() 或 Parent.prototype.fun1()。super() 等同于Parent.prototype.construtor()当我们没有写子类构造函数时:var Child = function (_Parent) { _inherits(Child, _Parent); function Child() { _classCallCheck(this, Child); return _possibleConstructorReturn(this, (Child.proto || Object.getPrototypeOf(Child)).apply(this, arguments)); } return Child;}(Parent);可见默认的构造函数中会主动调用父类构造函数,并默认把当前constructor传递的参数传给了父类。所以当我们声明了constructor后必须主动调用super(),否则无法调用父构造函数,无法完成继承。典型的例子就是Reatc的Component中,我们声明constructor后必须调用super(props),因为父类要在构造函数中对props做一些初始化操作。 ...

January 8, 2019 · 2 min · jiezi

Js高级编程笔记--面向对象的程序设计

理解对象属性类型1.数据属性特性:Configurable : 表示能否通过 delete 删除属性,能否修改属性特性,能否把属性改为访问器属性Enumerable : 表示能否通过 for in 循环返回Writable : 表示能否修改属性的值Value : 包含属性的值,读取或写入实际上操作的是这个值2.访问器属性特性:Configurable : 表示能否通过 delete 删除属性,能否修改属性特性,能否把属性改为访问器属性Enumerable : 表示能否通过 for in 循环返回Get : 读取时调用的参数.默认值为 undefinedSet : 写入时调用的参数。 默认值为 undefined3.注意:访问器属性不能直接定义,必须使用 Object.defineProperty()定义。修改属性默认的特性,必须使用 Object.defineProperty()方法get,set,并不一定要定义,只定义 get 为只读,只定义 set 为只写不可读。定义多个属性可以使用 Object.defineProperties()方法读取属性的特性,使用 Object.getOwnPropertyDescriptor()创建对象1.工厂模式定义一个方法接受参数,用于创建对象,并将其返回function createPerson(name, age) { var o = new Object(); o.name = name; o.age = age; return o;}var person1 = createPerson(‘andy_chen’, 18);var person2 = createPerson(‘andy_chen’, 18);工厂模式可以创建多个相似对象的问题,却没解决对象识别的问题。例如person1的类型是什么2.构造函数模式 :function Person(name, age) { this.name = name; this.age = age; this.sayName = function() { alert(this.name); };}var person1 = new Person(‘andy_chen’, 18);var person2 = new Person(‘andy_chen’, 18);person1.sayName();person2.sayName();使用 new 操作符。实际上有以下 4 个步骤:创建一个新对象将构造函数的作用域赋给对象(使 this 指向新对象)执行构造方法(为这个对象添加属性)返回新对象构造函数的问题在于,每个方法都要在每个实例中重新创建一遍。即例子中,person1和person2的sayName的不相等的。但是,完成同样的功能的方法,却每个实例都要创建一遍,这显然不合理,所以,又出现了下面的原型模式3.原型模式:理解原型对象一图胜千言:只要创建了一个新函数,就会根据一组特定规则为该函数创建一个 prototype,这个属性指向函数的对象原型。对象原型中,则默认有一个 constructor 属性,指向该新函数。通过新函数创建的实例,有一个[[prototype]]属性(在 chrome,firefox,safari 中该属性即为proto),指向了新函数的 prototype。注意:该属性仅仅是执行构造函数的 prototype,也即是说,他们与构造函数没有直接联系了读取某个对象的属性时,会先在实例上找,如果没找到,则进一步在实例上的 prototype 属性上找为实例添加属性的时候会屏蔽掉原型上属性。这个时候即使置为 null 也没法访问到原型上的属性,只有通过 delete 删掉之后才可以XXX.prototype.isPrototype(xxx), 可以用这个方法判定对象是否是该实例的原型对象Object.getPrototypeOf() 用这个可以获取实例对应的原型对象 (ES5 新增方法)in 操作符单独使用时: in 操作符 可以确定属性是否存在于对象上(无论是存在于实例上还是原型上)用于 for 循环中时,返回的是所有能够通过对象访问的,可枚举的属性。(IE8 中,如果开发者自定义 toString 类似的系统不可枚举的方法,浏览器还是不会将它遍历出来)ES5:Object.keys() 可以返回一个包含所有可枚举属性的字符串数组Object.getOwnPropertyNames() 可以返回所有实例属性,无论是否可枚举//原型模式的实现:function Person() {}Person.prototype.name = ‘andy chen’;Person.prototype.sayName = function() { alert(this.name);};更简单的原型语法重写整个 prototype,不过会导致 constructor 改变。所以需要重新指定 constructor.//更简单的原型语法function Person() {}Person.prototype = { constructor: Person, //因为这种写法会覆盖掉原来的Person.prototype,需要重新为constructor赋值 name: ‘andy chen’, sayName: function() { alert(this.name); }};var person1 = new Person();var person2 = new Person();原型模式的问题:所有实例都共享一个prototype,类似上面的例子,person1,person2的name属性是共享的。如果修改其中一个,会导致另一个也受影响。所以,才会出现下面构造函数与原型模式组合使用4.组合使用构造函数和原型模式创建自定义类型最常见的方式就是组合使用构造函数和原型模式构造函数定义实例属性,而原型模式用于定义方法和共享的属性. 所以,上面的例子可以改写成这样:function Person(name) { this.name = name;}Person.prototype = { constructor: Person, sayName: function() { alert(this.name); }};var person1 = new Person(‘andy chen’);var person2 = new Person(‘andy chen’);除了使用组合模式创建对象,还有以下几种方式,可以针对不同的情况选择。5.动态原型模式在构造方法中,判断是否是第一次进入使用构造方法,如果是,则添加一系列的方法到原型上6.寄生构造函数模式类基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码然后再返回新创建的对象。7.稳妥构造函数模式:稳妥对象指的是没有公共属性,而且其方法也不引用 this 对象。最适合用于一些安全的环境或者在防止数据被其他程序改动时使用稳妥构造函数遵循与寄生构造函数类似的模式,但有两点不同:新创建的对象实例不引用 this.不使用 new 操作符调用构造函数继承OO 语言一般拥有两种继承方式:接口继承(只继承方法签名)以及实现继承(继承实际方法)ES 无法像其他 OO 语言一样支持接口继承,只能依靠原型链实现 实现继承1. 原型链要了解原型链的概念,先回顾一下构造函数,原型和实例之间的关系(参考图 6-1)每个构造函数都有一个原型对象,原型对象包含一个指向构造函数的指针.每个实例都包含一个指向原型对象的内部指针的内部属性(在 chrome 中一般为proto属性)那么,如果我们有个新的构造函数,并让它的原型对象等于另一个类型的实例,结果会怎样.对于这个新的构造函数,它的原型对象就变成了另一个类型的实例,而这个实例中,又包含一个内部属性,指向了另一个原型对象(该原型对象内部 constructor 指向另一个构造函数),如果这个原型对象又是另一个类型的实例,则它又包含了一个内部属性,继续指向上层的原型对象。这样层层递进,就形成了原型链。如下图:特点在实例中搜索属性的时候,便是基于原型链来搜索的,先搜索实例,再在原型链上一层层往上搜,直到找到或者到原型链末端才会停下来由于所有引用类型都继承了 Object,所以原型链的最顶层是 Object使用原型链实现继承时,不能使用对象字面量创建原型方法,因为这样会重写原型链原型链实现继承的方式:function Animal() { this.name = ‘animal’;}Animal.prototype.getName = function() { return this.name;};function Cat() { this.catName = ‘cat’;}Cat.prototype = new Animal();var cat1 = new Cat();var cat2 = new Cat();alert(cat1.getName()); //由于第10行,将Cat的原型指向Animal的实例,因为实例中有指向Animal.prototype的指针。所以,这里可以访问到getName()cat1.name = ‘changed name’;alert(cat2.getName());原型链的问题:使用原型链,由于是使用新的实例作为子类型的原型,实例中却包含了父类型的属性,所以原来父类型的属性,就都到了子类型的原型上了。这就会造成子类型的不同实例会共享同个属性.如上例子中,第 15 行,改变 cat1 实例的 name 属性影响到了 cat2 的 name 属性创建子类型的时候,不能向父类型传递参数2. 借用构造函数由于原型链存在问题,所以便出现了借用构造函数的方法在子类型的构造方法中,调用父类型的构造方法:SuperType.call(this); 将父类型的属性添加到子类型上,并且可以传递参数给父类型借用构造函数实现继承的方式:function Animal() { this.name = ‘animal’;}function Cat() { Animal.call(this);}var cat1 = new Cat();var cat2 = new Cat();cat1.name = ‘changed name’;alert(cat1.name); //changed namealert(cat2.name); //animal //借用构造函数的方式,各实例之间的属性便不会互相影响借用构造函数问题:类似创建对象单纯使用构造方法一样,也会造成公有的方法无法公用。所以一般也很少单独使用此方式3. 组合继承组合原型链以及借用构造函数使用原型链实现对原型属性和方法的继承借用构造函数来实现对实例中属性的继承。function Animal() { this.name = ‘animal’;}Animal.prototype.getName = function() { return this.name;};function Cat() { Animal.call(this); //借用构造函数}Cat.prototype = new Animal(); //原型链方式Cat.prototype.constructor = Cat;//这里可以var cat1 = new Cat();var cat2 = new Cat();cat1.name = ‘changed name’;alert(cat1.getName()); //changed namealert(cat2.getName()); //animal组合继承的问题:父类的属性会存在于子类型的原型上,导致被不同实例共享。虽然由于借用构造函数之后,导致实例上又重写了这些属性,所以每个实例有各自的属性。另外,instanceof 和 isPrototypeOf 能够识别基于组合继承创建的对象组合继承,并不完美因为我们只需要继承父类型原型上的属性而已,不需要父类型实例的属性。还有更好的方法,但我们首先要先了解一下其他继承方式4. 原型式继承//如果o为某个对象的prototype,则object返回的 对象,包含了该对象原型上的所有方法function object(o) { function F() {} F.prototype = o; return new F();}Es5 新增的 Object.create() ,类似这样。 在没有必要创建构造函数,只想让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的。不过,包含引用类型值的属性始终会共享5. 寄生式继承在复制新对象后,继续以某种方式增强对象,即为寄生式继承。function createAnother(original) { var clone = object(original); clone.sayHi = function() { doSomeThing(); }; return clone;}在主要考虑对象而不是自定义类型和构造函数的时候,适合使用寄生式继承缺点: 类似单纯的构造函数模式使用,函数不能复用6. 寄生组合式继承通过原型式继承,继承父类的原型方法。再通过构造函数方法,继承父类的属性。function Animal() { this.name = ‘animal’;}Animal.prototype.getName = function() { return this.name;};function Cat() { Animal.call(this); //借用构造函数}//原型继承方式function object(superProto) { function F() {} F.prototype = superProto; return new F();}Cat.prototype = object(Animal.prototype); //通过一个空的函数作为媒介,将空函数的原型指向父类型原型,并将子类型的原型指向这个空函数的实例。便只继承父类原型上的属性及方法Cat.prototype.constructor = Cat;//这里可以之后添加子类的方法Cat.prototype.run = function() { alert(‘cat run’);};var cat1 = new Cat();var cat2 = new Cat();cat1.name = ‘changed name’;alert(cat1.getName()); //changed namealert(cat2.getName()); //animal最后,寄生组合式继承是引用类型最理想的继承范式。上述代码还能再进一步优化。//原型继承方式function object(superProto) { function F() {} F.prototype = superProto; return new F();}//公用的继承方法function inheritPrototype(subType, superType) { subType.prototype = object(superType.prototype); subType.prototype.constructor = subType;}function Animal() { this.name = ‘animal’;}Animal.prototype.getName = function() { return this.name;};function Cat() { Animal.call(this); //借用构造函数}inheritPrototype(Cat, Animal); //调用此方法继承原型//这里可以之后添加子类的方法Cat.prototype.run = function() { alert(‘cat run’);};var cat1 = new Cat();var cat2 = new Cat();cat1.name = ‘changed name’;alert(cat1.getName()); //changed namealert(cat2.getName()); //animal小结这是 js 对象的创建以及继承,es6 中新增了关键字class和extend。方便我们进行面向对象的编程。但是理解背后的继承原理对我们编程过程中也是极有帮助的:)喜欢就收藏或者点个赞呗 !! ...

January 2, 2019 · 3 min · jiezi

JS 总结之原型继承方式收录

以一个父类为前提条件,列举 js 继承的继承方式:function Person (age) { this.age = age || 18}Person.prototype.sleep = function () { console.log(‘sleeping’)}???? 方式 1:原型链继承(不推荐)function Programmer() {}Programmer.prototype = new Person ()Programmer.prototype.code = function () { console.log(‘coding’)}let jon = new Programmer()jon.code() // codingjon.sleep() // sleepingjon instanceof Person // truejon instanceof Programmer // trueObject.getPrototypeOf(jon) // Person {age: 18, code: ƒ}jon.proto // Person {age: 18, code: ƒ}缺点:无法向父类构造函数传参父类的所有属性被共享,只要一个实例修改了属性,其他所有的子类实例都会被影响???? 方式 2:借用构造函数(经典继承)(不推荐)复制父类构造函数内的属性function Programmer(name) { Person.call(this) this.name = name}let jon = new Programmer(‘jon’)jon.name // jonjon.age // 18jon.sleep() // Uncaught TypeError: jon.sleep is not a functionjon instanceof Person // falsejon instanceof Programmer // true优点:可以为父类传参避免了共享属性缺点:只是子类的实例,不是父类的实例方法都在构造函数中定义,每次创建实例都会创建一遍方法???? 方式 3:组合继承(推荐)组合 原型链继承 和 借用构造函数继承。function Programmer(age, name) { Person.call(this, age) this.name = name}Programmer.prototype = new Person()Programmer.prototype.constructor = Programmer // 修复构造函数指向let jon = new Programmer(18, ‘jon’)jon.age // 18jon.name // jonlet flash = new Programmer(22, ‘flash’)flash.age // 22flash.name // flashjon.age // 18jon instanceof Person // truejon instanceof Programmer // trueflash instanceof Person // trueflash instanceof Programmer // true优点:融合原型链继承和构造函数的优点,是 JavaScript 中最常用的继承模式缺点:调用了两次父类构造函数???? 方式 4:原型式继承(不推荐)function create(o) { function F() {} F.prototype = o return new F()}let obj = { gift: [‘a’, ‘b’]}let jon = create(obj)let xiaoming = create(obj)jon.gift.push(‘c’)xiaoming.gift // [‘a’, ‘b’, ‘c’]缺点:共享了属性和方法???? 方式 5:寄生式继承(不推荐)创建一个仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象,最后返回对象function createObj (o) { var clone = Object.create(o) clone.sayName = function () { console.log(‘hi’) } return clone}缺点:跟借用构造函数模式一样,每次创建对象都会创建一遍方法???? 方式 6:寄生组合继承(最佳)子类构造函数复制父类的自身属性和方法,子类原型只接受父类的原型属性和方法:function create(prototype) { function Super() {} Super.prototype = prototype return new Super()}function Programmer(age, name) { Person.call(this, age) this.name = name}Programmer.prototype = create(Person.prototype)Programmer.prototype.constructor = Programmer // 修复构造函数指向let jon = new Programmer(18, ‘jon’)jon.name // jon进阶封装:function create(prototype) { function Super() {} Super.prototype = prototype return new Super()}function prototype(child, parent) { let prototype = create(parent.prototype) prototype.constructor = child // 修复构造函数指向 child.prototype = prototype}function Person (age) { this.age = age || 18}Person.prototype.sleep = function () { console.log(‘sleeping’)}function Programmer(age, name) { Person.call(this, age) this.name = name}prototype(Programmer, Person)let jon = new Programmer(18, ‘jon’)jon.name // jon引用《JavaScript 高级程序设计》中对寄生组合式继承的夸赞就是:这种方式的高效率体现它只调用了一次 Parent 构造函数,并且因此避免了在 Parent.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用 instanceof 和 isPrototypeOf。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。???? 方式 7:ES6 extends(最佳)// 父类class Person { constructor(age) { this.age = age } sleep () { console.log(‘sleeping’) }}// 子类class Programmer extends Person { constructor(age, name) { super(age) this.name = name } code () { console.log(‘coding’) }}let jon = new Programmer(18, ‘jon’)jon.name // jonjon.age // 18let flash = new Programmer(22, ‘flash’)flash.age // 22flash.name // flashjon instanceof Person // truejon instanceof Programmer // trueflash instanceof Person // trueflash instanceof Programmer // true优点:不用手动设置原型。缺点:新语法,只要部分浏览器支持,需要转为 ES5 代码。???? 参考《JavaScript 高级程序设计(第 3 版)》6.3 继承《JavaScript 深入之继承的多种方式和优缺点》 by 冴羽《ECMAScript 6 入门》Class 的继承 by 阮一峰 ...

December 23, 2018 · 2 min · jiezi

一文让你理解什么是 JS 原型

前言最近整理了一部分的JavaScript知识点,由于js高级阶段涉及知识点比较复杂,文章一直没更新,这里单独将原型部分的概念拎出来理解下。1.原型1.1 传统构造函数存在问题通过自定义构造函数的方式,创建小狗对象:function Dog(name, age) { this.name = name; this.age = age; this.say = function() { console.log(‘汪汪汪’); }}var dog1 = new Dog(‘哈士奇’, 1.5);var dog2 = new Dog(‘大黄狗’, 0.5);console.log(dog1);console.log(dog2);console.log(dog1.say == dog2.say); //输出结果为false画个图理解下:每次创建一个对象的时候,都会开辟一个新的空间,我们从上图可以看出,每只创建的小狗有一个say方法,这个方法都是独立的,但是功能完全相同。随着创建小狗的数量增多,造成内存的浪费就更多,这就是我们需要解决的问题。为了避免内存的浪费,我们想要的其实是下图的效果:解决方法:这里最好的办法就是将函数体放在构造函数之外,在构造函数中只需要引用该函数即可。function sayFn() { console.log(‘汪汪汪’);}function Dog(name, age) { this.name = name; this.age = age; this.say = sayFn();}var dog1 = new Dog(‘哈士奇’, 1.5);var dog2 = new Dog(‘大黄狗’, 0.5);console.log(dog1);console.log(dog2);console.log(dog1.say == dog2.say); //输出结果为true这样写依然存在问题:全局变量增多,会增加引入框架命名冲突的风险代码结构混乱,会变得难以维护想要解决上面的问题就需要用到构造函数的原型概念。1.2 原型的概念prototype:原型。每个构造函数在创建出来的时候系统会自动给这个构造函数创建并且关联一个空的对象。这个空的对象,就叫做原型。关键点:每一个由构造函数创建出来的对象,都会默认的和构造函数的原型关联;当使用一个方法进行属性或者方法访问的时候,会先在当前对象内查找该属性和方法,如果当前对象内未找到,就会去跟它关联的原型对象内进行查找;也就是说,在原型中定义的方法跟属性,会被这个构造函数创建出来的对象所共享;访问原型的方式:构造函数名.prototype。示例图:示例代码: 给构造函数的原型添加方法function Dog(name,age){ this.name = name; this.age = age;}// 给构造函数的原型 添加say方法Dog.prototype.say = function(){ console.log(‘汪汪汪’);}var dog1 = new Dog(‘哈士奇’, 1.5);var dog2 = new Dog(‘大黄狗’, 0.5);dog1.say(); // 汪汪汪dog2.say(); // 汪汪汪我们可以看到,本身Dog这个构造函数中是没有say这个方法的,我们通过Dog.prototype.say的方式,在构造函数Dog的原型中创建了一个方法,实例化出来的dog1、dog2会先在自己的对象先找say方法,找不到的时候,会去他们的原型对象中查找。如图所示:在构造函数的原型中可以存放所有对象共享的数据,这样可以避免多次创建对象浪费内存空间的问题。1.3 原型的使用1、使用对象的动态特性使用对象的动态属性,其实就是直接使用prototype为原型添加属性或者方法。function Person () {}Person.prototype.say = function () { console.log( ‘讲了一句话’ );};Person.prototype.age = 18;var p = new Person();p.say(); // 讲了一句话console.log(p.age); // 182、直接替换原型对象每次构造函数创建出来的时候,都会关联一个空对象,我们可以用一个对象替换掉这个空对象。function Person () {}Person.prototype = { say : function () { console.log( ‘讲了一句话’ ); },};var p = new Person();p.say(); // 讲了一句话注意:使用原型的时候,有几个注意点需要注意一下,我们通过几个案例来了解一下。使用对象.属性名去获取对象属性的时候,会先在自身中进行查找,如果没有,就去原型中查找;// 创建一个英雄的构造函数 它有自己的 name 和 age 属性function Hero(){ this.name=“德玛西亚之力”; this.age=18;}// 给这个构造函数的原型对象添加方法和属性Hero.prototype.age= 30;Hero.prototype.say=function(){ console.log(‘人在塔在!!!’);}var h1 = new Hero();h1.say(); // 先去自身中找 say 方法,没有再去原型中查找 打印:‘人在塔在!!!‘console.log(p1.name); // “德玛西亚之力"console.log(p1.age); // 18 先去自身中找 age 属性,有的话就不去原型中找了使用对象.属性名去设置对象属性的时候,只会在自身进行查找,如果有,就修改,如果没有,就添加;// 创建一个英雄的构造函数function Hero(){ this.name=“德玛西亚之力”;}// 给这个构造函数的原型对象添加方法和属性Hero.prototype.age = 18;var h1 = new Hero();console.log(h1); // {name:“德玛西亚之力”}console.log(h1.age); // 18h1.age = 30; // 设置的时候只会在自身中操作,如果有,就修改,如果没有,就添加 不会去原型中操作console.log(h1); // {name:“德玛西亚之力”,age:30}console.log(h1.age); // 30一般情况下,不会将属性放在原型中,只会将方法放在原型中;在替换原型的时候,替换之前创建的对象,和替换之后创建的对象的原型不一致!!!// 创建一个英雄的构造函数 它有自己的 name 属性function Hero(){ this.name=“德玛西亚之力”;}// 给这个构造函数的默认原型对象添加 say 方法Hero.prototype.say = function(){ console.log(‘人在塔在!!!’);}var h1 = new Hero();console.log(h1); // {name:“德玛西亚之力”}h1.say(); // ‘人在塔在!!!’// 开辟一个命名空间 obj,里面有个 kill 方法var obj = { kill : function(){ console.log(‘大宝剑’); }}// 将创建的 obj 对象替换原本的原型对象Hero.prototype = obj;var h2 = new Hero();h1.say(); // ‘人在塔在!!!‘h2.say(); // 报错h1.kill(); // 报错h2.kill(); // ‘大宝剑’画个图理解下:图中可以看出,实例出来的h1对象指向的原型中,只有say()方法,并没有kill()方法,所以h1.kill()会报错。同理,h2.say()也会报错。1.4 __proto__属性在js中以_开头的属性名为js的私有属性,以__开头的属性名为非标准属性。__proto__是一个非标准属性,最早由firefox提出来。1、构造函数的 prototype 属性之前我们访问构造函数原型对象的时候,使用的是prototype属性:function Person(){}//通过构造函数的原型属性prototype可以直接访问原型Person.prototype;在之前我们是无法通过构造函数new出来的对象访问原型的:function Person(){}var p = new Person();//以前不能直接通过p来访问原型对象2、实例对象的 proto 属性__proto__属性最早是火狐浏览器引入的,用以通过实例对象来访问原型,这个属性在早期是非标准的属性,有了__proto__属性,就可以通过构造函数创建出来的对象直接访问原型。function Person(){}var p = new Person();//实例对象的__proto__属性可以方便的访问到原型对象p.proto;//既然使用构造函数的prototype和实例对象的__proto__属性都可以访问原型对象//就有如下结论p.proto === Person.prototype;如图所示:3、__proto__属性的用途可以用来访问原型;在实际开发中除非有特殊的需求,不要轻易的使用实例对象的__proto__属性去修改原型的属性或方法;在调试过程中,可以轻易的查看原型的成员;由于兼容性问题,不推荐使用。3.5 constuctor属性constructor:构造函数,原型的constructor属性指向的是和原型关联的构造函数。示例代码:function Dog(){ this.name=“husky”;}var d=new Dog();// 获取构造函数console.log(Dog.prototype.constructor); // 打印构造函数 Dogconsole.log(d.proto.constructor); // 打印构造函数 Dog如图所示:获取复杂类型的数据类型:通过obj.constructor.name的方式,获取当前对象obj的数据类型。在一个的函数中,有个返回值name,它表示的是当前函数的函数名;function Teacher(name,age){ this.name = name; this.age = age;}var teacher = new Teacher();// 假使我们只知道一个对象teacher,如何获取它的类型呢?console.log(teacher.proto.constructor.name); // Teacherconsole.log(teacher.constructor.name); // Teacher实例化出来的teacher对象,它的数据类型是啥呢?我们可以通过实例对象teacher.proto,访问到它的原型对象,再通过.constructor访问它的构造函数,通过.name获取当前函数的函数名,所以就能得到当前对象的数据类型。又因为.__proto__是一个非标准的属性,而且实例出的对象继承原型对象的方法,所以直接可以写成:obj.constructor.name。1.6 原型继承原型继承:每一个构造函数都有prototype原型属性,通过构造函数创建出来的对象都继承自该原型属性。所以可以通过更改构造函数的原型属性来实现继承。继承的方式有多种,可以一个对象继承另一个对象,也可以通过原型继承的方式进行继承。1、简单混入继承直接遍历一个对象,将所有的属性和方法加到另一对象上。var animal = { name:“Animal”, sex:“male”, age:5, bark:function(){ console.log(“Animal bark”); }};var dog = {};for (var k in animal){ dog[k]= animal[k];}console.log(dog); // 打印的对象与animal一模一样缺点:只能一个对象继承自另一个对象,代码复用太低了。2、混入式原型继承混入式原型继承其实与上面的方法类似,只不过是将遍历的对象添加到构造函数的原型上。var obj={ name:‘zs’, age:19, sex:‘male’ }function Person(){ this.weight=50;}for(var k in obj){ // 将obj里面的所有属性添加到 构造函数 Person 的原型中 Person.prototype[k] = obj[k];}var p1=new Person();var p2=new Person();var p3=new Person();console.log(p1.name); // ‘zs’console.log(p2.age); // 19console.log(p3.sex); // ‘male’面向对象思想封装一个原型继承我们可以利用面向对象的思想,将面向过程进行封装。function Dog(){ this.type = ‘yellow Dog’;}// 给构造函数 Dog 添加一个方法 extendDog.prototype.extend = function(obj){ // 使用混入式原型继承,给 Dog 构造函数的原型继承 obj 的属性和方法 for (var k in obj){ this[k]=obj[k]; }}// 调用 extend 方法Dog.prototype.extend({ name:“二哈”, age:“1.5”, sex:“公”, bark:function(){ console.log(‘汪汪汪’); }});3、替换式原型继承替换式原型继承,在上面已经举过例子了,其实就是将一个构造函数的原型对象替换成另一个对象。function Person(){ this.weight=50;}var obj={ name:‘zs’, age:19, sex:‘male’}// 将一个构造函数的原型对象替换成另一个对象Person.prototype = obj;var p1=new Person();var p2=new Person();var p3=new Person();console.log(p1.name); // ‘zs’console.log(p2.age); // 19console.log(p3.sex); // ‘male’之前我们就说过,这样做会产生一个问题,就是替换的对象会重新开辟一个新的空间。替换式原型继承时的bug替换原型对象的方式会导致原型的constructor的丢失,constructor属性是默认原型对象指向构造函数的,就算是替换了默认原型对象,这个属性依旧是默认原型对象指向构造函数的,所以新的原型对象是没有这个属性的。解决方法:手动关联一个constructor属性function Person() { this.weight = 50;}var obj = { name: ‘zs’, age: 19, sex: ‘male’}// 在替换原型对象函数之前 给需要替换的对象添加一个 constructor 属性 指向原本的构造函数obj.constructor = Person;// 将一个构造函数的原型对象替换成另一个对象Person.prototype = obj;var p1 = new Person();console.log(p1.proto.constructor === Person); // true4、Object.create()方法实现原型继承当我们想把对象1作为对象2的原型的时候,就可以实现对象2继承对象1。前面我们了解了一个属性:proto,实例出来的对象可以通过这个属性访问到它的原型,但是这个属性只适合开发调试时使用,并不能直接去替换原型对象。所以这里介绍一个新的方法:Object.create()。语法: var obj1 = Object.create(原型对象);示例代码: 让空对象obj1继承对象obj的属性和方法var obj = { name : ‘盖伦’, age : 25, skill : function(){ console.log(‘大宝剑’); }}// 这个方法会帮我们创建一个原型是 obj 的对象var obj1 = Object.create(obj);console.log(obj1.name); // “盖伦"obj1.skill(); // “大宝剑"兼容性:由于这个属性是ECMAScript5的时候提出来的,所以存在兼容性问题。利用浏览器的能力检测,如果存在Object.create则使用,如果不存在的话,就创建构造函数来实现原型继承。// 封装一个能力检测函数function create(obj){ // 判断,如果浏览器有 Object.create 方法的时候 if(Object.create){ return Object.create(obj); }else{ // 创建构造函数 Fun function Fun(){}; Fun.prototype = obj; return new Fun(); }}var hero = { name: ‘盖伦’, age: 25, skill: function () { console.log(‘大宝剑’); }}var hero1 = create(hero);console.log(hero1.name); // “盖伦"console.log(hero1.proto == hero); // true2.原型链对象有原型,原型本身又是一个对象,所以原型也有原型,这样就会形成一个链式结构的原型链。2.1 什么是原型链示例代码: 原型继承练习// 创建一个 Animal 构造函数function Animal() { this.weight = 50; this.eat = function() { console.log(‘蜂蜜蜂蜜’); }}// 实例化一个 animal 对象var animal = new Animal();// 创建一个 Preson 构造函数function Person() { this.name = ‘zs’; this.tool = function() { console.log(‘菜刀’); }}// 让 Person 继承 animal (替换原型对象)Person.prototype = animal;// 实例化一个 p 对象 var p = new Person();// 创建一个 Student 构造函数function Student() { this.score = 100; this.clickCode = function() { console.log(‘啪啪啪’); }}// 让 Student 继承 p (替换原型对象)Student.prototype = p;//实例化一个 student 对象var student = new Student();console.log(student); // 打印 {score:100,clickCode:fn}// 因为是一级级继承下来的 所以最上层的 Animate 里的属性也是被继承的console.log(student.weight); // 50student.eat(); // 蜂蜜蜂蜜student.tool(); // 菜刀如图所示:我们将上面的案例通过画图的方式展现出来后就一目了然了,实例对象animal直接替换了构造函数Person的原型,以此类推,这样就会形成一个链式结构的原型链。完整的原型链结合上图,我们发现,最初的构造函数Animal创建的同时,会创建出一个原型,此时的原型是一个空的对象。结合原型链的概念:“原型本身又是一个对象,所以原型也有原型”,那么这个空对象往上还能找出它的原型或者构造函数吗?我们如何创建一个空对象? 1、字面量:{};2、构造函数:new Object()。我们可以简单的理解为,这个空的对象就是,构造函数Object的实例对象。所以,这个空对象往上面找是能找到它的原型和构造函数的。// 创建一个 Animal 构造函数function Animal() { this.weight = 50; this.eat = function() { console.log(‘蜂蜜蜂蜜’); }}// 实例化一个 animal 对象var animal = new Animal();console.log(animal.proto); // {}console.log(animal.proto.proto); // {}console.log(animal.proto.proto.constructor); // function Object(){}console.log(animal.proto.proto.proto); // null如图所示:2.2 原型链的拓展1、描述出数组[]的原型链结构// 创建一个数组var arr = new Array();// 我们可以看到这个数组是构造函数 Array 的实例对象,所以他的原型应该是:console.log(Array.prototype); // 打印出来还是一个空数组// 我们可以继续往上找 console.log(Array.prototype.proto); // 空对象// 继续console.log(Array.prototype.proto.proto) // null如图所示:2、扩展内置对象给js原有的内置对象,添加新的功能。注意:这里不能直接给内置对象的原型添加方法,因为在开发的时候,大家都会使用到这些内置对象,假如大家都是给内置对象的原型添加方法,就会出现问题。错误的做法:// 第一个开发人员给 Array 原型添加了一个 say 方法Array.prototype.say = function(){ console.log(‘哈哈哈’);}// 第二个开发人员也给 Array 原型添加了一个 say 方法Array.prototype.say = function(){ console.log(‘啪啪啪’);}var arr = new Array();arr.say(); // 打印 “啪啪啪” 前面写的会被覆盖为了避免出现这样的问题,只需自己定义一个构造函数,并且让这个构造函数继承数组的方法即可,再去添加新的方法。// 创建一个数组对象 这个数组对象继承了所有数组中的方法var arr = new Array();// 创建一个属于自己的构造函数function MyArray(){}// 只需要将自己创建的构造函数的原型替换成 数组对象,就能继承数组的所有方法MyArray.prototype = arr;// 现在可以单独的给自己创建的构造函数的原型添加自己的方法MyArray.prototype.say = function(){ console.log(‘这是我自己添加的say方法’);}var arr1 = new MyArray();arr1.push(1); // 创建的 arr1 对象可以使用数组的方法arr1.say(); // 也可以使用自己添加的方法 打印“这是我自己添加的say方法”console.log(arr1); // [1]2.3 属性的搜索原则当通过对象名.属性名获取属性是 ,会遵循以下属性搜索的原则:1-首先去对象自身属性中找,如果找到直接使用,2-如果没找到去自己的原型中找,如果找到直接使用,3-如果没找到,去原型的原型中继续找,找到直接使用,4-如果没有会沿着原型不断向上查找,直到找到null为止。 ...

November 30, 2018 · 4 min · jiezi

JavaScript常用八种继承方案

更新:在常用七种继承方案的基础之上增加了ES6的类继承,所以现在变成八种啦,欢迎加高级前端进阶群一起学习(文末)。— 2018.10.301、原型链继承构造函数、原型和实例之间的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个原型对象的指针。继承的本质就是复制,即重写原型对象,代之以一个新类型的实例。function SuperType() { this.property = true;}SuperType.prototype.getSuperValue = function() { return this.property;}function SubType() { this.subproperty = false;}// 这里是关键,创建SuperType的实例,并将该实例赋值给SubType.prototypeSubType.prototype = new SuperType(); SubType.prototype.getSubValue = function() { return this.subproperty;}var instance = new SubType();console.log(instance.getSuperValue()); // true原型链方案存在的缺点:多个实例对引用类型的操作会被篡改。function SuperType(){ this.colors = [“red”, “blue”, “green”];}function SubType(){}SubType.prototype = new SuperType();var instance1 = new SubType();instance1.colors.push(“black”);alert(instance1.colors); //“red,blue,green,black"var instance2 = new SubType(); alert(instance2.colors); //“red,blue,green,black"2、借用构造函数继承使用父类的构造函数来增强子类实例,等同于复制父类的实例给子类(不使用原型)function SuperType(){ this.color=[“red”,“green”,“blue”];}function SubType(){ //继承自SuperType SuperType.call(this);}var instance1 = new SubType();instance1.color.push(“black”);alert(instance1.color);//“red,green,blue,black"var instance2 = new SubType();alert(instance2.color);//“red,green,blue"核心代码是SuperType.call(this),创建子类实例时调用SuperType构造函数,于是SubType的每个实例都会将SuperType中的属性复制一份。缺点:只能继承父类的实例属性和方法,不能继承原型属性/方法无法实现复用,每个子类都有父类实例函数的副本,影响性能3、组合继承组合上述两种方法就是组合继承。用原型链实现对原型属性和方法的继承,用借用构造函数技术来实现实例属性的继承。function SuperType(name){ this.name = name; this.colors = [“red”, “blue”, “green”];}SuperType.prototype.sayName = function(){ alert(this.name);};function SubType(name, age){ // 继承属性 // 第二次调用SuperType() SuperType.call(this, name); this.age = age;}// 继承方法// 构建原型链// 第一次调用SuperType()SubType.prototype = new SuperType(); // 重写SubType.prototype的constructor属性,指向自己的构造函数SubTypeSubType.prototype.constructor = SubType; SubType.prototype.sayAge = function(){ alert(this.age);};var instance1 = new SubType(“Nicholas”, 29);instance1.colors.push(“black”);alert(instance1.colors); //“red,blue,green,black"instance1.sayName(); //“Nicholas”;instance1.sayAge(); //29var instance2 = new SubType(“Greg”, 27);alert(instance2.colors); //“red,blue,green"instance2.sayName(); //“Greg”;instance2.sayAge(); //27缺点:第一次调用SuperType():给SubType.prototype写入两个属性name,color。第二次调用SuperType():给instance1写入两个属性name,color。实例对象instance1上的两个属性就屏蔽了其原型对象SubType.prototype的两个同名属性。所以,组合模式的缺点就是在使用子类创建实例对象时,其原型中会存在两份相同的属性/方法。4、原型式继承利用一个空对象作为中介,将某个对象直接赋值给空对象构造函数的原型。function object(obj){ function F(){} F.prototype = obj; return new F();}object()对传入其中的对象执行了一次浅复制,将构造函数F的原型直接指向传入的对象。var person = { name: “Nicholas”, friends: [“Shelby”, “Court”, “Van”]};var anotherPerson = object(person);anotherPerson.name = “Greg”;anotherPerson.friends.push(“Rob”);var yetAnotherPerson = object(person);yetAnotherPerson.name = “Linda”;yetAnotherPerson.friends.push(“Barbie”);alert(person.friends); //“Shelby,Court,Van,Rob,Barbie"缺点:原型链继承多个实例的引用类型属性指向相同,存在篡改的可能。无法传递参数另外,ES5中存在Object.create()的方法,能够代替上面的object方法。5、寄生式继承核心:在原型式继承的基础上,增强对象,返回构造函数function createAnother(original){ var clone = object(original); // 通过调用 object() 函数创建一个新对象 clone.sayHi = function(){ // 以某种方式来增强对象 alert(“hi”); }; return clone; // 返回这个对象}函数的主要作用是为构造函数新增属性和方法,以增强函数var person = { name: “Nicholas”, friends: [“Shelby”, “Court”, “Van”]};var anotherPerson = createAnother(person);anotherPerson.sayHi(); //“hi"缺点(同原型式继承):原型链继承多个实例的引用类型属性指向相同,存在篡改的可能。无法传递参数6、寄生组合式继承结合借用构造函数传递参数和寄生模式实现继承function inheritPrototype(subType, superType){ var prototype = Object.create(superType.prototype); // 创建对象,创建父类原型的一个副本 prototype.constructor = subType; // 增强对象,弥补因重写原型而失去的默认的constructor 属性 subType.prototype = prototype; // 指定对象,将新创建的对象赋值给子类的原型}// 父类初始化实例属性和原型属性function SuperType(name){ this.name = name; this.colors = [“red”, “blue”, “green”];}SuperType.prototype.sayName = function(){ alert(this.name);};// 借用构造函数传递增强子类实例属性(支持传参和避免篡改)function SubType(name, age){ SuperType.call(this, name); this.age = age;}// 将父类原型指向子类inheritPrototype(SubType, SuperType);// 新增子类原型属性SubType.prototype.sayAge = function(){ alert(this.age);}var instance1 = new SubType(“xyc”, 23);var instance2 = new SubType(“lxy”, 23);instance1.colors.push(“2”); // [“red”, “blue”, “green”, “2”]instance1.colors.push(“3”); // [“red”, “blue”, “green”, “3”]这个例子的高效率体现在它只调用了一次SuperType 构造函数,并且因此避免了在SubType.prototype 上创建不必要的、多余的属性。于此同时,原型链还能保持不变;因此,还能够正常使用instanceof 和isPrototypeOf()这是最成熟的方法,也是现在库实现的方法7、混入方式继承多个对象function MyClass() { SuperClass.call(this); OtherSuperClass.call(this);}// 继承一个类MyClass.prototype = Object.create(SuperClass.prototype);// 混合其它Object.assign(MyClass.prototype, OtherSuperClass.prototype);// 重新指定constructorMyClass.prototype.constructor = MyClass;MyClass.prototype.myMethod = function() { // do something};Object.assign会把 OtherSuperClass原型上的函数拷贝到 MyClass原型上,使 MyClass 的所有实例都可用 OtherSuperClass 的方法。8、ES6类继承extendsextends关键字主要用于类声明或者类表达式中,以创建一个类,该类是另一个类的子类。其中constructor表示构造函数,一个类中只能有一个构造函数,有多个会报出SyntaxError错误,如果没有显式指定构造方法,则会添加默认的 constructor方法,使用例子如下。class Rectangle { // constructor constructor(height, width) { this.height = height; this.width = width; } // Getter get area() { return this.calcArea() } // Method calcArea() { return this.height * this.width; }}const rectangle = new Rectangle(10, 20);console.log(rectangle.area);// 输出 200—————————————————————–// 继承class Square extends Rectangle { constructor(length) { super(length, length); // 如果子类中存在构造函数,则需要在使用“this”之前首先调用 super()。 this.name = ‘Square’; } get area() { return this.height * this.width; }}const square = new Square(10);console.log(square.area);// 输出 100extends继承的核心代码如下,其实现和上述的寄生组合式继承方式一样function _inherits(subType, superType) { // 创建对象,创建父类原型的一个副本 // 增强对象,弥补因重写原型而失去的默认的constructor 属性 // 指定对象,将新创建的对象赋值给子类的原型 subType.prototype = Object.create(superType && superType.prototype, { constructor: { value: subType, enumerable: false, writable: true, configurable: true } }); if (superType) { Object.setPrototypeOf ? Object.setPrototypeOf(subType, superType) : subType.proto = superType; }}总结1、函数声明和类声明的区别函数声明会提升,类声明不会。首先需要声明你的类,然后访问它,否则像下面的代码会抛出一个ReferenceError。let p = new Rectangle(); // ReferenceErrorclass Rectangle {}2、ES5继承和ES6继承的区别ES5的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上(Parent.call(this)).ES6的继承有所不同,实质上是先创建父类的实例对象this,然后再用子类的构造函数修改this。因为子类没有自己的this对象,所以必须先调用父类的super()方法,否则新建实例报错。《javascript高级程序设计》笔记:继承 MDN之Object.create() MDN之Class交流本人Github链接如下,欢迎各位Starhttp://github.com/yygmind/blog我是木易杨,现在是网易高级前端工程师,目前维护了一个高级前端进阶群,欢迎加入。接下来让我带你走进高级前端的世界,在进阶的路上,共勉! ...

November 3, 2018 · 2 min · jiezi

子类使用父类构造方法设置自己的属性

首先需要知道执行流程,当new一个子类的实例时,执行顺序为 父类构造方法—》子类属性赋值所以可以通过对子类属性设置set和get方法来实现对子类属性的赋值,请看以下代码父类: [code lang="java"] import com.taiji.tr.errorlog.MyLogger;import java.util.Set;/** * @Author: liyj * @Description:* @Date:Created in 2018/1/25 * @Modified by : */ public abstract class ProcedureSql {CompProperDataBean compProperDataBean;public ProcedureSql(CompProperDataBean compProperDataBean) { this.compProperDataBean = compProperDataBean; init(); }public CompProperDataBean getCompProperDataBean() { return compProperDataBean; }public abstract void init(); } [/code] 子类:[code lang=“java”]package com.taiji.tr.bean;import com.taiji.tr.errorlog.MyLogger;/*** @Author: liyj* @Description: &lt;p/&gt;* @Date:Created in 2018/1/31* @Modified by :*/public class HiveProcedureSql extends ProcedureSql {public String tempSqlStr;public String getTempSqlStr() {return tempSqlStr;}public void setTempSqlStr(String tempSqlStr) {this.tempSqlStr = tempSqlStr;}public HiveProcedureSql(CompProperDataBean compProperDataBean) {super(compProperDataBean);}@Overridepublic void init() {//判断是否需要创建分区if (compProperDataBean.isPartition()) {//创建分区setTempSqlStr("’ INTO table " + compProperDataBean.getTable_name() + " PARTITION (");} else {setTempSqlStr("’ into table " + compProperDataBean.getTable_name());}MyLogger.info(“sql语句”, compProperDataBean.getIdentify(), tempSqlStr);}}[/code] ...

February 6, 2018 · 1 min · jiezi