getting-started-with-javascript icon indicating copy to clipboard operation
getting-started-with-javascript copied to clipboard

【10.3练习】

Open xugy0926 opened this issue 7 years ago • 9 comments

var obj = {
  count: 1
}

function output(obj) {
  obj.count = obj.count + 1;
  console.log(obj.count);
}

在上面代码之后,执行下面代码分别输出什么?

output(obj);
console.log(obj.count);
  1. 假如两个值不一样,为什么?
  2. 假如两个值一样,为什么?有没有办法保证output函数内的obj.cout的改变不影响外面的obj.count?

xugy0926 avatar Oct 03 '17 06:10 xugy0926

实践是检验真理的唯一标准

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

同样都是传值,和之前给出的例子, 得到的结果 是不一样的。 同样都是传值,结果为何为不一样。

  1. 传普通变量 传的值 就是变量的值
  2. 传对象对象 传的值 是对象的地址 而这个地址 指向的 才是我们实际操作的对象。

这里涉及到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);
}

MyColourfulLife avatar Oct 03 '17 08:10 MyColourfulLife

把代码扔进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

zjutszl avatar Oct 03 '17 09:10 zjutszl

自己在运行代码之前心里想的结果是: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);


Ideal-Li avatar Oct 03 '17 12:10 Ideal-Li

两行代码第一次执行结果相同。 继续执行output(obj),调用output函数,值依次增加;执行console.log(obj.count);返回结果的是上次调用函数返回的obj值,不会继续调用函数,因此值不会增加; image

我自己没想到怎么修改,分析其他同学答案: MyColourfulLife提到了两种方法:

  1. 函数定义完就使用,用完立即销毁;
  2. 通过创建和原对象内容一样的新对象,把内容修改和原内容独立开,和李想童鞋的代码一个意思

shashawang avatar Oct 03 '17 14:10 shashawang

在执行 函数 output(obj) 中,语句直接对obj对象的属性进行了操作,改变了内存里obj对象的属性值。 想要保持一致, output(obj)函数应该单独定义一个变量 “x”

var obj = { count: 1 }

function output(obj) { x = obj.count + 1; console.log(x); }

k2eos avatar Oct 03 '17 14:10 k2eos

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);

antarts avatar Oct 03 '17 15:10 antarts

执行的结果是一样的,因为在使用对象作为函数参数时,实际上传送给函数的是对象的地址,即函数体内的对象和作为参数的原对象实际上是同一个。所以,想要不影响外面的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 avatar Oct 04 '17 03:10 freedomsky11

@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对象重新“复制”一份新的对象。

xugy0926 avatar Oct 04 '17 04:10 xugy0926

知识点

  1. 在javascript当中,当对象被赋值到一个变量时,传给变量的实际上是这个对象的引用。
  2. 当对象被传给函数的形参,形参其实也只是实际对象的一个引用,他们都指向同一个对象。
  3. 想要在函数体内的代码不影响到对象本体,我们可以在函数内复制一个。

原因

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)
}

Risexie avatar Oct 24 '17 13:10 Risexie