JavaScript面向对象编程浅析

JavaScript面向对象编程及设计模式

  • JavaScript面向对象编程及设计模式
    • 一、面向对象编程
      • 1、简述
      • 2、面向对象编程特点
      • 3、封装
      • 4、this
      • 5、call和apply
      • 6、new
      • 7、继承
      • 8、多态

JavaScript面向对象编程及设计模式

一、面向对象编程

1、简述

面向对象是一种程序的设计思想,与之对应的编程思想叫做面向过程

**例如:**比如我想要用代码描述一个场景,有一只叫做xiaoA的猫,吃了一个苹果,又吃了一条鱼,然后有一只叫做xiaoB的猫,吃了一根香蕉

// 面向过程
function xiaoAEatApple() {
     }
function xiaoAEatFish() {
     }
function xiaoBEatBanana() {
     }
xiaoAEatApple();
xiaoAEatFish();
xiaoBEatBanana();
// 面向对象
function Cat(name) {
     
	this.name = name
}
Cat.prototype.eat = function(something) {
     }
let xiaoA = new Cat('xiaoA')
let xiaoB = new Cat('xiaoB')
xiaoA.eat('apple')
xiaoA.eat('fish')
xiaoB.eat('banana')

2、面向对象编程特点

  • 面向对象注重于抽象事务,而面向过程注重于叙述事务
  • 面向对象逻辑清晰有条理,而面向过程比较方面
  • JS通过函数和原型,模拟了传统面向对象编程中类的概念实现了面向对象的编程模式
  • 面向对象的编程思想,主要为了实现3件事,封装,继承和多态

3、封装

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>

    <script>
    // 普通方法
    // let carA = {
     
    //     name: 'xiaoA',
    //     eat() {
     
    //         console.log('xiaoA eat something')
    //     }
    // }
    // let carB = {
     
    //     name: 'xiaoB',
    //     eat() {
     
    //         console.log('xiaoB eat something')
    //     }
    // }
    // let carC = {
     
    //     name: 'xiaoC',
    //     eat() {
     
    //         console.log('xiaoC eat something')
    //     }
    // }
    // 代码重复量多,需要封装
    // 封装
    // 使用工厂模式封装
    // function createCat(name) {
     
    //     let obj = {}
    //     obj.name = name;
    //     obj.eat = () => {
     
    //         console.log(name + 'eat something')
    //     }
    //     return obj;
    // }
    // let catA = createCat(xiaoA)
    // let catB = createCat(xiaoB)
    // let catC = createCat(xiaoC)

    // 使用面向对象的方式进行封装
    function CreateCat(name) {
      // 构造函数
        this.name = name;
        this.eat = () => {
     
            console.log(this.name + 'eat something')
        }
    }

    let catA = new CreateCat('xiaoA')
    let catB = new CreateCat('xiaoB')
    let catC = new CreateCat('xiaoC')

    </script>
</head>
<body>
    
</body>
</html>

4、this

// this
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script>
    
    // this 
    // 在函数执行的时候会在函数内部创建两个变量,arguments, this
    // arguments是存储着实参的一个类数组变量
    // this 指向函数的执行上下文 (谁调用这个函数, this就指向谁)

    function aaa (a, b) {
     
        console.log(arguments)
    }
    aaa(1, 2, 3, 4) 
    // 数组 var arr = {1, 2, 3, 4}
    // 类数组对象 var arrObj = {0: 1, 1: 2, 2: 3, 3: 4, length: 4}
    
    function bbb() {
     
        console.log(this)
    }
    var objA = {
     
        b: bbb,
        c: {
     
            d: bbb,
        }
    }

    bbb(); // this 指向 window
    objA.b() // this 指向 objA
    objA.c.d(); // this 指向 objA.c

    </script>
</head>
<body>
    
</body>
</html>

JavaScript面向对象编程浅析_第1张图片

5、call和apply

