极客江南 - JavaScript 基础 指趣学院JavaScript学习李南江JavaScript程序设计

09-JavaScript面向对象

2018-11-02  本文已影响1944人  极客江南

面向对象基本概念

面向对象思想


面向对象和面向过程区别






面向对象的特点


类与对象的关系


使用默认类创建对象

<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关键字

<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)

如何分析一个类

飞机
炮弹
装甲车
老王
热狗
草泥马

如何定义一个类

<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调用

构造函数作用

<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>

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

<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>
<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>
<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>

原型对象使用建议

  • 私有成员(一般就是非函数成员)放到构造函数中
  • 共享成员(一般就是函数)放到原型对象中
  • 如果重置了 prototype 记得修正 constructor 的指向

面向对象三大特性

<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>


<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>

<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方法

<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>

JavaScript中继承远不止如何, 更多惊喜尽在后续ES6中


对象的拷贝

<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>
上一篇下一篇

猜你喜欢

热点阅读