目 录CONTENT

文章目录

面向对象程序设计(基础篇)

Administrator
2020-07-24 / 0 评论 / 2 点赞 / 8315 阅读 / 17958 字

面向对象程序设计(基础篇)

在上一个章节,我们已经学过了面向对象的基础,知道怎么样创建对象了,也知道使用构造函数这种方式了。这些方式去创建的对象,我们都叫最基础的对象

我们还可以去创建一些比较高级的对象,以实现更为复杂的要求,如某些人的年龄应该是随着出生年月自动生成的,某些人的姓名应该是随着姓和名相加而成的,对于这些特殊的要求,我们就要使用特殊的方式去定义我们的对象

首先我们先从下面的一个代码来引伸今天的重点

var stu1 = {
    name:"杨兰芳",
    sex:"女",
    age:18,
    height:170,
    weight:55
}

现在在上面我们就创建了一个学生的对象,这个对象里面有5个属性分别是姓名,性别,年龄,身高和体重,但是现在这个同学跟我说,我的身高和体重是保密的,不能让别人知道,要删除这两个属性怎么办呢?同时我的性别是“女”,不能被变又,这又怎么办呢?

所以针对于这种特殊的对象,我们之前的对象创建方式就无能为力了

如果一个对象要是创建好了以后要删除这个对象里面的某一个属性,我们可以使用关键字delete

delete stu1.height;		//删除stu1对象的height这个属性
delete stu1.weightl		//删除stu1对象的weight这个属性

使用delete去删除对象的属性的时候,如果返回true则代表属性删除成功,如果返回false则代表属性删除失败

根据上面的这个关键字delete我们可以发现,其实默认情况下,对象的所有属性都是可以被删除的。这样我定义的对象就非常的危险。怎么样在定义对象的时候让别人不能够删除属性,或者让别人不能够去更改这个属性值呢。

这个时候我们䚱需 要使用特殊的方式去定义对象了

Object.defineProperty()定义对象属性

数据属性

通过这种方式,我们可以定义一些特殊的对象属性

//现在我定义了一个空的对象,这个对象没有任何属性
var stu1 = {};
//我现在经给它添加一些特殊的属性
Object.defineProperty(stu1,"name",{

});

现在我们在对象stu1上面定义了一个属性name,现在它还是一个普通的属性,我们要根据下面截图里面的特点来描述这个属性

image-20200507110451341

上面的截图来自红宝书,但是书本上面有一个地方错了,Enumerable这个属性的默认值是false,书上面写的是true,这是错误的

//现在我定义了一个空的对象,这个对象没有任何属性
var stu1 = {
    aaa:"hello"
};
//我现在经给它添加一些特殊的属性
Object.defineProperty(stu1, "name", {
    configurable: false,        //如果为true则可以通过delete删除该属性,false则不能删除
    enumerable:false,           //属性是否可以被for....in遍历出来 ,false则不能遍历出来
    value:"杨兰芳",              //代表当前的属性值  
    writable:false              //代表当前属性值是否可以更改,false不可以更改,true则可以更改
});

//属性的遍历 
for(var i in stu1){
    console.log(i);
}

访问器属性

image-20200507141141122

上面的截图来自红宝书,但是书本上面有一个地方错了,Enumerable这个属性的默认值是false,书上面写的是true,这是错误的

//先定义一个普通的对象
var stu1 = {
    birthday: "1997-5-4"
};
//现在上面的有了一个学生的对象, 我们在这个对象里面添加了一个属性birthday生日
//现在我们还希望给它添加一个属性age,但是这个age年龄属性应该是根据生日自动计算出来的
Object.defineProperty(stu1, "age", {
    configurable: false, //这个属性不可被delete
    enumerable: true, //代表这个属性可以遍历
    get: function () {
        return "你在读取age这个属性";
    },
    set: function (v) {
        //这里的v代表的就是你要赋的值
        console.log("你在对age这个属性赋值,你要赋的值是:" + v);
    }
});

上面的getset定义的属性sex就是一个访问器属性,它在取值与赋值的时候分别调用getset的方法

?问题:这个访问器属性到底有什么作用呢?请看下面的代码

var stu1 = {
    birthday: "1997-5-4",
    IDCard: "420984199705041064"
};

Object.defineProperty(stu1, "age", {
    // get是在读取这个属性的值的时候会调用
    get: function () {
        //当我想拿到年龄age的值的时候,我应该是用 当前日期 - 出生日期 = 年龄
        var currentDate = new Date(); //得到当前日期对象

        var birthdayDate = new Date(this.birthday); //生日的日期

        //日期相减有一套专门的格式, 这里先不用纠结,后面有专门的章节
        //getTime是获取当前时间的毫秒数  1秒钟是1000毫秒
        var totalTime = currentDate.getTime() - birthdayDate.getTime();

        //这样就得到的年龄
        var yearCount = parseInt(totalTime / 1000 / 60 / 60 / 24 / 365);

        return yearCount;
    }
    //我只有get方法,没有set方法,这样age这个属性就只可以值 ,不能赋值了
});
//当然我们也知道,一个人的身份证号可以决定一个的性别  身份证号的第17位如果是奇数,则性别为男
//如果是偶数,则性别为女

