JavaScript 面向对象编程思想(二)

一:面向对象编程思想

1.单例模式

在真实项目中,为了实现模块化开发或者团队协作开发,我们经常应用单例模式(一般业务逻辑部分的代码都是依托单例模式设计规划的)

单例模式的由来:在很久以前,JS都是值类型,没有引用数据类型,如果后面编写的代码,创建的变量或者函数名和之前一样,会把之前存储的值替换掉;真实项目中,团队协作开发,如果是这样来处理,经常会导致相互代码的冲突:‘全局变量污染’  ‘全局变量冲突’

后来JS中诞生了对象数据类型,解决了上面出现的污染或者冲突问题,把描述同一件事务的特征或者属性,进行归纳汇总(放在一起),以此来避免全局变量之前的冲突

我们把对象数据类型实现 ‘把描述同一件事务的属性或者特征归纳汇总在一起,以此避免全局变量冲突问题’ 的方式和思想叫做:“单例设计模式”

1) singleton不仅仅是对象名了,在单例模式中,singleton称之为“命名空间(nameSpace)”

把描述同一件事务的属性或者方法存放在某一个命名空间下,多个命名空间中的属性和方法是互不干扰的

2.使用单例模式实现模块化开发

模块化开发:在团队协作开发的时候,我们经常会把一个复杂的页面 按照具体的功能划分为几大块,然后分别去开发,这种模块划分的思想就是模块化开发思想

真实项目中,我们可以使用单例模式(建议也使用单例模式)来实现模块化开发

在当前的命名空间下调取其它命名空间的方法,指定好对应的命名空间名字即可,使用[nameSpace].[property]就可以操作了;调取本模块中的一些方法可以直接使用this处理即可,此方法中的this一般都是当前模块的命名空间

2.高级单例模式

基于JS高阶编程技巧 “惰性思想”来实现的单例模式叫高级单例模式,并且可以把一些常用的设计模式(例如:命令模式,发布订阅设计模式,promise设计模式等)融合进来,最后清晰的规划我们业务逻辑代码,方便后期二次开发和维护

这种设计思想综合体就是高级单例模式,也是项目中最常应用的

var searchModel = (function(){

    function submit(){};
    function fn(){};

    return {
    init:function(){
        this.submit();
        this.fn()
    }
}
})()        
//searchModel.submit()

二:面向对象(OOP)

面向对象编程思想(面向过程编程思想:c语言是面向过程的),Java,php,c#,c++,.net(dot.net),Js,python,Ruby...这些都是面向对象编程的

HTML和CSS是标记语言不是编程语言,没有所谓的面向对象编程;Less和Sass预编译语言,旨在把css变为编程语言(面向对象)

对象,类,实例

对象:编程语言中的对象是一个泛指,万物皆对象(我们所要研究学习以及使用的都是对象)

类:对象的具体细分(按照属性或者特性细分为一些类别)

实例:某一类中具体的事物

【实际生活中】

自然界中万物皆对象,我们为了认知自然界,我们会把自然界中的事物按照特征进行分类,例如:动物类,植物类,微生物类;实例:狗就是动物类的一个实例

JS本身就是基于面向对象创造出来的语言(所以它是面向对象编程),我们想要学习JS,首先也要给其进行分类,我们拿出某一类中的实例,进行学习和呀研究

三:JS中常用的内置类

1.关于数据类型的

  Number:每一个数字或者NaN是它的一个实例

  String:字符串类

  Boolean:布尔类

  Null

  Undefined:浏览器屏蔽了我们操作null或者undefined这个类

  Object:对象类,每一个对象数据类型都是它的实例 ①.Array:数组类 ②.RegExp:正则类 ③.Date:日期类

  Function:函数类,每一个函数都是它的一个实例

2.关于元素对象和元素集合的

  HTMLCollection:元素集合类(getElementsByTagName/getElementsByClassName/querySelectorAll)

  NodeList:节点集合类(getElementsByName/childNodes)

  HTMLDivElement

  HTMLElement

  Element

  Node

  EventTarget

  Object

3.目前阶段学习面向对象对于我们的意义

