Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【10.3练习】 #643

Open
xugy0926 opened this issue Oct 3, 2017 · 9 comments
Open

【10.3练习】 #643

xugy0926 opened this issue Oct 3, 2017 · 9 comments

Comments

@xugy0926
Copy link
Owner

xugy0926 commented Oct 3, 2017

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?
@MyColourfulLife
Copy link
Contributor

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

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

@zjutszl
Copy link
Contributor

zjutszl commented Oct 3, 2017

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

@Ideal-Li
Copy link
Contributor

Ideal-Li commented Oct 3, 2017

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


@shashawang
Copy link

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

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

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

@k2eos
Copy link

k2eos commented Oct 3, 2017

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

var obj = {
count: 1
}

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

@antarts
Copy link
Contributor

antarts commented Oct 3, 2017

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

@freedomsky11
Copy link
Contributor

freedomsky11 commented Oct 4, 2017

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

@xugy0926
Copy link
Owner Author

xugy0926 commented Oct 4, 2017

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

@Risexie
Copy link
Contributor

Risexie commented Oct 24, 2017

知识点

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants