JavaScript面向对象的程序设计

媒介

说到面向对象,能够第一想到的是C++或许Java如许的言语。这些言语有都一个标志,那就是引入了类的观点。我们能够经由历程类建立恣意数目的具有雷同属性和要领的对象。ECMAScript(JavaScript分为ECMAScript、DOM和BOM)中没有类的观点,所以它的对象相比较基于类的言语照样有所差别的。

说说对象的属性

 var person={
        name:'张三',
        age:23,
        sex:'男',
        sayName:function () {
            alert(this.name);
        }
    }

上面我们用了对象字面量的体式格局建了一个异常简朴的person对象,他具有name、age、sex、sayName这些属性,而这些属性在建立时都带有一些特征值,JavaScript经由历程这些特征值就能够定义这些属性的行动。在ECMAScript中,属性分为两种:数据属性和接见器属性。下面我们逐一进修下。

数据属性

数据属性有四个特征值,分别为以下四个:

  1. configurable
    示意可否delete删除属性,可否修正属性的特征,默许值是false

  2. enumerable
    示意可否经由历程for-in轮回返回属性,默许值是false

  3. writable
    示意可否修正属性的值,默许值是false

  4. value
    示意该属性的值,我们读取和修正都是在这个位置,默许值是undefined

接下来我们逐一明白这四个特征值。要修正属性默许的特征,必需运用ECMAScript 5的Object.defineProperty()要领

configurable

1.delete无效
    var person={};
    Object.defineProperty(person,"name",{
        configurable:false,
        value:"张三"
    });
    console.log(person.name);//张三
    delete person.name;
    console.log(person.name);//张三
2.不能修正属性的特征
    var person={};
    Object.defineProperty(person,"name",{
        configurable:true,
        value:"张三"
    });
    Object.defineProperty(person,"name",{
        value:"李四"
    });
    console.log(person.name);//李四
    var person={};
    Object.defineProperty(person,"name",{
        configurable:false,
        value:"张三"
    });
    Object.defineProperty(person,"name",{
        value:"李四"
    });
    console.log(person.name);
    
    //控制台报错 Uncaught TypeError: Cannot redefine property: name

enumerable

    var person={};
    Object.defineProperty(person,"name",{
        enumerable:true,
        value:"张三"
    });
    Object.defineProperty(person,"age",{
        value:"23"
    });
    Object.defineProperty(person,"sayName",{
        enumerable:true,
        value:function () {
            alert(this.name);
        }
    });
    for( var prop in person){
        console.log(prop);
    }
    //控制台输出 name和sayName

writable

    var person={};
    Object.defineProperty(person,"name",{
        writable:false,
        value:"张三"
    });
    console.log(person.name);//张三
    person.name="李四";
    console.log(person.name);//张三

value

    var person={};
    Object.defineProperty(person,"name",{
        writable:true,
        value:"张三"
    });
    console.log(person.name);//张三
    person.name="李四";
    console.log(person.name);//李四

接见器属性

接见器属性有四个特征值,分别为以下四个:

  1. configurable
    示意可否delete删除属性,可否修正属性的特征,默许值是false

  2. enumerable
    示意可否经由历程for-in轮回返回属性,默许值是false

  3. get
    在读取属性挪用的函数,默许值是undefined

  4. set
    在设置属性挪用的函数,默许值是undefined

下面我们逐一相识一下接见器属性的特征值,个中configurable和enumerable与上面数据范例一样,这里我们就不多做引见,重要我们说一下get和set。

 var person={
        name:"张三",
        age:32
    };
    Object.defineProperty(person,"sayAge",{
        get:function () {
            return this.name+":"+this.age+"岁";
        },
        set:function (newAge) {
            console.log("想要重返"+newAge+"岁?不存在的!");
        }
    });
    console.log(person.sayAge);//张三:32岁
    person.sayAge=18;//想要重返18岁?不存在的!
    console.log(person.sayAge);//张三:32岁

