一:面向对象编程思想
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__这个属性:因为即使有,也是指向自身