// call-apply
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script>
    // call apply 用来动态改变this的指向

    // function aaa() {
     
    //     console.log(this)
    // }
    // var objA = {
     
    //     b: aaa
    // }

    // aaa() // this 指向window
    // objA.b() // this 指向objA

    // aaa.call(objA); // call 函数将aaa内的this 从指向 window 改成了 指向objA
    // objA.b.call(window); // call 函数将aaa内的this 从指向 objA 改成了 指向window

    function aaa(name, age) {
     
        // console.log(this)
        this.name = name
        this.age = age
    }
    var objA = {
     
        b: aaa
    }

    aaa.call(objA, 'xiaoA', 23); // call 函数将aaa内的this 从指向 window 改成了 指向objA
    console.log(objA.name, objA.age); // 打印 xiaoA 23


    objA.b.call(window, ['xiaoB', 30]); // call 函数将aaa内的this 从指向 objA 改成了 指向window
    console.log(window.name, window.age)

    objA.b.call(window, 'xiaoB', 30); // call 函数将aaa内的this 从指向 objA 改成了 指向window
    console.log(window.name, window.age)

    objA.b.apply(window, ['xiaoB', 30]); // call 函数将aaa内的this 从指向 objA 改成了 指向window
    console.log(window.name, window.age)
    </script>
</head>
<body>
    
</body>
</html>

JavaScript面向对象编程浅析_第2张图片

6、new

new 做了哪些操作
1.创建一个空对象
2.将构造函数的prototype属性赋值给新对象的__proto__属性
3.将构造函数的this指向新对象
4.执行构造函数的代码
5.将新对象返回

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script>
    
    function CreateCat(name) {
      
        this.name = name
    }
    let catA = new CreateCat('xiaoA')

    console.log(catA.name)

    /*
        new 做了哪些操作
        1.创建一个空对象
        2.将构造函数的prototype属性赋值给新对象的__proto__属性
        3.将构造函数的this指向新对象
        4.执行构造函数的代码
        5.将新对象返回

    */
    // 闭包,自执行函数
    var catB = (function() {
     
        var obj = {
     }
        obj.__proto__ = CreateCat.prototype;
        CreateCat.call(obj, 'xiaoB');
        return obj
    })()
    console.log(catB.name)
    </script>
</head>
<body>
    
</body>
</html>

在这里插入图片描述

7、继承

在声明函数的时候,会自动创建一个prototype属性,我们管他叫做原型, 一般用来存放实例公用的方法

prototype:
是子类继承的父类的属性,也就是当调用子类构造函数时,总的来说,这里只能是继承一个具体的对象,不能是一个类(es6后会有所改变)

