《JavaScript高等程序设计》笔记:面向对象的程序设计(六)

面向对象的言语有一个标志,那就是它们都有类的观点,而经由过程类能够建立恣意多个具有雷同属性和要领的对象。

明白对象

建立自定义对象的最简朴的要领就是建立一个Object的实例,然后再为它增加属性和要领。比方:

var person = new Object();
    person.name="Nicholas";
    person.age=29;
    person.job="Software Engineer";
    person.SayName=function(){
        alert(this.name);
    }

一样上面的例子能够经由过程对象字面量语法写成以下:

var person ={
        name:"Nicholas",
        age:29,
        person.job:"Software Engineer",
        SayName:function(){
            alert(this.name);
        }
    }

属性范例
ECMAScript中有两种属性:数据属性和接见器属性。

1.数据属性
数据属性包括一个数据值的位置。在这个位置能够读取和写入值。数据属性有四个形貌其行动的特征。

Configurable:示意可否通delete删除属性从而从新定义属性,可否修正属性的特征,或许可否把属性修正为接见器属性。像前面的例子中那样直接在对象上定义属性,它们的这个特征默许值为true。

Enumerable:示意可否经由过程for-in轮回返回属性。像前面的例子中那样直接在对象上定义属性,它们的这个特征的默许值为true。

Writable:示意可否修正属性的值。前面例子直接在对象上定义的属性,它们的这个特征默许值为true。

Value:包括这个属性的数据值。读取属性值的时刻,从这个位置读;写入属性值的时刻,把新值保存到这个位置。这个特征默许值为undefined。

关于前面的例子,value特征被设置为特定的值。比方:

var person={
    name="Niceholas"
}

这里建立一个名为name的属性,为它指定的值是”Niceholas”。也就是说value特征将被设置为”Niceholas”,而对这个值的任何修正都将反应在这个位置。

要修正属性默许的特征,必须运用ECMAScript5的Object.defineProperty()要领。这个要领吸收三个参数:属性地点的对象、属性名字和一个形貌符对象。个中,形貌符对象的属性必须是Configurable、Enumerable、Writable、Value。设置个中的一或多个值。能够修正对应的特征值。比方:

var person={};
    Object.defineProperty(person,"name",{
        writable:false,
        value:'Nich'
    });
    
    alert(person.name);//Nich
    person.name="Greg";
    alert(person.name);//Nich

这个例子建立了一个名为name的属性,它的值为Nich是只读的。这个属性的值是不能够修正的,如果尝试为它指定新值,则在非严厉形式下,赋值操纵将被疏忽;在严厉形式下,赋值操纵将会抛出毛病。
类似的划定规矩也实用与不可设置的属性。比方:

var person={};
    Object.defineProperty(person,"name",{
        configurable:false,
        value:'Nich'
    });
    
    alert(person.name);//Nich
    delete person.name;
    alert(person.name);//Nich
    
    

注重:一旦把属性定义为不可设置的,就不能再把它变回可设置了。此时,再挪用Object.defineProperty()要领修正除了writable以外的特征,都邑致使毛病。

var person={};
    Object.defineProperty(person,"name",{
        configurable:false,
        value:'Nich'
    });

    //抛出毛病
    Object.defineProperty(person,"name",{
        configurable:true,
        value:'Nich'
    });
    

也就是说,屡次挪用Object.defineProperty()要领修正同一个属性,然则把configurable特征设置为false以后就会有限定了。
在挪用Object.defineProperty()要领时,如果不指定,configurable、Enumerable和writable特征的默许值为false。多半情况下,能够都没有必要应用Object.defineProperty()要领供应的这些高等功用。不过,明白这些观点关于明白javascript对象却异常有用。

注:IE8是第一个完成Object.defineProperty()要领的浏览器版本。但是,这个版本的完成存在诸多的限定:只能在DOM对象上运用这个要领,而且只能建立接见器属性。因为完成不完全,发起不要在IE8中运用Object.defineProperty()要领。

2.接见器属性
接见器属性不包括数据值;它们包括一对儿getter和setter函数(不过,这两个函数都不是必须的)。

 在读取接见器属性时,会挪用getter函数,这个函数担任返回有用的值;在写入接见器属性时,会挪用setter函数并传入新值,这个函数担任决议如何处置惩罚数据。接见器属性有以下4个特征。

