面向对象编程

了解面向对象编程思想,需要先了解对象、类、实例。万物皆对象,一切都可以看成对象,什么是类?比如咱们自然界中有很多的生物,咱们可以把这些生物分为:植物、动物。。。,那植物就是一个类,动物也是类,动物还可进行细分,比如:高级动物、低级动物,都是分类。什么是实例呢?比如说人类是一个分类,那具体的某个人,比如:丽丽就是一个人类中的一个实例,我想研究人类有哪些特点,就具体的拿这个人来研究就行。咱们在学习js的时候,也是按照这个思想去学习。

面向对象.png

  • 对象:万物皆对象
  • 类:对象的具体细分,按照功能或者特征进行分类
  • 实例:类中具体的一个事物(拿出具体的一个实例进行研究,当前类下的其他实例也会具有这些特点和特征)

一、单例设计模式(就是一个对象)

1、单例模式

单例模式:可以把描述一个事物的所有属性放到一个对象中,这样避免的相互的干扰,这种设计模式就是单例设计模式。

单例设计模式中,obj不仅是对象名,它也称为“命名空间”(NameSpace)。每个命名空间都是Object这个内置基类的一个实例,每个实例之间都是互相独立,互不干扰。

  1. var name="wangzhe";
  2. var age=18;
  3. var sex="女";
  4. var name="shuaiyuan";
  5. var age=20;
  6. var sex="男";
  1. var obj1={
  2. name:"wangzhe",
  3. age:18,
  4. sex:"女"
  5. }
  6. var obj2={
  7. name:"shuaiyuan",
  8. age:20,
  9. sex:"男"
  10. }

2、高级单例模式

nameSpace 和自执行函数没有关系,最终是等于自执行函数的返回值,最终是把返回对象的堆内存的地址给了nameSpace。在面试的时候,咱们可以写这个复杂的。


  1. var nameSpace=(function(){
  2. function fn(){},
  3. var a=2;
  4. //... 在这个作用域中还有很多的变量和方法,
  5. return{
  6. // 想要把谁暴露出去,就放到这对象中
  7. fn:fn
  8. }
  9. })();
  10. console.log(nameSpace.fn)

3、【项目实战中 单例模式的应用】

  • 在公司一般都是团队协作开发,每人都会分配不同的任务,开发自己的模块
  • 对于整个项目中,公用的功能,可以提取出来,供大家使用 ```javascript //员工A:首页模块的开发 var indexModel=(function(){

    1. function fn2(){
    2. }
    3. //...
    4. return {
    5. init:function(){
    6. // 想要调用自己模块里面的方法:
    7. fn2();
    8. },
    9. // 如果 员工B 想要调员工A里面的方法fn2,只用把这个方法暴露出去
    10. fn2:fn2
    11. }

    })(); // 想要初始化函数:indexModel.init();

//员工B:详情页的开发 var messageModel=(function(){ function fn(){ } //… return { init:function(){ // 想要调用自己模块里面的方法: fn(); // 调用A员工模块里面的fn2方法 indexModel.fn2();

  1. }
  2. }

})();

// 想要初始化函数:messageModel.init();

  1. **对于公用性模块也可以用单例模式**
  2. ```javascript
  3. var utils=(function(){
  4. function fn(){
  5. };
  6. function b(){
  7. };
  8. // ....
  9. return {
  10. fn:fn,
  11. b:b
  12. }
  13. })();
  14. // 想要用这些方法:
  15. utils.fn();
  16. utils.b();
  17. .....

二、工厂设计模式

把实现相同功能的代码进行封装,后期在使用的时候,只用调用这个函数即可,方便后期的“批量生产”。减少了页面中冗余的代码,实现了“高耦合低内聚”。

  1. var person1={
  2. name:"lili",
  3. age:18
  4. }
  5. var person2={
  6. name:"dava",
  7. age:20
  8. }
  9. //....每次都需要重复的去写,很麻烦,所以就可以用工厂模式
  10. function person(name,age){
  11. return{
  12. name:name,
  13. age:age
  14. }
  15. }
  16. person("lili","18")
  17. person("dava","20")

三、构造函数模式(自定义类)

  • 由于工厂模式里边没有类的概念,所以出现了构造函数
  • 把一个普通的函数,在执行的时候加了一个new 那这个函数就是构造函数,当前的函数名称为“类名”比如下面例子中的Fn 就是类,f1,f2 就是实例
  • 函数执行的返回结果就是当前构造函数的一个实例,比如f1、f2 就是当前函数的一个实例
  • 实例是对象数据类型(实例和实例的空间地址不同)
  • 构造函数一般名字都大写
  • f1 和f2 是独立的堆内存,不相等
  • 这种构造函数设计模式一般主要用在类库、框架等的封装,在日常的业务逻辑中很少去使用。
  • 如果构造函数没有形参,在调用的时候可以直接省区() // var fn1 = new fn;


