• JS面向对象整理


    有次面试的时候,面试官让我谈谈对面向对象的理解,让我一下蒙了,竟然不知道从哪里说起?自己都是在看完视频后,直接用面向对象写东西,也没有好好梳理,导致自己只会简单的用一下,却不会说。于是我就翻了翻《JavaScript高级程序设计》,对其进行整理了一下。

    1.什么是对象

    在ECMAScript中,对象就是一堆无序属性的集合,这些属性可以是基本值,也可以是别的对象和函数。所以我们也可以吧对象当成一组名值对,一个属性名对应一个值,值可以是数据或方法。

    2.创建一个对象

    (1)创建一个最简单的对象是通过创建一个Object的实例,再为它添加属性和方法:

    var person = new Object();
    person.name = "Tom";
    person.age = 18;
    person.sayName = function(){
    alert(this.name);
    };

    (2)使用Object创建对象的缺点很明显,就是它只能创建一个对象,所以我们开始使用工厂模式来创建对象

                function createPerson(name,age,job){
                    var o = new Object();
                    o.name = name;
                    o.job = job;
                    o.age = age;
                    o.sayname = function(){
                        alert("你好" + name);
                    }
                    return o;
                }
                var person1 = createPerson("tom",16,"doctor");
                person1.sayname();    
                var person2 = createPerson("Candy",18,"police");

    工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题,即怎样知道一个对象的类型。

    (3)构造函数模式

    构造函数模式就可以创建特定类型的对象,我们将上一个函数改成构造函数如下:

                function Person(name,age,job){
                    this.name = name;
                    this.age = age;
                    this.job = job;
                    this.sayname = function(){
               alert("My name is" + this.name); 
             }; }var person1 = new Person("tom",16,"doctor"); var person2 = new Person("Candy",18,"police"); alert(person1 instanceof Object); alert(person1 instanceof Person);

    上面的函数与工厂模式的主要不同在于它直接将属性复制给this对象,并没有返回值,并且构造函数名都应是首字母大写,以便与其他函数做区别,因为构造函数本身也是函数,只不过可以用来创建对象而已 。

    然后是创建Person的实例,这就用到了new操作符,我们就是用new创建了2个实例。在这两种实例中,都包含有一个constructor属性,这个属性指向的就是Person,就是实例的构造函数,例如:

    alert(person1.constructor == Person);

    这里弹出的就是true,person2同样是这样。而我们最后的instanceof操作符就是为了检测对象类型。上面的两条检测语句弹出的都是true,说明我们创建的实例,既是Person的实例,有属于Object的实例。

    但构造函数也有一个缺点,就是相同的属性或方法会创建多遍,这样确实有些不必要。

    (4)原型模式

    首先,我们要知道每一个函数都有一个prototype的原型属性,这个属性是指向一个对象,这个对象可以包括属性和方法用来给实例共享,也可以说prototype就是通过调用构造函数而创建那个对象实例的原型对象。所以称prototype为原型属性是针对于构造函数的,原型对象是针对创建的实例的

                function Person(){
                    
                }
                Person.prototype.name = "Nancy";
                Person.prototype.age = 16;
                Person.prototype.sayName = function(){
                    alert(this.name);
                }
                var person1 = new Person();
                var person2 = new Person();

    这个原型模式有一个最大的问题,不是数据重复,而是在对于引用类型的属性,例如:

    function Person(){
    }
    Person.prototype = {
    constructor: Person,
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",
    friends : ["Shelby", "Court"],
    sayName : function () {
    alert(this.name);
    }
    };
    var person1 = new Person();
    var person2 = new Person();
    person1.friends.push("Van");
    alert(person1.friends); //"Shelby,Court,Van"
    alert(person2.friends); //"Shelby,Court,Van"
    alert(person1.friends === person2.friends); //true

    我们发现我们修改了person1的friends数组,但person2引用的friends也发生改变

    (5)构造函数与原型函数相结合使用

    function Person(name, age, job){
      this.name = name;
      this.age = age;
      this.job = job;
      this.friends = ["Shelby", "Court"];
    }
    Person.prototype = {
      constructor : Person,
      sayName : function(){
        alert(this.name);
      }
    }
    var person1 = new Person("Nicholas", 29, "Software Engineer");
    var person2 = new Person("Greg", 27, "Doctor");
    person1.friends.push("Van");
    alert(person1.friends); //"Shelby,Count,Van"
    alert(person2.friends); //"Shelby,Count"
    alert(person1.friends === person2.friends); //false
    alert(person1.sayName === person2.sayName); //true

    这种构造函数与原型混成的模式,是目前在 ECMAScript 中使用最广泛、认同度最高的一种创建自定义类型的方法。

    (6)其它的模式

    在《JavaScript高级程序设计》众还有其它的设计模式,如动态原型模式,寄生构造函数模式,稳妥构造函数模式等,但我们平时掌握上一种方法就够用了,所以我就偷个懒,就不介绍这几种了。

    1.继承

    许多 OO 语言都支持两种继承方式:接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。由于函数没有签名,ECMAScript 中无法实现接口继承。 ECMAScript 只支持实现继承,而且其实现继承主要是依靠原型链来实现的。

    (1)原型链

    讲到继承就不得不讲一讲原型链了,我们先讲一个对象的原型链:

    function Person(name,age){
          this.name = name;
          this.age = age;  
    }
    Person.prototype.sayName = function(){
          alert(this.name);
    }
    
    var person1 = new Person("tom",18);

    在上面的例子中,我们创建了一个Person构造函数,这个构造函数有一个原型对象prototype,并且这个原型对象有一个属性constructor,这个属性是指向它的构造函数,即Person,在创建的实例中,person1也有一个原型属性_proto_,这个属性指向prototype,即Person.prototype。总的来说就是:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。这就是圆形脸的基本概念。

    利用原型链实现继承可以这么写:

                function SuperType(){
                    this.property = true;
                }
                SuperType.prototype.getSuperValue = function(){
                    return this.property;
                };
                
                function SubType(){
                    this.SubProperty = false;
                }
                //继承了SuperType
                SubType.prototype = new SuperType();
                
                SubType.prototype.getSubValue = function(){
                    return this.SubProperty;
                };
                
                var instance = new SubType();
                alert(instance.getSuperValue()); //true

    利用原型链虽然好用,但缺点也很大,其中一个就是方才讲到的对引用类型属性的改变:

    function SuperType(){
        this.colors = ["red", "blue", "green"];
    }
    function SubType(){
    }
    //继承了 SuperType
    SubType.prototype = new SuperType();
    var instance1 = new SubType();
    instance1.colors.push("black");
    alert(instance1.colors); //"red,blue,green,black"
    var instance2 = new SubType();
    alert(instance2.colors); //"red,blue,green,black"

    原型链的第二个问题是:在创建子类型的实例时,不能向父类型的构造函数中传递参数。

    (2)借用构造函数

    这种技术的基本思想相当简单,即在子类型构造函数的内部调用父类型构造函数。因此通过使用 apply()和 call()方法也可以在(将来)新创建的对象上执行构造函数。

    function SuperType(){
        this.colors = ["red", "blue", "green"];
    }
    function SubType(){
        //继承了 SuperType
        SuperType.call(this);
    }
    var instance1 = new SubType();
    instance1.colors.push("black");
    alert(instance1.colors); //"red,blue,green,black"
    var instance2 = new SubType();
    alert(instance2.colors); //"red,blue,green"

    通过使用 call()方法(或 apply()方法也可以),我们实际上是在(未来将要)新创建的 SubType 实例的环境下调用了 SuperType 构造函数。这样一来,就会在新 SubType 对象上执行 SuperType()函数中定义的所有对象初始化代码。

    但是,根据我们的套路,如果只是用构造函数的来实现继承,还是有问题的,那就是方法都在构造函数中定义,因此函数复用就无从谈起了。而且,在父类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。所以我们和创建对象一样,采取两者相结合的方法。

    (3)组合继承

                function SuperType(name){
                    this.name = name;
                    this.colors = ["red","black"];
                }
                SuperType.prototype.sayName = function(){
                    alert(this.name);
                }
                function SubType(name,age){
                    //继承属性
                    SuperType.call(this,name);            
                    this.age = age;
                }
                //继承方法
                SubType.prototype = new SuperType();
                SubType.prototype.constructor = SubType;
                SubType.prototype.sayAge = function(){
                    alert(this.age);
                }
                
                var insstance = new SubType("tom",16);
                insstance.sayName();
                insstance.sayAge();
                
                insstance.colors.push("yellow");
                console.log(insstance.colors);
                
                var insstance2 = new SubType("Nancy",100);
                console.log(insstance2.colors);
                insstance2.sayName();
                insstance2.sayAge();

    在这个例子中, SuperType 构造函数定义了两个属性: name 和 colors。 SuperType 的原型定义了一个方法 sayName()。 SubType 构造函数在调用 SuperType 构造函数时传入name 参数,紧接着又定义了它自己的属性 age。然后,将 SuperType 的实例赋值给 SubType 的原型,然后又在该新原型上定义了方法 sayAge()。这样一来,就可以让两个不同的 SubType 实例既分别拥有自己属性——包括 colors 属性,又可以使用相同的方法了。

    组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为 JavaScript 中最常用的继承模式。但它也有缺点,就是它在继承的时候会调用两次父类型构造函数 。

    (4)这本书还有其他的继承方法,但是对于我这样写一写小项目的来说,上一种就够了,其他的我也只是做一种了解,我就讲一个寄生组合式的继承方式,

    我们先写一个利用原型继承的函数

    function object(o){
        function F(){}
        F.prototype = o;
        return new F();
    }

    我们在 object()函数内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例

    //寄生组合式继承
    function inheritPrototype(subType,superType){
        var prototype = object(superType.prototype);
        prototype.constructor = subType;
        subType.prototype = prototype;
    }


    在函数内部,第一步是创建超类型原型的一个副本。第二步是为创建的副本添加constructor 属性,从而弥补因重写原型而失去的默认的 constructor 属性。最后一步,将新创建的对象赋值给子类型的原型。

    利用这个寄生组合的模式可以解决上面组合继承的不足,被开发人员认为是引用类型最理想的继承范式 。

  • 相关阅读:
    第二周总结
    第一次结组作业概述
    Spring_Bean的自动装配
    Spring_依赖注入
    Spring_配置
    第一周总结
    1680. Concatenation of Consecutive Binary Numbers
    1631. Path With Minimum Effort
    1657. Determine if Two Strings Are Close
    1673. Find the Most Competitive Subsequence
  • 原文地址:https://www.cnblogs.com/rongy/p/6629255.html
Copyright © 2020-2023  润新知