美文网首页
【js高程第6章】 — 面向对象的程序设计

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

作者: 南慕瑶 | 来源:发表于2018-05-05 10:15 被阅读0次

    一、属性类型(两种)

    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

    完整原型链图示

    【原型链的问题】

    (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章】 — 面向对象的程序设计

          本文链接:https://www.haomeiwen.com/subject/omqkfftx.html