Object.defineProperty(stu1, "sex", {
    get: function () {
        //性别是根据身份号第17位来的,如果是奇数就是男,如果是偶数就是女
        if (this.IDCard[16] % 2 == 0) {
            return "女";
        } else {
            return "男";
        }
    }
});

通过上面的访问器属性,我们设置了agesex这两个属性,age是根据出生年月来计算的,sex则是根据身份证号来决定的

所以通过上面的案例,我们可以得到访问器属性的优点

  1. get方法主要赋值对这个属性进行取值的控制,set方法主要是对这个属性的赋值控制
  2. 在上面的案例里面,我们都没有写set方法,这样age属性就与sex属性只能取值 ,不能赋值

上面的agesex这两个属性在定义的时候我们分别调用了两次Object.defineProperty()这样会显得很麻烦,如果我们要同时定义多个属性应该怎么办呢?

Object.defineProperties()定义对象属性

它是Object.defineProperty()的复数形式,可以一次性定义多个属性。我们现在就将上面的两个属性同时定义一下

var stu1 = {
    birthday: "1997-5-4",
    IDCard: "420984199705041014"
};
//我们分别要定义name属性不可更改,不能删除,
//age属性根据生日来,
//sex属性根据身份证号灭
Object.defineProperties(stu1, {
    name: {
        value: "杨兰芳",
        configurable: false, //不能通过delete去删除
        writable: false //不能更改
    },
    age: {
        //在这里定义了一个get的访问器属性
        get: function () {
            var now = new Date(); //当前时间
            var birthdayDate = new Date(this.birthday);
            var totalTime = now.getTime() - birthdayDate.getTime();
            var yearCount = parseInt(totalTime / 1000 / 60 / 60 / 24 / 365);
            return yearCount;
        }
    },
    sex: {
        //在这里定义了一个get的访问器属性
        get: function () {
            return this.IDCard[16] % 2 == 0 ? "女" : "男";
        }
    }
});

上面的两个种方式都是用于定义对象的特殊属性,在定义特殊属性的时候,我们可以使用数据属性和访问器属性

image-20200507150511930


获取对象属性的描述信息

当我们通过上面的方法定义特殊的属性以后,怎么样获取这些属性的描述呢?例如,我怎么知道哪些属性可以可以删除,哪些属性可以更改默认值呢。所以这个时候有一个特殊的方法用于获取刚刚的属性描述

也就是说我们刚刚所总结的数据属性与访问器属性其实都是属性的描述信息descriptor

如下所示的的对象,在这人stu1的对象里面,我们怎么知道name属性是否可以被删除呢

var stu1 = {
 	//代码就是上面的stu1对象里面的代码,这里省略了,具体可以看上面  
};
//得到name这个属性的描述信息
var nameDesc = Object.getOwnPropertyDescriptor(stu1,"name");
/*  nameDesc的结果如下所示
{
   value: "杨兰芳"
   writable: false
   enumerable: false
   configurable: false
}
*/

//得到sex这个属性的描述信息
var sexDesc = Object.getOwnPropertyDescriptor(stu1,"sex");
/* sexDesc的结束如下所示
{
   get: ƒ ()
   set: undefined
   enumerable: false
   configurable: false
}
*/

构造函数与特殊属性的结合

构造函数与特殊属性的结合主要指的就是构造函数与Object.defineProperties()的结合

案例:现要定义一个构造函数Person, 这个构造函数创建的对象有5个属性,分别是name,birthday,IDCard,age,sex,这5个属性都不可以被delete,同时都是只主读的的

age属性要根据生日属性birthday计算,sex属性要根据身份证号IDCard来计算

针对上面的要求,我们就可以把构造函数定义如下

function Person(_name, _birthday, _IDCard) {
    //在构造函数的内部,this指针是指向当前对象的
    // this.name = _name;      //通过这一种方式定义出来的属性只是一个普通的属性
    Object.defineProperties(this, {
        name: {
            value: _name,
            configurable: false,
            writable: false
        },
        birthday: {
            value: _birthday,
            writable: false,
            configurable: false
        },
        IDCard: {
            value: _IDCard,
            writable: false,
            configurable: false
        },
        age: {
            configurable: false,
            get: function () {
                var now = new Date();
                var birthdayDate = new Date(this.birthday);
                var totalTimes = now.getTime() - birthdayDate.getTime();
                var yearCount = parseInt(totalTimes / 1000 / 60 / 60 / 24 / 365);
                return yearCount;
            }
        },
        sex: {
            configurable: false,
            get: function () {
                return this.birthday[16] % 2 == 0 ? "女" : "男";
            }
        }

        //如果我现在使用构造函数去创建调用,创建对象,并返回对象
        //而this就指向这个对象
    });
}