1.例如研究数组

1)创建一个数组类的实例,研究其基础语法和结构 

2)如果想要研究数据具备的功能方法,我们只需要看Array/Object这些类上都赋予了它什么样的方法

问题: document.getElementById它的上下文只能是document,其它不可以?

解答:    因为getElementById这个方法只有Document这个类才有,其它的类没有,所以只有document这个作为Document的实例才能使用这个方法

4.基于面向对象创建数据值

var ary = [1,3,4] //字面量创建方式  -》不严谨

var ary = new Array(); //严谨的基于面向对象(构造函数)方式创建一个数组

//两种创建方式在核心意义上没有差别,都是创建Array这个类的一个实例,但是在语法上是有区别的

1.字面量创建方式传递进来什么,都是给数组每一项加入的内容

2.构造函数创建方式new Array(10) :创建一个长度为10的数组,数组中每一项都是空;  new Array('10'):如果只传递一个实参,并且实参不是数字,相当于把当前值作为数组的第一项存储进来;new Array(10,20,30):如果传递多个实参,不是设置长度,而是把传递的内容当做数组中的每一项存储起来

通过字面量创建出来的是一个基本类型值,但是通过构造函数方式创建出来的是对象数据类型的

5.构造函数设计模式(constructor)

1.使用构造函数方式,主要是为了创建类和实例的,也就是基于面向对象编程思想来实现一些需求的处理

在JS中,当我们使用new xxx()执行函数的时候,此时的函数就不是普通的函数了,而是变为一个类,返回的结果叫做当前类的实例,我们把new xxx()执行的方式称之为 ‘构造函数设计模式’

function fn(){
}
var f = new fn()  //fn是一个类,f是当前这个类的一个实例 “构造函数设计模式”
(我们一般会把类名第一个字母大写)

2. 普通函数执行 VS  构造函数执行

普通函数执行

//1.开辟一个新的私有作用域
//2.形参赋值
//3.变量提升
//4.代码自上而下执行(return后面的值就是当前函数返回的结果)
//5.栈内存释放或者不释放问题
function fn(num){
    var total = null
    total+=num
    return total 
}
  var f = fn(10)   // f=10

构造函数执行

//1.首先和普通函数一样,也需要开辟一个新的私有作用域
//2.在私有作用域中完成类似于普通函数的操作:形参赋值以及变量提升
//3.在代码自上而下执行之前,构造函数有属于自己比较特殊的操作:浏览器会在当前的作用域中默认创建一个对象数据类型的值,并且会让当前函数中的执行主体(this)指向这个创建的这个对象
//4.像普通函数一样,代码自上而下执行:this.xxx = xxx 这里操作都是在给创建的这个对象增加属性名和属性值
//5.代码执行完成后,即使函数中没有写return,在构造函数模式中:浏览器会默认的把创建的对象返回到函数的外面
function Fn(num){
//在构造函数模式中,方法体中出现的this是当前类的一个实例(this.xx = xx 都是在给当前实例增加一些私有的属性)
this.num = num } var f = new Fn(10) 构造函数执行,既具备普通函数执行的一面,也同时具有自己独有的一些操作 在构造函数执行期间,浏览器默认创建的对象(也就是函数体中的this)就是当前这个类的实例。浏览器会把默认创建的实例返回,所以我没说:new Fn()执行,Fn是一个类,返回的结果是Fn这个类的一个实例

3.深入理解构造函数执行的步骤

1)当构造函数或者类,执行的时候不需要传递任何的实参值,此时我们是否加小括号就不重要了(不传递实参的情况下,小括号可以省略

2)构造函数执行,同时具备了普通函数的一面,也有自己特殊的一面,但是和实例相关的,只有自己特殊的一面才相关(也就是this.xxx = xxx才相当于给当前实例增加的私有属性),函数体中出现的私有变量,和实例都没有直接的关系

不同的实例是不同的空间地址 是不相等的 ;通过类创建出来的每一个实例都是单独的个体(单独的堆内存空间),实例和实例之间是不相等并且独立且互不影响的(市面上部分开发把这种模式叫做单例模式,这种说法是错的,JS中的这种模式叫做构造函数设计模式)

