第六章 理解对象

来源:互联网 发布:表格中如何建立数据库 编辑:程序博客网 时间:2024/06/08 19:55

面对对象(Object-Oriented,OO)的语言有一个标志,就是它们都有一个类的概念,而通过类可以创建任意多个具有相同属性和方法的对象。
ECMA-262 把对象定义为:无需属性的集合,其属性可以包含基本值、对象或者函数。

理解对象

ECMAScript 第五版定义只有内部才有的特征(attribute)时,描述了属性(property)的各种特征,这些特征是为了实现JavaScript 引擎用的,因此在JavaScript 中不能直接访问它们。
ECMAScript 中有两种属性:数据属性 和 访问器属性

数据属性

数据属性包含一个数据值的位置。这个位置可以读取和写入值。
数据属性有4个描述其行为特征:

[[Configurable]]表示能否通过delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。对于直接在对象上定义的属性,默认为 true。 [[Enumerable]]表示能否通过for-in 循环返回属性。对于直接在对象上定义的属性,默认为 true。[[Writable]]表示能否修改属性的值。对于直接在对象上定义的属性,默认为 true。[[Value]]包含这个属性的数据值。默认值是undefined
    <script>        // 修改属性默认的特性,必须使用Object.defineProperty() 方法        "use strict";        var o = {}; //创建新对象,默认属性都为false        Object.defineProperty(o,"a",{            value:37,            writable:true,            enumerable:true,            configurable:true        });// 对象o 拥有了属性a,值为37        Object.defineProperty(o,"b",{            value:47,            writable:false        });        console.log(o.a);        console.log(o.b);        o.b = 25; // 严格模式抛出错误        console.log(o.b); // 47,non-wirtable    </script>

访问器属性

访问器属性不包含数据值

[[Configurable]]表示能否通过delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。对于直接在对象上定义的属性,默认为 true。 [[Enumerable]]表示能否通过for-in 循环返回属性。对于直接在对象上定义的属性,默认为 true。[[Get]]在读取属性时调用的函数。默认值为undefined[[Set]]在写入属性时调用的函数。默认值是undefined
    <script>        // 访问器属性不能直接定义,必须通过Object.defineProperty() 来定义        // _xxx 前面的下划线是一种常用的记号,用于表示只能通过对象方法访问的属性        "use strict";        var book = {            _year : 2017,            edition : 1        };        Object.defineProperty(book,"year",{            get:function(){                return this._year            },            set:function(newValue){                if(newValue >2017){                    this._year = newValue;                    this.edition += newValue-2017;                }            }        })        book.year = 2018; //year 访问器属性        console.log(book.edition) ;//2        // 旧版本一般使用非标准的方法:__defineGetter__() 和 __defineSetter__()        var foot = {            _price : 20,            calorie : 100        };        foot.__defineGetter__("price",function(){            return this._year;        });        foot.__defineSetter__("price",function(newPrice){            if(newPrice>20){                this._price=newPrice;                this.calorie += newPrice-20;            }        })        foot.price=25;        console.log(foot.calorie); //25    </script>

定义 多个属性

        <script>        // Object.defineProperties() 方法。可以通过描述符一次定义多个属性        // 接收两个参数:第一个对象是天价和修改其属性的对象,第二个对象的属性与第一个对象中腰添加或修改的属性一一对应        var book = {};        Object.defineProperties(book,{            _year:{                writable:true,                value:2017            },            edition:{                writable:true,                value:1            },            year:{                get:function(){                    return this._year;                },                set:function(newValue){                    if(newValue>2017){                        this._year = newValue;                    this.edition +=newValue -2017;                    }                }            }        })        book.year = 2019;        console.log(book.edition); //3    </script>

读取属性的特性

        // Object.getOwnPropertyDescriptor() 可以取得给定属性的描述符        var descriptor = Object.getOwnPropertyDescriptor(book,"_year");        console.log(descriptor.value); //2017        console.log(descriptor.configurable);//false        console.log(descriptor.writable);//true        console.log(descriptor.get); //undefined        console.log(descriptor.set); //undefined        var descriptor1 = Object.getOwnPropertyDescriptor(book,"year");        console.log(descriptor1.value); //undefined        console.log(descriptor1.configurable);//false        console.log(descriptor1.enumerable);//flase        console.log(descriptor1.get); //function    </script>

创建对象

使用同一个借口创建很多对象,会产生大量的重复代码。

工厂模式

    <script>    "use strict";    // ECMAScript 中无法创建类,用函数来封装以特定接口创建对象的细节。    // 工厂模式虽然解决创建多个相似对象问题,但却没有解决对象识别问题(即怎么知道一个对象类型)    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("Lilian","20","Doctor");    console.log(person1)    </script>