//使用构造函数创建了一个对象
var p1 = new Person("杨标", "2010-1-5", "420984201001051016");

变量名做为属性名

一般情况下,我们在定义对象的时候,对象里面的属性可以是字符串,也可以是数字,如下所示

//用变量做属性名这种情况,前期接触得比较少,后期频繁使用

var obj1 = {
    name: "标哥哥",
    1: "数字属性",
    "a-b": "特殊属性"
};

var str = "age"; //不能能把这个str当成对象的属性名呢???

var obj2 = {
    name: "标哥哥",
    str: 18
};
//这个时候得到的obj2对象的结果如下
/* obj2对象如下
{
	name: "标哥哥"
	str: 18
}
*/
//这个时候,我们并没有看到str这个变量变成了age

那么同学们思考一下,有没有把变量当成属性名的呢???

//如果变量要做属性名,应该采用下面这种方式
var obj3 = {
    name: "标哥哥",
    [str]: 19
}
//如果要把变量做为属性名,这个时候可以把变量加上一个中括号。这个时候得到的对象obj3如下所示
/*
{
    name: "标哥哥"
    age: 19
}
*/
//这个时候我们已经可以看到str这个变量已经被替换成一个age了

对象的遍历

这个东西其实在很早之前就已经提到过了,我们之前对数组进行了遍历 ,其实对象了是可以进行遍历的

通过for…in进行遍历

下面我们先来看一下“红宝书”是怎么讲解对象的遍历的

image-20200507111310266

现在根据上面的说明,我们来实际操作一下

var stu1 = {
    name: "杨兰芳",
    sex: "女",
    age: 18,
    height: 170,
    weight: 55
};

//现在我们就开始遍历这个对象。使用for...in来进行遍历 
for (var i in stu1) {
    //这个i指代的就是对象的属性名
    console.log("属性名:" + i + "------属性值:" + stu1[i]);
}

程序运行结果如下

image-20200507111810717

这个时候我们可以看到所有的属性名都被遍历出来了

?注意:for…in在进行遍历的时候,对象内部enumerable:false的属性是遍历不出来的

通过Object.keys()来遍历对象

在JavaScript的内部,我们有一个方法叫Object.keys()它可以获取到我们对象内部的所有属性名(也是仅仅只能获取enumearble:true的属性)

var obj1 = {
    name: "标哥哥",
    sex: "男",
    age: 18
}
Object.defineProperty(obj1, "hobby", {
    writable: true,
    configurable: false,
    enumerable: false, //设置为不可遍历 
    value: "看书,睡觉"
});
var arr = Object.keys(obj1);		//["name", "sex", "age"]
// arr是一个数组,数组就有我们之前所学过的遍历方法,我们现在把数组所有的元素遍历出来
arr.forEach(function (item, index, _arr) {
    console.log("属性名:" + item + "------属性值:" + obj1[item]);
});

Object.keys(对象)会将对象里面所有可遍历的属性拿出来组成一个数组

上面的的两种方式都只是能够遍历enumerable:true的属性,如果现在想遍历所有的属性呢?

通过Object.getOwnPropertyNames()来遍历

通过这个方法,我们可以获取自己对象的所有属性名。现在还是对上面的对象进行遍历操作

var obj1 = {
    name: "标哥哥",
    sex: "男",
    age: 18
}
Object.defineProperty(obj1, "hobby", {
    writable: true,
    configurable: false,
    enumerable: false, //设置为不可遍历 
    value: "看书,睡觉"
});

//获取obj1这个对象所有的属性名称,得到一个数组    ["name", "sex", "age", "hobby"]
var arr = Object.getOwnPropertyNames(obj1);
//得到这个数组以后,我们就可以按照之前的方式来进行遍历 

arr.forEach(function (item, index, _arr) {
    console.log("属性名:" + item + "------属性值:" + obj1[item]);
});

?注意:这种方式可以将enumerable:false的属性也遍历出来,它是获取当前对象的所有属性名称来遍历

image-20200508104122051

判断某一个对象是否具备某一个属性

假设现有一个对象,我想看一下这个对象是否有这么一个属性怎么办呢?

var stu1 = {
    name: "杨兰芳",
    sex: "女"
};
Object.defineProperty(stu1, "hobby", {
    value: "看书,睡觉",
    enumerable: false
});

现在就有上面这一个对象,在上面这个对象里面,我们想判断一下,这个对象是否有nameagehobby这三个属性,怎么判断呢?