prototype内容:
JavaScript面向对象编程浅析_第3张图片
__proto__内容
是类继承父类之后,子类对象中的父类属性,里面的属性是可以通过子类对象直接取的,不需要用__proto__。
这里检索逻辑是:
先检索子类的直接属性是否存在,然后再检索__proto__,然后就是俄罗斯套娃,一层一层套下去,到最后还是没有就返回 undefined

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script>
    /*
        new 做了哪些操作
        1.创建一个空对象
        2.将构造函数的prototype属性赋值给新对象的__proto__属性
        3.将构造函数的this指向新对象
        4.执行构造函数的代码
        5.将新对象返回

    */
    
    // 在声明函数的时候,会自动创建一个prototype属性,我们管他叫做原型, 一般用来存放实例公用的方法

    function CreateCat(name) {
      
        this.name = name;
    } 
    // console.log('prototype:', CreateCat.prototype)

    CreateCat.prototype.eat = function(something) {
     
        console.log(this.name + ' eat ' + something)
    }

    var catA = new CreateCat('xiaoA')
    catA.eat('fish')

    /*
        carA:
        1. {}  // 第一步创建新对象
        2. {
                __proto__: CreateCat.prototype
            }
        3. {
                __proto__: CreateCat.prototype,
                name:  'xiaoA'
            }
        4.执行构造函数的代码
        5.return
    */
    // 在JS里规定,访问对象属性的时候,如果对象下面没有这个属性,则去他下面的__proto__去寻找,如果没有,就一直向下寻找直到没有__proto__为止
    console.log(catA)

    // 类式继承
    function A(name) {
     
        this.name = name;
        this.list = [1, 2, 3]
    }

    A.prototype.getName = () => {
     
        console.log(this.name);
    }

    function SubA(name) {
     
        this.subName = 'sub' + this.name;
    }

    SubA.prototype = new A()

    var sa1 = new SubA('sa1');
    console.log(sa1.list, sa1.name); // 打印出 [1, 2, 3] undefined

    /*
        new A() -> {
            name: undefined,
            list: [1,2,3],
            __proto__: {
                getName: fn,
                constructor....
            }
        }

        new SubA('sa1') -> {
            subName: 'sub sa1',
            __proto__: {
                name: undefined,
                list: [1,2,3],
                __proto__: {
                    getName: fn,
                    constructor....
                }
            }
        }

        // 类式继承的问题
        1.这种方法不支持父构造函数带参数
        2.父构造函数里的方法和属性都会变成共有属性
    */

    var sa1 = new SubA('sa1'); 
    var sa2 = new SubA('sa2'); 

    A.prototype.getName = function() {
     
        console.log('fixed getName')
    }
    A.prototype.newFn = function() {
      
        console.log('new Fn')
    }
    sa1.getName(); // 打印 fixed getName
    sa2.newFn();   // 打印 new Fn

    // 构造函数继承

    function A(name) {
     
        this.name = name;
        this.list = [1, 2, 3]
    }

    A.prototype.getName = () => {
     
        console.log(this.name);
    }

    function SubA(name) {
     
        A.call(this, name)
        this.subName = 'sub' + this.name;
    }

    var sa1 = new SubA('xiaoA');
    console.log(sa1.name, sa1.subName); // 打印出 xiaoA subxiaoA
    sa1.getName(); // 报错

    /*
        new SubA('xiaoA'); -> {
            __proto__: {
                constructor....
            },
            name: 'xiaoA',
            list: [1, 2, 3],
            subName: 'sub xiaoA'
        }

        // 构造函数继承问题
        1.不能继承父构造函数的原型方法
    
    */

    // 组合式继承

    function A(name) {
     
        this.name = name;
        this.list = [1, 2, 3]
    }

    A.prototype.getName = () => {
     
        console.log(this.name);
    }

    function SubA(name) {
     
        A.call(this, name)
        this.subName = 'sub' + this.name;
    }

    SubA.prototype = new A()
    var sa1 = new SubA('xiaoA');
    console.log(sa1.name, sa1.subName); // 打印出 xiaoA subxiaoA
    sa1.getName(); // xiaoA
    /*
        new A() ->  {
            name: undefined,
            list: [1, 2, 3],
            __proto__: {
                getName: fn
            }
        }
        new SubA('xiaoA'); -> {
            name: 'xiaoA',
            list: [1, 2, 3],
            subName: 'sub xiaoA',
            __proto__: {
                name: undefined,
                list: [1, 2, 3],
                __proto__: {
                    getName: fn
                }
            },
        }

        // 组合式继承问题
        1.__proto__里的属性没有用
        2.执行了两次父构造函数
        问题点在于: SubA.prototype = new A()
    */

    // 4.寄生组合式继承
    function A(name) {
     
        this.name = name;
        this.list = [1, 2, 3]
    }

    A.prototype.getName = () => {
     
        console.log(this.name);
    }

    function SubA(name) {
     
        A.call(this, name)
        this.subName = 'sub' + this.name;
    }

    // SubA.prototype = new A() // 优化这一句代码
    function inheritPrototype(subClass, superClass) {
      // 子构造函数 父构造函数
        function F() {
     };
        F.prototype = superClass.prototype;
        subClass.prototype = new F()
        subClass.prototype.constructor = subClass; // 可有可无
    }
    inheritPrototype(SubA, A)

    var sa1 = new SubA('xiaoA');
    console.log(sa1.name, sa1.subName); // 打印出 xiaoA subxiaoA
    sa1.getName(); // xiaoA
    </script>
</head>
<body>
    
</body>
</html>

8、多态

多态: 表示不同对象调用相同方法会产生不同结果

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script>
    // 多态: 表示不同对象调用相同方法会产生不同结果
    function Base() {
     }

    Base.prototype.initial = function() {
     
        this.init()
    }

    function SubA() {
     
        this.init = function () {
      
            console.log('subA init')
        }
    }
    function SubB() {
     
        this.init = function () {
      
            console.log('subB init')
        }
    }
    
    SubA.prototype = new Base();
    SubB.prototype = new Base();

    var subA = new SubA()
    var subB = new SubB()

    subA.initial(); 
    subB.initial();

    </script>
</head>
<body>
    
</body>
</html>

你可能感兴趣的:(JavaScript)