get和set并不是须要同时都要指定。假如只指定get,那末这个属性就是不可写的;假如只指定set,那末这个属性就是不可读的。

 var person1={
        name:"张三",
        age:32
    };
    Object.defineProperty(person1,"sayAge",{
        get:function () {
            return this.name+":"+this.age+"岁";
        }
    });
    console.log(person1.sayAge);//张三:32岁
    person1.sayAge=18;
    console.log(person1.sayAge);//张三:32岁

    var person2={
        name:"李四",
        age:46
    };
    Object.defineProperty(person2,"sayAge",{
        set:function () {
            console.log("想要重返18岁?不存在的!");
        }
    });
    console.log(person2.sayAge);//undefined
    person2.sayAge=18;//想要重返18岁?不存在的!
    console.log(person2.sayAge);//undefined

定义多个属性

这个里我们就要说一个Object.defineProperties()要领,具体用下看以下示例:

    var person = {};
    Object.defineProperties(person, {
        name: {
            writable: true,
            value: "张三"
        },
        age: {
            enumerable: true,
            value: 23,
        },
        sayName: {
            get: function () {
                return this.name;
            },
            set: function (newName) {
                console.log("名字修正完成");
                this.name=newName+"(修正)";
            }
        }
    });

读取属性的特征

这里我们能够恰好考证我们前面一切默许值。

    var person={};
    Object.defineProperty(person,'name',{
        value:"张三"
    });
    var descriptor=Object.getOwnPropertyDescriptor(person,"name");
    console.log("configurable:"+descriptor.configurable);
    //configurable:false
    console.log("enumerable:"+descriptor.enumerable);
    //enumerable:false
    console.log("writable:"+descriptor.writable);
    //writable:false
    console.log("value:"+descriptor.value);
    //张三

建立对象

字面量形式

    var person={};
    person.name="张三";
    person.age=22;
    person.sex="男";
    person.sayName=function () {
        alert(this.name);
    }

长处:建立单个对象简朴轻易
瑕玷:建立多个相似对象会发生大批代码

工场形式

    function createPerson(name, age, sex) {
        var person = new Object();
        person.name = name;
        person.age = age;
        person.sex = sex;
        person.sayName = function () {
            alert(this.name);
        };
        return person;
    }
    var person=createPerson("张三",22,"男");

长处:能够疾速建立多个相似对象
瑕玷:没法举行对象的辨认

组织函数形式

     function Person(name, age, sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.sayName = function () {
            alert(this.name);
        };
    }
    var person=new Person("张三",22,"男");

以组织函数的体式格局建立对象要阅历下面四个步骤:

  1. 建立一个新对象

  2. 将组织函数的作用域赋给新对象(因而this指向这个新对象)

  3. 实行组织函数中的代码,为这个新对象增加属性

  4. 返回新对象

这里person有一个constructor(组织函数)属性指向Person,我们能够考证一下。

    alert(person.constructor===Person);//true

鉴于这个特征我们能够用constructor来考证对象的范例。除了这个,我们还能够应用instanceof。

    alert(person instanceof Person);//true

虽然我们运用组织函数形式能够举行对象的辨认,然则组织函数形式却有一个瑕玷,就是每一个要领都要在每一个实例上从新建立一遍。下面我们举个例子申明一下。

    function Person(name, age, sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.sayName = function () {
            alert(this.name);
        };
    }
    var person1=new Person("张三",22,"男");
    var person2=new Person("李四",25,"男");
    alert(person1.sayName===person2.sayName);//false

从上面的例子我们看出来,person1和person2的sayName函数并不是共用同一个。

长处:能够举行对象的辨认
瑕玷:组织函数内里的函数在实例化的时刻都须要每次都建立一遍,致使差别作用域链和标识符剖析。

原型形式

   function Person() {

   }
   Person.prototype.name="张三";
   Person.prototype.age=22;
   Person.prototype.sex="男";
   Person.prototype.sayName=function () {
       alert(this.name);
   };
   var person=new Person();

任何时刻我们只需建立一个新函数,就会依据一组特定的规则为该函数建立一个prototype属性这个属性指向函数的原型对象。默许状况下,一切原型对象都邑自动取得一个constructor(组织函数)属性会实行prototype属性地点函数。以上面这个例子为例,即:

Person.prototype.constructor===Person//true

别的我们的实例对象都邑有一个__proto__属性指向组织函数的原型对象。即:

person.__proto__===Person.prototype //true