面对一个像这样的问题,很多人可能会想到使用上面的对象的遍历这种方式来进行

第一种方式

直接采用上一个章节的遍历属性的方式来进行

 //这里不能用Object.kyes(),因为这个方法只能用于获取可遍历 的属性
var arr = Object.getOwnPropertyNames(stu1); //arr代表的就是当前对象所有的属性的集合
//去数组里面去查找某个元素,如果存在则索引不为-1
if (arr.indexOf("name") != -1) {
    //说明这个属性存在
    console.log("name属性存在");
} else {
    console.log("name属性不存在");
}

if (arr.indexOf("age") != -1) {
    console.log("age属性存在");
} else {
    console.log("age属性不存在");
}

if (arr.indexOf("hobby") != -1) {
    console.log("hobby属性存在");
}
else{
    console.log("hobby属性不存在");
}

第二种方式

判断某一个对象是否具备某一个属性其实在JavaScript里面有特殊关键字来进行,这个关键字就是in

我们现在将上面的判断过来再来进行一下

"name" in stu1;        //如果返回true则说明存在这个属性,如果返回false则说明不存在这个属性
"age" in stu1;         //false
"hobby" in stu1;       //true

in去检测的时候,如果返回true则代表对象存在这个属性,如果false则代表对象不存在这个属性

说明一下:后期讲到对象的继承以后还要讲这个判断过程

第三种方式

在每个对象的内部,最终都会继承自Object这个对象(对于继承,我们下节课来讲),结构图如下

image-20200508111327259

我们可以看到,stu1这个对象有个特殊的属性叫__proto__,这个属性就是继承,它继承自Object这个对象,而在Object的这个对象里面有一个方法叫hasOwnProperty。这个方法就是用于判断当前对象是否具备某一个属性的

stu1.hasOwnProperty("name");	//true代表具备这个属性,false代表没有这个属性
stu1.hasOwnProperty("age");     //false
stu1.hasOwnProperty("hobby");   //true

最后要说明一点,一个属性是否存在与enumerable这个没有关系,属性不可遍历 不代表 这个属性不存在,所以在判断属性是否存在的时候要忽略掉enumerable这个特性

image-20200508112019596


练习作业

  1. 根据要求创建对象

    定义一构造函数Person,它有5个属性,分别是姓名,性别,年龄,生日,身份证号,

    用户在创建对象的时候,这个构造函数接收二个参数,分别是姓名,和身份证号

    所有的属性都不能被delete,也不能更改它的初始值

    所以的属性都可以被 for…in遍历出来

    根据身份证号来得到用户的性别

    根据身份证号来得到用户的生日(身份证号里面是包含生日的,如420984199905041016)。

    提示:字符串可以通过索引取到里面的某个值var str =“abc”; str[0]==“a”

    根据生日来得到年龄

    根据上面定义好的构造函数去创建一个对象,然后再去遍历这个对象,打印这个对象里面的属性名与当前属性的值

    function Person(_name, _IDCard) {
        //根据要求,我们不能定义普通属性,应该要定义特殊的属性
        Object.defineProperties(this, {
            name: {
                value: _name,
                configurable: false,
                writable: false,
                enumerable: true
            },
            IDCard: {
                value: _IDCard,
                configurable: false,
                writable: false,
                enumerable: true
            },
            //性别这个属性是依赖于身份证号的
            sex: {
                configurable: false,
                enumerable: true,
                // writable:false,          访问器属性里面不能有writable
                get: function () {
                    //计算以后去得到性别
                    return this.IDCard[16] % 2 == 0 ? "女" : "男";
                }
                //它只有get方法,没有set方法,所以只能取值 ,不能赋值
            },
            //生日这个属性也依赖于身份证号
            birthday: {
                configurable: false,
                enumerable: true,
                get: function () {
                    //通过身份证号,来截取年月日  我们可以像数组一样去操作字符串
                    var year = this.IDCard.slice(6, 10);
                    var month = this.IDCard.slice(10, 12);
                    var day = this.IDCard.slice(12, 14);
    
                    return [year, month, day].join("-");
                }
            },
            //这个年龄应该是根据上面的生日来得到的
            age: {
                configurable: false,
                enumerable: true,
                get: function () {
                    var now = new Date(); //当前时间
                    var birthdayDate = new Date(this.birthday); //生日的时间
    
                    var totalTimes = now.getTime() - birthdayDate.getTime();
    
                    var yearCount = parseInt(totalTimes / 1000 / 60 / 60 / 24 / 365);
    
                    return yearCount;
                }
            }
        });
    }
    //上面已经定义好了这个构造函数了
    var p1 = new Person("标哥哥", "420984199905041016");
    
    for (var i in p1) {
        console.log("属性名:" + i + "------属性值:" + p1[i]);
    }
    
2

评论区