面向对象

129 阅读2分钟

1.基础知识

基本数据类型 number string boolean null undefined Symbol
引用数据类型 object(Function Array RegExp)

Symbol

es6 新增了一个基本数据类型Symbol,表示独一无二的值,最大的用法是用来定义对象的唯一属性名。

    <script>
        let car=Symbol('car');
        let obj={
            [car]:'bwm'
        }
        obj.car="audi";
        console.log(obj);
        console.log(obj.car);
        console.log(obj[car]);
    </script>

image.png

2.栈内存与堆内存

    <script>
        /* 引用数据类型 会开辟一个内存【栈内存(内存的地址) 和 堆内存(内存的值)】 */
        let obj1 = {name:'leo',age:20};
        let obj2 = {};
        
        /* obj1的栈内存的地址给了obj2 */
        obj2 = obj1;
        
        /* obj2 把obj1的堆内存的name值 由leo 改成了 lily */
        obj2.name="lily"

        /* 所以 obj1 和 obj2 的值是一样的,都是obj1的堆内存的值 */
        console.log('obj1',obj1);
        console.log('obj2',obj2);
    </script>

3.const

    <script>
        /* 定义常量 不能给常量重复地赋值 */
        const url = 'http://timemeetyou.com:8889/api/private/v1/';
        /* url = 'http://www.baidu.com' */ /* 会报错 */

        /* 定义对象 引用数据类型 会开辟一个堆内存 内存地址不会变化*/
        /* 地址不会被改变 也就不会报错 */
        const obj = {
            name:"zhangsan"
        }
        obj.name = 'lili'
        obj.age = 20
        console.log(obj);   
        
        /* 改变obj的内存地址会报错
        let obj2 = {};
        obj = obj2;  */  
    </script>

4.工厂模式

软件工程领域的一种设计模式, 抽象了创建对象的过程, 通过函数封装创建对象的细节。
🍎工厂模式的缺点:
看不出类型(比如是时间对象 会具有时间对象的属性和方法)--解决:构造函数
函数重复、浪费资源(消耗内存)--解决:原型

    <script>
        function getPerson(name, age, job, address) {
            let person = new Object();
            person.name = name
            person.age = age
            person.job = job
            person.address = address
            person.intro = function () {
                document.write(
                    `<p>姓名:${this.name}</p>
                     <p>年龄:${this.age}</p>
                     <p>职业:${this.job}</p>
                     <p>住址:${this.address}</p> `
                )
            }
            return person
        }
        let person1 = getPerson('lily', 20, '工人', '南京');
        person1.intro()
    </script>

5.构造函数

系统提供的构造函数:String Number Boolean Object RegExp Date Array
构造函数一般以大写字母开头。
构造函数也是函数,只不过可以用来创建对象。
🍎与工厂模式对比:
没有显式创建对象
直接将属性和方法赋给了this对象
没有return

    <script>
        function Person(){
            this.name = '张三'
            this.fn = function (){
                document.write(this.name)
            }
        }
        /* new 先创造了一个是实例化对象 并且把this给了实例化对象per1 */
        /* 也就是把属性和方法给了per1 */
        let per1 = new Person();
        console.log(per1);
    </script>

6.原型 prototype

每个函数都有一个prototype(原型)属性。是一个指针,指向一个对象。 这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。

    <script>
        /*  构造函数+prototype
            构造函数:属性
            原型prototype:方法*/
        // 用混合方式构造对象
        function Person(name,age){
            this.name = name;
            this.age = age;
        }
        /* 函数重复、浪费资源--解决:原型 */   
        Person.prototype.eat = function (){
            document.write(`姓名:${this.name} 年龄:${this.age} 会吃饭`);
        }
        Person.prototype.weight = '70kg'
        let p1 = new Person('lily',20);
        p1.eat();
        document.write(p1.weight); 
    </script>

练习:利用原型的方法去除字符串空格

    <script>
        //去除前面空格
        // String.prototype.clearQSpace=function(){
        //     return this.replace(/^\s+/,'')
        // }
        // let nStr='   abc'.clearQSpace();
        // console.log(nStr);

        //去除后面空格
        // String.prototype.clearHSpace= function () {
        //     return this.replace(/\s+$/, '')
        // }
        // let nStr = 'abc  '.clearHSpace();
        // console.log(nStr.length);

        //去除前后空格
        String.prototype.clearQHSpace= function () {
            return this.replace(/^\s+|\s+$/g,'')
        }
        let nStr = ' a b  c  '.clearQHSpace();
        console.log(nStr.length);

        //去除所有空格
        // String.prototype.clearASpace= function () {
        //     return this.replace(/\s+/g, '')
        // }
        // let nStr = ' a b  c  '.clearASpace();
        // console.log(nStr.length);
    </script>        

