关于面向对象的理解(构造函数、原型链)
OOP(面向对象编程思想)
它是一种编程思想,让我们基于类和实例的概念来编程开发和学习。
单例模式
单例设计模式
是一种把对象数据类型实现把描述同一件事物的属性或者特征归纳汇总在一起,以此避免全局变量冲突问题的方式和思想。
把描述同一件事务的属性或者方法存放在某一个命名空间下,多个命名空间中的属性和方法是互不干扰的。
1 | //=>单例模式 |
高级单例模式
基于JS高阶编程技巧惰性思想,来实现的单例模式,并且可以把一些常用的设计模式(如:命令模式、发布订阅模式、promise设计模式等)融合进来,最后清晰的规划我们的业务逻辑代码,方便后期二次开发和维护,这种设计思想综合体就是高级单例模式,也是最常用的。
1 | var serchModel=(function(){ |
对象、类、实例
对象:万物皆对象
类:对象的具体细分(按照属性或特性细分的一些类别)
实例:某一个类中的具体事物
JS常用的内置类
数据类型的类
Number:每个数字或者NaN是它的一个实例
String:字符串类
Boolean:布尔类
Null
Undefined:浏览器屏蔽了我们操作null和udnefined这个类
Object:对象类,每个对象数据类型都是它的实例
• Array:数组类
• RegExp:正则类
• Date:日期类
Function:函数类,每个函数都是它的一个实例
元素对象或者元素集合的类
HTMLCollection:元素集合类
• getElementsByTagName()
• getElementsByClassName()
• querySelectorAll
NodeList:节点集合类
• childNodes
• getElementsByName()
HTMLDivElement
HTMLElement
Element(标签类)
Node(节点类,Element只是其中的一个元素节点)
为什么getElementById的上下文只能是document?(即getElementById为什么只能通过document来调用)?
因为只有在Document这个类上才有getElementById这个方法,其他类上(如:HTMLDivElement类)没有getElementById这个方法,而document是HTMLDocument这个类的一个实例,能通过document.proto.proto找到Document这个类的原型上公有的getElementById方法。
基于面向对象创建数据
创建方式:两种
1.字面量创建方式
• var obj={};
2.实例创建方式(构造函数方式)
• var obj=new Array();如果传递的参数只是一个数值
1、对于引用数据类型来说,两种创建方式是大致相同的,只不过,两种方法创建的语法不同。
两种创建方式在核心意义上没有差别,都是创建Array这个类的一个实例,但是在语法上是有区别的
2、构造函数创建方式
new Array(10):创建一个长度为10的数组,数组中的每一项都是空
new Array(‘10’):如果只传递一个实参,并且实参不是数字,相当于把当前值作为数组的第一项存储进来
new Array(10,20,30):如果传递多个实参,不是设置长度,而是把传递的内容当做数组中的每一项存储起来
2、对于基本数据类型来说,字面量方式创建出来的结果和实例方式创建出来的结果是有一定区别的,从严格意义上来讲,只有实例创建出来的结果才是标准的对象,数据类型值也是标准的对象数据类型,也是标准的内置类的实例;对于字面量方式创建出来的结果是基本数据类型的值,不是严格的实例,但是由于JS的松散特点,导致了可以使用 内置类.prototype上提供的方法;
构造函数设计模式
使用构造函数方式,主要是为了创建类和实例的,也就是基于面向对象思想来实现一些需求
在JS中,当我们使用new xxx()执行函数的时候,此时的函数就不是普通的函数了,而是变为一个类,返回的结果叫做当前类的实例,我们这种new xxx执行的方式称之为构造函数设计模式
1 | function fn(){ } |
构造函数执行时new都干了些什么?
在new Fn(),执行的时候,是先把函数执行了,后面的Fn()先执行,形成一个私有作用域,形参赋值变量提升,在变量提升完了之后,new操作符才起了作用,此时,浏览器开始创建一个新的对象,让Fn中的this指向这个新创建的对象(实例),然后让这个对象的proto指向Fn.prototype,然后JS代码才开始继续往下执行,开始往类中添加每个实例私有的属性和方法。JS代码执行完成后,会默认返回当前创建的这个对象。
普通函数执行与构造函数执行的区别
- 在代码从上而下执行之前,构造函数有特殊的操作:浏览器会在当前的作用域中默认创建一个对象数据类型的值,并且会让当前函数中的this指向创建的这个对象。然后JS代码再执行,
- 代码执行完成后,即使函数中没有写return,在构造函数模式中:浏览器会默认的把创建的对象返回到函数外面
- 总结: 构造函数执行期间,既具备函数执行的一面,也同时具备自己独有的操作:在构造函数执行期间,浏览器会默认创建一个对象,这个对象就是当前这个构造函数(类)实例,函数执行完成后,浏览器会默认的把这个实例返回。所以new Fn()执行,Fn是一个类,返回的结果就是Fn这个类的一个实例
构造函数执行后面的‘()’问题
构造函数执行如果不需要传递参数,函数后面的()可省略,如new Fn()可写为new Fn;
注意:
• 如果要在new Fn之后直接调用实例的方法,则必须要加小括号,即必须写成new Fn().方法名
构造函数模式的返回值问题
构造函数模式中默认返回值是当前的实例,如果有return,返回分2种情况:
1、return 后面是一个基本数据类型的值,当前实例是不变的,例如return 100;我们的返回值还是当前类的实例;
2、return 后面是一个引用数据类型的值(window除外),当前实例会被返回的值给替换掉例如return {name:”李雷”}我们的返回值就不再是当前类的实例了,而是对象 {name:”李雷”};
A instanceof B
检测某一个实例是否属于这个类, 判断A实例是否属于B类
hasOwnProperty VS in
in:用来检测当前这个属性是否隶属于对象(不管是对象私有的还是公有的属性,只要有返回的就是true)
hasOwnProperty:用来检测当前这个属性是否是对象的私有属性(不仅要是对象的属性,而且需要是私有的才可以)
1 | attr in object |
原型链模式
基于构造函数模式的原型链模式解决了方法或者属性公有的问题,把实例之间公有的属性和方法写在当前类的prototype属性上;
- 每一个函数数据类型都有一个天生自带的属性:prototype(原型),并且这个属性的属性值是一个对象数据类型的值,浏览器默认为其开辟一个堆内存;
- 在浏览器给prototype开辟的这个堆内存上浏览器天生给它加了一个constructor属性(构造函数),属性值是当前函数(类)本身;
- 每一个对象数据类型(普通对象、数组、正则、实例、protoype..)也天生自带一个属性:proto,属性值指向当前实例所属类的原型(prototype);
(IE中屏蔽了对象的proto属性,但是确实有,只是不让我们使用而已) - Object是JS中所有对象数据类型的基类(最顶层的类);
原型链模式中的this分两种情况
原型模式中的this分两种情况:
- 在类中this.xxx=xxx;this->当前类的实例
- 原型链中提供的私有(公有)方法中的this问题:
总结:看执行的时候”.”前面是谁this就是谁。具体操作步骤如下
- 需要先确定this的指向(this)
- 把this替换成对应的的代码
- 按照原型链查找的机制,一步步的查找结果
重构原型
让某个构造函数的原型指向自己开辟的堆内存,但是自己开辟的堆内存当中是没有constructor属性的,所以要往自己开辟的堆内存中添加constructor属性,属性值为当前构造函数本身; contrcutor:fn
缺点:重构原型后,会导致之前添加的属性和方法都没有了,只能使用重构之后添加的属性和方法;
注意:
• 不要忘了重构之后要添加constructor属性指向当前构造函数;
• 内置类的原型不能重构,浏览器不允许我们这么做;
类的继承、封装和多态
它是一种编程思想(Object Oriented Programming),我们的编程和学习其实是按照
类和实例
来完成的
我们要学习类的继承、封装、多态
封装
把实现一个功能的代码封装在一个函数中,以后再要实现这个功能,只要执行函数方法即可,不需要重新编写代码
低耦合高内聚
减少页面代码冗余,提高代码利用率
多态
一个类(函数)的多种形态:重载、重写
重载
后台JAVA等编程语言中,对于重载的概念:方法名相同,参数名不同,叫做方法的重载
JS中没有类似于后台严格意义的重载,JS中,如果方法名相同了,最后只能保留一个(和实参没有关系)
JS中的重载:同一个方法,通过传递实参的不同,(arguments)我们完成不同的功能,我们把这个也可以理解为重载
1 | function sum(num1,num2){ |
重写
不管是后台语言还是JS,都有重写,子类重写父类的方法;
继承
原理
:子类继承父类的中的一些属性和方法
原型继承
让子类的原型指向父类的实例;
child.prototype=new parent();
- 我们首先让子类的原型指向父类的实例,然后在向子类原型上扩展方法,防止提前增加方法,等原型重新指向后,之前在子类原型上扩展的方法都没用了(子类原型已经指向新的空间地址了);
- 让子类原型重新指向父类实例,子类原型上的原有constructor就没了,为了保证构造函数的完整性,我们需要重新手动设置constructor: child.protype.constructor.call(child)
*原理 *
原型继承并不是把父类的属性和方法copy一份给子类,而是给子类和父类之间搭建一个连接的桥梁,以后子类或者子类的实例,可以通过原型链的查找机制,找到父类原型的方法,从而调取这些方法。
特征
子类不仅可以继承父类原型上的公有属性方法,也可以继承父类提供给实例的私有属性和方法,并把其放在子类的公有属性和方法上。
call继承
(继承私有的)
原理在子类的构造体中,把父类做普通方法执行,让父类方法中的this指向子类实例
特点:把父类 构造体中的私有属性和方法,原封不动复制了一份给子类实例(继承完成后,子类和父类是没有关系的)
注意我们一般把call放在第一行,就是创建子类实例的时候,首先继承,然后在给实例赋值自己私有的(好处:自己可以把继承的结果替换掉)
1 | function A(){ |
寄生组合式继承
Object.create([obj]):创建一个空对象,把obj作为新对象的原型(继承公有)
Object.create不兼容
1 | var obj={name:'李雷'}; |
child.protype= Object.create(parent.protorype);
- 利用Object.create创建一个的空对象;
- 该空对象指向parent.protorype;
- 将该对象的地址赋值给child.protype,从而导致child和child的实例都可以通过原型链找到parent的原型;
- 该方法只能继承parent的公有方法和属性
自己实现类似于Object.create的方法
1 | Object.myCreate=function myCreate(){ |
ES6中的类及继承
1 | class Fn { |