当前位置: 移动技术网 > IT编程>开发语言>JavaScript > 《JavaScript高级程序设计》笔记:面向对象的程序设计(六)

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

2018年12月12日  | 移动技术网IT编程  | 我要评论

面向对象的语言有一个标志,那就是它们都有类的概念,而通过类可以创建任意多个具有相同属性和方法的对象。

理解对象

创建自定义对象的最简单的方法就是创建一个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);
}

 

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网