--------------------------------------------------单例模式---------------------------------------------------- var person1={ name:'张三', age:18 }; var person2={ name:'李四', age:19 } 对象数据类型的作用: 把描述同一个事物或的属性和方法放在一个内容空间下,起到了分组的作用,这样不同事物之间的属性即使属性名相同, 相互也不会冲突,我们把这种编写代码的模式叫做"单例模式" 在单例模式中我们把person1和person2这两个名字叫做"命名空间" 单例模式是一种项目开发中经常使用的模式,因为项目中我们可以使用单例模式进行我们的“模块化开发” “模块化开发”对于一个相对来说比较大的项目需要多人协作开发,我们一般情况下会根据项目的需求划分几个功能板块,每个人负责一部分,同时开发,最后把每个人的代码合并 上课举例: //公共模块 var utils = { select: function () { } }; var serarhRender = { change: function () { utils.select();//在自己的命名空间下调用其他命名空间的方法 } }; var typeRender = { change: function () { this.clickevent();//在自己的命名空间下调用自己命名空间下的方法 }, clickevent: function () { } }; typeRender.change();//.前面是谁,这个方法中的this就是谁 -----------------------------------------------工厂模式--------------------------------------------------------- 单例模式虽然解决了分组作用,但是不能实现批量生产,属于手工作业模式 工厂模式---->把实现同一件事情的相同代码放到一个函数中,以后如果想实现这个功能,不需要重新编写这些代码, 只需要执行当前的函数即可(也叫做函数的封装)(低耦合高内聚) 继承:子类继承父类中的属性和方法 多态:当前方法的多种形态,在后台语言中多态只包含了重载和重写 (js中不存在重载,方法名一样的话,后面的会把前面的覆盖掉,最后只保留一个) (js中有一个类似重载但不是重载:我们可以根据传递的参数不一样,实现不同的功能) 重写:子类重写父类的方法 js中的多态 (类重载) function sum(n){ if(typeof n=="undefined"){ return 0; } return n; } sum(100); sum(); --------------------------------------构造函数模式基础------------------------------------------- 构造函数基础 构造函数的目的就是为了创建一个自定义类,并且创建这个类的实例 上课举例构造函数: function Createperson(name, age) { this.name = name; this.age = age; this.writejs = function () { console.log(this.name + 'aaaa'); }; } var p1 =new Createperson('hong', 18); p1.writejs(); var p2 =new Createperson(ming, 18); p2.writejs(); 构造函数和工厂模式的区别? 1、执行的时候 普通函数执行----->createperson(); 构造函数模式执行----->new createperson() 通过new执行后我们的createperson就是一个类了,js中所有的类都是函数数据类型的,但是它本身就是普通函数 p1就是createperson类的实例,js中所有的实例都是对象数据类型的 2、在函数代码执行的时候 相同:都是形成一个私有的作用域,然后经历了形参赋值,预解释,代码从上往下执行(类和普通函数一样,它有普通函数的一面) 不同:在代码执行之前不用自己手动创建对象了,浏览器会默认的创建一个对象数据类型的值(这个对象数据类型的值就是我们当前类的一个实例),接下来代码从上到下执,以当前的实例为执行主体(this代表的就是当前实例),然后分别把属性名和属性值赋给当前的实例 注意: 1、js中所有的类都是函数数据类型的,它通过new执行变成了一个类,它本身还是一个普通的函数,js中所有的实例都是对象数据类型的 2、在构造函数中this.xxx=xxx出现的this指的是当前类的实例 3、p1和p2都是createperson这个类的实例,都拥有wirteJs这个方法,但是不同实例之间的方法是不一样的 在类中给实例增加属性(this.xxx=xxx)属于当前实例的私有的属性,实例和实例之间是单独的个体,所以私有的属性之间是不相等的 上课作业: function Fn() { this.x = 100; this.getx = function () { console.log(this.x); } } var f1 = new Fn; f1.getx(); var ss = f1.getx; ss(); --------------------------------------------构造函数模式扩展---------------------------------------------------- 上课案例1: function Fn() { var num = 10; this.x = 100; this.getX = function () { console.log(this.x); }; } var f1 = new Fn; console.log(f1.num); 结论:类都有普通函数的一面,当函数执行的时候,var num其实只是当前形成的私有作用域中私有的变量而已,它和我们的f1这个实例没有任何关系;只有this.xxx=xxx才相当与给f1这个实例增加私有的属性和方法,才和f1有关系 上课思考2: function Fn() { this.x = 100; this.getX = function () { console.log(this.x); }; return 100; } var f1 = new Fn; console.log(f1); 在构造函数模式中,浏览器会默认的把我们的实例返回(返回的是对象数据类型的值),如果我们自己动手写了return返回,会有两种情况考虑。 1、返回的是一个基本数据类型的值,返回的是当前的实例,与基本数据类型无关 2、返回的是引用数据类型的值,当前的实例会被自己返回的值给替换掉,这样f1就不再是Fn的实例了 上课思考案例3:检测数据类型初探 对于检测数据类型来说,typeof有自己的局限性,不能细分object下的对象 function Fn() { this.x = 100; this.getX = function () { console.log(this.x); }; } var f1=new Fn; 1、检测某一个实例是否属于这个类-------》 instanceof console.log(f1 instanceof Fn); true console.log(f1 instanceof Object); true 因为所有的实例都是对象数据类型的,而每一个对象数据类型都是Object这个内置类的一个实例,所以f1也是Object的一个实例 var a=[]; console.log(a instanceof Array); true 说明a是一个数组 2、检测某一个属性是否属于这个对象 ---------》in (无论是私有的还是公有的属性,只要存在,用in来检测都是true) console.log("x" in f1); 3、检测某一个属性是否为这个对象的“私有属性”-----------》hasOwnProperty console.log(f1.hasOwnProperty("getX")); 4、检测某一个属性是否为该对象的“公有属性” function publicfn(attr,obj){ return (attr in obj) && !obj.hasOwnProperty(attr); } ----------------------------------------原型链模式基础---------------------------------------------------- 构造函数模式中拥有了类和实例的概念,并且实例和实例之间是相互独立开的,我们称为实例识别 基于构造函数模式的原型模式解决了属性或者方法公有的问题,把实例之间相同的属性和方法提取成公有的属性和方法,想让谁公有就把它放在类的原型上(createperson.prototype) 如下所示案例1: function Fn() { this.x = 100; } Fn.prototype.getX = function () { console.log(this.x); }; var f1 = new Fn; var f2 = new Fn; Js中规定的规则: 1、每一个函数数据类型(普通函数、类)都有一个天生自带的属性:prototype(原型) 属性值是对象数据类型的值 2、在prototype上,浏览器天生给它加了一个属性,constructor(构造函数),属性值是当前函数(类)本身 3、每一个对象数据类型(普通的对象、实例、prototype)也天生自带一个属性,叫做__proto__,属性值是当前实例所属类的原型 4、所有的对象数据类型的都是Object的一个实例 注意: Object是js中所有对象数据类型的基类(最顶层的类) 1、f1 instanceof Object---->true因为f1通过__proto__可以向上级查找,不管多少级,最后总能找到Object 2、在Object.prototype上没有__proto__这个属性 原型链查找机制: 1>通过对象名.属性名的方式获取属性值的时候,首先在对象的私有的属性上进行查找,如果私有属性中存在这个属性,则获取的是私有的属性值 2>如果私有的没有,则通过__proto__找到所属类的原型(类的原型上定义的属性和方法都是当前实例公有的属性和方法),原型上存在的话,获取的是公有的属性值 3>如果原型上也没有,则继续通过原型上的__proto__继续向上查找,一直找到Object.prototype为止 继续思考案例1: console.log(f1.getX==f2.getX); console.log(f1.__proto__.getX==f2.getX); console.log(f1.getX==Fn.prototype.getX); 在ie浏览器中原型也是同样的原理,但是ie浏览器怕你通过__proto__把公有的修改,禁止我们使用__proto__。 -------------------------------原型链模式扩展(this和原型扩展)------------------------------ 思考1: function Fn(){ this.x=100; this.y=200; this.gety= function () { console.log(this.y); }; } Fn.prototype={ constructor:Fn, y:300, getx: function () { console.log(this.x); }, gety: function () { console.log(this.y); } }; var f=new Fn; f.getx(); f.__proto__.getx(); Fn.prototype.getx(); f.gety(); f.__proto__.gety(); 思考2:this指向问题 Array.prototype.myfn = function () {console.log(this)}; var arr = [];//实例化一个对象 arr.myfn();//myfn中的this指的是arr Array.prototype.myfn(); //myfn中的this指的是Array.prototype 思考3:在原型上实现数组去重 Array.prototype.myfn = function () { var obj = {}; for (var i = 0; i < this.length; i++) { var cur = this[i]; if (obj[cur] == cur) { this[i] = this[this.length - 1]; this.length--; i--; continue; } obj[cur] = cur; } }; var arr = [12, 12, 13];//实例化一个对象 arr.myfn(); console.log(arr); 思考4:链式写法 var arr = [12, 34, 56, 6, 13, 45, 67]; arr.sort(function (a,b) { return a-b; }).reverse().pop(); 链式写法----->执行完成数组的一个方法可以紧接着执行下一个方法 原理:arr为什么可以使用sort方法,因为sort是Array.prototype上公有的方法,而数组arr是Array这个类的一个实例,所以arr可以使用sort方法,--->只有数组才能使用我们Array原型上定义的属性和方法 作业:链式写法实现数组去重并且从大到小排序 ---------------------------原型链模式扩展(批量设置公有属性)--------------------------------------------- 在原型上增加公有的属性和方法: 1、起别名的方式 2、重构原型对象的方式:自己开辟一个堆内存,存储我们的公有的属性和方法,把浏览器原来给Fn.prototype开辟的那个替换掉 1、别名方式 function Fn() {} Fn.prototype.getx = function () {}; Fn.prototype.gety = function () {}; Fn.prototype.getz = function () {}; var f1 = new Fn; 2、重构原型对象的方式 function Fn() {} Fn.prototype = { constructor:Fn, a: function () {}, b: function () {} }; var f = new Fn; console.log(f.constructor); 注意: 1、只有浏览器天生给Fn.prototype开辟的堆内存才有constructor,而我们自己开辟的这个堆内存没有这个属性,这样constructor指向就变成了Object了,为了和原来保持一致我们需要手动增加constructor的指向 2、用这个方式给内置类增加公有的属性 Array.prototype = { constructor: Array, myfn: function () {} }; console.dir(Array.prototype); 我们这种方式会把之前存在于原型上的属性和方法给替换掉,所以我们这种方式修改内置类的话,浏览器会屏蔽掉 但是我们可以一个个修改内置的方法,当我们通过下面的方式在数组的原型上增加方法,如果方法名和原来内置的重复了,会把人家的内置修改掉,所以我们以后在内置类的原型上添加方法,命名都需要添加特殊的前缀 Array.prototype.sort= function () { console.log('aaa'); }; var arr=[12,13,14]; arr.sort() --------------------------深入扩展原型链模式常用的五种继承方式------------------------ 知识小扩展1(如何只遍历私有的属性?) 1、for in循环在遍历的时候可以把自己私有的和在它所属类的原型上扩展的属性和方法都可以遍历到,但是一般情况下,我们遍历对象只需要遍历私有的即可,我们可以使用以下的判断进行处理 2、在原型上的方法都是不可枚举的 Object.prototype.aaa = function () {}; var obj = {name: '张三', age: 18}; for (var key in obj) { //只把私有的遍历到 if(obj.propertyIsEnumerable(key)){ console.log(key); } //或者(和以上原理相同) if(obj.hasOwnProperty(key)){ console.log(key); } } console.dir(Object.prototype); 知识小扩展2:Object.create()有兼容性问题 Object.create() 创建一个新的空对象,这个对象的原型是第一个参数值 var obj = {name: '张三', age: 18}; var obj3 = Object.create(obj); console.dir(obj3); 思考1: var obj = { getx: function () { } }; var obj2 = Object.create(obj); obj.gety = function () { console.log('ok'); }; obj2.gety(); 思考自己封装一个类似于Object.create() var obj = { getx: function () {} }; function myfn(n) { function Fn() {} Fn.prototype = n; return new Fn; } var obj11 = myfn(obj); console.log(obj11); 第一种:原型继承 function A(){ this.x=100; } A.prototype.getx= function () { console.log(this.x); }; function B(){ this.y=200; } B.prototype=new A; var b1=new B; var b2=new B; var a1=new A; 原型继承原理:子类B想要继承A中的所有的属性和方法(私有+公有),只需要让B.prototype=new A; 原型继承的特点:父类中私有的和公有的都继承到了子类的原型上变成了子类公有的。 原型链继承核心:原型继承并不是把父类中的属性和方法复制一份一模一样的给B,而是让B和A之间增加了原型链的链接,以后B的实例想要A中的getx()方法,需要一级一级向上查找 重写:子类重写父类的方法(在类的继承当中,由于原型继承并不是把父类拿过来一份一模一样的而是让子类和父类之间增加了一个原型链的一个桥梁,这样就导致了子类或者子类的实例通过原型链机制把父类原型上方法进行修改,导致了父类中其他实例也会受到影响) 第二种:call继承 把父类私有的属性和方法克隆一模一样的给子类私有的属性 function A() { this.x = 100; } A.prototype.getx = function () { console.log(this.x) }; function B() { A.call(this); } var n = new B; console.log(n.x); 第三种:冒充对象继承 // 冒充对象继承 把父类私有的+公有的克隆一份一模一样的给子类私有的 function A() { this.x = 100; } A.prototype.getx = function () { console.log(this.x) }; function B() { var temp = new A; for (var key in temp) { this[key]=temp[key]; } temp=null; } var n = new B; n.getx(); 第四种:混合模式继承 指的是原型继承+call继承 //A执行了两次,私有属性分别在B的私有和公有上。 function A(){ this.x=100; } A.prototype.getx= function () { console.log(this.x); }; function B(){ A.call(this);//n.x=100; } B.prototype=new A; //B.prototype x getx B.prototype.constructor=B; var n=new B; n.getx(); 第五种:寄生组合式继承 //私有的继承私有的,公有的继承公有的 function A(){ this.x=100; } A.prototype.getx= function () { console.log(this.x); }; function B(){ A.call(this);//n.x=100; } B.prototype= myFn(A.prototype); //B.prototype x getx B.prototype.constructor=B; var n=new B; n.getx(); function myFn(o){ function Fn(){ } Fn.prototype=o; return new Fn; } 在ie中不支持__proto__ 思考: function Fn(num) { this.x = this.y = num; } Fn.prototype = { x: 20, sum: function () { console.log(this.x + this.y); } }; var f = new Fn(10); console.log(f.sum == Fn.prototype.sum); f.sum(); Fn.prototype.sum(); console.log(f.constructor); --------------------------------------------------原型深入--------------------------------------------------------- 原型链终结版 在js中最大的类并不是Object类 所有的对象都是Object类的一个实例,所有的函数(function)都是Function类的实例 一个函数天生自带的属性既自带prototype,又自带__proto__ ---------------------------------------------函数的三种角色------------------------------------------------ 一个函数存在多面性 1、本身是普通函数,执行的时候形成私有的作用域(闭包),形参赋值,预解释,代码执行,执行完成后栈内存销毁/不销毁 2、“类”有自己的实例,也有一个叫做prototype属性是自己的原型,他的实例都可以指向自己的原型 3、还是一个普通的对象,和var obj={}中obj一样,就是一个普通对象,它作为对象可以有一些自己私有的属性,也可以通过__proto__找到Function.prototype 这三者之间没有必然联系。 特殊特殊记忆的一点:Function.prototype是函数数据类型的,但是相关操作和对象数据类型操作一模一样 思考: function Fn() { var num = 500; this.x = 100; } Fn.prototype.getx = function () { console.log(this.x); }; Fn.aaa=1000; var f=new Fn; console.log(f.num); console.log(f.aaa); console.log(Fn()); console.log(Fn.aaa); --------------------------------------call方法深入----------------------------------------------------- 思考: var obj= {name: '张三'}; function fn() { console.log(this); } obj.fn(); 这个案例并不能改变this的指向,那如何改变this的指向呢? fn.call(obj) 上课案例思考: function fn() { console.log(this); } var obj = {name:"张三"}; fn.call(obj); 首先fn通过原型链机制找到Function.prototype的call这个方法,并且让call这个方法执行(call()这个方法的作用是让fn执行,并且把fn中的this变成obj) 思考: function fn1() { console.log(1); } function fn2() { console.log(2); } fn1.call(fn2); fn1.call.call.call(fn2); fn1.call.call.call.call.call(fn2); call方法的作用:让某一个方法中的this变成一个值,然后再让这个方法执行 call的细节知识点: function fn1(num1, num2) { console.log(num1 + num2); console.log(this); } fn1.call(100, 200); 在执行call的时候第一个参数是给fn中的this,第二个及以后的参数就是fn的形参值 ------------------------------------------------call、apply、bind的区别--------------------------------------- var obj={name:'aa'}; function fn(num1, num2) { console.log(this); console.log(num1+num2); } fn.call(obj,100,200); fn.apply(obj,[100,200]); var aaa=fn.bind(obj,100,200); aaa(); apply是把要给fn传递的参数值统一放在一个数组中进行操作,和call功能相同,只是语法不同而已。 bind方法在ie6~8不兼容,和call/apply类似,都是用来改变this关键字的,call和apply都是改变this和执行fn函数一起执行,而bind是改变了fn中的this,但是此时并没有把fn这个函数执行,需要手动执行这个函数 -----------------------------------------获取数组中的最大值/最小值-------------------------------- 利用了apply传参的时候是一个数组来获取数组中的最大值/最小值 var arr = [10, -1, 200, 300, 34, 9, 0]; var min=Math.min.apply(null,arr); var max=Math.max.apply(null,arr); ---------------------------------------------------------获取平均数-------------------------------------------- function svgFn() { var arr=Array.prototype.slice.call(arguments);//将类数组转换成数组 // var arr=[].slice.call(arguments); arr.sort(function (a, b) { return a - b; }); arr.shift(); arr.pop(); return eval(arr.join('+')) / arr.length.toFixed(2); } var a = svgFn(1, 1, 1, 1, 1, 1, 3, 7); console.log(a); 1、将类数组转换为数组:把arguments克隆一份一模一样的数组出来 2、借用数组原型上的slice方法,当slice执行的时候,让方法中的 this变为我要处理的arguments,实现将数组arguments转换为数组 ----------------------------------------------类数组转换为数组-------------------------------------------- var odiv1=document.getElementsByTagName('div') odiv1是HTMLCollection元素集合类的一个实例,是一个类数组集合 var odiv2=document.getElementsByName('a') NodeList节点集合,是一个类数组集合 思考:如何将类数组集合变成数组而且没有兼容性问题? //如果try中的代码执行出错了,会默认的去执行catch中的代码 try{ console.log(num); }catch(e){ //形参一定要写,一般我们起名叫e console.log(e.message); //可以收集当前代码报错的原因 throw new Error('正在加载哦。。');//手动抛出一条错误信息,终止代码执行 }finally{ // 一般都不用,无论try中的代码是否报错,都要执行finally中的代码 } //将类数组转换成数组 var utils={ listToArray: function (likeArray) { var arr=[]; try{ arr=Array.prototype.slice.call(likeArray); }catch(e){ for(var i=0;i< likeArray.length;i++){ arr[arr.length]=likeArray[i] } } return arr; } }; --------------------------------------------------------sort深入研究---------------------------------------------- 回调函数:把一个方法当做参数值传递给另外一个函数B,然后在B执行的过程中,我们随时根据需求让A执行 ary.sort(function (a, b) { //a是每一个执行匿名函数时候,找到数组中的当前项 //b当前项的后一项 return a - b;//升序 如果a>b,返回>0,a和b交换位置 return b - a;//降序 如果b>a,返回>0,a和b交换位置 //return只要是>0就交换位置,小于或等于0位置不动 }); ary.sort(function (a,b) { return 1; //不管a和b谁大,每一次都返回一个恒大于0的数,也就是每一次a和b都要交换位置,最后的结果就相等于reverse }) a.name.localeCompare(b.name)//用于非数字之间的比较