[Configurable]:示意可否经由过程delete删除属性从而从新定义属性,可否修正属性的特征,或许可否把属性修正为数据属性。关于直接在对象上定义的属性,这个特征的默许值为true。
[Enumerable]:示意可否经由过程for-in轮回返回属性。关于直接在对象上定义的属性,这个特征默许值为true。
[Get]:在读取属性时挪用的函数。默许值为undefined。
[Set]:在写入属性时挪用的函数。默许值为undefined。

接见器属性不能直接定义,必须运用Object.defineProperty()来定义。下面例子:

var book={
        _year:2004,
        edition:1
    }
    Object.defineProperty(book,"year",{
        get:function(){
            return this._year;
        },
        set:function(newValue){
            console.log(newValue);
            if(newValue>2004){
                this._year=newValue;
                this.edition+=newValue-2004;
            }
        }
    });
    book.year=2005;
    console.log(book.edition);//2

上面代码建立了一个book对象,并给它定义两个默许的属性:_year和edition。_year前面的下划线是一种经常使用的暗号,用于示意只能经由过程对象要领接见的属性。


支撑ECMAScript5的这个要领的浏览器有IE9+、Firefox4+、SaFari5+、Opera12+和Chrome。在这个要领之前,要建立接见器属性,平常都运用两个非标准的要领:__defineGetter__()和__defineSetter__()。这2个要领最初是由Firefox引入的,厥后SaFari3、Chrome1、opera9.5也给出了雷同的完成。运用这2个遗留的要领,能够完成上面的例子以下:
var book={
    _year:2004,
    edition:1
}
//定义接见器的旧有要领
book.__defineGetter__('year',function(){
    return this._year;
});
book.__defineSetter__('year',function(newValue){
    if(newValue>2004){
        this._year=newValue;
        this.edition+=newValue-2004;
    }
});
book.year=2005;
alert(book.edition);//2

在不支撑Object.defineProperty()要领的浏览器中不能修正[Configurable] 和[Enumerable]。

定义多个属性

ECMAScript5又定义了一个Object.defineProperties()要领。这个要领吸收两个对象参数:第一个对象是要增加和修正其属性的对象;第二个对象的属性与第一个对象中增加或修正的属性一一对应。比方:
var book={}


Object.defineProperties(book,{

    _year:{
        value:2004
    },
    edition:{
        value:1
    },
    year:{

        get:function(){
            return this._year;
        },
        set:function(newValue){
            if(newValue>2004){
                this._year=newValue;
                this.edition+=newValue-2004;
            }
        }
    }
})

读取属性的特征

var book={};

Object.defineProperties(book,{

    _year:{
        value:2004
    },
    edition:{
        value:1
    },
    year:{

        get:function(){
            return this._year;
        },
        set:function(newValue){
            if(newValue>2004){
                this._year=newValue;
                this.edition+=newValue-2004;
            }
        }
    }
})

var descriptor=Object.getOwnPropertyDescriptor(book,'_year');
alert(descriptor.value);//2004
alert(descriptor.configurable);//false
alert(typeof descriptor.get);//undefined

var descriptor=Object.getOwnPropertyDescriptor(book,'year');
alert(descriptor.value);//undefined
alert(descriptor.configurable);//false
alert(typeof descriptor.get);//'function'

建立对象

虽然object组织函数或对象字面量都能够用来建立单个对象。但这些体式格局有个显著的瑕玷:运用同一个接口建立许多对象,会发生大批反复代码。

工场形式

function createPerson(name, age,job){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function(){
        alert(this.name);
    }

    return o;
}

var person1 = createPerson("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");

工场形式虽然处理了建立多个类似对象的题目,但却没有处理对象辨认的题目(即如何晓得一个对象的范例)。

组织函数形式

function Person(name, age,job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function(){
        alert(this.name);
    }
   
}

var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");


1.将组织函数当函数
比方前面例子中的Person函数能够用下面任何一种体式格局挪用:

//当做组织函数运用
var person1 = new Person("Nicholas", 29, "Software Engineer");
person1.sayName();//Nicholas
//作为一般函数挪用
Person("Greg", 27, "Doctor");
window.sayName();//Greg 


//在另一个对象的作用域中挪用
var o=new Object();
Person.call(o,"Kristen",25,"Nurse");
o.sayName();
    

2.组织函数的题目