3)在构造函数体中,通过this.xxx给实例设置的属性都是当前实例的私有属性

4)当构造函数体中,(默认返回的是实例:对象类型值)我们自己手动设置了return,return的是一个基本数据类型值,对最后返回的实例没有任何的影响,但是如果返回是引用数据类型的值,会把默认返回的实例替换掉

4.检测数据类型的方式

①:instanceof 用来检测当前实例是否隶属于某个类 instanceof解决了typepf无法识别是数组还是正则的问题 [] instanceof Array  : true

②:typeof  typeof [] : Object 

③:constructor

④:Object.prototype.toString().apply('''')

5.hasOwnProperty  VS in

in:用来检测当前这个属性是否隶属于对象(不管对象是私有还是公有的属性,主要有返回的就是true)

hasOwnProperty:用来检测当前这个属性是否是对象的私有属性(不仅是对象的属性,而且需要是私有的才可以)

var obj = {name:'alhh',age:'18'}

'name' in obj //true
'hasOwnProperty' in obj // true  
//hasOwnProperty 是Object 这个内置类中提供的属性方法,只有当前对象是Object的一个实例,就可以使用这个方法
function Fn(){
this.num =100
}
var f = new Fn()

//this.xxx是给这个实例添加的私有属性
f.hasOwnProperty('num') //true

检测一个属性是否是当前对象的公有属性

1.是对象的一个属性

2.不是对象的私有属性

方法如下:
function
hasPublicProperty(attr,obj){ return (attr in obj) && (obj.hasOwnProperty(attr) === false) } hasPublicProperty('hasOwnProperty',{xxx:'xxx'})

6.JS中的对象和函数汇总

对象数据类型的值:{}包起来的普通对象;[]数组; / ^$/正则;Math数学函数;一般类的实例都是对象数据类型的;函数的prototype属性;实例的 __proto__属性

函数数据类型的值:普通函数;所有的类(内置类和自定义类)

7.原型和原型链

(1).所有的函数都天生自带一个属性:prototype(原型),它是一个对象数据类型的值,在当前prototype对象中,存储了类需要给其实例使用的公有的属性和方法

(2).prototype这个对象,浏览器会默认为其开一个堆内存,在这个堆内存中天生自带一个属性:constructor(构造函数),这个属性存储的值就是当前函数本身

(3).每一个类的实例(每一个对象)都天生自带一个属性:__proto__,属性值是当前对象所属类的原型(prototype)

function Fn(name,age){
    this.name = name;
    this.age = age;
    this.say = function(){
    console.log('name:'+this.name+'age:'+this.age)
    }
}

Fn.prototype.say = function(){
  console.log('hello world')
}

Fn.prototype.eat = function(){
  console.log('吃东西')
}
var f1 = new Fn('alhh',18)
var f2 = new Fn('yt',30)

原型的作用:提供实例所需的公有的属性和方法 

原型链:先找私有,私有没有找公有 ,公有没有 ,一直向上找,一直找到Object的基类为止  (类比作用域链)

例如见图: 数组push有三种方法执行 ① :ary.push():ary首先通过原型链的查找机制,找到Array原型上的push方法,然后让push方法执行(执行push内置方法实现向数组末尾追加新内容),这里面push的方法this是ary

  ②:ary.__proto__.push():this相对于第一种来说改变了this,这里的this是ary.__proto__

③:Array.prototype.push() 也是让push方法执行

例2:ary.hasOwnProperty():通过原型链找到Object基类原型上的hasOwnProperty这个方法,并且执行这个方法

某一个实例或者某一个对象之所以能操作某些属性和方法,是因为在它的原型链上能找到这些属性和方法,如果找不到则不能进行操作

 数组和类数组的区别 :两者结构一样,数组是Array的实例 但是类数组是Object实例,所以类数组不能用数组的那些方法

 把类数组转化为数组 Array.prototype.slice.call(arguments)

(1):私有和公有是一个相对论,我们需要看相对于哪个对象而言:

1.相对于实例来说,push是公有的

2.相对于Array.prototype来说,push就是自己私有的

凡是通过__proto__找到的属性都是公有的,反之都是私有的

(2):关于原型链中提供的私有(公有)方法中的this指向问题:

  1.看点前面是谁,this就是谁

  f1.__proto__.say():this->f1.__proto__   ;Fn.prototype.say():this->Fn.prototype

  2.把需要执行方法中的this进行替换

  3.替换完成后,如果想知道结果,只需要按照原型链的查找机制去查找即可

(3):在原型上批量扩展属性和方法

1.设置别名(小名)

var pro  = Fn.prototype; //指向同一个堆内存

pro.aa = function(){}

2.重新构造原型

新的:Fn.prototype.cc = function(){}

旧的:Fn.prototype = {

 //=>让原型指向自己开辟的堆内存有个问题:自己开辟的堆内存中没有constructor这个属性,所以实例在调用constructor的时候找到是Object,这样不好,此时我们应该重新设置一下constructor,保证机制的完整性

 constructor:Fn,  //此举解决上述问题

 aa:function(){},

 bb:function(){}

}

var f = new Fn()

f.cc //undefined :重新做原型指向后,之前在浏览器默认开辟的堆内存中存储的属性和方法都没用了(之前开辟的cc,后来aa,bb又重新开辟了,所以cc没用了),只有在新内存中存储的才有用的(就是此时的aa),

Array.prototype.aa = 11 //这样可以

Array.prototype = { } //内置类原型不允许我们进行重构 ,这样是不行的 ,原有的会没有的

//jQuery源码片段
(function(){
    var jQuery  = function(selector,context){
    return new jQuery.fn.init(selector,context)
}
    jQuery.fn = jQuery.prototype = {
        constructor:jQuery,
        init:function(selector,context){
    }
  } 
    window.jQuery = window.$ = jQuery;
    
}()

3.基于内置类的原型扩展方法

基本方法数组去重

function distinct(ary){
var obj = {}
for(var i=0;i<ary.length;i++){
    var item = ary[i]
    if(typeof obj[item] !=='undefined'){
    arr[i] = ary[ary.lenth-1];
    i--;
    continue;
    }
    obj[item] = item
  }
    obj =null
    return ary
}

(1).新增加的方法最后设置一个前缀,防止我们新增的方法和内置的方法冲突,把内置方法替换了

Array.prototype.myDistinct = function myDistinct(){
//this:ary当前要处理的那个数组
var obj = {}
for(var i=0;i<this.length;i++){
    var item = ary[i]
    if(typeof obj[item] !=='undefined'){
    this[i] = this[this.lenth-1];
    i--;
    continue;
   }
    obj[item] = item
  }
    obj =null
  return this //加return可以实现链式写法,返回去重后的数组,这样执行完成这个方法后,我们还可以继续调取数组中的其它方法 } ary.myDistinct()
//不用return,这里直接修改this的值也就是修改ary的值
//链式写法:执行完成一个方法紧跟着就调取下一个方法(原因:执行完成一个方法后,返回的结果依然是当前类的实例,这样就可以继续调取当前类的其它方法操作了)
ary.sort(function(a,b){
    return a-b
    }
).push(100)

ary.sort(function(a,b){
    return a-b
    }
).push(100).pop() //就会报错 因为push之后返回的是数组的长度是个数字,pop是数组的实例

(2).面试题 (3).plus(2).minus(1) =>4

Number.prototype.plus = function plus(){
//this是3
  return this+arguments[0] 
}
Number.prototype.minus = function minus(){
return this-arguments[0] 
}

!!!

继承的方式

1. 原型继承

//原型继承是js中最常用的一种继承方式
function A(){
    this.x = 100
}
A.prototype.getX = funtion (){
    console.log(this.x)
}
function B(){
    this.y = 1
}
B.prototype = new A

总结:IE浏览器屏蔽了我们使用或者修改__proto__  ;所有的类都是Object的实例,Object是对象类型的基类,在它的原型上没有__proto__这个属性:因为即使有,也是指向自身

猜你喜欢

转载自www.cnblogs.com/alhh/p/10273546.html