第一天
一、基本语法
1.1 输入输出
1.2 数据类型
- 数字型
// 八进制,数字前加0就是八进制
var num1 = 010;
console.log(num1);
// 十六进制,数字前加0x就是十六进制
var num2 = 0xa;
console.log(num2);
-
字符串型
-
字符串转义符\
-
字符串长度
var str2 = '部落文化为'; console.log(str2.length);
- 字符串拼接:数值相加,字符相连
// 字符串拼接 console.log('沙漠' + '骆驼'); console.log(100 + 10); console.log('12' + 12); var age = 18; console.log('赵' + age + '岁');
-
-
typeof可以鉴定是什么数据类型
// typeof可以鉴定是什么数据类型
console.log(typeof num1);
console.log(typeof str1);
var flag = true;
console.log(typeof flag);
var vari = undefined;
console.log(typeof vari);
var timer = null;
console.log(typeof timer);
二、数据类型转换
2.1 转换为字符串型
// 转换成字符串
// 1.
num1 = 10;
str1 = num1.toString();
console.log(str1);
// 2.
console.log(String(num1));
// 3.加号
console.log(num1 + '');
2.2 转换为数字型
// 转换成数字类型
var age = prompt('输入年龄');
// 1.整数型
console.log(parseInt(age));
// 2.浮点型
console.log(parseFloat('3.232'));
// 3.利用Number(变量)
var str ='123';
console.log(Number(str));
console.log(Number('12'));
// 4.利用了算术运算 - * / 隐式转换
console.log('12'-0); // 12
console.log('123' - '120'); //3
console.log('123' * 1); // 123
2.3 转换成布尔型
console.log(Boolean(null));
- 代表空,否定的值会被转换为false,如 ’ ’ , 0, NaN , null , undefined
- 其余的值都会被被转换为true
三、运算符
3.1 前置后置运算符
-
前置 ++a
使用口诀:先自加,后返回值
var num = 10;
alert (++num + 10); // 21
-
后置 a++
使用口诀:先返回原值,后自加
var num = 10;
alert(10 + num++); // 20
-
前置递增和后置递增运算符可以简化代码的编写,让变量的值 + 1 比以前写法更简单
-
单独使用时,运行结果相同,与其他代码联用时,执行结果会不同
-
开发时,大多使用后置递增/减,并且代码独占一行
3.2 比较运算符
-
===全等
判断两边的值和数据类型是否完全相同
console.log(18 == '18'); //true
console.log(18 === '18'); //false
3.3 短路运算(逻辑中断)
-
短路运算的原理:当有多个表达式(值)时,左边的表达式值可以确定结果时,就不再继续运算右边的表达式的值
-
逻辑与
-
语法:表达式1 && 表达式2
-
如果第一个表达式的值为真,则返回表达式2
-
如果第一个表达式的值为假,则返回表达式1
-
-
console.log(123 && 456); //456 console.log(0 && 456); //0 console.log(123 && 456 && 789); //789
-
逻辑或
-
语法:表达式1 || 表达式2
-
如果第一个表达式的值为真,则返回表达式1
-
如果第一个表达式的值为假,则返回表达式2
-
-
console.log(123 || 456); //123 console.log(0 || 456); //456 console.log(123 || 456 || 789); //123
四、流程控制
4.1 三元表达式
- 语法结构 : 表达式1 ? 表达式2 : 表达式3
- 执行思路:如果表达式1为true,则返回表达式2的值,如果表达式1为false,则返回表达式3的值
4.2 switch
- 规则
switch(表达式){
case value1:
//表达式等于 value1 时要执行的代码
break;
case value2:
//表达式等于value2 时要执行的代码
break;
default:
//表达式不等于任何一个value时要执行的代码
}
- 例子
// 用户在弹出框里面输入一个水果,如果有就弹出该水果的价格, 如果没有该水果就弹出“没有此水果”
var fruit = prompt('请您输入查询的苹果');
switch (fruit) {
case '苹果':
alert('苹果的价格为3.5元/千克');
break;
case '香蕉':
alert('香蕉的价格为3元/千克');
break;
default:
alert('没有这种水果');
}
- switch :开关 转换 , case :小例子 选项
- 关键字 switch 后面括号内可以是表达式或值, 通常是一个变量
- 关键字 case , 后跟一个选项的表达式或值,后面跟一个冒号
- switch 表达式的值会与结构中的 case 的值做比较
- 如果存在匹配全等(===) ,则与该 case 关联的代码块会被执行,并在遇到 break 时停止,整个 switch 语句代码执行结束
- 如果所有的 case 的值都和表达式的值不匹配,则执行 default 里的代码
- 执行case 里面的语句时,如果没有break,则继续执行下一个case里面的语句
五、循环
5.1 do while
- 先执行一次循环体代码
- 再执行表达式,如果结果为true,则继续执行循环体代码,如果为false,则退出循环,继续执行后面的代码
- 先执行再判断循环体,所以dowhile循环语句至少会执行一次循环体代码
5.2 continue 关键字
continue 关键字用于立即跳出本次循环,继续下一次循环(本次循环体中 continue 之后的代码就会少执行一次)。
5.3 break关键字
break 关键字用于立即跳出整个循环
六、数组
6.1 创建数组
// 1.利用数组字面量方式创建空的数组
var 数组名 =[];
// 2.使用数组字面量方式创建带初始值的数组
var 数组名 =['小白','小黑','小黄','瑞奇'];
// 3.数组中可以存放任意类型的数据,例如字符串,数字,布尔值等
var arrStus =['小白',12,true,28.9];
6.2 数组中新增元素
- 通过修改 length 长度新增数组元素
var arr = ['red', 'green', 'blue', 'pink'];
arr.length = 7;
console.log(arr);
console.log(arr[4]);
console.log(arr[5]);
console.log(arr[6]);
- 通过修改数组索引新增数组元素
var arr = ['red', 'green', 'blue', 'pink'];
arr[4] = 'hotpink';
console.log(arr);
第二天
一、函数
1.1 函数样式
//声明函数
function getArrMax(arr) {
var max = arr[0];
for (var i = 0; i < arr.length; i++) {
max = arr[i] > max ? arr[i] : max;
}
return max;
}
//调用函数
console.log(getArrMax([1, 85, 73, 83, 73, 9, 27]));
1.2 形参和实参
// 带参数的函数声明
function 函数名(形参1, 形参2 , 形参3...) { // 可以定义任意多的参数,用逗号分隔
// 函数体
}
// 带参数的函数调用
函数名(实参1, 实参2, 实参3...);
- 函数可以带参数也可以不带参数
- 声明函数的时候,函数名括号里面的是形参,形参的默认值为 undefined
- 调用函数的时候,函数名括号里面的是实参
- 多个参数中间用逗号分隔
- 形参的个数可以和实参个数不匹配,但是结果不可预计,我们尽量要匹配
1.3 函数的返回值
- 在使用 return 语句时,函数会停止执行,并返回指定的值
- 如果函数没有 return ,返回的值是 undefined
- return 语句之后的代码不被执行
- return 只能返回一个值。如果用逗号隔开多个值,以最后一个为准
1.4 arguments的使用
-
当我们不确定有多少个参数传递的时候,可以用 arguments 来获取。在 JavaScript 中,arguments 实际上它是当前函数的一个内置对象。所有函数都内置了一个 arguments 对象,arguments 对象中存储了传递的所有实参。
-
arguments存放的是传递过来的实参
-
arguments展示形式是一个伪数组,因此可以进行遍历。伪数组具有以下特点
①:具有 length 属性
②:按索引方式储存数据
③:不具有数组的 push , pop 等方法
<script>
function getMax() {
max = arguments[0];
for (var i = 1; i < arguments.length; i++) {
max = arguments[i] > max ? arguments[i] : max;
}
return max;
}
console.log(getMax(1, 2, 3, 4, 5));
console.log(getMax(425, 783, 18, 19, 39));
</script>
1.5 函数的两种声明方式
- 自定义函数方式(命名函数)
// 声明定义方式
function fn() {...}
// 调用
fn();
调用函数的代码既可以放到声明函数的前面,也可以放在声明函数的后面
- 函数表达式方式(匿名函数)
// 这是函数表达式写法,匿名函数后面跟分号结束
var fn = function(){...};
// 调用的方式,函数调用必须写到函数体下面
fn();
- 因为函数没有名字,所以也称为匿名函数
- 这个fn 里面存储的是一个函数
- 函数调用的代码必须写到函数体后面
二、作用域
2.1 全局作用域与局部(函数)作用域
- 全局作用域:作用于所有代码执行的环境(整个 script 标签内部)或者一个独立的 js 文件
- 局部(函数)作用域:作用于函数内的代码环境,就是局部作用域。 因为跟函数有关系,所以也称为函数作用域
2.2 变量的作用域
-
全局变量
在全局作用域下声明的变量叫做全局变量(在函数外部定义的变量)
- 全局变量在代码的任何位置都可以使用
- 在全局作用域下 var 声明的变量 是全局变量
- 特殊情况下,在函数内不使用 var 声明的变量也是全局变量(不建议使用)
-
局部变量
在局部作用域下声明的变量叫做局部变量(在函数内部定义的变量)
- 局部变量只能在该函数内部使用
- 在函数内部 var 声明的变量是局部变量
- 函数的形参实际上就是局部变量
-
区别
- 全局变量:在任何一个地方都可以使用,只有在浏览器关闭时才会被销毁,因此比较占内存
- 局部变量:只在函数内部使用,当其所在的代码块被执行时,会被初始化;当代码块运行结束后,就会被销毁,因此更节省内存空间
2.3 作用域链
- 作用域链:采取就近原则的方式来查找变量最终的值
三、预解析
3.1 变量预解析
- 变量预解析也叫做变量提升、函数提升
- 变量提升: 变量的声明会被提升到当前作用域的最上面,变量的赋值不会提升
3.2 函数预解析
- 函数提升: 函数的声明会被提升到当前作用域的最上面,但是不会调用函数。
第三天
一、对象
1.1 利用字面量创建对象
var keke = {
uname: 'keke',
age: 3,
type: '阿拉斯加',
color: 'brown',
back: function () {
console.log('汪汪汪!');
},
showFilm: function () {
console.log('电影');
}
}
console.log(keke.type);
keke.showFilm();
- 对象里面的方法调用:对象.方法名() ,注意这个方法名字后面一定加括号
1.2 利用new object 创建对象
var obj = new Object();
obj.uname = '赵';
obj.age = 18;
obj.sex = '女';
obj.sayHi = function () {
console.log('Hi~');
}
console.log(obj.uname);
obj.sayHi();
1.3 利用构造函数创建对象
function Star(uname, age, sex) {
this.name = uname;
this.age = age;
this.sex = sex;
this.sing = function (sang) {
console.log(sang);
}
}
var ldh = new Star('刘德华', 18, '男');
console.log(ldh.name);
ldh.sing('冰雨')
var zxy = new Star('张学友', 28, '男');
console.log(zxy.age);
- 构造函数 :是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,它总与 new 运算符一起使用。我们可以把对象中一些公共的属性和方法抽取出来,然后封装到这个函数里面。
- 构造函数用于创建某一类对象,其首字母要大写
- 构造函数要和 new 一起使用才有意义
- 函数内的属性和方法前面需要添加 this ,表示当前对象的属性和方法。
- 当我们创建对象的时候,必须用 new 来调用构造函数。
- new在执行时会做:
- 在内存中创建一个新的空对象。
- 让 this 指向这个新的对象。
- 执行构造函数里面的代码,给这个新对象添加属性和方法
- 返回这个新对象(所以构造函数里面不需要return)
1.4 遍历对象属性
for (var k in keke) {
console.log(k);//输出属性名
console.log(keke[k]);//输出属性值
}
二、Math对象
2.1 Math对象最大值
console.log(Math.max());//-infinity
console.log(Math.max(1, 2, 3));//3
console.log(Math.max(1, 2, 'zjj'));//Nan
2.2 封装自己的数学对象
var mymath = {
PI: 3.1425926,
max: function () {
var max = arguments[0];
for (i = 1; i < arguments.length; i++) {
if (max < arguments[i]) {
max = arguments[i];
}
}
return max;
},
min: function () {
var min = arguments[0];
for (i = 1; i < arguments.length; i++) {
if (min > arguments[i]) {
min = arguments[i];
}
}
return min;
}
}
console.log(mymath.PI);
console.log(mymath.max(1, 2, 3, 4));
console.log(mymath.min(1, 2, 3, 4));
2.3 Math绝对值与三个取正方法
Math.abs()
取绝对值
console.log(Math.abs(1));
console.log(Math.abs(-1));
- 三个取整方法:
Math.floor()
: 向下取整Math.ceil()
: 向上取整Matg.round()
: 四舍五入,其他数字都是四舍五入,但是5特殊,它往大了取
// 向下取整
console.log(Math.floor(1.1));
console.log(Math.floor(1.9));
// 向上取整
console.log(Math.ceil(1.1));
console.log(Math.ceil(1.9));
// 四舍五入
console.log(Math.round(1.1));
console.log(Math.round(1.5));
console.log(Math.round(1.9));
2.4 随机数方法
- random() 方法可以随机返回一个小数,其取值范围是 [0,1),左闭右开 0 <= x < 1
- 得到一个两数之间的随机整数,包括第一个数,不包括第二个数
console.log(Math.random());
// 随机整数
function getRandom(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min; //含最大值,含最小值
}
console.log(getRandom(1, 10));
// 点名
var arr = ['皮', '卡', '丘', '皮卡丘', 'pikapika', '皮卡皮卡']
console.log(arr[getRandom(0, arr.length - 1)]);
- 猜数字案例
function getRandom(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min; //含最大值,含最小值
}
var random = getRandom(1, 10);
console.log(random);
while (true) {
var num = prompt('请输入1~10的数字');
if (num > random) {
alert('猜大了');
} else if (num < random) {
alert('猜小了');
} else {
alert('猜对了');
break;
}
}
三、Date日期对象
3.1 Date日期对象的使用
- 如果括号里面有时间,就返回参数里面的时间。例如日期格式字符串为
‘2019-5-1’
,可以写成new Date('2019-5-1')
或者new Date('2019/5/1')
- 如果Date()不写参数,就返回当前时间
var date = new Date();
console.log(date);
var date1 = new Date(2022, 10, 1);
console.log(date1);
var date2 = new Date('2022-1-6 8:9:8');
console.log(date2);
3.2 日期格式化
- 年月日星期
console.log(date.getFullYear());//返回当前日期的年
console.log(date.getMonth());//返回当前日期的月小一个月
console.log(date.getDate());//返回当前日期的日
console.log(date.getDay());//周一返回1,周六返回6,周日返回0
- 封装一个函数返回当前的时分秒,例08:08:08
function getTime() {
var time = new Date();
var h = time.getHours();
h = h < 10 ? '0' + h : h;
var m = time.getMinutes();
m = m < 10 ? '0' + m : m;
var s = time.getSeconds();
s = s < 10 ? '0' + s : s;
return h + ':' + m + ':' + s;
}
console.log(getTime());
3.3 时间戳
-
date.valueOf()
:得到现在时间距离1970.1.1总的毫秒数 -
date.getTime()
:得到现在时间距离1970.1.1总的毫秒数
var date = new Date();
console.log(date.valueOf());
console.log(date.getTime());
- 简单写法
var date1 = +new Date();
console.log(date1);
- HTML5的方法,有兼容性问题
console.log(Date.now());
3.4 倒计时案例
function conutTime(time) {
var timenow = +new Date();
var timeinput = +new Date(time);
times = (timeinput - timenow) / 1000;
var d = parseInt(times / 60 / 60 / 24);
d = d < 10 ? '0' + d : d;
var h = parseInt(times / 60 / 60 % 24);
h = h < 10 ? '0' + h : h;
var m = parseInt(times / 60 % 60);
m = m < 10 ? '0' + m : m;
var s = parseInt(times % 60);
s = s < 10 ? '0' + s : s;
return d + '天' + h + '时' + m + '分' + s + '秒';
}
var re = conutTime('2022-1-6 23:59:6');
console.log(re);
四、数组对象
4.1数组对象的创建
// 1.利用数组自变量
var arr = [1, 2, 3];
console.log(arr[0]);
// 2.利用new array
var arr1 = new Array(2);
console.log(arr1);
var arr2 = new Array(1, 2, 3);
console.log(arr2[1]);
4.2 判断是否为数组
- instanceof 运算符,可以判断一个对象是否属于某种类型
-
Array.isArray()
用于判断一个对象是否为数组,isArray() 是 HTML5 中提供的方法
// 1.用instanceof判断
var arr = [];
var obj = {};
console.log(arr instanceof Array);
console.log(obj instanceof Array);
// 2. 用Array.isArray(参数)
console.log(Array.isArray(arr));
console.log(Array.isArray(obj));
4.3 添加和删除数组元素
- push 添加
var arr1 = [1, 2, 3];
arr1.push(4, 5, 6, 'pikaqiu');
// console.log(arr1.push(4, 5, 6, 'pikaqiu'));
console.log(arr1);
- unshift 添加
var arr2 = [1, 2, 3];
arr2.unshift(4, 5);
console.log(arr2);
- pop 删除
var arr3 = [1, 2, 3];
arr3.pop();
console.log(arr3);
- shift
var arr4 = ['hello', 1, 2, 3];
arr4.shift();
console.log(arr4);
4.4 数组排序
- reverse() 数组翻转
var arr1 = [1, 2, 3];
arr1.reverse();
console.log(arr1);
- sort() 数组冒泡排序
var arr2 = [17, 30, 1, 45, 109, 12];
arr2.sort(function (a, b) {
// return a - b;//升序排列
return b - a;//降序排列
})
console.log(arr2);
4.5 数组索引
-
indexOf()
- 返回数组中查找给定元素的第一个索引
- 如果存在返回索引号,如果不存在,则返回-1
var arr1 = ['A', 'B', 'C', 'A', 'F'];
console.log(arr1.indexOf('A'));
console.log(arr1.indexOf('E'));
-
lastIndexOf()
- 在数组的最后一个索引,从后向前索引
- 如果存在返回索引号,如果不存在,则返回-1
var arr1 = ['A', 'B', 'C', 'A', 'F'];
console.log(arr1.lastIndexOf('A'));
console.log(arr1.lastIndexOf('a'));
(※重要案例)数组去重
-
问题
有一个数组[‘a’, ‘b’, ‘a’, ‘c’, ‘b’, ‘z’, ‘a’, ‘z’, ‘c’,],要求去除数组中的重复元素
-
分析
把旧数组里面不重复的元素选取出来放到新数组中,重复的元素只保留一个,放到新数组中去重。
-
核心算法
我们遍历旧数组,然后拿着旧数组元素去查询新数组,如果该元素在新数组里面没有出现过,我们就添加,否则不添加。
我们怎么知道该元素没有存在? 利用 新数组.indexOf(数组元素) 如果返回是 -1 就说明 新数组里面没有改元素
-
代码
function unique(arr) {
var newArr = [];
for (i = 0; i < arr.length; i++) {
if (newArr.indexOf(arr[i]) == -1) {
newArr[newArr.length] = arr[i];
}
}
return newArr;
}
var demo = unique(['a', 'b', 'a', 'c', 'b', 'z', 'a', 'z', 'c',]);
console.log(demo);
4.6 数组转化为字符串
-
toString()
把数组转换成字符串,逗号分隔每一项
var arr1 = [1, 2, 3]
console.log(arr1.toString());
-
join(‘分隔符’)
方法用于把数组中的所有元素转换为一个字符串,用分隔符分隔每一项
var arr2 = ['a', 'b', 'c'];
console.log(arr2.join());
console.log(arr2.join('-'));
console.log(arr2.join('~'));
五、字符串对象
5.1基本包装类型
- 基本包装类型就是把简单数据类型包装成为复杂数据类型,这样基本数据类型就有了属性和方法
5.2 字符串的不可变
- 指的是里面的值不可变,虽然看上去可以改变内容,但其实是地址变了,内存中新开辟了一个内存空间。
5.3 根据字符返回位置
- 字符串所有的方法,都不会修改字符串本身(字符串是不可变的),操作完成会返回一个新的字符串
- indexOf(‘要查找的字符’,开始的位置)
- 返回指定内容在元字符串中的位置,如果找不到就返回-1,开始的位置是index索引号
- lastIndexOf()
- 从后往前找,只找第一个匹配的
var str = '嘀嘀嘀在码代码';
console.log(str.indexOf('码'));
console.log(str.indexOf('码', 5));
- 案例:字符串中某个字符出现的位置及次数
var str = 'abcoefoxyozzopp';
var index = str.indexOf('o');
var num = 0;
while (index != -1) {
console.log(index);
num++;
index = str.indexOf('o', index + 1);
}
console.log('次数为:' + num);
5.4 根据位置返回字符
- charAt(index) 返回指定位置的字符(index字符串的索引号)
var str1 = 'pikapika';
console.log(str1.charAt(2));
for (i = 0; i < str1.length; i++) {
console.log(str1.charAt(i));
}
- charCodeAt(index) 获取指定位置处字符的ASCII码(index索引号)
console.log(str1.charCodeAt(2));
- str[index] 获取指定位置处字符
console.log(str1[2]);
-
案例
-
问题:判断一个字符串 “abcoefoxyozzopp” 中出现次数最多的字符,并统计其次数
-
核心算法:利用 charAt() 遍历这个字符串,把每个字符都存储给对象, 如果对象没有该属性,就为1,如果存在了就 +1,遍历对象,得到最大值和该字符
-
代码:
var str = 'abcoefoxyozzopp'; // 1.放到对象里 var o = {}; for (i = 0; i < str.length; i++) { var chars = str.charAt(i); if (o[chars]) { o[chars]++; } else { o[chars] = 1; } } console.log(o); // 2.遍历对象 var max = 0; var ch = ''; for (var k in o) { if (o[k] > max) { // k是属性名,o[k]是属性值 max = o[k]; ch = k; } } console.log(max); console.log(ch);
-
5.5 拼接及截取字符串
//1.拼接
var str1 = 'pika';
console.log(str1.concat('pika', 'qiu'));
//2.截取
var str2 = '改革春风吹满地';
console.log(str2.substr(2, 3));//从 start 位置开始(索引号), length 取的个数
console.log(str2.slice(2, 4));//从 start 位置开始,截取到 end 位置 ,end 取不到 (两个都是索引号)
console.log(str2.substring(2, 4));//从 start 位置开始,截取到 end 位置 ,end 取不到 (基本和 slice 相同,但是不接受负)
</script>
5.6 替换字符串
- 其使用格式:
replace(被替换的字符,要替换为的字符串)
// (1)只能替换一个
var str1 = 'ooooooo';
console.log(str1.replace('o', '*'));
// (2)要想替换所有
var str2 = 'ooooooo';
while (str2.indexOf('o') != -1) {
str2 = str2.replace('o', '*');
}
console.log(str2);
5.7 转换为数组
- split() 方法用于切分字符串,它可以将字符串切分为数组。在切分完毕之后,返回的是一个新数组。
var str3 = 'red,blue,pink';
console.log(str3.split(','));
var str3 = 'red&blue&pink';
console.log(str3.split('&'));
5.8 转换大小写
toUpperCase()
转换大写toLowerCase()
转换小写
六 简单类型与复杂类型
6.1 简单类型与复杂类型
- 简单类型又叫做基本数据类型或者值类型,复杂类型又叫做引用类型。
- 值类型:简单数据类型/基本数据类型,在存储时变量中存储的是值本身,因此叫做值类型
string ,number,boolean,undefined,null - 引用类型:复杂数据类型,在存储时变量中存储的仅仅是地址(引用),因此叫做引用数据类型
通过 new 关键字创建的对象(系统对象、自定义对象),如 Object、Array、Date等
- 值类型:简单数据类型/基本数据类型,在存储时变量中存储的是值本身,因此叫做值类型
6.2 堆与栈
- 简单数据类型存放到栈里面
- 复杂数据类型存放到堆里面
6.3 简单类型的内存分配
- 如果有个变量我们以后打算存储为对象,暂时没想好放啥, 这个时候就给 null
- 简单数据类型 是存放在栈里面 里面直接开辟一个空间存放的是值
- 复杂数据类型 首先在栈里面存放地址 十六进制表示 然后这个地址指向堆里面的数据
6.4 传参
-
简单数据类型传参
函数的形参也可以看做是一个变量,当我们把一个值类型变量作为参数传给函数的形参时,其实是把变量在栈空间里的值复制了一份给形参,那么在方法内部对形参做任何修改,都不会影响到的外部变量。
-
复杂数据类型传参
函数的形参也可以看做是一个变量,当我们把引用类型变量传给形参时,其实是把变量在栈空间里保存的堆地址复制给了形参,形参和实参其实保存的是同一个堆地址,所以操作的是同一个对象。