1、构造函数的分类:

  1. 内置类:Number Boolean String Null Undefined Function Object Date Math 。。。
  2. 自定义类: ```javascript // 内置类 var obj1 = new Object(); console.log(obj1);

// 自定义类 function fn(name, age) { this.name = name, this.age = age }

  1. <a name="eKoaK"></a>
  2. ### 2、构造函数的this
  3. **构造函数中的this就是当前的实例**
  4. ```javascript
  5. // fn1、fn2为实例,构造函数中的this就是当前的实例,通过this添加的属性名和属性值都是給当前实例添加的
  6. var fn1 = new fn("lili", 15);
  7. var fn2 = new fn("nana", 16);
  8. console.log(f1, f2);

创建数组的两种方式:

  1. 字面量方式
  2. 构造函数方式(内置类)
    1. 当new Array() 传入一个参数的时候,这个参数表示的是新创建数组的长度
    2. var ary2 = new Array(5); // [empty × 5] 表示创建了一个数组长度为5的数组,每项值为空。
      1. // 创建数组的两种方式:
      2. // 字面量的方式
      3. var ary = [1, 2, 3];
      4. // 构造函数的方式
      5. // 当new Array() 传入一个参数的时候,这个参数表示的是新创建数组的长度
      6. var ary1 = new Array(1, 2, 3);
      7. var ary2 = new Array(5); // [empty × 5] 表示创建了一个数组长度为5的数组,每项值为空。

3、构造函数的原理

构造函数的原理.png
思考题

  1. function Fn(name,age){
  2. var n=10;
  3. this.name=name;
  4. this.age=age;
  5. }
  6. var f1=new Fn("lili",18);
  7. var f2=new Fn("dawei",20);
  8. console.log(f1==f2);
  9. console.log(f1.name);
  10. console.log(f1.n);
  11. console.log("name" in f1);

4、构造函数中,如果我们手动的return了一个值,那结果又如何呢?

  • 在构造函数中,如果我们手动返回一个基本数据类型的值,没有任何的影响,最终返回的还是浏览器自己创建的那个实例;
  • 如果return;也没有任何的影响,最终返回的也还是浏览器自己创建的那个实例;
  • 如果我们手动返回一个引用数据类型的值,最终返回的结果就是我们手动返回的这个引用数据。
  • 所以在构造函数中,我们尽量减少return 的使用,防止覆盖原来的。
  1. // 当返回的是基本数据类型
  2. function Fn(){
  3. this.name="lili";
  4. return 100
  5. }
  6. var f1=new Fn(); //{name: "lili"}
  7. function Fn(){
  8. this.name="lili";
  9. return {
  10. age:30,
  11. fn:1
  12. }
  13. }
  14. var f1=new Fn();
  15. console.log(f1);//{age: 30, fn: 1}
  16. function Fn(name,age){
  17. var n=10;
  18. this.name=name;
  19. this.age=age;
  20. return; // 这里的return 会阻止下面的代码不再运行,但是没有覆盖原来的返回值(浏览器机制创建的空对象)
  21. console.log(1);
  22. }
  23. var f1=new Fn("lili",18);
  24. var f2=new Fn("dawei",20);

5、instanceof

判断 某个实例是否隶属于那个类(构造函数)

  1. function Fn(name,age){
  2. var n=10;
  3. this.name=name;
  4. this.age=age;
  5. return;
  6. console.log(1);
  7. }
  8. var f1=new Fn("lili",18);
  9. var f2=new Fn("dawei",20);
  10. console.log(f1 instanceof Fn);// true
  11. console.log(f1 instanceof Array);//false
  12. console.log(f1 instanceof Object);//true 万物皆对象,多有的对象,及实例都是Object这个基类的实例

6、【in】检测当前对象是否存在某个属性(不论私有属性,还是公有属性)

  1. function Fn(name,age){
  2. this.name=name;
  3. this.age=age;
  4. }
  5. var f1=new Fn("lili",18);
  6. /*
  7. “name” 是f1 的私有属性,
  8. "toString"是f1的共有属性,
  9. 不论私有还是公有,用in检测的结果都是true
  10. */
  11. console.log("age" in f1);//true
  12. console.log("toString in f1")//true

7、[hasOwnProperty]

检测一个属性是不是这个对象的私有属性,如果是,返回true,如果不是返回false
**

  1. function Fn(name,age){
  2. this.name=name;
  3. this.age=age;
  4. }
  5. var f1=new Fn("lili",18);
  6. f1.hasOwnProperty("name");// true
  7. f1.hasOwnProperty("toString");//fasle

8、【思考题】:编写一个hasOwnPubProperty方法

编写一个hasOwnPubProperty方法,检测一个属性是不是公有的

函数才有prototype属性,实例身上没有prototype属性,

