JavaScript对象
概念
JS对象是一种比较特殊的数据结构,可以用来描述我们生活中的某种具体的事物,这种结构内部还可以存储其他复杂的数据结构,具有强大的封装性。
注意:我们暂时可以这样理解JS对象,之前我们学习了很多基本数据类型,当时我们说出了基本数据类型以外,还有一种叫做引用类型,到现在为止,我们可以说这些引用类型所创建(声明)出来的’变量’,都可以叫做’对象’。
内置的各种类型(引用类型)对象
- window窗口对象
- document文档对象
- Math数学类型对象
- String字符串类型对象
- Array数组类型对象
- Date日期类型对象
- RegExp正则表达式类型对象
- …
以上这些都是我们之前学过的各种’对象’,我们发现这些对象都有一个共同的特点,就是每个对象都提供了各式各样的功能(就是我们日后学习的方法)以及有一定的层级结构(在浏览器中可以测试一下)。
我们会随着深入的学习,回头在来看这些对象,自然很清晰。
自定义对象
创建方式
构造函数创建
通过构造函数创建(这种方式暂时作为了解,明天我们会详细讲解这种形式)
// 使用构造函数new Object(); 来创建一个对象,然后定义一个obj变量来指向这个对象的内存地址,从而可以操作这个对象
var obj = new Object();
new Array()
new RegExp()
new Date()
new String()
以上都是构造函数形式来创建不同类型的对象。
字面量语法创建
字面量语法是声明某种数据结构的简单写法,这种形式在很多编程语言中都适用。
“str”
[10, 20, 30]
/^abc$/
以上是字面量形式来创建的不同类型的对象。
自定义JS对象的字面量
语法规则
1、对象的整体用大括号表示{}
var obj = {}; // 表示创建一个对象字面量,对象内部没有任何属性和方法
2、对象内部由很多组属性(键)和值组成,每组属性和值之间用冒号:关联, 在对象字面量内部,每组属性值后一定不要加分号;
var obj = {
name: '小雪'
}
3、如果有多组属性和值的话,每组属性值用逗号,隔开,注意:最后一组属性值最好不要加逗号,
var obj = {
name: '小雪',
age: 18,
sex: '女'
}
4、属性(键):一般来说,如果我们写的是合法标识符的话,就不需要添加任何双引号"" 或 单引号’’,但如果是非合法标识符(除去数字和汉字。。)的话,就需要使用双引号"" 或 单引号’'括起来。
值:可以是JS中任何类型数据。(甚至还可以是数组、函数或者是另外一个对象结构)
var obj = {
name: '小雪',
"{2": true,
4: '哈哈',
张: 333
}
5、对象的属性是不能重复的,值可以任意重复。属性如果重复的话,后面的属性对应的值会覆盖前面属性对应的值。
var obj = {
name: 'liu',
name: 'sheng'
}
对象的操作
这里的操作指的是,类似于像对学习数组那样,有一套对对象进行增、删、查、改的对应操作。
查
在对象里的查我们一般是指通过某个属性,来获取对应的值。因为我们当时定义对象时候的属性顺序和最后显示是不一样的,所以这里我们不能通过以前那种"索引、下标"机制来获取某个值,我们这里会通过属性名来获取值。
点语法
var obj = {
name: 'liu',
age: 18
}
// 获取这个对象的name属性对应的值 (以后简称获取name)
console.log(obj.name);
// 获取age
console.log(obj.age);
中括号语法
var obj = {
name: 'liu',
age: 18
}
// 获取name
console.log(obj['name']);
// 获取age
console.log(obj['age']);
两种语法区别
当属性不是合法标识符的时候,我们无法使用点.语法来获取对应的值,这时候只能通过中括号[]语法来获取
var obj = {
'v-#': 'liu'
}
// console.log(obj.v-#); 这种写法是语法错误的
// console.log(obj.'v-#'); 使用点语法的时候无法添加单引号或者双引号,也是错误的
// 这时候只能使用中括号语法
console.log(obj['v-#']);
当我们不知道要获取的属性名字的时候(意思是一个变量),我们是无法使用点.语法,只能通过中括号[]语法获取
var obj = {
name: 'liu',
age: 18
}
// 假如某个函数具有一个通过某个对象获取某个属性对应的值的功能
var obj = {
name: 'liu',
age: 18
}
function objGetValueByProp (obj, prop) {
// 这样获取方式,在语法上是没有错的,它被解析为,要获取属性名字为prop的属性,因为obj中没有该属性,所以结果是undefined。
// 注意:这里结果不是xxx is not defined,这是一般变量和对象属性的区别
// return obj.prop;
// 而改成使用中括号语法后就好了。就会把这个prop解析成一个变量,我们获取的是这个prop变量对应的值的属性
// 同理,我们也不要在prop上添加双引号或者单引号,否则会变成和点语法一样的解释
// return obj['prop'];
return obj[prop];
}
console.log(objGetValueByProp(obj, 'name'));
增
意思就是对某个对象进行添加属性和值的操作。
注意:添加的时候属性和值是成对儿出现的,否则会语法错误
点语法
var obj = {
}
// 添加了一个属性名字为name的属性,对应的值是'liu'
obj.name = 'liu';
console.log(obj);
中括号语法
var obj = {
}
// 添加了一个属性名字为name的属性,对应的值是'liu'
obj['name'] = 'liu';
// 中括号语法可以使用变量的形式以及非合法标识符
obj['#-#'] = 'liu';
var a = 'age';
obj[a] = 18;
console.log(obj);
改
意思就是对某个对象的属性进行修改对应值的操作。
点语法
var obj = {
name: 'liu'
}
// 其实修改的操作和新增完全一样,就是如果对象内如果有该属性,则对应值会被修改掉而已,如果没有就是新增操作
obj.name = 'zhang';
console.log(obj);
中括号语法
var obj = {
name: 'zhang',
'#-#': 'a'
}
// 修改name属性
obj['name'] = 'liu';
// 中括号语法可以使用变量的形式以及非合法标识符
obj['#-#'] = 'b';
var a = 'name';
obj[a] = 'li';
console.log(obj);
删
意思就是通过某个属性来删除对应的属性和值,这里需要delete关键字来删除
点语法
var obj = {
name: 'liu'
}
// 这里我们要配合delete关键字来
delete obj.name;
// 如果删除一个不存在的属性,不会有任何效果
delete obj.age;
console.log(obj);
中括号语法
var obj = {
name: 'liu'
}
delete obj['name'];
console.log(obj);
遍历
遍历对象,我们指的就是通过循环得到对象中每个属性对应的值
在我们学过的循环中,目前为止只能通过for in循环来遍历
var obj = {
name: 'liu',
age: 18
}
for (var prop in obj) {
console.log(prop + ": " + obj[prop]);
}
对象深入研究
理解内存
我们平时电脑运行后,我们使用的软件都是由代码编写的,都会运行在内存当中。
js中的变量分为两种,原始值和引用值。原始值指的是原始数据类型的值,比如undefined, null, number, string, boolean类型所表示的值。引用值指的是复合数据类型的值,即Object, Function, Array等。
原始值和引用值存储在内存中的位置分别为栈 和 堆。原始值是存储在栈中的简单数据段,他们的值直接存储在变量访问的位置。引用值是存储在堆中的对象。
存储区别
刚我们知道基本数据类型存储在栈 ,引用数据类型存储在堆,在平时使用的时候有很大的区别
基本类型的特点
1、如果大家的"值"都相同,那么我们使用运算符===比较的时候是相等的。因为这里单纯比较的就是具体值。
var a = 10;
var c = 10;
console.log(a === c); // true
2、如果某个变量赋值给某个变量,那么只是单纯的值一样,其中改变某一方的变量值,对另外一方并没有影响。
var a = 10;
var b = a;
a = 20;
console.log(a, b);
引用类型的特点
1、如果大家都"长的一样",无论怎么比较都是不相等的。因为这里比较的是引用地址
var obj = {};
var obj2 = {};
console.log(obj === obj2);
2、如果引用类型的某个变量赋值给另外一个变量,和基本类型完全不一样,这里相当于这两个变量所指向的引用地址是相同的,换句话说,就是这两个变量操作的对象是同一个。
var obj = {
name: 'liu'
};
var obj2 = obj;
console.log(obj === obj2);
// 修改obj或者obj2,对方都会跟着改变,因为操作的是同一个对象
obj.name = 'zhang';
对象浅拷贝
浅拷贝:不会考虑对象的层次结构,不会考虑任何引用类型, 换句话说,浅拷贝可以把内容全部拷贝过来,但不保证内部的引用类型 和拷贝后的对象没有脱离关系。
var obj1 = {
name: '小黑',
age: 18,
girlFriend: {
name: '小红'
}
}
var obj2 = {};
for (var prop in obj1) {
obj2[prop] = obj1[prop];
}
obj2.girlFriend.name = '小丽';
console.log(obj1);
console.log(obj2);
console.log(obj1.girlFriend === obj2.girlFriend);
对象深拷贝
深拷贝也是能完全把内容拷贝过来,不管层次结构有多深,完全和之前的对象脱离关系。
var obj1 = {
name: '小黑',
age: 18,
girlFriend: {
name: '小红'
}
}
// 实现深拷贝的递归函数,需要两个参数,分别是源对象和拷贝后的对象
function deepCopy (originalObj, copyObj) {
for (var prop in originalObj) {
if (typeof originalObj[prop] == 'object'){
// 指向一个新的对象
copyObj[prop] = {};
deepCopy(originalObj[prop], copyObj[prop]);
} else {
copyObj[prop] = originalObj[prop];
}
}
}
var obj3 = {};
deepCopy(obj1, obj3);
obj3.girlFriend.name = "小雪";
console.log(obj1, obj3);
console.log(obj1.girlFriend === obj3.girlFriend);
JS对象的使用场景
数据解析
解析数据,级联菜单。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>01_下拉框作业</title>
</head>
<body>
<select id="left_sel">
<option>请选择省份</option>
</select>
<select id="right_sel">
<option>请选择城市</option>
</select>
<script>
var rootObj = {
// 省份数组 存储的 很多省份对象
provinceArr: [
{
provinceName: '黑龙江省',
cityArr: [
{
cityName: '哈尔滨市'
},
{
cityName: '大庆市'
}
]
},
{
provinceName: '辽宁省',
cityArr: [
{
cityName: '沈阳市'
},
{
cityName: '大连市'
}
]
},
{
provinceName: '吉林省',
cityArr: [
{
cityName: '长春市'
},
{
cityName: '四平市'
}
]
},
{
provinceName: '河北省',
cityArr: [
{
cityName: '石家庄市'
},
{
cityName: '廊坊市'
}
]
}
]
}
// 获取左右两边的sel
var left_sel = document.querySelector('#left_sel');
var right_sel = document.querySelector('#right_sel');
// 初始化数据(左边的sel数据--省份)
loadData();
// 初始化数据
function loadData () {
// 遍历省份数组得到每个省份对象
for (var provinceObj of rootObj.provinceArr) {
// 创建option,添加到左边的sel中
left_sel.appendChild(createOptoin(provinceObj.provinceName));
}
}
console.log(left_sel);
// 以后select要使用change事件!!
left_sel.onchange = function () {
// 每次都需要清除所有option(除了第一个默认的option)
right_sel.options.length = 1;
// while (right_sel.options.length > 1) {
// right_sel.removeChild(right_sel.options[right_sel.options.length - 1]);
// }
// 遍历省份数组得到每个省份对象
for (var provinceObj of rootObj.provinceArr) {
// 判断选择的省份名字和当前的省份对象里的名字是否对应上
if (this.value == provinceObj.provinceName) {
// 遍历找到的省份对象中的城市数组,得到这个省份下的每一个城市对象
for (var cityObj of provinceObj.cityArr) {
// 创建option,添加到右边的sel
right_sel.appendChild(createOptoin(cityObj.cityName));
}
// 因为已经找到该省份了,所以没必要继续在找了。跳出循环即可。
break;
}
}
}
// 创建option
function createOptoin (optionText) {
var option = document.createElement('option');
option.innerText = optionText;
return option;
}
</script>
</body>
</html>
封装功能
可以对某些功能进行封装,比如cookie、事件等。
var Cookie = {
/*
* cookieObj: 是批量增加、修改的cookie键值对对象
* expiresDate: 是过期时间(咱们这里暂时定义为毫秒)
*/
//新增、修改
setCookie : function (cookieObj, expiresDate) {
var date = new Date();
date.setTime(date.getTime() + expiresDate);
for (var tempProp in cookieObj) {
document.cookie = tempProp + "=" + escape(cookieObj[tempProp])
+ ";expires=" + date.toGMTString();
}
},
//查询cookie中的某个键值对
getCookie : function (key) {
var cookieStr = document.cookie;
var cookieArr = cookieStr.split('; ');
for (var tempCookie of cookieArr) {
var tempKey = tempCookie.split('=')[0];
var tempValue = tempCookie.split('=')[1];
if (key == tempKey) {
return unescape(tempValue);
}
}
},
//删除cookie中的某组
delCookie : function (key) {
this.setCookie({
[key] : ''
}, -10);
}
}
面向对象编程
面向过程编程思想
解决某个问题,看的是“如何”解决这个问题,是一种数学逻辑的映射,按步骤执行。
优点
- 流程化使得编程任务明确,在开发之前基本考虑了实现方式和最终结果;
- 效率高,面向过程强调代码的短小精悍,善于结合数据结构来开发高效率的程序。。
- 流程明确,具体步骤清楚,便于节点分析。
缺点
需要深入的思考,耗费精力,代码重用性低,扩展能力差,维护起来难度比较高,对复杂业务来说,面向过程的模块难度较高,耦合度也比较高。
面向对象编程思想
解决某个问题,看的是“谁”能解决这个问题,把问题拆解成各个“类或对象(谁)”,“对象”作为接收消息的单位(编程单位),“对象”处理相应的功能逻辑,“对象”之间协同完成工作,是一种生活逻辑的映射。
优点
- 结构清晰,程序便于模块化,结构化,抽象化,更加符合人类的思维方式;
- 封装性,将事务高度抽象,从而便于流程中的行为分析,也便于操作和自省;
- 容易扩展,代码重用率高,可继承,可覆盖;
- 实现简单,可有效地减少程序的维护工作量,软件开发效率高。
缺点
效率低,面向对象在面向过程的基础上高度抽象,从而和代码底层的直接交互非常少机会,
面向对象相关术语
类
是某种事物的抽象,是创建对象的模板,是一个泛指的概念。
对象
是某种事物的具体实例(真实的,唯一的)。是面向对象编程的基本单位。
属性
是对象的特征。
方法
是对象的行为。
对比类和对象
生活角度
类:人 对象:这个人、你、我、川普
类:电脑 对象:你的电脑、我的电脑
类:动漫人物 对象:超人、蝙蝠侠、喜羊羊
封装角度
结构体(C语言中的):是对变量的封装
函数:是对功能逻辑的封装。
类:是对属性(特征)和方法(行为)的封装。
编码角度
面向对象编程是以对象为基本编程单位,对象是通过某个类去创建出来的,类是我们设计出来的,但是JS中没有类的概念(ES6后就有class类的概念了…不过也不是真正的类,以后接触多了就会理解),所以我们需要去模拟类的概念。
创建对象的方式
对象字面量创建
我们昨天学习的方式,但有明显的缺点,每次都需要写一个结构出来。
var person = {
name: '小雪',
age: 18
}
工厂函数创建
工厂函数创建对象:所谓工厂就是一个函数,我们通过给这个函数传递参数,根据参数来生成对象,这种操作就像一个工厂车间一样。
function makePerson (name, age) {
return {
name : name,
age : age,
sayHello : function () {
console.log("大家好,我叫:" + this.name + ",年龄:" + this.age);
}
}
}
var person1 = makePerson('大娃', 20);
var person2 = makePerson('三娃', 18);
console.log(person1);
console.log(person2);
console.log(typeof person1);
console.log(typeof person2);
console.log(person1 === person2);
person1.sayHello();
person1.name = "六娃";
person1.sayHello();
person2.sayHello();
console.log(person1 instanceof makePerson);
console.log(person2 instanceof makePerson);
工厂方式创建有一个缺点,就是没有我们所谓的"类型"区分。
构造函数创建
通过构造函数方式创建对象,构造函数其实就是一个普通的函数,只不过我们习惯性把构造函数名字的首字母大写(这是程序猿、程序媛之间的一种约定)。
//一般首字母要大写
function Person (name, age, friend) {
//属性
this.name = name;
this.age = age;
// this.friend = {
// name : friend.name,
// age : friend.age
// }
this.friend = friend;
//方法
this.sayHello = function () {
console.log("大家好,我叫:" + this.name + ",年龄:" + this.age);
}
//通过构造函数方式创建对象,构造函数没有显性的返回值,如果是通过new关键字配合构造函数使用的话,就相当于创建了一个当前类型的实例对象,程序会默认给我们加上一个 return this
//如果使用new来调用,this就是指向当前对象
}
//创建一个人的对象
var person1 = new Person('小雪', 18);
var person2 = new Person('小黑', 20);
//我们利用instanceof这个运算符来判断某个对象是否是某个构造函数函数创建出来的
console.log(person1 instanceof Person);
console.log(person2 instanceof Person);
console.log(new Person() instanceof Object);
console.log(new String() instanceof Object);
console.log(new Array() instanceof Object);
console.log(new Object() instanceof Object)