接下来我们要说的一点是我们能够经由历程对象实例接见保存在原型中的值,然则不能经由历程对象实例重写原型中的值。下面我们看个例子:

   function Person() {

   }
   Person.prototype.name="张三";
   Person.prototype.age=22;
   Person.prototype.sex="男";
   Person.prototype.sayName=function () {
       alert(this.name);
   };
   var person1=new Person();
   var person2=new Person();
   person1.name='李四';
   alert(person1.name);//李四
   alert(person2.name);//张三

从上面的例子我们能够看出,我们修正了person1的name属性现实是实例对象person1中的属性,而不是Person.prototype原型对象。假如我们想要person1.name指向Person.prototype.name则须要删除实例对象person1中name属性,以下所示:

   delete person1.name;
   alert(person1.name);//张三

说到这里我们碰到一个一个题目,就是怎样推断一个属性在原型上照样在实例对象上?这个是有要领能够做到的,那就是hasOwnProperty()要领,接着上面的代码,我们能够用这个hasOwnProperty()要领去考证一下。

   alert(person1.hasOwnProperty("name"));//false

上面我们删除实例对象person1中name属性以后,name应当不属于实例对象person1的属性,所以hasOwnProperty()返回false.
假如只是想知道person1可否接见name属性,不管在实例对象上照样原型上的话,我们能够用in操作符。以下所示:

   alert("name" in person1);//true

相对上面的原型语法,我们有一个相对简朴的原型语法。

    function Person() {

    }
    Person.prototype = {
        constructor:Person,
        name: '张三',
        age: 22,
        sex:"男",
        sayName: function () {
            alert(this.name);
        }
    };

这里注重的是,须要从新设置constructor为Person,不然constructor指向Object而不是Person。然则如许有一个瑕玷,就是constructor的enumerable特征被设为true。致使constructor属性由底本不可罗列变成可罗列。假如想处理这个题目能够尝试这类写法:

    function Person() {

    }
    Person.prototype = {
        name: '张三',
        age: 22,
        sex:"男",
        sayName: function () {
            alert(this.name);
        }
    };
    Object.defineProperty(Person.prototype,"constructor",{
        enumerable:false,
        value:Person
    });

说完这个以后,我们来讲一下原型的动态性。因为在原型中查找值的历程是一次搜刮,因而我们对原型对象所做的任何修正都能够马上从实例上反应出来。我们看一下下面的例子:

   function Person() {

   }
   var person=new Person();
   person.name="张三";
   person.sayName=function () {
     alert(this.name);
   };
   person.sayName();//张三

然则在重写悉数原型对象的时刻状况就不一样了,我们看一下下面这个例子:

    function Person() {

    }
    var person = new Person();
    Person.prototype = {
        constructor:Person,
        name: '张三',
        age: 22,
        sayName: function () {
            alert(this.name);
        }
    };
    person.sayName();
    //Uncaught TypeError: person.sayName is not a function

重写原型对象会割断现有原型与任何之前已存在的对象实例之间的联络,援用的仍然是最初的原型,上面的例子因为最初的原型的没有sayName()要领,所以会报错。

长处:能够举行对象的辨认,以及实例对象的函数不会被反复建立,从而不会致使差别的作用域链。
瑕玷:省略了为组织函数通报初始化参数这一环节,一切实例在默许状况都取雷同的值。

组合运用组织函数形式和原型形式

    function Person(name, age) {
        this.name = name;
        this.age = age;
    }
    Person.prototype = {
        constructor:Person,
        sayName: function () {
            alert(this.name);
        }
    };
    var person1 = new Person('张三', 22);
    var person2 = new Person("李四", 23);
    alert(person1.sayName===person2.sayName);//true

长处:连系组织函数形式和原型形式的长处,是现在运用最普遍、认同度最高的一种建立自定义范例的要领。

动态原型形式

    function Person(name,age,sex) {
        this.name=name;
        this.age=age;
        this.sex=sex;
        if(typeof this.sayName!="function"){
            Person.prototype.sayName=function () {
              alert(this.name);
            };
        }
    }
    var person=new Person("张三",22,"男");
    person.sayName();//张三

上面代码中if语句只要在首次挪用组织函数时才会实行。今后,原型已初始化,不须要再做什么修正了。
长处:保留了组织函数形式和原型形式的长处,又将一切信息封装信息封装在了组织函数中。

