【js高程第6章】 — 面向对象的程序设计

一、属性类型(两种)

1.数据属性

数据属性有 4个 描述其行为的特性:

[[Configurable]]:默认值 true。表示能否通过 delete 删除属性,能否修改属性的特

性,或者能否把属性修改为访问器属性。

[[Enumerable]]:默认值 true。表示能否通过 for-in 循环返回属性。

[[Writable]]:默认值 true。表示能否修改属性的值。

[[Value]]:默认值undefined。包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。

修改特性值:Object.defineProperty(),Configurable、Enumerable、Writable均默认为false。

var person = {};

Object.defineProperty(person, "name", {

    writable: false,

    value: "Nicholas"

});

alert(person.name); //"Nicholas"

person.name = "Greg";//严格模式下,这样赋值会抛出错误。

alert(person.name); //"Nicholas" writable设为false,name修改失败

2.访问器属性

访问器属性也有 4个 特性:

[[Configurable]]:默认值为true。表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特

性,或者能否把属性修改为数据属性。

[[Enumerable]]:默认值为 true。表示能否通过 for-in 循环返回属性。

[[Get]]:默认值为 undefined。在读取属性时调用的函数。

[[Set]]:默认值为 undefined。在写入属性时调用的函数。

【注】

访问器属性不能直接定义,必须使用 Object.defineProperty()来定义。

3.定义多个属性

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;

            }

        }

    }

});

4.读取属性的特性

Object.getOwnPropertyDescriptor():

返回一个对象,对象中存储的是所访问属性的特性值。

注:只能用于实例属性,要取得原型属性的描述符:Object.getOwnPropertyDescriptor(Person.prototype)。

接收两个参数:

(1)obj:属性所在对象 

(2)propertyName:要读取其描述符(特性值)的属性名称


二、创建对象(7种方式)

1.工厂模式

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("Sherry",23,"Software Engineer");

var person2 = createPerson("Greg",25,"Doctor");

【缺点】无法知道一个对象的类型

2.构造函数模式

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

    this.job = job;

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

}

var person1 = new Person("Sherry",23,"Software Engineer");

var person2 = new Person("Greg",25,"Doctor");

【new做了什么?】

(1)创建一个新对象

(2)将构造函数的作用域赋给新对象(因此this指向了这个新对象)

(3)执行构造函数中的代码(为这个新对象添加属性)

(4)返回新对象

【关于constructor】

person1.constructor == Person; //true

person2.constructor == Person; //true

【缺点】

每个方法都要在每个实例上重新创建一遍。person1和person2创建了两个名为sayName的函数实例。

person1.sayName == person2.sayName; //false

【改进】

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("Sherry",23,"Software Engineer");

var person2 = new Person("Greg",25,"Doctor");

【新缺点】

全局作用域定义的函数,实际上只能被某个对象调用。这点与全局作用域不符。同时,如果对象需要定义很多方法,就要定义很多个全局函数,这使得我们自定义的引用类型丝毫没有封装性可言了。


3.原型模式

function Person(){ }

Person.prototype.name = "Sherry";

Person.prototype.age = 29;

Person.prototype.job = "Software Engineer";

Person.prototype.sayName = function(){

    alert(this.name);

};

var person1 = new Person();

var person2 = new Person();

(1)【达到效果】
person1.sayName == person2.sayName; //true

(2)【实例对象、原型对象与constructor的关系】

person1.__proto__ === person2.__proto__ === Person.prototype

Person.prototype.constructor === Person

(3)【原型检测】

Person.prototype.isPrototypeOf(person1); //true

Object.getPrototypeOf(person1) === Person.prototype; //true

(4)【in操作符——实例属性+原型属性】

"name" in person1; //true 实例属性、原型属性均可以访问

for-in:返回所有能通过对象访问的可枚举的属性,实例属性、原型属性均可以访问。

注:所有的实例属性都是可枚举的。

(5)【Object.keys()——实例属性】

获取对象上所有可枚举的实例属性

(6)【Object.getOwnPropertyNames()——实例属性】

获取所有实例属性,无论是否可枚举

(7)【原型对象切断与构造函数联系的情况

//改变定义原型对象的方式

Person.prototype = {

    name:"Sherry",

    age:23,

    job:"Software Engineer",

    sayName:function(){

        alert(this.name);

    }

}

!!注!!:

这种定义原型对象的写法,相当于完全重写了原有的原型对象,且会切断其与构造函数之间的联系

即:相当于创建了一个新的普通对象,并把Person.prototype指向了这个新对象。所以,这时候的Person.prototype也就是一个普通对象,只不过Person的实例可以读取它里面的属性和方法。

因为这种写法完全重写了默认的prototype对象,相当于创建了一个新的对象,所以constructor属性就变成了新对象的constructor属性,指向Object。