function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = new Function("console.log(this.name)"); // 与声明函数在逻辑上是等价的
}

以这类要领建立函数,会致使差别的作用域链和标示符剖析。差别实例上的同名函数是不相等的。

var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
console.log(person1.sayName == person2.sayName); // false  

然后,建立两个完成一样使命的Function实例确实没有必要;何况有this对象在,基础不用在实行代码前就把函数绑定到特定对象上面。因而,大可像下面如许,经由过程把函数定义转移到组织函数外部来处理这个题目。

function Person(name, age,job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = sayName;
}

function sayName(){
    alert(this.name);
}

var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");


但是新题目又来了:在全局作用域中定义的函数实际上只能被某个对象挪用,这让全局作用域有点有名无实。而更让人没法接收的是:如果对象须要定义许多要领,那末就要定义许多多个全局函数,因而我们这个自定义的援用范例就涓滴没有封装性可言了。幸亏,这些题目能够经由过程运用原型形式来处理。

原型形式

function Person(){}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software  Engineer";
Person.prototype.sayName = function(){
    alert(this.name);
}

var person1 = new Person();
person1.sayName(); // Nicholas

var person2 = new Person();
person2.sayName(); // Nicholas
alert(person1.sayName == person2.sayName);

isPrototypeOf()

console.log(Person.prototype.isPrototypeOf(person1)); // true
console.log(Person.prototype.isPrototypeOf(person2)); // true

hasOwnProperty()

function Person(){}

Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software  Engineer";
Person.prototype.sayName = function(){
    console.log(this.name);
}

var person1 = new Person();
var person2 = new Person();

console.log(person1.hasOwnProperty("name")); // false

person1.name = "Greg";
console.log(person1.name); // Greg
console.log(person1.hasOwnProperty("name")); // true

console.log(person2.name); // Nicholas
console.log(person2.hasOwnProperty("name")); // false

delete person1.name;
console.log(person1.name); // Nicholas
console.log(person1.hasOwnProperty("name")); // false

原型与in操纵符

function Person(){}

Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software  Engineer";
Person.prototype.sayName = function(){
    console.log(this.name);
}

var person1 = new Person();
var person2 = new Person();

console.log(person1.hasOwnProperty("name")); // false
console.log("name" in person1); // true

person1.name = "Greg";
console.log(person1.name); // Greg
console.log(person1.hasOwnProperty('name')); // true
console.log("name" in person1); // true


console.log(person2.name); // Nicholas
console.log(person2.hasOwnProperty('name')); // false
console.log("name" in person2); // true

delete person1.name;
console.log(person1.name); // Nicholas
console.log(person1.hasOwnProperty('name')); // false
console.log("name" in person1); // true

同时运用hasOwnProperty()要领和in操纵符,就能够肯定该属性究竟是存在于对象中,照样存在于原型中,以下:

function hasPrototypeProperty(object,name){
    return !object.hasOwnProperty(name)&&(name in object);
}

只需in操纵符返回true而hasOwnProperty()返回false,就能够肯定属性是原型中的属性。

更简朴的原型语法

function Person(){}

Person.prototype = {
    name: "Nicholas", 
    age:29,
    job: "Software Engineer",
    sayName: function(){
        console.log(this.name);
    }
}

var friend = new Person();
console.log(friend instanceof Object); // true
console.log(friend instanceof Person); // true
console.log(friend.constructor == Person); // false
console.log(friend.constructor == Object); // true

如果constructor的值真的很主要,能够像下面如许特地将它设置回恰当的值。

function Person(){}

Person.prototype = {
    constructor: Person,
    name: "Nicholas", 
    age:29,
    job: "Software Engineer",
    sayName: function(){
        console.log(this.name);
    }
}

原型对象的题目

function Person(){}

Person.prototype = {
    constructor: Person,
    name: "Nicholas", 
    age:29,
    job: "Software Engineer",
    friends: ['Shelby', "Court"],
    sayName: function(){
        console.log(this.name);
    }
}

var person1 = new Person();
var person2 = new Person();

person1.friends.push("Van");

console.log(person1.friends); //Shelby,Court,Van
console.log(person2.friends); //Shelby,Court,Van
console.log(person1.friends===person2.friends); // true

如果我们的初志就是像如许在一切实例中同享一个数组,那末对这个效果无话可说。但是,实例平常都是要有属于本身的悉数属性的。而这个题目恰是我们很少看到有人零丁运用原型形式的缘由地点。

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