7.继承的实现方式

(1)原型链继承

prototype属性+实例化对象
    <script>
        function Car() {
            this.color = 'red';
            this.price = 3000;
        }
        function BMW() {
            this.print = function () {
                document.write(`${this.color}${this.price}`)
            }
        }
        //让BMW继承Car的属性
        BMW.prototype = new Car(); 
        //子类的prototype指向一个父类的实例
        BMW.prototype.constructor = BMW; 
        //子类加上constructor属性,并将这个属性指回原来的构造函数
        let car1 = new BMW();
        console.log(car1);
        car1.print()
    </script>
直接继承prototype

缺点:
① 任何对子类.prototype的修改,都会反映到父类.prototype
② 子类的 prototype.constructor 不会指向自己,指向父类。

    <script>
        function Car() {}
        Car.prototype.color ='red';
        Car.prototype.price = 3000;
        function BMW() {
            this.print = function () {
                document.write(`${this.color}${this.price}`)
            }
        }
        BMW.prototype=Car.prototype
        let car1=new BMW();
        console.log(car1);
        car1.print()   
    </script>

(2)利用空对象作为中介

优点:空对象,几乎不占内存。对子类.prototype的修改,不会影响到父类.prototype

    <script>
        function Car() { }
        Car.prototype.color = 'red';
        Car.prototype.price = 3000;

        /* 封装
         function f() { }; // 新建一个空对象
         f.prototype = Car.prototype; //把父类的原型直接赋值给空对象的原型上
         BMW.prototype = new f(); //把空对象的实例化对象 给到子类的原型上
         BMW.prototype.constructor = BMW; //★constructor构造器都是指向自己的 */

        function extend(child, parent) {
            function f() { };
            f.prototype = parent.prototype;
            child.prototype = new f();
            child.prototype.constructor = child;
        }
        extend(BMW, Car)

        function BMW() {
            this.print = function () {
                document.write(`${this.color}${this.price}`)
            }
        }
        let car1 = new BMW();
        console.log(car1);
        car1.print()
    </script>

(3)构造函数绑定 - apply/call 方法

在子类的内部调用父类的方法,通过 call() 或 apply() 方法。

    <script>
        function Car(gang, bsx) {
            this.wheel = 4;
            this.gang = gang;
            this.bsx = bsx
        }
        function Production(name, price, gang, bsx) {
            this.name = name;
            this.price = price;
            //使用call或apply来改变this指向,把父类中的this指向子类
            Car.call(this, gang, bsx);
            // Car.apply(this,[gang, bsx]);
            this.print = function () {
                document.write(
                `${this.name}${this.price}${this.wheel}个轮子,${this.gang}发动机,${this.bsx}。`
                )
            }
        }
        let car1 = new Production('奔驰', '100w', '3缸', 'at变速箱');
        console.log(car1);
        car1.print()
    </script>

(4)组合继承

亦称“伪经典继承”, 将原型链继承和构造函数继承组合在一块, 原型链实现对原型属性和方法的继承, 借用构造函数实现对实例属性的继承。

    <script>
        function Car() {
            this.wheel = 4;
        }
        Car.prototype.run = function () {
            document.write(`${this.name}${this.wheel}个轮子,会跑赛道。`)
        }
        function production(name) {
            Car.call(this); //借用构造函数实现对实例属性的继承
            this.name = name
        }
        //原型链实现对原型属性和方法的继承
        production.prototype = new Car();
        production.prototype.constructor = production;
        let car1 = new production('奔驰');
        console.log(car1);
        car1.run()
    </script>

(5)拷贝继承

将父对象的prototype对象中的属性,一一拷贝给子对象的prototype对象。

    <script>
        function Car() { }
        Car.prototype.wheel = 4;
        Car.prototype.run = function () {
            document.write(`${this.name}${this.wheel}个轮子,会跑赛道。`)
        }
        function production(name) {
            this.name = name
        }
        function extend(child, parent) {
            for (let key in parent.prototype) {
                child.prototype[key] = parent.prototype[key]
            }
        }        
        extend(production, Car);
        let car1 = new production('奔驰');
        console.info(car1);
        car1.run()
    </script>