所以給原型链上添加属性:函数(类).prototype.属性名 = 属性值(可以为函数)

  1. // 封装hasPublicProperty
  2. function fn5(name, age) {
  3. this.name = name;
  4. this.age = age;
  5. }
  6. // 不可以直接給obj5添加原型属性; 因为obj5是实例,实例中没有prototype属性
  7. fn5.prototype.f2 = function () {
  8. console.log("共有属性");
  9. };
  10. var obj5 = new fn5("1", 2);
  11. function hasPublicProperty(obj, attr) {
  12. if (attr in obj) {
  13. if (obj.hasOwnProperty(attr)) {
  14. return false;
  15. }
  16. return true;
  17. }
  18. return false;
  19. }
  20. console.log(hasPublicProperty(obj5, "f2")); // true 有 false 没有 私有

9、【js创建值的两种方式】

  • 字面量方式:var obj={};
  • 基于构造函数: var obj2=new Object()

    不管是哪种形式创建的,这些对象都是Object的一个实例。

注意:基本数据类型用字面量方式创建的实例,不是标准的实例,所以用instanceof 进行检测的时候不行,但是类的所有方法都可以照常使用

  1. // 字面量创建的基本数据类型,用instanceof检测某个实例是否某个类 的时候,方法不能用
  2. var m = 1;
  3. undefined
  4. m instanceof Number;
  5. false
  6. // 构造函数创建的基本数据类型,instanceof可以正常检测
  7. var n = new Number(1);
  8. undefined
  9. n instanceof Number;
  10. 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、画图理解

  1. function Fn(){
  2. var n=100;
  3. this.A=function(){console.log("私有A")};
  4. this.B=function(){console.log("私有B")}
  5. }
  6. Fn.prototype.A=function(){console.log("公有A")};
  7. var f1=new Fn();
  8. var f2=new Fn();
  9. console.log(f1.A==f2.A);
  10. console.log(f1.__proto__.A==f2.__proto__.A);
  11. console.log(f1.__proto__.A==Fn.prototype.A)

原型和原型链.png
原型链2.png

3、【原型链查找机制】:

  • 当我们要查找或者操作实例上的某个方法或者属性的时候,我们会先查找实例的私有属性,看看私有上是否有,如果有,停止查找;
  • 如果没有,就会基于proto向上查找,如果找到,就是公有属性;
  • 如果还没有,继续基于proto原型链向上查找,直到Object基类,如果都没有,就是操作方法或者属性不存在(返回值为undefined)


五、练习题

  1. var num=10;
  2. var obj={num:20};
  3. obj.fn=(function(num){
  4. this.num=num*3;
  5. num++;
  6. return function(n){
  7. this.num+=n;
  8. num++;
  9. console.log(num);
  10. }
  11. })(obj.num);
  12. var fn=obj.fn;
  13. fn(5);
  14. obj.fn(10);
  15. console.log(num,obj.num)
  1. function Fn(){
  2. this.x=100;
  3. this.y=200;
  4. this.getX=function(){
  5. console.log(this.x);
  6. }
  7. }
  8. Fn.prototype.getX=function(){
  9. console.log(this.x);
  10. }
  11. Fn.prototype.getY=function(){
  12. console.log(this.y);
  13. }
  14. var f1=new Fn;
  15. var f2=new Fn;
  16. console.log(f1.getX==f2.getX);
  17. console.log(f1.getY==f2.getY);
  18. console.log(f1.__proto__.getY==Fn.prototype.getY);
  19. console.log(f1.__proto__.getX==f2.getX);
  20. console.log(f1.getX===Fn.prototype.getX);
  21. console.log(f1.constructor);
  22. console.log(Fn.prototype.__proto__.constructor);
  23. f1.getX();
  24. f1.__proto__.getX();
  25. f2.getY();
  26. Fn.prototype.getY();
  1. var name="window";
  2. var Tom={
  3. name:"Tom",
  4. show:function(){
  5. console.log(this.name);
  6. },
  7. wait:function(){
  8. var fun=this.show;
  9. fun();
  10. }
  11. };
  12. Tom.wait();
  1. // !!
  2. function fun(){
  3. this.a=0;
  4. this.b=function(){
  5. alert(this.a);
  6. }
  7. }
  8. fun.prototype={
  9. b:function(){
  10. this.a=20;
  11. alert(this.a);
  12. },
  13. c:function(){
  14. this.a=30;
  15. alert(this.a)
  16. }
  17. };
  18. var my_fun=new fun();
  19. my_fun.b();
  1. var n=2;
  2. var obj={
  3. n:3,
  4. fn:(function(n){
  5. n+=2;
  6. this.n+=2;
  7. var n=5;
  8. return function (m){
  9. this.n*=2;
  10. console.log(m+(++n));
  11. }
  12. })(n)
  13. };
  14. var fn=obj.fn;
  15. fn(3);
  16. obj.fn(3);
  17. console.log(n,obj.n)