面向对象编程
了解面向对象编程思想,需要先了解对象、类、实例。万物皆对象,一切都可以看成对象,什么是类?比如咱们自然界中有很多的生物,咱们可以把这些生物分为:植物、动物。。。,那植物就是一个类,动物也是类,动物还可进行细分,比如:高级动物、低级动物,都是分类。什么是实例呢?比如说人类是一个分类,那具体的某个人,比如:丽丽就是一个人类中的一个实例,我想研究人类有哪些特点,就具体的拿这个人来研究就行。咱们在学习js的时候,也是按照这个思想去学习。
- 对象:万物皆对象
- 类:对象的具体细分,按照功能或者特征进行分类
- 实例:类中具体的一个事物(拿出具体的一个实例进行研究,当前类下的其他实例也会具有这些特点和特征)
一、单例设计模式(就是一个对象)
1、单例模式
单例模式:可以把描述一个事物的所有属性放到一个对象中,这样避免的相互的干扰,这种设计模式就是单例设计模式。
单例设计模式中,obj不仅是对象名,它也称为“命名空间”(NameSpace)。每个命名空间都是Object这个内置基类的一个实例,每个实例之间都是互相独立,互不干扰。
var name="wangzhe";
var age=18;
var sex="女";
var name="shuaiyuan";
var age=20;
var sex="男";
var obj1={
name:"wangzhe",
age:18,
sex:"女"
}
var obj2={
name:"shuaiyuan",
age:20,
sex:"男"
}
2、高级单例模式
nameSpace 和自执行函数没有关系,最终是等于自执行函数的返回值,最终是把返回对象的堆内存的地址给了nameSpace。在面试的时候,咱们可以写这个复杂的。
var nameSpace=(function(){
function fn(){},
var a=2;
//... 在这个作用域中还有很多的变量和方法,
return{
// 想要把谁暴露出去,就放到这对象中
fn:fn
}
})();
console.log(nameSpace.fn)
3、【项目实战中 单例模式的应用】
- 在公司一般都是团队协作开发,每人都会分配不同的任务,开发自己的模块
对于整个项目中,公用的功能,可以提取出来,供大家使用 ```javascript //员工A:首页模块的开发 var indexModel=(function(){
function fn2(){
}
//...
return {
init:function(){
// 想要调用自己模块里面的方法:
fn2();
},
// 如果 员工B 想要调员工A里面的方法fn2,只用把这个方法暴露出去
fn2:fn2
}
})(); // 想要初始化函数:indexModel.init();
//员工B:详情页的开发 var messageModel=(function(){ function fn(){ } //… return { init:function(){ // 想要调用自己模块里面的方法: fn(); // 调用A员工模块里面的fn2方法 indexModel.fn2();
}
}
})();
// 想要初始化函数:messageModel.init();
**对于公用性模块也可以用单例模式**
```javascript
var utils=(function(){
function fn(){
};
function b(){
};
// ....
return {
fn:fn,
b:b
}
})();
// 想要用这些方法:
utils.fn();
utils.b();
.....
二、工厂设计模式
把实现相同功能的代码进行封装,后期在使用的时候,只用调用这个函数即可,方便后期的“批量生产”。减少了页面中冗余的代码,实现了“高耦合低内聚”。
var person1={
name:"lili",
age:18
}
var person2={
name:"dava",
age:20
}
//....每次都需要重复的去写,很麻烦,所以就可以用工厂模式
function person(name,age){
return{
name:name,
age:age
}
}
person("lili","18")
person("dava","20")
三、构造函数模式(自定义类)
- 由于工厂模式里边没有类的概念,所以出现了构造函数
- 把一个普通的函数,在执行的时候加了一个new 那这个函数就是构造函数,当前的函数名称为“类名”比如下面例子中的Fn 就是类,f1,f2 就是实例
- 函数执行的返回结果就是当前构造函数的一个实例,比如f1、f2 就是当前函数的一个实例
- 实例是对象数据类型(实例和实例的空间地址不同)
- 构造函数一般名字都大写
- f1 和f2 是独立的堆内存,不相等
- 这种构造函数设计模式一般主要用在类库、框架等的封装,在日常的业务逻辑中很少去使用。
- 如果构造函数没有形参,在调用的时候可以直接省区() // var fn1 = new fn;
1、构造函数的分类:
- 内置类:Number Boolean String Null Undefined Function Object Date Math 。。。
- 自定义类: ```javascript // 内置类 var obj1 = new Object(); console.log(obj1);
// 自定义类 function fn(name, age) { this.name = name, this.age = age }
<a name="eKoaK"></a>
### 2、构造函数的this
**构造函数中的this就是当前的实例**
```javascript
// fn1、fn2为实例,构造函数中的this就是当前的实例,通过this添加的属性名和属性值都是給当前实例添加的
var fn1 = new fn("lili", 15);
var fn2 = new fn("nana", 16);
console.log(f1, f2);
创建数组的两种方式:
- 字面量方式
- 构造函数方式(内置类)
- 当new Array() 传入一个参数的时候,这个参数表示的是新创建数组的长度
- var ary2 = new Array(5); // [empty × 5] 表示创建了一个数组长度为5的数组,每项值为空。
// 创建数组的两种方式:
// 字面量的方式
var ary = [1, 2, 3];
// 构造函数的方式
// 当new Array() 传入一个参数的时候,这个参数表示的是新创建数组的长度
var ary1 = new Array(1, 2, 3);
var ary2 = new Array(5); // [empty × 5] 表示创建了一个数组长度为5的数组,每项值为空。
3、构造函数的原理
思考题
function Fn(name,age){
var n=10;
this.name=name;
this.age=age;
}
var f1=new Fn("lili",18);
var f2=new Fn("dawei",20);
console.log(f1==f2);
console.log(f1.name);
console.log(f1.n);
console.log("name" in f1);
4、构造函数中,如果我们手动的return了一个值,那结果又如何呢?
- 在构造函数中,如果我们手动返回一个基本数据类型的值,没有任何的影响,最终返回的还是浏览器自己创建的那个实例;
- 如果return;也没有任何的影响,最终返回的也还是浏览器自己创建的那个实例;
- 如果我们手动返回一个引用数据类型的值,最终返回的结果就是我们手动返回的这个引用数据。
- 所以在构造函数中,我们尽量减少return 的使用,防止覆盖原来的。
// 当返回的是基本数据类型
function Fn(){
this.name="lili";
return 100
}
var f1=new Fn(); //{name: "lili"}
function Fn(){
this.name="lili";
return {
age:30,
fn:1
}
}
var f1=new Fn();
console.log(f1);//{age: 30, fn: 1}
function Fn(name,age){
var n=10;
this.name=name;
this.age=age;
return; // 这里的return 会阻止下面的代码不再运行,但是没有覆盖原来的返回值(浏览器机制创建的空对象)
console.log(1);
}
var f1=new Fn("lili",18);
var f2=new Fn("dawei",20);
5、instanceof
判断 某个实例是否隶属于那个类(构造函数)
function Fn(name,age){
var n=10;
this.name=name;
this.age=age;
return;
console.log(1);
}
var f1=new Fn("lili",18);
var f2=new Fn("dawei",20);
console.log(f1 instanceof Fn);// true
console.log(f1 instanceof Array);//false
console.log(f1 instanceof Object);//true 万物皆对象,多有的对象,及实例都是Object这个基类的实例
6、【in】检测当前对象是否存在某个属性(不论私有属性,还是公有属性)
function Fn(name,age){
this.name=name;
this.age=age;
}
var f1=new Fn("lili",18);
/*
“name” 是f1 的私有属性,
"toString"是f1的共有属性,
不论私有还是公有,用in检测的结果都是true
*/
console.log("age" in f1);//true
console.log("toString in f1")//true
7、[hasOwnProperty]
检测一个属性是不是这个对象的私有属性,如果是,返回true,如果不是返回false
**
function Fn(name,age){
this.name=name;
this.age=age;
}
var f1=new Fn("lili",18);
f1.hasOwnProperty("name");// true
f1.hasOwnProperty("toString");//fasle
8、【思考题】:编写一个hasOwnPubProperty方法
编写一个hasOwnPubProperty方法,检测一个属性是不是公有的
函数才有prototype属性,实例身上没有prototype属性,
所以給原型链上添加属性:函数(类).prototype.属性名 = 属性值(可以为函数)
// 封装hasPublicProperty
function fn5(name, age) {
this.name = name;
this.age = age;
}
// 不可以直接給obj5添加原型属性; 因为obj5是实例,实例中没有prototype属性
fn5.prototype.f2 = function () {
console.log("共有属性");
};
var obj5 = new fn5("1", 2);
function hasPublicProperty(obj, attr) {
if (attr in obj) {
if (obj.hasOwnProperty(attr)) {
return false;
}
return true;
}
return false;
}
console.log(hasPublicProperty(obj5, "f2")); // true 有 false 没有 私有
9、【js创建值的两种方式】
- 字面量方式:var obj={};
- 基于构造函数: var obj2=new Object()
不管是哪种形式创建的,这些对象都是Object的一个实例。
注意:基本数据类型用字面量方式创建的实例,不是标准的实例,所以用instanceof 进行检测的时候不行,但是类的所有方法都可以照常使用
// 字面量创建的基本数据类型,用instanceof检测某个实例是否某个类 的时候,方法不能用
var m = 1;
undefined
m instanceof Number;
false
// 构造函数创建的基本数据类型,instanceof可以正常检测
var n = new Number(1);
undefined
n instanceof Number;
true
四、原型和原型链
思考: var ary=[1,2,3]; 为什么 ary就可以用Array这个类上面的公用方法:push、splice、pop…,甚至还可以用Object这个类上面的方法,比如toString呢?要想知道这个答案,咱们就需要来了解下原型(prototype)和原型链(proto)
1、重要知识
- 所有的函数都有一个属性prototype,这个属性是对象数据类型,浏览器会默认给它开辟一个堆内存
- prototype是对象数据类型(prototype上存储的公有属性),实例也是一个对象(实例中存储的私有属性)
- prototype是函数才有的属性
- 【函数】:普通函数、构造函数
- prototype这个原型上天生自带一个属性:constructor,指的是当前的构造函数
- 所有的对象都天生自带一个属性proto,它指向当前实例所属类的原型(构造器的原型)
- 【对象】:普通的对象、数组、实例、prototype(原型)等
2、画图理解
function Fn(){
var n=100;
this.A=function(){console.log("私有A")};
this.B=function(){console.log("私有B")}
}
Fn.prototype.A=function(){console.log("公有A")};
var f1=new Fn();
var f2=new Fn();
console.log(f1.A==f2.A);
console.log(f1.__proto__.A==f2.__proto__.A);
console.log(f1.__proto__.A==Fn.prototype.A)
3、【原型链查找机制】:
- 当我们要查找或者操作实例上的某个方法或者属性的时候,我们会先查找实例的私有属性,看看私有上是否有,如果有,停止查找;
- 如果没有,就会基于proto向上查找,如果找到,就是公有属性;
- 如果还没有,继续基于proto原型链向上查找,直到Object基类,如果都没有,就是操作方法或者属性不存在(返回值为undefined)
五、练习题
var num=10;
var obj={num:20};
obj.fn=(function(num){
this.num=num*3;
num++;
return function(n){
this.num+=n;
num++;
console.log(num);
}
})(obj.num);
var fn=obj.fn;
fn(5);
obj.fn(10);
console.log(num,obj.num)
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);
console.log(f1.getY==f2.getY);
console.log(f1.__proto__.getY==Fn.prototype.getY);
console.log(f1.__proto__.getX==f2.getX);
console.log(f1.getX===Fn.prototype.getX);
console.log(f1.constructor);
console.log(Fn.prototype.__proto__.constructor);
f1.getX();
f1.__proto__.getX();
f2.getY();
Fn.prototype.getY();
var name="window";
var Tom={
name:"Tom",
show:function(){
console.log(this.name);
},
wait:function(){
var fun=this.show;
fun();
}
};
Tom.wait();
// !!
function fun(){
this.a=0;
this.b=function(){
alert(this.a);
}
}
fun.prototype={
b:function(){
this.a=20;
alert(this.a);
},
c:function(){
this.a=30;
alert(this.a)
}
};
var my_fun=new fun();
my_fun.b();
var n=2;
var obj={
n:3,
fn:(function(n){
n+=2;
this.n+=2;
var n=5;
return function (m){
this.n*=2;
console.log(m+(++n));
}
})(n)
};
var fn=obj.fn;
fn(3);
obj.fn(3);
console.log(n,obj.n)