function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["Shelby", "Court"];
}

Person.prototype = {
    constructor: Person,
    sayName: function(){ console.log(this.name);}
}

var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

person1.friends.push("Van");
console.log(person1.friends); // Shelby, Count, Van
console.log(person2.friends); // Shelby, Count
console.log(person1.friends === person2.friends); // false
console.log(person1.sayName === person2.sayName); // true

在这个例子中,实例属性都是在组织函数中定义的,而由一切实例同享的属性constructor和要领sayName()则是在原型中定义的。这类组织函数与原型混成的形式,是现在认同度最高的一种建立自定义范例的要领。

动态原型形式

function Person(name, age,job){
    this.name = name;
    this.age = age;
    this.job = job;
}

if (typeof this.sayName!='function'){
    Person.prototype.sayName = function(){
        console.log(this.name);
   }
}

var friend = new Person("Nicholas",29,"Software Engineer");
friend.sayName(); //Nicholas

寄生组织函数形式

function Person(name,age,job){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function(){
        console.log(this.name);
    };
    return o;
}

var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName(); // Nicholas

关于寄生组织函数形式,返回的对象与组织函数或许组织函数的原型属性之间没有关系;也就是说,组织函数返回的对象与在组织函数外部建立的对象没有什么差别。

 function SpecialArray(){
        var values=new Array();
        values.push.apply(values,arguments);
        values.toPipedString=function(){
            return this.join("|");
        }
        return values;
    }
    var colors=new SpecialArray("red","blue","green");
    console.log(colors.toPipedString()); //red|blue|green
    

继续

原型链

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();
console.log(instance.getSuperValue()); // 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;
    },
    someOtherMethod: function(){
        return false;
    }
};

var instance = new Subtype();
console.log(instance.getSuperValue()); // error

原型链的题目

包括援用范例值的原型属性会被一切实例同享;而这也恰是为何要在组织函数中,而不是在原型对象中定义属性的缘由。
在建立子范例的实例时,不能向超范例的组织函数中通报参数。实际上,应当说是没有办法在不影响一切对象实例的情况下,给超范例的组织函数通报参数。

function SuperType(){
    this.colors = ["red", "blue", "green"];
}

function Subtype(){
    
}
Subtype.prototype= new SuperType();
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, black

通报参数

function SuperType(name){
    this.name = name;
}

function Subtype(){
    SuperType.call(this,"Nicholas");
    this.age = 29;
}

var instance = new Subtype();
console.log(instance.name); //Nicholas
console.log(instance.age); // 29

组合继续

function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}

SuperType.prototype.sayName = function(){
    console.log(this.name);
};

function Subtype(name,age){
    SuperType.call(this,name);
    this.age = age;
}

Subtype.prototype = new SuperType();
Subtype.prototype.sayAge = function(){
   console.log(this.age);
};

var instance1 = new Subtype("Nicholas", 29);
instance1.colors.push("black");
console.log(instance1.colors); // red, blue, green, black
instance1.sayName(); // Nicholas
instance1.sayAge(); //29

var instance2 = new Subtype("Greg", 2);
console.log(instance2.colors); // red, blue, green
instance2.sayName(); // Greg
instance2.sayAge(); //2

组合继续避免了原型链和借用函数的缺点,融会了它们的长处,成为Javascript中最经常使用的继续形式。

原型式继续

function object(o){
    function F(){}
    F.prototype = o;
    return new F();
}
var person = {
    name:"Nicholas",
    friends:["Shelby", "Court", "Van"]
};

var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");

var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");

console.log(person.friends); // Shelby, Court, Van, Rob, Barbie

Object.create()

Object.create()要领范例了原型式继续。

var person = {
    name:"Nicholas",
    friends:["Shelby", "Court", "Van"]
};

var anotherPerson = Object.create(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");

var yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");

console.log(person.friends); // Shelby, Court, Van, Rob, Barbie

寄生式继续

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

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(){
    console.log(this.name);
}

function Subtype(name,age){
    SuperType.call(this,name);
    this.age = age;
}

inheritPrototype(Subtype, SuperType);

Subtype.prototype.sayAge = function(){
    console.log(this.age);
}
    原文作者:风雨后见彩虹
    原文地址: https://segmentfault.com/a/1190000000728079
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