美文网首页
JavaScript--面向对象

JavaScript--面向对象

作者: 绚丽多彩的白 | 来源:发表于2020-09-24 22:26 被阅读0次

    面向对象基本概念

    • 面向对象(Object Oriented,OO)是软件开发方法
    • 面向对象是一种对现实世界抽象的理解,是计算机编程技术发展到一定阶段后的产物
    • Object Oriented Programming-OOP ——面向对象编程



    面向对象和面向过程区别

    • 面向过程
      • 强调的是功能行为
      • 关注的是解决问题需要哪些步骤
    • 回想下前面我们完成一个需求的步骤:
      • 首先搞清楚我们要做什么
      • 然后分析怎么做
      • 最后用代码体现
      • 一步一步去实现,而具体的每一步都需要我们去实现和操作
    • 在上面每一个具体步骤中我们都是参与者, 并且需要面对具体的每一个步骤和过程, 这就是面向过程最直接的体现

    • 面向对象是基于面向过程而言

    • 面向对象和面向过程都是一种思想

    • 面向对象

      • 将功能封装进对象,强调具备了功能的对象
      • 关注的是解决问题需要哪些对象
    • 当需求单一, 或者简单时, 我们一步一步去操作没问题, 并且效率也挺高。 可随着需求的更改, 功能的增加, 发现需要面对每一个步骤非常麻烦, 这时就开始思索, 能不能把这些步骤和功能再进行封装, 封装时根据不同的功能,进行不同的封装,功能类似的封装在一起。这样结构就清晰多了, 用的时候, 找到对应的类就可以了, 这就是面向对象思想


    • 示例
    • 买电脑
      • 面向过程
        • 了解电脑
        • 了解自己的需求
        • 对比参数
        • 去电脑城
        • 砍价,付钱
        • 买回电脑
        • 被坑
      • 面向对象
        • 找班长
        • 描述需求
        • 班长把电脑买回来

    • 吃饭
      • 面向过程
        • 买菜
        • 摘菜
        • 洗菜
        • 切菜
        • 炒菜
        • 盛菜
      • 面向对象
        • 去饭店
        • 点菜

    • 洗衣服
      • 面向过程
        • 脱衣服
        • 放进盆里
        • 放洗衣液
        • 加水
        • 放衣服
        • 搓一搓
        • 清一清
        • 拧一拧
        • 晒起来
      • 面向对象
        • 脱衣服
        • 打开洗衣机
        • 丢进去
        • 一键洗衣烘干
      • 终极面向对象
        • 买电脑/吃饭/洗衣服
        • 找个对象

    面向对象的特点

    • 是一种符合人们思考习惯的思想
    • 可以将复杂的事情简单化
    • 将程序员从执行者转换成了指挥者
    • 完成需求时:
      • 先要去找具有所需的功能的对象来用
      • 如果该对象不存在,那么创建一个具有所需功能的对象
      • 这样简化开发并提高复用

    类与对象的关系

    • 面向对象的核心就是对象,那怎么创建对象?
      • 现实生活中可以根据模板创建对象,编程语言也一样,也必须先有一个模板,在这个模板中说清楚将来创建出来的对象有哪些属性行为
    • JavaScript中的类相当于图纸,用来描述一类事物。
    • JavaScript中可以自定义类, 但是也提供了一个默认的类叫做Object

    使用默认类创建对象

    • 通过 new Object() 创建对象
    <script>
        // 1.使用默认类创建一个空对象
        var obj = new Object()
        // 2.动态给空对象新增属性
        obj.name = "lnj";
        obj.age = 33;
        // 3.动态给空对象新增方法
        obj.say = function () {
            console.log("hello");
        }
        // 4.使用对象的属性和方法
        console.log(obj.name);
        console.log(obj.age);
        obj.say();
    </script>
    
    • 通过字面量创建对象
    <script>
        /*
        // 1.使用字面量创建对象
        var obj = {}; // 相当于var obj = new Object()
        // 2.动态给空对象新增属性
        obj.name = "lnj";
        obj.age = 33;
        // 3.动态给空对象新增方法
        obj.say = function () {
            console.log("hello");
        }
        */
    
        // 1.使用字面量创建对象
        var obj = {
            name : 'lnj',
            age: 33,
            say : function () {
                console.log("hello");
            }
        }
        // 2.使用对象的属性和方法
        console.log(obj.name);
        console.log(obj.age);
        obj.say();
    </script>
    
    • 使用工厂函数创建对象
      • 上面的创建方式, 没多创建一个人都需要将代码再写一遍, 冗余代码太多, 所以我们可以创建创建对象的代码封装到一个函数中
      • 专门用于创建对象的函数我们称之为工厂函数
    <script>
        function createPerson(name, age) {
            var obj = new Object();
            obj.name = name;
            obj.age = age;
            obj.say = function () {
                console.log("hello");
            }
            return obj;
        }
        var obj1 = createPerson("lnj", 33);
        var obj2 = createPerson("zq", 18);
        console.log(obj1);
        console.log(obj2);
    </script>
    
    <script>
        function createPerson(name, age) {
            var obj = {
                name: name,
                age: age,
                say: function () {
                    console.log("hello");
                }
            }
            return obj;
        }
        var obj1 = createPerson("lnj", 33);
        var obj2 = createPerson("zq", 18);
        console.log(obj1);
        console.log(obj2);
    </script>
    

    函数中的this关键字

    • 每个函数中都有一个this关键字, 谁调用当前函数, this关键字就是谁
    <script>
        function test() {
            console.log(this);
        }
        // 默认情况下直接调用的函数都是由window调用的
        // 所以test函数中的this是window
        test(); // 相当于window.test();
        
        var obj = new Object()
        obj.name = "lnj";
        obj.age = 33;
        obj.say = function () {
            console.log(this.name, this.age);
        }
    
        // 这里的say是一个方法, 方法必须通过对象来调用
        // 所以这里的say方法是由obj对象调用的, 所以say方法中的this是obj对象
        obj.say();
    </script>
    

    如何设计一个类

    • 生活中描述事物无非就是描述事物的属性行为
      • 如:人有身高,体重等属性,有说话,打架等行为。
    事物名称(类名):人(Person)
    属性:身高(height)、年龄(age)
    行为(功能):跑(run)、打架(fight)
    
    • JavaScript中用类来描述事物也是如此
      • 属性:对应类中的成员变量。
      • 行为:对应类中的成员方法。
    • 定义类其实在定义类中的成员(成员变量和成员方法)
    • 拥有相同或者类似属性(状态特征)和行为(能干什么事)的对象都可以抽像成为一个类

    如何分析一个类

    • 一般名词都是类(名词提炼法)

    如何定义一个类

    • 在JavaScript中可以通过构造函数来定义一个类
    • 构造函数也是一个函数, 只不过函数的名称必须大写(帕斯卡命名)
    • 构造函数也是一个函数, 只不过调用时必须通过new来调用
    <script>
        // 通过构造函数定义一个类
        // 在构造函数中描述该类事物共性的属性和行为
        function Person(name, age) {
            this.name = name;
            this.age = age;
            this.say = function () {
                console.log(this.name, this.age);
            }
        }
    </script>
    

    如何通过类创建一个对象

    • 不过就是创建结构体的时候, 根据每个对象的特征赋值不同的属性罢了
    <script>
        // 通过构造函数定义一个类
        // 在构造函数中描述该类事物共性的属性和行为
        function Person(name, age) {
            this.name = name;
            this.age = age;
            this.say = function () {
                console.log(this.name, this.age);
            }
        }
        // 通过构造函数创建对象
        // new 的执行过程
        // 1 在内存中创建了一个空的对象
        // 2 让构造函数中的this指向刚刚创建的对象
        // 3 执行构造函数,在构造函数中设置属性和方法(当然也可以做其它事情)
        // 4 返回了当前对象
        var p1 = new Person("lnj", 33);
        var p2 = new Person("zq", 18);
        p1.say();
        p2.say();
    </script>
    
    • 与工厂函数的不同之处
      • 不用我们自己手动创建对象
      • 不用我们手动返回创建好的对象
      • 构造函数名称首字母必须大写
      • 构造函数必须通过new调用

    构造函数作用

    • 使用构造函数的好处不仅仅在于代码的简洁性,更重要的是我们可以识别对象的具体类型了
    • 每个对象都可以访问一个名称叫做constructor的属性, 属性指向创建该实例的构造函数
    <script>
        var obj = new Object();
        obj.name = "lnj";
        console.log(obj); // Object
    
        function Person(name) {
           this.name = name;
        }
        var p = new Person("zs");
        console.log(p); // Person
        // 可以通过对象的constructor属性判断某个对象是否是某个类的实例
        console.log(p.constructor === Person); // true
        // 也可以通过instanceof判断某个对象是否是某个类的实例
        // 企业开发推荐
        console.log(p instanceof Person);
    </script>
    

    构造函数的内存优化问题

    • 每当通过一个构造函数创建一个对象, 就会在内存中开辟一块存储空间, 该存储空间中保存了对象的所有属性和方法
    • 如果构造函数中的某些属性或方法是需要变化的, 那么每份存储空间中都应该保存一份独有, 但是如果某些属性和方法是不变的, 那么每份存储空间中都保存一份则造成了内存浪费
    <script>
        function Person(name, age) {
            this.name = name;
            this.age = age;
            this.type = "人";
            this.say = function () {
                console.log(this.name, this.age, this.type);
            }
        }
        // 所有对象的type属性和say方法都是一样的
       // 但是还会在每个对象的存储空间中都存储一份
        var p1 = new Person("lnj", 33);
        var p2 = new Person("zs", 33);
        console.log(p1.say === p2.say); // false
    </script>
    
    • 对于这种问题我们可以把需要共享的函数定义到构造函数外部
    <script>
        function say() {
            console.log(this.name, this.age, this.type);
        }
        function Person(name, age) {
            this.name = name;
            this.age = age;
            this.type = "人";
            this.say = say;
        }
        var p1 = new Person("lnj", 33);
        var p2 = new Person("zs", 33);
        console.log(p1.say === p2.say); // true
    </script>
    
    • 但是如果有多个需要共享的函数, 就会造成全局命名空间冲突的问题(同一作用域不能出现同名的标识符)
    <script>
        // 将共享函数封装到一个对象中, 与外界隔绝, 这样就不会污染全局命名空间了
        var fns = {
            say: function () {
                console.log(this.name, this.age, this.type);
            },
            setName: function (name) {
                this.name = name;
            },
            setAge: function (age) {
                this.age = age;
            }
        }
        function Person(name, age) {
            this.name = name;
            this.age = age;
            this.type = "人";
            this.say = fns.say;
            this.setName = fns.setName;
            this.setAge = fns.setAge;
        }
        var p1 = new Person("lnj", 33);
        var p2 = new Person("zs", 33);
        console.log(p1.say === p2.say); // true
    </script>
    

    构造函数的内存优化问题(更好的方案)

    • JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象。
    • 这个对象的所有属性和方法,都会被构造函数的所拥有
    • 也就意味着,我们可以把所有对象实例需要共享的属性和方法直接定义在 prototype 对象上
    <script>
        function Person(name, age) {
            this.name = name;
            this.age = age;
        }
        // 给原型对象添加新的属性和方法
        Person.prototype.say = function () {
            console.log(this.name, this.age, this.type);
        };
        Person.prototype.type = "人";
    
        var p1 = new Person("lnj", 33);
        var p2 = new Person("zs", 33);
        console.log(p1.say === p2.say); // true
        console.log(p1.type === p2.type); // true
    
        // 当调用对象的属性或者方法的时候,先去找对象本身的属性/方法
        // 如果对象没有该属性或者方法。此时去原型中查找对应的属性/方法
        // 如果对象本身没有该属性/方法,原型中也没有该属性或者方法,此时会报错
        p1.say();
        console.log(p1.type);
    </script>
    
    • 任何函数都具有一个 prototype 属性,该属性是一个对象
    • 构造函数的 prototype 对象默认都有一个 constructor 属性,指向 prototype 对象所在函数
    • 通过构造函数得到的实例对象内部会包含一个指向构造函数的 prototype 对象的指针 proto

    原型链

    <script>
        function Person(name, age) {
            this.name = name;
            this.age = age;
            this.type = "超人";
        }
        // 给原型对象添加新的属性和方法
        Person.prototype.say = function () {
            console.log(this.name, this.age, this.type);
        };
        Person.prototype.type = "人";
        var p = new Person("lnj", 33);
        console.log(p.type);
    
        console.log(p.__proto__);
        console.log(p.__proto__.__proto__);
        console.log(p.__proto__.__proto__.__proto__);
    </script>
    
    • 方法查找规则
      • 先查找当前对象, 当前对象有就使用当前对象的方法
      • 当前对象没有再逐层在原型链上查找, 最先找到那个就使用哪个
      • 如果找到null都没找到就报错
    <script>
        function Person(name, age) {
            this.name = name;
            this.age = age;
            // this.say = function () {
            //     console.log("自己的", this.name, this.age, this.type);
            // };
        }
        // 给原型对象添加新方法
        // Person.prototype.say = function () {
        //     console.log("原型链上的", this.name, this.age, this.type);
        // };
        var p = new Person("lnj", 33);
        // 自己有吗? 有, 使用自己的
        // 自己有吗? 没有, 去原型链查找
        // 都没有吗? 报错
        p.say();
    </script>
    
    • 属性查找规则
      • 先查找当前对象, 当前对象有就使用当前对象的方法
      • 当前对象没有再逐层在原型链上查找, 最先找到那个就使用哪个
      • 如果找到null都没找到就输出undefined
    <script>
        function Person(name, age) {
            this.name = name;
            this.age = age;
            // this.type = "超人";
        }
        // 给原型对象添加新的属性和方法
        // Person.prototype.type = "人";
        var p = new Person("lnj", 33);
        // 自己有吗? 有, 使用自己的
        // 自己有吗? 没有, 去原型链查找
        // 都没有吗? undefined
        console.log(p.type);
    </script>
    
    • 属性的设置规则
      • 不会修改原型链上的属性, 会给当前对象新增一个属性
    <script>
        function Person(name, age) {
            this.name = name;
            this.age = age;
        }
        // 给原型对象添加新的属性和方法
        Person.prototype.type = "人";
        var p = new Person("lnj", 33);
        console.log(p.__proto__.type);
        // p.__proto__.type = "超人";
        // 自己有这个属性吗? 没有, 新增
        p.type = "超人";
        console.log(p.__proto__.type);
    </script>
    

    自定义原型对象

    • 原型对象是构造函数的一个属性, 所以我们可以通过修改属性值的方式来自定义原型对象
    • 需要注意的是, 自定义原型对象不能破坏原有的三角恋关系
    <script>
        function Person(name, age) {
            this.name = name;
            this.age = age;
        }
        Person.prototype = {
            constructor: Person, // 手动还原constructor属性, 保持三角恋关系
            type: "人",
            say: function () {
                console.log(this.name, this.age, this.type);
            }
        }
        var p = new Person("lnj", 33);
        p.say();
    </script>
    

    面向对象三大特性

    • 封装性
      • 封装性就是隐藏实现细节,仅对外公开接口


      • 类是数据与功能的封装,数据就是成员变量,功能就是方法
    • 为什么要封装?
      • 不封装的缺点:当一个类把自己的成员变量暴露给外部的时候,那么该类就失去对该成员变量的管理权,别人可以任意的修改你的成员变量
      • 封装就是将数据隐藏起来,只能用此类的方法才可以读取或者设置数据,不可被外部任意修改是面向对象设计本质(将变化隔离)。这样降低了数据被误用的可能 (提高安全性灵活性)
    <script>
        function Person(name) {
            this.name = name; // 公有属性
            // 只能在内部使用, 不能在外部使用
            var age = 666; // 私有属性
            // 公有方法
            this.say = function () {
                console.log(this.name, age);
                test();
            }
            this.setAge = function (num) {
                age = num;
            }
            this.getAge = function () {
                return age;
            }
            // 私有方法
            function test() {
                console.log("test");
            }
        }
        var p = new Person("lnj");
        console.log(p.name);
        p.say();
        console.log(p.age); // undefined
        p.setAge(123);
        console.log(p.getAge());
        p.test(); // 报错
    </script>
    
    • 封装原则
      • 将不需要对外提供的内容都隐藏起来,把属性都隐藏,提供公共的方法对其访问

    实例属性和实例方法/静态属性和静态方法

    • 通过构造函数创建出来的对象访问的属性和方法,我们称之为实例属性和实例方法
      • 实例属性和实例方法都是绑定在构造函数创建出来的对象上的
    <script>
        function Person(name) {
            this.name = name; // 实例属性
            this.eat = function () { // 实例方法
                console.log("eat");
            }
        }
        Person.prototype.age = "0"; // 实例属性
        Person.prototype.say = function () { // 实例方法
            console.log("hello");
        }
        var p = new Person("lnj");
        console.log(p.name); // 通过对象访问
        console.log(p.age); // 通过对象访问
        p.eat(); // 通过对象访问
        p.say(); // 通过对象访问
    </script>
    
    • 通过构造函数直接调用的属性和方法,我们称之为静态属性和静态方法
      • 静态属性和静态方法都是绑定在构造函数上的
    <script>
        function Person() {
            Person.name = "lnj"; // 静态属性
            Person.eat = function () { // 静态方法
                console.log("eat");
            }
        }
        Person.count = 0; // 静态属性
        Person.say = function () { // 静态方法
            console.log("hello");
        };
    
        console.log(Person.name); // 通过构造函数访问
        console.log(Person.count); // 通过构造函数访问
        Person.eat(); // 通过构造函数访问
        Person.say(); // 通过构造函数访问
    </script>
    

    • 继承性
      • 儿子继承父亲的物品就是继承最好的体现
      • js中继承目的: 把子类型中共同的属性和方法提取到父类型中
      • 较少代码的冗余度, 提升代码的复用性




    • 借用原型链实现继承
      • 直接将子类的原型对象修改为父类对象, 这样就能使用原型链上的属性和方法
    <script>
        // 父类
        function Person() {
            this.name = "lnj";
            this.age = 33;
            this.gender = "male";
        }
        // 子类
        function Student(score) {
            this.score = score;
        }
        // 由于是直接将子类原型对象修改为了父类对象
        // 所以继承的属性值很难自定义
        Student.prototype = new Person();
        Student.prototype.constructor = Student;
    
        var stu1 = new Student(99);
        console.log(stu.name, stu.age, stu.gender, stu.score);
        var stu2 = new Student(66);
        console.log(stu.name, stu.age, stu.gender, stu.score);
    </script>
    
    • 借用构造函数实现继承
      • 在子类中调用父类构造函数, 并且将父类构造函数的this修改为子类对象
    <script>
        // 父类
        function Person(name, age, gender) {
            this.name = name;
            this.age = age;
            this.gender = gender;
        }
        // 借用构造函数只是调用了父类的构造函数, 借用了构造函数中的代码
        // 相当于动态的给子类添加了许多属性, 但是并没有修改子类的原型
        // 所以子类无法继承父类的方法
        Person.prototype.say = function () {
            console.log(this.name, this.age, this.gender);
        }
        // 子类
        function Student(score, name, age, gender) {
            Person.call(this, name, age, gender);
            this.score = score;
        }
    
        var stu1 = new Student(99, "lnj", 33, "male");
        var stu2 = new Student(66, "zq", 18, "female");
        console.log(stu1.name, stu1.age, stu1.gender, stu1.score);
        console.log(stu2.name, stu2.age, stu2.gender, stu2.score);
        stu1.say(); // 报错
        stu2.say(); // 报错
    </script>
    
    • 借用构造函数+借用原型链组合继承
      • 通过借用构造函数实现属性继承
      • 通过借用原型链实现方法继承
    <script>
        // 父类
        function Person(name, age, gender) {
            this.name = name;
            this.age = age;
            this.gender = gender;
        }
        Person.prototype.say = function () {
            console.log(this.name, this.age, this.gender);
        }
        // 子类
        function Student(score, name, age, gender) {
            Person.call(this, name, age, gender);
            this.score = score;
        }
        Student.prototype = Person.prototype;
        Student.prototype.constructor = Student;
        // 由于子类的原型指向了父类的原型, 所以操作的都是同一个原型对象
        // 给子类的原型新增方法或者属性, 父类也会受到影响
        Student.prototype.study = function () {
            console.log("好好学习天天向上");
        };
        Student.prototype.type = "学生";
    
        var stu = new Student(99, "lnj", 33, "male");
        stu.say();
        stu.study();
        console.log(stu.type);
    
        var p = new Person("zq", 18, "female");
        p.say();
        p.study();
        console.log(p.type);
    </script>
    
    • 终极方案
    <script>
        // 父类
        function Person(name, age, gender) {
            this.name = name;
            this.age = age;
            this.gender = gender;
        }
        Person.prototype.say = function () {
            console.log(this.name, this.age, this.gender);
        }
        // 子类
        function Student(score, name, age, gender) {
            Person.call(this, name, age, gender);
            this.score = score;
        }
        Student.prototype = new Person();
        Student.prototype.constructor = Student;
        // 由于子类的原型指向一个全新的对象
        // 所以给子类的原型新增方法或者属性, 父类不会受到影响
        Student.prototype.study = function () {
            console.log("好好学习天天向上");
        };
        Student.prototype.type = "学生";
    
        var stu = new Student(99, "lnj", 33, "male");
        stu.say();
        stu.study();
        console.log(stu.type);
    
        var p = new Person("zq", 18, "female");
        p.say();
        p.study(); // 报错
        console.log(p.type); // 报错
    </script>
    

    对象的增删改查

    <script>
        // 1.定义构造函数
        function Person(name, age) {
            this.name = name;
            this.age = age;
        }
        Person.prototype.type = "人";
    
        // 2.利用构造函数创建对象
        var p1 = new Person("lnj", 33);
        var p2 = new Person("zq", 18);
        // 3.动态增加属性
        p1.score = 99;
        console.log(p1);
        console.log(p2);
        // 4.删除某个属性
        delete p1.name;
        console.log(p1);
        console.log(p2);
        // 5.修改某个属性的值
        p1.age = 66;
        console.log(p1);
        console.log(p2);
        // 6.查询是否包含某个属性
        // in 会到原型对象里面查找
        console.log("type" in p1);
        // hasOwnProperty: 只在对象自身查找
        console.log(p1.hasOwnProperty("type"));
    </script>
    

    Object对象的bind-call-apply方法

    • 默认情况下所有对象都有bind-call-apply方法
    • 这三个方法的作用是用于修改指定函数中this的指向
    <script>
        function sum(a, b) {
            // 默认情况下函数中的this谁调用就是谁
            console.log(this);
            console.log(a, b);
        }
        // test();
        var obj = {
            name: "zq"
        };
        // bind方法可以修改指定函数中this的指向
        // 但是bind方法并不会调用函数, 如果需要手动调用
        // sum.bind(obj)(10, 20);
    
        // call方法也可以修改指定函数中this的指向
        // 并且call方法会自动调用函数, 不用手动调用
        // sum.call(obj, 10, 20);
        
        // apply方法也可以修改指定函数中this的指向
        // 并且apply方法会自动调用函数, 不用手动调用
        // 和call方法的区别是传递参数的形式不同而已
        sum.apply(obj,[10, 20]);
    </script>
    

    对象的拷贝

    • 浅拷贝
      • 对于基本类型属性无论是深浅拷贝,都会复制一份
      • 对于引用类型属性浅拷贝拷贝的是引用类型的地址
      • 简而言之, 浅拷贝修改引用类型属性, 拷贝前拷贝后的对象都会受到影响
    <script>
        var obj1 = {
            name: "lnj", // 基本类型属性
            age: 33,     // 基本类型属性
            dog: {       // 引用类型属性
                name: "wc",
                age: 1,
            }
        };
    
        var obj2 = {};
        /*
        for(var key in obj1){
            obj2[key] = obj1[key];
        }
        */
        function copy(o1, o2){
            for(var key in o1){
                o2[key] = o1[key];
            }
        }
        copy(obj1, obj2);
        console.log(obj1);
        console.log(obj2);
    
        // 修改基本类型属性, 不会影响原有对象
        obj2.name = "zs";
        console.log(obj1.name);
        console.log(obj2.name);
    
        // 修改引用类型属性, 会影响原有对象
        obj2.dog.name = "xq";
        console.log(obj1.dog.name);
        console.log(obj2.dog.name);
    </script>
    
    • 深拷贝
      • 对于基本类型属性无论是深浅拷贝,都会复制一份
      • 对于引用类型属性深拷贝会将引用类型复制一份
      • 简而言之, 深拷贝修改引用类型属性, 只会影响当前修改对象
    <script>
        var obj1 = {
            name: "lnj", // 基本类型属性
            age: 33,     // 基本类型属性
            dog: {       // 引用类型属性
                name: "wc",
                age: 1,
            }
        };
        var obj2 = {};
    
        function copy(o1, o2){
            for(var key in o1){
                var item = o1[key];
                // 判断当前属性是否是引用类型
                if(item instanceof Object){
                    // 创建一个新引用类型属性
                    var o = {};
                    o2[key] = o;
                    // 进一步拷贝引用类型属性
                    copy(item, o);
                }
                // 判断当前属性是否数组
                else if(item instanceof Array){
                    // 创建一个数组属性
                    var arr = [];
                    o2[key] = arr;
                    // 进一步拷贝数组属性
                    copy(item, arr);
                }
                // 如果不是引用类型, 直接拷贝即可
                else{
                    o2[key] = item;
                }
            }
        }
        copy(obj1, obj2);
        console.log(obj1);
        console.log(obj2);
    
        // 修改基本类型属性, 不会影响原有对象
        obj2.name = "zs";
        console.log(obj1.name);
        console.log(obj2.name);
    
        // 修改引用类型属性, 会影响原有对象
        obj2.dog.name = "xq";
        console.log(obj1.dog.name);
        console.log(obj2.dog.name);
    </script>
    

    相关文章

      网友评论

          本文标题:JavaScript--面向对象

          本文链接:https://www.haomeiwen.com/subject/yjfsyktx.html