寄生组织函数形式(相识即可)

    function Person(name,age,sex) {
        var object=new Object();
        object.name=name;
        object.age=age;
        object.sex=sex;
        object.sayName=function () {
            alert(this.name);
        };
        return object;
    }
    var person=new Person("张三",22,"男");
    person.sayName();//张三

因为我们能够重写挪用组织函数时的返回值,所以我们能够在特别状况下为对象建立组织函数。比方我们想建立一个具有特别要领的数组,因为我们不能修正Array组织函数,因而能够运用这类体式格局。

    function SpecialArray() {
        var values=new Array();
        values.push.apply(values,arguments);
        values.toPipedString=function () {
            return this.join("|");
        };
        return values;
    }
    var colors=new SpecialArray("red","yellow","white");
    alert(colors.toPipedString());//"red|yellow|white"

但这类体式格局瑕玷也是很明显,因为组织函数返回的对象与组织函数外部建立的对象没有什么差别。所以,instanceof操作符不能肯定对象范例。因而这类形式优先级很低,不引荐优先运用。
长处:能够重写组织函数函数的返回值,特别状况下比较好用。
瑕玷:instanceof操作符不能肯定对象范例。

稳妥组织函数形式

    function Person(name) {
        var object=new Object();
        var name=name;
        object.sayName=function () {
            alert(name);
        };
        return object;
    }
    var person=new Person("张三",22,"男");
    person.sayName();//张三
    alert(person.name);//undefined

在这类形式下,想接见name这个数据成员时,除了挪用sayName()要领,没有其他要领能够接见传入组织函数中的原始数据。这类形式的安全性就很高。
长处:安全性高。
瑕玷:instanceof操作符不能肯定对象范例。

继承

原型链

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

上面代码中,我们没有运用SubType默许供应的原型,而是给它换了一个新原型(SuperType实例)。因而,新原型不仅具有作为SuperType的实例所具有的悉数属性和要领,而且其内部还具有一个指针,指向了SuperType的原型。终究效果以下:

《JavaScript面向对象的程序设计》

依据上面,须要申明一点的是一切函数的默许原型都是Object的实例,因而默许原型都邑包括一个内部指针指向Object.prototype。

在我们运用原型链的历程会有一个题目就是肯定原型和实例之间的关联。这里我们有两种体式格局,我们接着上面代码继承看。

第一种:instanceof操作符,测试实例与原型中涌现过的组织函数

    alert(instance instanceof Object);//true
    alert(instance instanceof SuperType);//true
    alert(instance instanceof SubType);//true

第二种:要领isPrototypeOf(),测试原型链中涌现过的原型

   alert(Object.prototype.isPrototypeOf(instance));//true
   alert(SuperType.prototype.isPrototypeOf(instance));//true
   alert(SubType.prototype.isPrototypeOf(instance));//true

假如子范例须要掩盖超范例中的某个要领,或许须要增加超范例中不存在的某个要领,我们要遵照一个准绳,给原型增加要领的代码一定要放在替代原型的语句以后。以下所示:

    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;
    };
    //重写超范例中的要领
    SubType.prototype.getSuperValue=function () {
        return false;
    };
    var instance=new SubType();
    alert(instance.getSuperValue());

别的我们还须要注重在增加要领时刻,不能运用对象字面量建立原型要领。以下所示:

    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());
    //Uncaught TypeError: instance.getSuperValue is not a function

说到这里我们,我来总结一下原型链的优瑕玷:
长处:功用很壮大,能够一连继承多个原型的悉数属性和要领。
瑕玷:

1.原型的通用题目就是属性被共用,修正原型的属性将会动态映射到一切指向该原型的实例。
2.鉴于属性是共用的,我们没法给超范例的组织函数通报参数。

借用组织函数

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

经由历程运用call()要领(或apply()要领),在新建的SubType实例的环境下条用了SuperType组织函数。如许一来,就会在新的SubType对象上实行SuperType()函数中定义的一切对象初始化代码。效果,SubType的每一个实例就都邑具有本身的colors属性的副本了。