但,instanceof依然可以检测Person类型。

var friend = new Person();

friend instanceof Person; //true

friend.constructor == Person; //false

friend.constructor == Object; //true

【显式设置constructor】

Person.prototype = {   

    constructor:Person,

    name:"Sherry",   

    age:23,   

    job:"Software Engineer",   

    sayName:function(){       

        alert(this.name);   

     }

}

 注:这样设置会导致constructor的[[Enumerable]]被置为true。

(这样设置constructor,js会认为它是新对象的实例属性,而实例属性都可枚举)

——解决:使用defineProperty重置constructor的特性。

【原型的动态性】

function Person(){ }

var friend = new Person();

Person.prototype = {       

    constructor:Person,    

    name:"Sherry",      

    age:23,       

    job:"Software Engineer",       

    sayName:function(){               

        alert(this.name);        

    }

};

friend.sayName(); //error

解析:

创建friend对象时,获取到调用构造函数时创建的原型对象。

经过Person.prototype = {    xxx   }重写,Person的原型对象相当于被更改为新创建的对象,之后再new实例,均会指向新的原型对象。而已经创建过的实例对象friend,仍指向旧的原型对象。

【原型模式缺点】

(1)省略了为构造函数传递初始化参数的环节,结果所有实例在默认情况下都会获得相同的属性值。

(2)最大问题:共享属性的问题。尤其是,共享引用类型属性的问题。

function Person(){ }

Person.prototype = {         

    constructor:Person,        

    name:"Sherry",         

    age:23,           

    job:"Software Engineer",           

    friends:["Greg","Court"],

    sayName:function(){                       

        alert(this.name);            

    }

};

var person1 = new Person();

var person2 = new Person();

person1.friends.push("Van");

person1.friends; //["Greg","Court","Van"]

person2.friends; //["Greg","Court","Van"] 非理想效果


4.构造函数模式+原型模式(最常见方式,定义引用类型的默认模式)*暂无缺陷^_^

function Person(name,age,job){

    this.name = name;

    this.age = age;

    this.job = job;

    this.friends = ["Greg","Court"];

}

Person.prototype = {

    constructor:Person,

    sayName:function(){

        alert(this.name);

    }

}

var person1 = new Person("Sherry",23,"Software Engineer");

var person2 = new Person("Greg",25,"Doctor");

person1.friends.push("Van");

person1.friends; //["Greg","Court","Van"]

person2.friends; //["Greg","Court"]

person1.friends === person2.friends; //false

person1.sayName === person2.sayName; //true

5.动态原型模式(为了提高模式4的封装性)*暂无缺陷^_^

function Person(name,age,job){

    this.name = name;

    this.age = age;

    this.job = job;

    if(typeof this.sayName != "function"){

        /**注意不要用对象字面量定义原型对象。因为这里判断的时候,实例对象已经被创建,切断实例和原有原型之间的联系,会导致新的原型对象不对已创建的实例生效。(仅第一次创建的实例方法无法访问)*/

        Person.prototype.sayName = function(){ 

            alert(this.name);

        }

    }

}

var friend = new Person("Sherry",23,"Software Engineer");

friend.sayName();


6.寄生构造函数模式(可以使用其他模式的情况下,不要使用这种模式)

//与工厂模式形式相同

