getting-started-with-javascript
getting-started-with-javascript copied to clipboard
【10.3练习】
var obj = {
count: 1
}
function output(obj) {
obj.count = obj.count + 1;
console.log(obj.count);
}
在上面代码之后,执行下面代码分别输出什么?
output(obj);
console.log(obj.count);
- 假如两个值不一样,为什么?
- 假如两个值一样,为什么?有没有办法保证output函数内的obj.cout的改变不影响外面的obj.count?
实践是检验真理的唯一标准
var obj = {
count: 1
}
function output(obj) {
obj.count = obj.count + 1;
console.log(obj.count);
}
output(obj);
console.log(obj.count);
我初步的分析是 结果应该是 1 和 2 但结果是 2 2
2
2
[Finished in 0.1s]
原因何在
** 首先我们要明确一点:JavaScript中所有函数的参数都是按值来传递的 **
(function (number) {
number += 1;
console.log(number);
})(number);
console.log(number);
上述的打印结果是 2 1
同样都是传值,和之前给出的例子, 得到的结果 是不一样的。 同样都是传值,结果为何为不一样。
- 传普通变量 传的值 就是变量的值
- 传对象对象 传的值 是对象的地址 而这个地址 指向的 才是我们实际操作的对象。
这里涉及到JavaScript的数据类型
JavaScript数据类型
- 原始值类型: 比如Undefined,Null,Boolean,Number,String
- 引用值类型:也就是对象类型 Object type,比如Object,Array,Function,Date等
原始值类型 变量存储的就是具体的值 引用值类型 变量存储的是对象的引用(地址),这个引用(或者说这个地址)指向了真实的对象
回光返照
output(obj); 传入了一个变量 obj, 函数传递参数 都是按值传递 而这个 obj 是个对象类型 是个引用类型 其值 是一个 引用(地址),因此我们操作的还是引用(地址)指向的那个对象。其实是操作的同一个对象。 所以 函数内部修改变对象的值,函数外部的值也就跟着变了。
如何才能不影响外面的对象
答案就是: 不能操作同一个对象 而要操作不同的对象。 也就是说 要对这个 对象 做个拷贝 而不是对这个引用做个拷贝。 所谓深拷贝 就是 从新定义一个对象 把另一个对象的所有内容 全部拷贝过来 我自以为 对象 会有个 copy方法muticopy方法或者deepcopy方法 可事实是都没有 你可能要手动复制了,例如用下面的函数代替案例中的函数
function output(obj) {
var copyobj = {};
for (prototy in obj) {
copyobj[prototy] = obj[prototy]
}
copyobj.count = copyobj.count + 1;
console.log(copyobj.count);
}
幸运的时 ES6 有个 assign 方法 就是做这个事的 具体用法参见Object.assign()
function output(obj) {
var mutilcopy = {};
Object.assign(mutilcopy,obj);
mutilcopy.count = mutilcopy.count + 1;
console.log(mutilcopy.count);
}
把代码扔进chrome浏览器,返回结果
2
2
两次的返回结果是一样的。下面我们来一行行解释代码运行
定义obj 对象
var obj = {
count: 1
}
output 函数声明
function output(obj) {
obj.count = obj.count + 1;
console.log(obj.count);
}
调用函数output。
output(obj);
console.log(obj.count);
发生了什么? 将obj对象赋值给output函数中的形参obj。 注意,现在函数中的obj参数和obj对象都指向同一个对象 { count : 1 }. 而函数第一行 obj.count = obj.count+1 改变了 { count:1 }中的count的值。这个行为影响了指向它的两个变量:obj对象和函数中的obj变量。 所以,下面两行执行的console.log(obj.count); 返回值都是2.
题外话
《Javascript 面向对象精要》指出,JS的数据类型可大致分为两种:原始类型和引用类型。 原始类型包括:字符串、数字、布尔值、null、undefined 引用类型包括:对象 为什么叫引用类型呢?比如说一行代码: var obj = new Object(); 此时创建的并不是一个完全的对象,实际上obj变量储存的是一个指针(pointer)——不太恰当的类比:类似windows的快捷方式——我们调用这个obj的时候,其实真正操作的是obj指向的"对象"。
——《javascript 面向对象精要》1.3
自己在运行代码之前心里想的结果是:false和1(因为1不等于2,所以应该就是false吧)。运行结果出现2和2的时候,发现根本没有在用计算机语言思考问题。
obj这个对象中count的值是1,属性是number,而number是原始值,是不可更改的。
根据犀牛书page46:“存取数字时创建的临时对象称作包装对象,它只是偶尔用来区分数字和数值对象。"(通常,包装对象只是被看作是一种实现细节,而不用特别关注)
把console.log(obj.count)这一行的位置调整一下,输出结果变为:1和2
function output(obj) {
console.log(obj.count);
obj.count = obj.count + 1;
}
所以,原来的函数output(obj)中:obj.count = obj.count + 1;可以理解为是在原始值1的基础上增加1,经过这一行代码的计算之后,新的临时值变成2,所以output(obj)的输出结果为2. 在此基础上运行console.log(obj.count)输出的应该还是包装对象的值,所以还是2.
我能够想到的办法让函数内的计算不影响函数外的obj.count办法是: 函数内的变量换一个名字,比如obj.Count,最终运行代码输出值为2和1
var obj = {
count: 1
}
function output(obj) {
obj.Count = obj.count + 1;
console.log(obj.Count);
}
output(obj);
console.log(obj.count);
两行代码第一次执行结果相同。
继续执行output(obj),调用output函数,值依次增加;执行console.log(obj.count);返回结果的是上次调用函数返回的obj值,不会继续调用函数,因此值不会增加;
我自己没想到怎么修改,分析其他同学答案: MyColourfulLife提到了两种方法:
- 函数定义完就使用,用完立即销毁;
- 通过创建和原对象内容一样的新对象,把内容修改和原内容独立开,和李想童鞋的代码一个意思
在执行 函数 output(obj) 中,语句直接对obj对象的属性进行了操作,改变了内存里obj对象的属性值。 想要保持一致, output(obj)函数应该单独定义一个变量 “x”
var obj = { count: 1 }
function output(obj) { x = obj.count + 1; console.log(x); }
var obj = {
count: 1
}
function output(obj) {
obj.count = obj.count + 1;
console.log(obj.count);
}
在上面代码之后,执行下面代码分别输出什么?
output(obj);
console.log(obj.count);
初步分析与实践
未运行之前,判断输出是:2,1; 可运行结果却是:2,2。
于是我重新录入变量和函数,这次把最后的两行代码调换一下:
console.log(obj.count);
output(obj);
得到的结果是:1,2。
原因分析:
函数内未定义的变量默认是全局变量。 每一次调用函数时,更改对象属性的值都会被保存下来。于是,当我们打印对象属性的值都是更改过的值。 为了避免这种情况,我们可以在函数内定义一个局部变量,用来接收obj对象属性的值,然后再进行运算。
var obj = {
count: 1
}
function output(obj) {
var count = obj.count
count = count + 1;
console.log(count);
}
output(obj);
console.log(obj.count);
执行的结果是一样的,因为在使用对象作为函数参数时,实际上传送给函数的是对象的地址,即函数体内的对象和作为参数的原对象实际上是同一个。所以,想要不影响外面的obj,就需要在函数体内定义一个局部的obj对象,并复制外部obj的值。 本例可实现如下:
var obj = {
count: 1
}
function output(obj) {
var obj = JSON.parse(JSON.stringify(obj));
obj.count = obj.count + 1;
console.log(obj.count);
}
@freedomsky11 的答案正解!
先看一种情况。
var obj = {
count: 1
}
var anotherObj = {
count: 1
}
console.log(anotherObj === obj);
=> false
分别定义了obj和anotherOjb,他们的对象实体内容虽然都一样,但还是不同的对象。
对象赋值并不是等于复制对象,而是引用。
var obj = {
count: 1
}
var anotherObj = obj;
此时anotherObj和obj是指向同一个对象实体。anotherObj此时是obj的引用,引用就像别名一样。
比如王宝强这个人还有一个小名叫“傻根”。不管是王宝强还是傻根,都是指向同一个人。
console.log(anotherObj === obj);
=> true
通过以上判断,可以认为两者是同一个对象。
function output(obj) {
obj.count = obj.count + 1;
console.log(obj.count);
}
基于以上分析,output函数的形参对象变量其实就是实参变量的引用,所有他们是同一个。 在output函数内为了不影响真正的obj实体,有两种方式。
function output(obj) {
obj = Object.assign({}, obj);
obj.count = obj.count + 1;
console.log(obj.count);
}
function output(obj) {
obj = JSON.parse(JSON.stringify(obj));
obj.count = obj.count + 1;
console.log(obj.count);
}
以上两种方式都是对形参obj对象重新“复制”一份新的对象。
知识点
- 在javascript当中,当对象被赋值到一个变量时,传给变量的实际上是这个对象的引用。
- 当对象被传给函数的形参,形参其实也只是实际对象的一个引用,他们都指向同一个对象。
- 想要在函数体内的代码不影响到对象本体,我们可以在函数内复制一个。
原因
var obj{ //定义一个对象obj,并设定他的属性count 为1
count = 1
}
function f(obj) { //传给函数f 形参obj的其实是obj本体,所以函数内的代码将obj的count属性+1 ,其实是会影响到obj本体的属性的。
obj.count +1
console.log(obj.count)
}
console.log(obj.count) //因为obj的本体属性已经被影响,所以输出的值为2
f (obj)
解题方法
var obj{
count = 1
}
function f (obj){
obj=Object.assign({} ,obj) //调用Object.assign()方法将源属性的值复制到目标对象obj。
obj.count+1 //这时候被改变属性的已经不是原来定义的obj了
console.log(obj.count)
}