相对于原型链而言,借用组织函数有一个很大的上风,即能够在子范例组织函数中向超范例组织函数通报参数。以下所示:

    function SuperType(name) {
        this.name=name;
    }
    function SubType() {
        //继承了SuperType,同时还通报了参数
        SuperType.call(this,"张三");
        //实例属性
        this.age=22;
    }
    var instance=new SubType();
    alert(instance.name);//张三
    alert(instance.age);//22

长处:填补原型链的共用属性和不能通报参数的瑕玷。
瑕玷:函数不能复用,超范例的原型中定义的要领在子范例中是不可见的。

组合继承

    function SuperType(name) {
        this.name=name;
        this.colors=["red","blue","green"];
    }
    SuperType.prototype.sayName=function () {
        alert(this.name);
    };
    function SubType(name,age) {
        SuperType.call(this,name);//第二次挪用SuperType()
        this.age=age;
    }
    //继承要领
    SubType.prototype=new SuperType();
    SubType.prototype.constructor=SubType;
    SubType.prototype.sayAge=function () {
      alert(this.age);
    };
    var instance1=new SubType("张三",22);//第一次挪用SuperType()
    instance1.colors.push("black");
    console.log(instance1.colors);//["red", "blue", "green", "black"]
    instance1.sayName();//张三
    instance1.sayAge();//22


    var instance2=new SubType("李四",25);
    console.log(instance2.colors);//["red", "blue", "green"]
    instance2.sayName();//李四
    instance2.sayAge();//25
    

瑕玷:建立对象时都邑挪用两次超范例组织函数。
长处:融会了原型链和借助组织函数的长处,避免了他们的缺点。Javascript中最经常使用的继承形式。

原型式继承

    var person={
        name:"张三",
        friends:["李四","王五"]
    };
    var person1=Object(person);//或许Object.create(person)
    person1.name="赵六";
    person1.friends.push("孙七");
    var person2=Object.create(person);
    person2.name="周八";
    person2.friends.push("吴九");
    console.log(person.friends);//["李四", "王五", "孙七", "吴九"]

原型式继承现实上是把实例的__proto__属性指向了person。

长处:只想让一个对象跟另一个对象坚持相似的状况下,代码变得很简朴。
瑕玷:同享了响应的值,原型的通病。

寄生式继承

    function createPerson(obj) {
        var clone=Object(obj);
        clone.sayMyfriends=function () {
            console.log(this.friends);
        };
        return clone;
    }
    var person={
        name:"张三",
        friends:["李四","王五","赵六"]
    };
    var anotherPerson= createPerson(person);
    anotherPerson.sayMyfriends();//["李四", "王五", "赵六"]

长处:能够为恣意对象增加指定属性,代码量很少。
瑕玷: 在为对象增加函数,因为函数不能复用。每次增加都邑新建一个函数对象,降低了效力。这一点与组织函数形式相似。

寄生组合式继承


    function inheritPrototype(subType,superType) {
        var prototype=Object(superType.prototype);//建立对象
        prototype.constructor=subType;//加强对象
        subType.prototype=prototype;//指定对象
    }
    function SuperType(name) {
        this.name=name;
        this.colors=["red","blue","green"];
    }
    SuperType.prototype.sayName=function () {
        alert(this.name);
    };
    function SubType(name,age) {
        SuperType.call(this,name);
        this.age=age;
    }
    inheritPrototype(SubType,SuperType);
    SubType.prototype.sayAge=function () {
        alert(this.age);
    };
    var instance1=new SubType("张三",22);
    instance1.colors.push("yellow");
    instance1.sayName();//张三
    instance1.sayAge();//22

    var instance2=new SubType("李四",25);
    console.log(instance2.colors);// ["red", "blue", "green"]
    instance2.sayName();//李四
    instance2.sayAge();//25

上面的inheritPrototype()函数吸收两个参数:子范例组织函数和超范例组织函数。在函数内部,第一部是建立超范例原型的一个副本。第二步是为建立的副本增加constructor属性,从而填补因重写原型而落空的默许的constructor属性。末了一步,将新建立的对象(即副本)赋值给子范例的原型。如许,我们就能够用挪用inheritPrototype()函数的语句,去替代前面例子中为子范例原型赋值的语句。
长处:集寄生式继承和组合继承的长处于一身,是完成基于范例继承的最有用体式格局。

    原文作者:wuming
    原文地址: https://segmentfault.com/a/1190000009680131
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