构造函数模式

<body>    <script>        "use strict";        // 自定义构造函数,从而定义自定义对象类型的属性和方法        // 构造函数始终都应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头        // 构造函数本身也是函数,只不过可以用来创建对象        function Person(name,age,job){            this.name = name;            this.age = age;            this.job = job;            this.sayName = function(){                console.log(this.name);            };        }        var person1 = new Person("Lilian",20,"doctor");        console.log(person1);    </script>

原型模式

每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。
按照字面量理解,prototype 就是通过调用构造函数而创建的那个对象实例的原型对象。
这里写图片描述

这里写图片描述

继承

ECMAScript 只支持实现集成,而且其实现集成主要是依靠原型链来实现的。
继承是OO的概念,许多OO语言都支持两种继承方式:接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法

原型链

基本思想:利用原型让一个引用类型继承另一个引用类型的属性和方法。
构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。

    <script>    function SuperType(){        this.prototype = true;        this.day = "sun";    }    SuperType.prototype.getSuperValue = function(){        return this.prototype+" "+this.day;    }    function SubType(){        this.subPrototype = false;    }    //继承了SuperType    SubType.prototype = new SuperType();    SubType.prototype.getSubValue=function(){        return this.subPrototype;    };    var instance = new SubType();    console.log(instance.getSuperValue());    </script>

这里写图片描述
所有引用类型默认都继承了Object,而这个继承也是通过原型链实现
这里写图片描述

    //确认原和实例的关系,instanceof 操作符    // instance 是Object、SubType、SuperType 任何一个类型的实例    console.log(instance instanceof Object);  //true    console.log(instance instanceof SubType); //true    console.log(instance instanceof SuperType); //true    // isPrototypeOf() 方法,只要是原型链出现过的原型,都可以说是该原型链派生的实例的原型console.log(Object.prototype.isPrototypeOf(instance)); //true   console.log(SubType.prototype.isPrototypeOf(instance)); //true  console.log(SuperType.prototype.isPrototypeOf(instance)); //true

给原型添加方法的代码一定要放在替换原型的语句之后

    // 重写超类型中的方法    SubType.prototype.getSuperValue=function(){        return false;    };    var instance = new SubType();    var instance1 = new SuperType();    console.log(instance.getSuperValue()); //false    console.log(instance1.getSuperValue()); //false    // 不能使用字面量创建原型方法,因为会重写原型链    SubType.prototype={        //    }

原型链问题

    // 最主要问题来自包含引用类型值的原型    // 在通过原型来实现继承时,原型实际会变成另一个类型的实例,原先的实例属性顺理成章变成了现在原型属性    function SuperType(){        this.colors = ["red","yellow","green"];    }    function SubType(){}    // 继承SuperTyper    SubType.prototype = new SuperType();    var instance1 = new SubType();    instance1.colors.push("black");    console.log(instance1.colors); //["red", "yellow", "green", "black"]    var instance2 =new SubType();    console.log(instance2.colors); //["red", "yellow", "green", "black"]

在创建子类型实例时,不能向超类型的构造函数中传递参数。实际上,应该说没有办法在不影响对象实例情况下,给超类型的构造函数传递参数。

实践中很少单独使用原型链。

借用构造函数

解决原型中包含引用类型所带来的问题的过程中,使用借用构造函数(constructor stealing)的技术(有时候也叫做伪造对象或经典继承)。

基本思想:在子类型构造函数的内部调用超类型构造函数,使用apply() 和call()

        //相对于原型链而言,借用构造函数一个很大优势,即可以在子类型构造函数中向超类型构造函数传递参数        function SuperType(name){            this.name = name;        }        function SubType(){            // 继承了SuperType,同时还传递了参数            SuperType.call(this,"Lilith");            // 实例属性            this.age=29;        }        var instance = new SubType();        console.log(instance.name); //Lilith        console.log(instance.age); //29

借用构造函数存在的问题—方法都在构造函数中定义,无法复用函数。借用构造函数的结束很少单独使用。

组合继承

组合继承(combination inheritance),有时候也叫做伪经典继承,指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。

思路:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承

JavaScript 最常用的继承模式

        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.constructor = SubType;        SubType.prototype.sayAge = function(){            console.log(this.age);        };        var instance1 = new SubType("Lilith",20);        instance1.colors.push("black");        console.log(instance1.colors); //["red", "blue", "green", "black"]        instance1.sayName(); //Lilith        instance1.sayAge(); //20        var instance2 = new SubType("Greg",29);        console.log(instance2.colors); // ["red", "blue", "green"]        instance2.sayName(); //Greg        instance2.sayAge(); //29
原创粉丝点击