面向对象
对象:大自然所有的事物都是可以被称为对象的类:把抽象的‘对象’,按照特点进行详细的分类(大类/小类),把共同的东西进行抽取,放到对应的类别里=>'类'是对象的细分,和公共部分的封装,类是函数数据类型实例:类别中派出来的具体事物叫做类的'实例'=>实例既有属于自己私有的东西,也有继承各个类别中的共有信息,对象数据类型的面向对象:掌握对象,实例,类之间的关系和知识。实例都是对象数据类型的研究一个实例1. 私有的信息2. 所属类共有的信息3. 一层层从小类象大类研究属性和方法(直到ObJECT)js的类内置类和自定义类内置类:Number String Boolean Null Object Aray...
单例模式
把描述同一件事物的不同属性放进同一个命名空间下,避免全局变量的干扰和冲突,
/** 公共模块*/let utils=(function(){let queryElement=function(){...}return {//queryElement:queryElementqueryElement}})();/** 负责的页卡模块*/let pageTabModule=(function(){//=>获取元素(调取其它命名空间下的方法)let tabBox=utils.queryElement('.tabBox');let show=function(){...}...return {init:function(){//调用自己模块下的方法show();}}})();pageTabModule.init();
工厂模式
把实现统一功能的代码放进函数体中,当实现类似的功能,直接执行当前这个函数;减少代码冗余,实现高内聚,低耦合->函数的封装
function descPerson(name,age) {var obj = {};obj.name = name;obj.age = age;obj.hobby = function () {console.log("I like JS")}return obj;};var a =descPerson("黄",9);var b =descPerson("张",13);console.log(a);console.log(b);
实例创建
1.字面量方式创建
//var num=1;//字面量创建的是number类,但是属于基本数据类型通过字面量方式创建的引用数据类型的值是一个标准的实例let obj={};//实例创建,如果只有一个参数实例创建;如果只有一个参数并且是数字,那么代表当前实例的长度// 如果两个及以上的,把当前当成数组的每一项返回;var num1 = new Number(12);// 标准的实例;
构造函数创建
构造函数执行,因为也具备普通函数执行的特点
1.和实例有关系的操作一定是 this.xxx=xxx ,因为this是当前类创造出来的实例
2.私有变量和实例没有必然的联系
var n=new Number(12);//创建的结果是number类的实例,是对象数据类型值,他的原始值是12// console.log(Number.prototype);//=>查看Number类为其实例赋予的属性和方法// console.log(num.toFixed(2));//=>虽然是基本类型值,但是也是Number的实例,依然可以调取类中的属性和方法(浏览器在执行的时候会默认的把num进行new Number(num)的处理)// console.log(n.toFixed(2));
- 引用数据类型创建的区别
//=>对于引用数据类型两种创建方式的区别:仅仅是语法上的区别// var obj1 = {name: '珠峰培训'};//=>字面量创建方式// var obj2 = new Object();//=>构造函数创建方式// obj2['name'] = '珠峰培训';// var ary1 = [12];//=>创建一个数组存储12// var ary2 = new Array(12);//=>创建一个数组,长度是12位,每一项都是空// function fn() {// console.log('ok');// }// var fn = new Function("console.log('ok')");
构造函数
在普通函数前加一个new,当前函数就变成一个构造函数,构造函数执行会默认创建一个实例,类一定是一个函数,但函数不一定是类。
/*NEW执行也会把类当做普通函数执行(当然也有类执行的一面)* 1、new Fn也是先把Fn执行,形成一个新的私有作用域* 2、形参赋值* 3、变量提升 (构造函数执行也具备普通函数的一面)* 4、代码执行前:浏览器会在当前栈内存中创建一个对象数据类型值,而且让作用域中的THIS指向创建的这个对象,而这个对象就是当前类的一个实例(构造函数执行形成的栈内存中THIS是当前类的实例)* 5、代码自上而下执行:如果遇到了this.xxx=xxx相当于给当前类的实例增加一些私有的属性* 6、即时我们不写return,浏览器也会默认把创建的实例返回(如果我们自己写了return:返回的是基本类型值对返回的实例不影响,如果返回的是引用类型值,会把默认返回的实例替换掉)*/function Fn(){this.x = arguments[0];this.y = arguments[1];}//Fn();// undefined//Fn;var f = new Fn(10, 20);// 1.让函数执行; 并且让当前函数变成一个构造函数(类);//console.log(f);// {}
构造函数与普通函数的区别
1. 函数执行:形成私有作用域--> 形参赋值---> 变量提升--> (会默认创建一个空对象,并且把函数中的this指向当前这个对象)代码从上到下运行(会把这个对象返回)--->作用域销毁;2. 函数执行时,私有变量和实例没有直接的关系;和函数体中的this有关;3. 构造函数如果没有实参,那么构造函数小括号可以省去;4. 构造函数中如果return出基本数据类型值,那么不会影响构造函数返回的实例;如果是return出一个引用数据类型值;那么会把默认return的实例覆盖;
原型&原型链
原型
1. 每一个函数数据类型的值,都有一个天生自带的属性:prototype(原型),这个属性的属性值是一个对象(“用来存储实例公用属性和方法”)- 普通的函数- 类(自定义类和内置类)2. 在prototype这个对象中,有一个天生自带的属性:constructor,这个属性存储的是当前函数本身```javascriptFn.prototype.constructor === Fn
每一个对象数据类型的值,也有一个天生自带的属性:_proto_,这个属性指向“所属类的原型prototype”
- 普通对象、数组、正则、Math、日期、类数组等等
- 实例也是对象数据类型的值
- 函数的原型prototype属性的值也是对象类型的
- 函数也是对象数据类型的值 ```
function Fn(name,age){this.age=age;this.name=name;this.say=function(){console.log('my name is'+this.name);}}Fn.prototype.say=function(){console.log('i am ' + this.age + ' years old');}Fn.prototype.eat = function () {console.log('i love food');};var f1 = new Fn('张学波', 20);var f2 = new Fn('冀闯', 21);f1.say();//=>this:f1 查找私有的f1.__proto__.say();//=>查找的是公有的属性和方法 this:f1.__proto__ 'i am ' + f1.__proto__.age (undefined) + ' years old'Fn.prototype.say();
- 原型链中的this指向
原型链中提供的私有(公有)方法中的this指向1. 点前面是谁this就是谁2. 把需要执行方法中的this进行替换3. 替换完成后,如果想要知道结果,只需要按照原型链的查找机制去查找即可
原型链查找机制
1.先找自己私有的属性,有则调取使用,没有继续找2.基于\__proto\__找所属类原型上的方法(Fn.prototype),如果还没有则继续基于\__proto\__往上找...一直找到Object.prototype为止function Fn() {this.x = 100;this.y = 200;this.getX = function () {console.log(this.x);}}Fn.prototype.getX = function () {console.log(this.x);};Fn.prototype.getY = function () {console.log(this.y);};var f1 = new Fn;var f2 = new Fn;console.log(f1.getX === f2.getX);//=>falseconsole.log(f1.getY === f2.getY);//=>trueconsole.log(f1.__proto__.getY === Fn.prototype.getY);//=>trueconsole.log(f1.__proto__.getX === f2.getX);//=>falseconsole.log(f1.getX === Fn.prototype.getX);//=>falseconsole.log(f1.constructor);//=>Fnconsole.log(Fn.prototype.__proto__.constructor);//=>Objectf1.getX();//=>this:f1 =>console.log(f1.x); =>100f1.__proto__.getX();//=>this:f1.__proto__ =>console.log(f1.__proto__.x); =>undefinedf2.getY();//=>this:f2 =>console.log(f2.y); =>200Fn.prototype.getY();//=>this:Fn.prototype =>console.log(Fn.prototype.y); =>undefined
重定向问题
/** 重构类的原型:让某个类的原型指向新的堆内存地址(重定向指向)* 问题:重定向后的空间中不一定有CONSTRUCTOR属性(只有浏览器默认给PROTOTYPE开辟的堆内存中才存在CONSTRUCTOR),这样导致类和原型机制不完整;所以需要我们手动再给新的原型空间设置CONSTRUCTOR属性;* 问题:在重新指向之前,我们需要确保原有原型的堆内存中没有设置属性和方法,因为重定向后,原有的属性和方法就没了(如果需要克隆到新的原型堆内存中,我们还需要额外的处理) =>但是内置类的原型,由于担心这样的改变会让内置的方法都消失,所以禁止了我们给内置类原型的空间重定向,例如:Array.prototype={...}这样没有用,如果想加方法Array.prototype.xxx=function(){...}可以这样处理*/)//内置类原型不能进行重构//=>当我们需要给类的原型批量设置属性和方法的时候,一般都是让原型重定向到自己创建的对象中function Fn() {// ...}Fn.prototype.xxx = function () {}//=>批量给原型设置属性方法的时候:重构类的原型Fn.prototype = {constructor: Fn,getA: function () {},getB: function () {}}; *//* //=>批量给原型设置属性方法的时候:设置别名let proto = Fn.prototype;proto.getA = function () {}proto.getB = function () {}proto.getC = function () {}proto.getD = function () {} */
基于内置类的原型扩展方法
- 在内置类原型上的方法,类所对应的实例可以直接调取使用,例如:实例.方法() ary.push()
- 如果我们也把自己写的方法放到原型上,那么当前类的实例也可以直接这样调取使用了
注意的地方
1.自己扩展的方法不能影响原有内置的方法(我们自己设置的方法最好加前缀:my)
2.扩展方法中的THIS一般都是当前类的实例(也就是要操作的值):实例.方法()~ function () {/** myUnique : 实现数组去重* @params* @return* [Array] 去重后的数组* by zhouxiaotian on 20190805*/function myUnique() {//此时没有传递要操作的ARY进来,但是方法中的THIS是当前要操作的数组:ARY.MYUNIQUE()let obj = {};for (let i = 0; i < this.length; i++) {let item = this[i];if (typeof obj[item] !== 'undefined') {this[i] = this[this.length - 1];this.length--;i--;continue;}obj[item] = item;}obj = null;// 保证当前方法执行完返回的结果依然是ARRAY类的一个实例return this;}//=>扩展到内置类的原型上Array.prototype.myUnique = myUnique;}();let ary = [12, 23, 13, 12, 23, 24, 34, 13, 23];ary.myUnique();
链式写法
执行完成一个方法紧跟着就会调去下一个方法(执行完成一个方法后,返回的结果依然是当前类的实例,就可以继续调取当前类的其他方法操作)
任务:hasPubProperty,slice,去重
