zoukankan      html  css  js  c++  java
  • JavaScript 面向对象

    本文为慕课网 JavaScript深入浅出 JavaScript 面向对象笔记。

    概念

    面向对象程序设计(Object-oriented programming,OOP)是一种程序设计范型,同时也是一种程序开发的方法。对象指的是类的实例。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性。

    ——维基百科

    一般面向对象包含:继承,封装,多态,抽象


    基于原型的继承

    function Foo() {
        this.y = 2;
    }
    console.log(typeof Foo.prototype); //object
    
    Foo.prototype.x = 1;
    var obj3 = new Foo();
    
    console.log(obj3.y); //2
    console.log(obj3.x); //1
    

    创建函数 Foo 的时候,就会有一个内置的 Foo.prototype 属性,并且这个属性是对象。

    在使用 new Foo(); 创建对象实例时。this 会指向一个对象,并且这个对象的原型会指向 Foo.prototype 属性。this.y = 2 给这个对象赋值,并把这个对象返回。把这个对象赋值给 obj3

    yobj3 上的,xobj3 的原型 Foo.prototype 上的。

    prototype


    prototype 属性与原型

    prototype 是函数对象上预设的对象属性。

    原型是对象上的原型,通常是构造器的 prototype 属性。


    function Person(name, age) {
        this.name = name;
        this.age = age;
    }
    
    Person.prototype.LEGS_NUM = 2;
    Person.prototype.ARMS_NUM = 2;
    
    Person.prototype.hi = function() {
        console.log('Hi, my name is ' + this.name + ". I'm " + this.age + ' years old now');
    };
    
    Person.prototype.walking = function() {
        console.log(this.name + ' is walking...');
    };
    
    function Student(name, age, className) {
        Person.call(this, name, age); //使 Person 中的 this 指向 Student
        this.className = className;
    }
    
    Student.prototype = Object.create(Person.prototype);
    Student.prototype.constructor = Student;
    
    Student.prototype.hi = function() {
        console.log('Hi, my name is ' + this.name + ". I'm " + this.age + ' years old now, and from ' + this.className + ".");
    };
    
    Student.prototype.learn = function(subject) {
        console.log(this.name + ' is learning ' + subject + ' at ' + this.className + '.');
    }
    
    //test
    var gao = new Student('Gao', '24', 'Class 3123');
    console.log(gao); // 这个对象的具体内容见下图
    gao.hi(); //Hi, my name is Gao. I'm 24 years old now, and from Class 3123.
    gao.LEGS_NUM; //2
    gao.walking(); //Gao is walking...
    gao.learn('JavaScript'); //Gao is learning JavaScript at Class 3123.
    
    • Object.create(arg) 创建一个空对象,并且这个对象的原型指向参数 arg
    • Student.prototype.constructor = Student 为了保证一致性,否则 constructor 指向 Person。

    原型链

    gao 对象的原型链:

    Object

    下面通过图形展示原型链:

    原型链


    Object.create(null) & .bind(null)

    这两种算是特例。

    Object.create(null).bind(null) 这两种方式创建出来的对象是没有 prototype 属性的,为 undefined


    prototype 属性

    改变 prototype

    JavaScript 中的 prototype 是对象,在运行的时候可以修改。

    给 prototype 添加或删除一些属性,是会影响到已经创建好的实例对象的。

    但是,直接修改 prototype 属性,是不会影响到已经创建好的实例对象的。但是会影响到新的实例对象。如下代码:

    // 上接上面的代码
    
    // 给 prototype 添加或删除一些属性
    Student.prototype.x = 101;
    console.log(gao.x); //101
    
    // 直接修改 prototype 属性
    Student.prototype = {
        y: 2
    };
    
    // 不会影响到已创建好的实例对象
    console.log(gao.x); //101
    console.log(gao.y); //undefined
    
    // 会影响到新创建的实例对象
    var ying = new Student('Ying', 24, 'UI');
    console.log(ying.x); //undefined
    console.log(ying.y); //2
    

    内置构造器的 prototype 属性

    修改内置构造器的 prototype 属性后,在实例化这个对象后,枚举其属性时,会把修改的内置构造器的 prototype 属性也枚举出来,有时候这是要避免的。可用 defineProperty 方法解决。如下代码:

    Object.prototype.x = 1;
    var obj = {};
    console.log(obj.x); //1
    console.log(obj);
    
    for (var k in obj) {
        console.log('result--->' + k);
    }
    // result--->x
    

    使用 defineProperty 后:

    Object.defineProperty(Object.prototype, 'x', {
        writable: true,
        value: 1
    });
    var obj = {};
    console.log(obj.x);//1
    console.log(obj);
    for (var k in obj) {
        console.log('result--->' + k);
    }
    // nothing output here
    
    大专栏  JavaScript 面向对象v>

    其实也可以这样枚举,使用 hasOwnProperty 方法:

    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            console.log("result--->" + key);
        }
    }
    

    创建对象-new/原型链

    prototype


    instanceof

    console.log([1, 2] instanceof Array); //true
    console.log([1, 2] instanceof Object); //true
    console.log(new Object() instanceof Array); //false
    

    左边要求是对象,右边要求是构造器或函数。它会判断:右边的构造器中的 prototype 属性是否出现在左边的对象的原型链上。

    • 注意:不同的 window 或 iframe 间的对象类型检测不能使用 instanceof

    实现继承的方式

    function Person() {}
    
    function Student() {}
    
    Student.prototype = Person.prototype; //1
    
    Student.prototype = new Person(); //2
    
    Student.prototype = Object.create(Person.prototype); //3
    
    Student.prototype.constructor = Student;
    

    注释中:

    1 是错误的。如果改变了 Student 就会改变 Person

    2 可以实现继承,但是其调用了构造函数,若父类构造函数中有形参,那么传值就会比较奇怪。

    3 是最好的方法。创建了一个空对象,并且对象的原型指向参数 Person.prototype。这样便实现了继承。同时原型链写,不向上查找。但是 Object.create 是ES5 中的方法,所以可以使用下列代码做兼容:

    if (!Object.create) {
        Object.create = function(proto) {
            function F() {}
            F.prototype = proto;
            return new F;
        };
    }
    

    模拟重载

    function Person() {
        var args = arguments;
        if (typeof args[0] === 'object' && args[0]) {
            if (args[0].name) {
                this.name = args[0].name;
            }
            if(args[0].age){
                this.age = args[0].age;
            }
        } else {
            if (args[0]) {
                this.name = args[0];
            }
            if (args[1]) {
                this.age = args[1];
            }
        }
    }
    
    //重写 toString 方法
    Person.prototype.toString = function() {
        console.log('name='+this.name+', age='+this.age);
    };
    
    var gao = new Person({name:'Gao',age:24});
    gao.toString(); // name=Gao, age=24
    
    var ying = new Person('Ying',25);
    ying.toString(); // name=Ying, age=25
    

    对参数进行判断,模拟实现重载。


    调用子类方法

    function Person(name) {
        this.name = name;
    }
    
    function Student(name, className) {
        this.className = className;
        Person.call(this, name); // 调用基类的构造器
    }
    
    var gao = new Student('Gao', '3123');
    console.log(gao); // Student {className: "3123", name: "Gao"}
    
    Person.prototype.init = function() {};
    
    Student.prototype.init = function() {
        // do sth...
        Person.prototype.init.apply(this, arguments); // 同时也想调用父类被覆盖的方法
    };
    

    主要是两种:调用父类的构造器,调用原型链上父类被覆盖的方法。


    链式调用

    function ClassManager() {}
    ClassManager.prototype.addClass = function(str) {
        console.log('Class: ' + str + ' added');
        return this;
    };
    
    var manager = new ClassManager();
    manager.addClass('classA').addClass('classB').addClass('classC');
    // Class: classA added
    // Class: classB added
    // Class: classC added
    

    重点在于 return this。返回这个 ClassManager 的实例。这样这个实例又可以继续调用方法。


    抽象类

    在构造器中 throw new Error(''); 抛异常。这样防止这个类被直接调用。

    function DetectorBase() {
        throw new Error('Abstract class can not be invoked directly!');
    }
    
    DetectorBase.detect = function() {
        console.log('Detection starting...');
    }
    DetectorBase.stop = function() {
        console.log('Detection stopped.');
    };
    DetectorBase.init = function() {
        throw new Error('Error');
    }
    
    var d = new DetectorBase();// Uncaught Error: Abstract class can not be invoked directly!
    
    function LinkDetector() {}
    LinkDetector.prototype = Object.create(DetectorBase.prototype);
    LinkDetector.prototype.constructor = LinkDetector;
    
    var l = new LinkDetector();
    console.log(l); //LinkDetector {}__proto__: LinkDetector
    l.detect(); //Uncaught TypeError: l.detect is not a function
    l.init(); //Uncaught TypeError: l.init is not a function
    

    var d = new DetectorBase(); 是不能实例化的,会报错

    l.detect(); 但是这个为什么报错我就不知道了。

    已经在原课程下提问了,期待老师的讲解。 抽象类中子类为什么不能调用父类的非抽象方法?

    问题已经解决了,应该是老师当时的课件写错了,应该再基类中将这两个方法写在其原型 prototype 上。如下:

    function DetectorBase() {
        throw new Error('Abstract class can not be invoked directly!');
    }
    
    DetectorBase.prototype.detect = function() {
        console.log('Detection starting...');
    };
    DetectorBase.prototype.stop = function() {
        console.log('Detection stopped.');
    };
    DetectorBase.prototype.init = function() {
        throw new Error('Error');
    };
    
    // var d = new DetectorBase();// Uncaught Error: Abstract class can not be invoked directly!
    
    function LinkDetector() {}
    LinkDetector.prototype = Object.create(DetectorBase.prototype);
    LinkDetector.prototype.constructor = LinkDetector;
    
    var l = new LinkDetector();
    console.log(l); //LinkDetector {}__proto__: LinkDetector
    l.detect(); //Detection starting...
    l.init(); //Uncaught Error: Error
    

    模块化

    var moduleA;
    moduleA = function() {
        var prop = 1;
    
        function func() {}
    
        return {
            func: func,
            prop: prop
        };
    }(); // 立即执行匿名函数
    

    prop,func 不会被泄露到全局作用域。

    或者另一种写法,使用 new

    moduleA = new function() {
        var prop = 1;
    
        function func() {}
    
        this.func = func;
        this.prop = prop;
    }
    

    更复杂的可以使用 Sea.js Kissy Require.js 模块化工具。


    最后补充一点设计模式相关的资料,我还没有来得及看的:

    如有任何想说的请留言哦,我会根据大家的建议修改有疑义的内容。
    欢迎大家关注我的 Github。

  • 相关阅读:
    【常用配置】Spring框架web.xml通用配置
    3.从AbstractQueuedSynchronizer(AQS)说起(2)——共享模式的锁获取与释放
    2.从AbstractQueuedSynchronizer(AQS)说起(1)——独占模式的锁获取与释放
    1.有关线程、并发的基本概念
    0.Java并发包系列开篇
    SpringMVC——DispatcherServlet的IoC容器(Web应用的IoC容器的子容器)创建过程
    关于String的问题
    Spring——Web应用中的IoC容器创建(WebApplicationContext根应用上下文的创建过程)
    <<、>>、>>>移位操作
    System.arraycopy(src, srcPos, dest, destPos, length) 与 Arrays.copyOf(original, newLength)区别
  • 原文地址:https://www.cnblogs.com/lijianming180/p/12302325.html
Copyright © 2011-2022 走看看