function Person(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 friend = new Person("Sherry",23,"Software Engineer");

firend.sayName(); //"Sherry"

【与工厂模式的区别】

(1)使用new调用

(2)把Person称作构造函数,不是普通函数

【适用场景】

为特定类型的对象创建构造函数,增加特殊功能。如为Array构造函数添加功能。

eg:创建一个具有额外方法的特殊数组

function SpecialArray(){
    var values = new Array();

    values.push.apply(values,arguments);

    values.toPipedString = function(){

        return this.join("|");

    }

    return values;
}

var colors = new SpecialArray("red","green","blue");

alert(colors.toPipedString()); //"red|green|blue"

【缺点】

不能用instanceof确定对象类型。


7.稳妥构造函数模式(安全性高)

【稳妥对象】

没有公共属性,而且其方法也不引用this的对象。

function Person(name,age,job){

    var o = new Object();

    o.sayName = function(){

        alert(name);

    };

    return o;

}

这里,除了使用sayName()方法之外,没有其他办法访问name的值。

【与寄生构造函数模式的区别】

(1)新创建对象的实例方法不引用this

(2)不使用new操作符调用构造函数

【使用场景】

需要高安全性的场景。

【缺点】

不能用instanceof确定对象类型。


三、继承(6种方式)

1.原型链

function SuperType(){

    this.property = true;

}

SuperType.prototype.getSuperValue = function (){

    return this.property;

};

function SubType(){

    this.subproperty = false;

}

SubType.prototype = new SuperType(); //重写原型对象,和字面量方式定义原型对象相同

SubType.prototype.getSubValue = function(){

    return this.subproperty;

};

var instance = new SubType();

instance.getSuperValue(); //true


【js高程第6章】 — 面向对象的程序设计_第1张图片
完整原型链图示


【原型链的问题】

(1)最大问题来自,包含引用类型值的原型。

子类的原型对象,是父类的实例。所以,父类的实例属性,会变成子类实例的原型属性,从而造成一些,不希望共享的属性、被共享了的问题。

(2)创建子类的实例时,不能向超类型的构造函数中传递参数。或者说,没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。

【问题一】eg:

function SuperType(){

    this.colors = ["red","green","blue"];

}

function SubType(){

}

SubType.prototype = new SuperType();

var instance1 = new SubType();

instance1.colors.push("black");

alert(instance1.colors); //"red,green,blue,black"

var instance2 = new SubType();

alert(instance2.colors); //"red,green,blue,black" 期望colors数组不改变


2.借用构造函数(在子类构造函数内部调用超类的构造函数)

function SuperType(){   

    this.colors = ["red","green","blue"];

}

function SubType(){

    SuperType.call(this); //改变父类的this指向

}

SubType.prototype = new SuperType();

var instance1 = new SubType();

instance1.colors.push("black");

alert(instance1.colors);//"red,green,blue,black"

var instance2 = new SubType();

alert(instance2.colors); //"red,green,blue" 

【优势】

在子类构造函数中给父类构造函数传递参数。

function SuperType(name){

    this.name = name;

}

function SubType(){

    SuperType.call(this,"Sherry");

    this.age = 23;

}

var instance = new SubType();

alert(instance.name); //"Sherry"

alert(instance.age); //23

【问题】

(同创建对象的构造函数模式)如果方法都在构造函数中定义,每个实例都会创建一个新的方法,但方法执行相同的功能,不具备函数复用性。


3.组合继承(原型链+借用构造函数,最常用的继承模式)

function SuperType(name){   

    this.name = name;

    this.colors = ["red","green","blue"];

}

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 instance1 = new SubType("Sherry",23);

instance1.colors.push("black");

alert(instance1.colors); //"red,green,blue,black"

instance1.sayName(); //"Sherry"

instance1.sayAge(); //23


var instance2 = new SubType("Greg",25);

alert(instance2.colors); //"red,green,blue"

instance2.sayName(); //"Greg"

instance2.sayAge(); //25

【缺点】

SubType.prototype = new SuperType();把父类所有实例属性都赋给了子类的原型对象。

SuperType.call(this,name);又把父类所有的实例属性都赋给了子类的实例。

两次调用父类构造函数,子类实例上挂载的父类实例属性,一定会屏蔽子类原型对象上挂载的父类实例属性。造成浪费。


4.原型式继承

function object(o){
    function F(){    }

    F.prototype = o;

    return new F();
}

【注】

传入的参数o中如果包含引用类型,则所有新创建的对象都会共享这个引用类型的值。

【规范化】

Object.create()实现原型式继承。传入一个参数时,行为与上述object方法相同。

两个参数:

(1)用作新对象原型的对象

(2)(可选)为新对象定义额外属性的对象。格式与Object.defineProperties()第二个参数相同。

eg:

var person = {

    name:"Sherry",

    friends:["Kaven","Greg"]

}

var anotherPerson = Object.create(person,{

    name:{

        value:"Lucy"

    }

});

anotherPerson.name; //"Lucy"

【适用场景】

只是想让一个对象与另一个对象保持类似的情况。

没必要兴师动众创建构造函数,原型式继承便可以胜任。


5.寄生式继承(与工厂模式、寄生构造函数模式类似)

function createAnother(original){

    var clone = Object.create(original);

    clone.sayHi = function(){

        alert("hi");

    };

    return clone;

}

【适用场景】

主要考虑对象,而不是自定义类型和构造函数的情况。

【缺点】

sayHi函数不能被复用,降低效率。同构造函数模式创建对象的缺点。


6.寄生组合式继承(最理想的继承模式)*无缺陷^_^

function SuperType(name){

    this.name = name;

    this.colors = ["red","green","blue"];

}

SuperType.prototype.sayName = function(){

    alert(this.name);

};

function SubType(name,age){

    SuperType.call(this,name);

    this.age = age;

}

SubType.prototype = Object.create(SuperType.prototype);

SubType.prototype.constructor = SubType;

SubType.prototype.sayAge = function(){

    alert(this.age);

};

你可能感兴趣的:(【js高程第6章】 — 面向对象的程序设计)