面向对象
对象:大自然所有的事物都是可以被称为对象的
类:把抽象的‘对象’,按照特点进行详细的分类(大类/小类),把共同的东西进行抽取,放到对应的类别里
=>'类'是对象的细分,和公共部分的封装,类是函数数据类型
实例:类别中派出来的具体事物叫做类的'实例'
=>实例既有属于自己私有的东西,也有继承各个类别中的共有信息,对象数据类型的
面向对象:掌握对象,实例,类之间的关系和知识。实例都是对象数据类型的
研究一个实例
1. 私有的信息
2. 所属类共有的信息
3. 一层层从小类象大类研究属性和方法(直到ObJECT)
js的类
内置类和自定义类
内置类:Number String Boolean Null Object Aray...
单例模式
把描述同一件事物的不同属性放进同一个命名空间下,避免全局变量的干扰和冲突,
/*
* 公共模块
*/
let utils=(function(){
let queryElement=function(){...}
return {
//queryElement:queryElement
queryElement
}
})();
/*
* 负责的页卡模块
*/
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,这个属性存储的是当前函数本身
```javascript
Fn.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);//=>false
console.log(f1.getY === f2.getY);//=>true
console.log(f1.__proto__.getY === Fn.prototype.getY);//=>true
console.log(f1.__proto__.getX === f2.getX);//=>false
console.log(f1.getX === Fn.prototype.getX);//=>false
console.log(f1.constructor);//=>Fn
console.log(Fn.prototype.__proto__.constructor);//=>Object
f1.getX();//=>this:f1 =>console.log(f1.x); =>100
f1.__proto__.getX();//=>this:f1.__proto__ =>console.log(f1.__proto__.x); =>undefined
f2.getY();//=>this:f2 =>console.log(f2.y); =>200
Fn.prototype.getY();//=>this:Fn.prototype =>console.log(Fn.prototype.y); =>undefined
重定向问题
/*
* 重构类的原型:让某个类的原型指向新的堆内存地址(重定向指向)
* 问题:重定向后的空间中不一定有CONSTRUCTOR属性(只有浏览器默认给PROTOTYPE开辟的堆内存中才存在
CONSTRUCTOR),这样导致类和原型机制不完整;所以需要我们手动再给新的原型空间设置CONSTRUCT
OR属性;
* 问题:在重新指向之前,我们需要确保原有原型的堆内存中没有设置属性和方法,因为重定向后,原有的属性
和方法就没了(如果需要克隆到新的原型堆内存中,我们还需要额外的处理) =>但是内置类的原
型,由于担心这样的改变会让内置的方法都消失,所以禁止了我们给内置类原型的空间重定向,例如:
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,去重